From 26d44d524c19f6e0a967c90e4d62c31363ab763b Mon Sep 17 00:00:00 2001 From: Alex Liberzon Date: Mon, 23 Feb 2026 22:06:46 +0200 Subject: [PATCH 01/27] added uv.lock and pyproject.toml --- pyproject.toml | 7 ++ uv.lock | 198 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 205 insertions(+) create mode 100644 uv.lock diff --git a/pyproject.toml b/pyproject.toml index efcba21..7fca336 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,6 +18,13 @@ dynamic = ["version"] license = {file = "LICENSE"} name = "openptv-python" readme = "README.md" +dependencies = [ + "numba>=0.64.0", + "numpy>=2.4.2", + "pytest>=9.0.2", + "pyyaml>=6.0.3", + "scipy>=1.17.1", +] [tool.coverage.run] branch = true diff --git a/uv.lock b/uv.lock new file mode 100644 index 0000000..1626878 --- /dev/null +++ b/uv.lock @@ -0,0 +1,198 @@ +version = 1 +revision = 3 +requires-python = ">=3.14" + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "iniconfig" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, +] + +[[package]] +name = "llvmlite" +version = "0.46.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/74/cd/08ae687ba099c7e3d21fe2ea536500563ef1943c5105bf6ab4ee3829f68e/llvmlite-0.46.0.tar.gz", hash = "sha256:227c9fd6d09dce2783c18b754b7cd9d9b3b3515210c46acc2d3c5badd9870ceb", size = 193456, upload-time = "2025-12-08T18:15:36.295Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/95/ae/af0ffb724814cc2ea64445acad05f71cff5f799bb7efb22e47ee99340dbc/llvmlite-0.46.0-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:d252edfb9f4ac1fcf20652258e3f102b26b03eef738dc8a6ffdab7d7d341d547", size = 37232768, upload-time = "2025-12-08T18:15:25.055Z" }, + { url = "https://files.pythonhosted.org/packages/c9/19/5018e5352019be753b7b07f7759cdabb69ca5779fea2494be8839270df4c/llvmlite-0.46.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:379fdd1c59badeff8982cb47e4694a6143bec3bb49aa10a466e095410522064d", size = 56275173, upload-time = "2025-12-08T18:15:28.109Z" }, + { url = "https://files.pythonhosted.org/packages/9f/c9/d57877759d707e84c082163c543853245f91b70c804115a5010532890f18/llvmlite-0.46.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2e8cbfff7f6db0fa2c771ad24154e2a7e457c2444d7673e6de06b8b698c3b269", size = 55128628, upload-time = "2025-12-08T18:15:31.098Z" }, + { url = "https://files.pythonhosted.org/packages/30/a8/e61a8c2b3cc7a597073d9cde1fcbb567e9d827f1db30c93cf80422eac70d/llvmlite-0.46.0-cp314-cp314-win_amd64.whl", hash = "sha256:7821eda3ec1f18050f981819756631d60b6d7ab1a6cf806d9efefbe3f4082d61", size = 39153056, upload-time = "2025-12-08T18:15:33.938Z" }, +] + +[[package]] +name = "numba" +version = "0.64.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "llvmlite" }, + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/23/c9/a0fb41787d01d621046138da30f6c2100d80857bf34b3390dd68040f27a3/numba-0.64.0.tar.gz", hash = "sha256:95e7300af648baa3308127b1955b52ce6d11889d16e8cfe637b4f85d2fca52b1", size = 2765679, upload-time = "2026-02-18T18:41:20.974Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3d/8a/77d26afe0988c592dd97cb8d4e80bfb3dfc7dbdacfca7d74a7c5c81dd8c2/numba-0.64.0-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:f565d55eaeff382cbc86c63c8c610347453af3d1e7afb2b6569aac1c9b5c93ce", size = 2683590, upload-time = "2026-02-18T18:41:12.897Z" }, + { url = "https://files.pythonhosted.org/packages/8e/4b/600b8b7cdbc7f9cebee9ea3d13bb70052a79baf28944024ffcb59f0712e3/numba-0.64.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:9b55169b18892c783f85e9ad9e6f5297a6d12967e4414e6b71361086025ff0bb", size = 3781163, upload-time = "2026-02-18T18:41:15.377Z" }, + { url = "https://files.pythonhosted.org/packages/ff/73/53f2d32bfa45b7175e9944f6b816d8c32840178c3eee9325033db5bf838e/numba-0.64.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:196bcafa02c9dd1707e068434f6d5cedde0feb787e3432f7f1f0e993cc336c4c", size = 3481172, upload-time = "2026-02-18T18:41:17.281Z" }, + { url = "https://files.pythonhosted.org/packages/b5/00/aebd2f7f1e11e38814bb96e95a27580817a7b340608d3ac085fdbab83174/numba-0.64.0-cp314-cp314-win_amd64.whl", hash = "sha256:213e9acbe7f1c05090592e79020315c1749dd52517b90e94c517dca3f014d4a1", size = 2754700, upload-time = "2026-02-18T18:41:19.277Z" }, +] + +[[package]] +name = "numpy" +version = "2.4.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/57/fd/0005efbd0af48e55eb3c7208af93f2862d4b1a56cd78e84309a2d959208d/numpy-2.4.2.tar.gz", hash = "sha256:659a6107e31a83c4e33f763942275fd278b21d095094044eb35569e86a21ddae", size = 20723651, upload-time = "2026-01-31T23:13:10.135Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/88/b7df6050bf18fdcfb7046286c6535cabbdd2064a3440fca3f069d319c16e/numpy-2.4.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:444be170853f1f9d528428eceb55f12918e4fda5d8805480f36a002f1415e09b", size = 16663092, upload-time = "2026-01-31T23:12:04.521Z" }, + { url = "https://files.pythonhosted.org/packages/25/7a/1fee4329abc705a469a4afe6e69b1ef7e915117747886327104a8493a955/numpy-2.4.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:d1240d50adff70c2a88217698ca844723068533f3f5c5fa6ee2e3220e3bdb000", size = 14698770, upload-time = "2026-01-31T23:12:06.96Z" }, + { url = "https://files.pythonhosted.org/packages/fb/0b/f9e49ba6c923678ad5bc38181c08ac5e53b7a5754dbca8e581aa1a56b1ff/numpy-2.4.2-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:7cdde6de52fb6664b00b056341265441192d1291c130e99183ec0d4b110ff8b1", size = 5208562, upload-time = "2026-01-31T23:12:09.632Z" }, + { url = "https://files.pythonhosted.org/packages/7d/12/d7de8f6f53f9bb76997e5e4c069eda2051e3fe134e9181671c4391677bb2/numpy-2.4.2-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:cda077c2e5b780200b6b3e09d0b42205a3d1c68f30c6dceb90401c13bff8fe74", size = 6543710, upload-time = "2026-01-31T23:12:11.969Z" }, + { url = "https://files.pythonhosted.org/packages/09/63/c66418c2e0268a31a4cf8a8b512685748200f8e8e8ec6c507ce14e773529/numpy-2.4.2-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d30291931c915b2ab5717c2974bb95ee891a1cf22ebc16a8006bd59cd210d40a", size = 15677205, upload-time = "2026-01-31T23:12:14.33Z" }, + { url = "https://files.pythonhosted.org/packages/5d/6c/7f237821c9642fb2a04d2f1e88b4295677144ca93285fd76eff3bcba858d/numpy-2.4.2-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bba37bc29d4d85761deed3954a1bc62be7cf462b9510b51d367b769a8c8df325", size = 16611738, upload-time = "2026-01-31T23:12:16.525Z" }, + { url = "https://files.pythonhosted.org/packages/c2/a7/39c4cdda9f019b609b5c473899d87abff092fc908cfe4d1ecb2fcff453b0/numpy-2.4.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b2f0073ed0868db1dcd86e052d37279eef185b9c8db5bf61f30f46adac63c909", size = 17028888, upload-time = "2026-01-31T23:12:19.306Z" }, + { url = "https://files.pythonhosted.org/packages/da/b3/e84bb64bdfea967cc10950d71090ec2d84b49bc691df0025dddb7c26e8e3/numpy-2.4.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7f54844851cdb630ceb623dcec4db3240d1ac13d4990532446761baede94996a", size = 18339556, upload-time = "2026-01-31T23:12:21.816Z" }, + { url = "https://files.pythonhosted.org/packages/88/f5/954a291bc1192a27081706862ac62bb5920fbecfbaa302f64682aa90beed/numpy-2.4.2-cp314-cp314-win32.whl", hash = "sha256:12e26134a0331d8dbd9351620f037ec470b7c75929cb8a1537f6bfe411152a1a", size = 6006899, upload-time = "2026-01-31T23:12:24.14Z" }, + { url = "https://files.pythonhosted.org/packages/05/cb/eff72a91b2efdd1bc98b3b8759f6a1654aa87612fc86e3d87d6fe4f948c4/numpy-2.4.2-cp314-cp314-win_amd64.whl", hash = "sha256:068cdb2d0d644cdb45670810894f6a0600797a69c05f1ac478e8d31670b8ee75", size = 12443072, upload-time = "2026-01-31T23:12:26.33Z" }, + { url = "https://files.pythonhosted.org/packages/37/75/62726948db36a56428fce4ba80a115716dc4fad6a3a4352487f8bb950966/numpy-2.4.2-cp314-cp314-win_arm64.whl", hash = "sha256:6ed0be1ee58eef41231a5c943d7d1375f093142702d5723ca2eb07db9b934b05", size = 10494886, upload-time = "2026-01-31T23:12:28.488Z" }, + { url = "https://files.pythonhosted.org/packages/36/2f/ee93744f1e0661dc267e4b21940870cabfae187c092e1433b77b09b50ac4/numpy-2.4.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:98f16a80e917003a12c0580f97b5f875853ebc33e2eaa4bccfc8201ac6869308", size = 14818567, upload-time = "2026-01-31T23:12:30.709Z" }, + { url = "https://files.pythonhosted.org/packages/a7/24/6535212add7d76ff938d8bdc654f53f88d35cddedf807a599e180dcb8e66/numpy-2.4.2-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:20abd069b9cda45874498b245c8015b18ace6de8546bf50dfa8cea1696ed06ef", size = 5328372, upload-time = "2026-01-31T23:12:32.962Z" }, + { url = "https://files.pythonhosted.org/packages/5e/9d/c48f0a035725f925634bf6b8994253b43f2047f6778a54147d7e213bc5a7/numpy-2.4.2-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:e98c97502435b53741540a5717a6749ac2ada901056c7db951d33e11c885cc7d", size = 6649306, upload-time = "2026-01-31T23:12:34.797Z" }, + { url = "https://files.pythonhosted.org/packages/81/05/7c73a9574cd4a53a25907bad38b59ac83919c0ddc8234ec157f344d57d9a/numpy-2.4.2-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:da6cad4e82cb893db4b69105c604d805e0c3ce11501a55b5e9f9083b47d2ffe8", size = 15722394, upload-time = "2026-01-31T23:12:36.565Z" }, + { url = "https://files.pythonhosted.org/packages/35/fa/4de10089f21fc7d18442c4a767ab156b25c2a6eaf187c0db6d9ecdaeb43f/numpy-2.4.2-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e4424677ce4b47fe73c8b5556d876571f7c6945d264201180db2dc34f676ab5", size = 16653343, upload-time = "2026-01-31T23:12:39.188Z" }, + { url = "https://files.pythonhosted.org/packages/b8/f9/d33e4ffc857f3763a57aa85650f2e82486832d7492280ac21ba9efda80da/numpy-2.4.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:2b8f157c8a6f20eb657e240f8985cc135598b2b46985c5bccbde7616dc9c6b1e", size = 17078045, upload-time = "2026-01-31T23:12:42.041Z" }, + { url = "https://files.pythonhosted.org/packages/c8/b8/54bdb43b6225badbea6389fa038c4ef868c44f5890f95dd530a218706da3/numpy-2.4.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5daf6f3914a733336dab21a05cdec343144600e964d2fcdabaac0c0269874b2a", size = 18380024, upload-time = "2026-01-31T23:12:44.331Z" }, + { url = "https://files.pythonhosted.org/packages/a5/55/6e1a61ded7af8df04016d81b5b02daa59f2ea9252ee0397cb9f631efe9e5/numpy-2.4.2-cp314-cp314t-win32.whl", hash = "sha256:8c50dd1fc8826f5b26a5ee4d77ca55d88a895f4e4819c7ecc2a9f5905047a443", size = 6153937, upload-time = "2026-01-31T23:12:47.229Z" }, + { url = "https://files.pythonhosted.org/packages/45/aa/fa6118d1ed6d776b0983f3ceac9b1a5558e80df9365b1c3aa6d42bf9eee4/numpy-2.4.2-cp314-cp314t-win_amd64.whl", hash = "sha256:fcf92bee92742edd401ba41135185866f7026c502617f422eb432cfeca4fe236", size = 12631844, upload-time = "2026-01-31T23:12:48.997Z" }, + { url = "https://files.pythonhosted.org/packages/32/0a/2ec5deea6dcd158f254a7b372fb09cfba5719419c8d66343bab35237b3fb/numpy-2.4.2-cp314-cp314t-win_arm64.whl", hash = "sha256:1f92f53998a17265194018d1cc321b2e96e900ca52d54c7c77837b71b9465181", size = 10565379, upload-time = "2026-01-31T23:12:51.345Z" }, +] + +[[package]] +name = "openptv-python" +source = { editable = "." } +dependencies = [ + { name = "numba" }, + { name = "numpy" }, + { name = "pytest" }, + { name = "pyyaml" }, + { name = "scipy" }, +] + +[package.metadata] +requires-dist = [ + { name = "numba", specifier = ">=0.64.0" }, + { name = "numpy", specifier = ">=2.4.2" }, + { name = "pytest", specifier = ">=9.0.2" }, + { name = "pyyaml", specifier = ">=6.0.3" }, + { name = "scipy", specifier = ">=1.17.1" }, +] + +[[package]] +name = "packaging" +version = "26.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/65/ee/299d360cdc32edc7d2cf530f3accf79c4fca01e96ffc950d8a52213bd8e4/packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4", size = 143416, upload-time = "2026-01-21T20:50:39.064Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529", size = 74366, upload-time = "2026-01-21T20:50:37.788Z" }, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, +] + +[[package]] +name = "pygments" +version = "2.19.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, +] + +[[package]] +name = "pytest" +version = "9.0.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d1/db/7ef3487e0fb0049ddb5ce41d3a49c235bf9ad299b6a25d5780a89f19230f/pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11", size = 1568901, upload-time = "2025-12-06T21:30:51.014Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801, upload-time = "2025-12-06T21:30:49.154Z" }, +] + +[[package]] +name = "pyyaml" +version = "6.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" }, + { url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" }, + { url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" }, + { url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" }, + { url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" }, + { url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" }, + { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" }, + { url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" }, + { url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" }, + { url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" }, + { url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" }, + { url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" }, + { url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" }, + { url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" }, + { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" }, +] + +[[package]] +name = "scipy" +version = "1.17.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7a/97/5a3609c4f8d58b039179648e62dd220f89864f56f7357f5d4f45c29eb2cc/scipy-1.17.1.tar.gz", hash = "sha256:95d8e012d8cb8816c226aef832200b1d45109ed4464303e997c5b13122b297c0", size = 30573822, upload-time = "2026-02-23T00:26:24.851Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cf/83/333afb452af6f0fd70414dc04f898647ee1423979ce02efa75c3b0f2c28e/scipy-1.17.1-cp314-cp314-macosx_10_14_x86_64.whl", hash = "sha256:a48a72c77a310327f6a3a920092fa2b8fd03d7deaa60f093038f22d98e096717", size = 31584510, upload-time = "2026-02-23T00:21:01.015Z" }, + { url = "https://files.pythonhosted.org/packages/ed/a6/d05a85fd51daeb2e4ea71d102f15b34fedca8e931af02594193ae4fd25f7/scipy-1.17.1-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:45abad819184f07240d8a696117a7aacd39787af9e0b719d00285549ed19a1e9", size = 28170131, upload-time = "2026-02-23T00:21:05.888Z" }, + { url = "https://files.pythonhosted.org/packages/db/7b/8624a203326675d7746a254083a187398090a179335b2e4a20e2ddc46e83/scipy-1.17.1-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:3fd1fcdab3ea951b610dc4cef356d416d5802991e7e32b5254828d342f7b7e0b", size = 20342032, upload-time = "2026-02-23T00:21:09.904Z" }, + { url = "https://files.pythonhosted.org/packages/c9/35/2c342897c00775d688d8ff3987aced3426858fd89d5a0e26e020b660b301/scipy-1.17.1-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:7bdf2da170b67fdf10bca777614b1c7d96ae3ca5794fd9587dce41eb2966e866", size = 22678766, upload-time = "2026-02-23T00:21:14.313Z" }, + { url = "https://files.pythonhosted.org/packages/ef/f2/7cdb8eb308a1a6ae1e19f945913c82c23c0c442a462a46480ce487fdc0ac/scipy-1.17.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:adb2642e060a6549c343603a3851ba76ef0b74cc8c079a9a58121c7ec9fe2350", size = 32957007, upload-time = "2026-02-23T00:21:19.663Z" }, + { url = "https://files.pythonhosted.org/packages/0b/2e/7eea398450457ecb54e18e9d10110993fa65561c4f3add5e8eccd2b9cd41/scipy-1.17.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:eee2cfda04c00a857206a4330f0c5e3e56535494e30ca445eb19ec624ae75118", size = 35221333, upload-time = "2026-02-23T00:21:25.278Z" }, + { url = "https://files.pythonhosted.org/packages/d9/77/5b8509d03b77f093a0d52e606d3c4f79e8b06d1d38c441dacb1e26cacf46/scipy-1.17.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d2650c1fb97e184d12d8ba010493ee7b322864f7d3d00d3f9bb97d9c21de4068", size = 35042066, upload-time = "2026-02-23T00:21:31.358Z" }, + { url = "https://files.pythonhosted.org/packages/f9/df/18f80fb99df40b4070328d5ae5c596f2f00fffb50167e31439e932f29e7d/scipy-1.17.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:08b900519463543aa604a06bec02461558a6e1cef8fdbb8098f77a48a83c8118", size = 37612763, upload-time = "2026-02-23T00:21:37.247Z" }, + { url = "https://files.pythonhosted.org/packages/4b/39/f0e8ea762a764a9dc52aa7dabcfad51a354819de1f0d4652b6a1122424d6/scipy-1.17.1-cp314-cp314-win_amd64.whl", hash = "sha256:3877ac408e14da24a6196de0ddcace62092bfc12a83823e92e49e40747e52c19", size = 37290984, upload-time = "2026-02-23T00:22:35.023Z" }, + { url = "https://files.pythonhosted.org/packages/7c/56/fe201e3b0f93d1a8bcf75d3379affd228a63d7e2d80ab45467a74b494947/scipy-1.17.1-cp314-cp314-win_arm64.whl", hash = "sha256:f8885db0bc2bffa59d5c1b72fad7a6a92d3e80e7257f967dd81abb553a90d293", size = 25192877, upload-time = "2026-02-23T00:22:39.798Z" }, + { url = "https://files.pythonhosted.org/packages/96/ad/f8c414e121f82e02d76f310f16db9899c4fcde36710329502a6b2a3c0392/scipy-1.17.1-cp314-cp314t-macosx_10_14_x86_64.whl", hash = "sha256:1cc682cea2ae55524432f3cdff9e9a3be743d52a7443d0cba9017c23c87ae2f6", size = 31949750, upload-time = "2026-02-23T00:21:42.289Z" }, + { url = "https://files.pythonhosted.org/packages/7c/b0/c741e8865d61b67c81e255f4f0a832846c064e426636cd7de84e74d209be/scipy-1.17.1-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:2040ad4d1795a0ae89bfc7e8429677f365d45aa9fd5e4587cf1ea737f927b4a1", size = 28585858, upload-time = "2026-02-23T00:21:47.706Z" }, + { url = "https://files.pythonhosted.org/packages/ed/1b/3985219c6177866628fa7c2595bfd23f193ceebbe472c98a08824b9466ff/scipy-1.17.1-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:131f5aaea57602008f9822e2115029b55d4b5f7c070287699fe45c661d051e39", size = 20757723, upload-time = "2026-02-23T00:21:52.039Z" }, + { url = "https://files.pythonhosted.org/packages/c0/19/2a04aa25050d656d6f7b9e7b685cc83d6957fb101665bfd9369ca6534563/scipy-1.17.1-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:9cdc1a2fcfd5c52cfb3045feb399f7b3ce822abdde3a193a6b9a60b3cb5854ca", size = 23043098, upload-time = "2026-02-23T00:21:56.185Z" }, + { url = "https://files.pythonhosted.org/packages/86/f1/3383beb9b5d0dbddd030335bf8a8b32d4317185efe495374f134d8be6cce/scipy-1.17.1-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e3dcd57ab780c741fde8dc68619de988b966db759a3c3152e8e9142c26295ad", size = 33030397, upload-time = "2026-02-23T00:22:01.404Z" }, + { url = "https://files.pythonhosted.org/packages/41/68/8f21e8a65a5a03f25a79165ec9d2b28c00e66dc80546cf5eb803aeeff35b/scipy-1.17.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a9956e4d4f4a301ebf6cde39850333a6b6110799d470dbbb1e25326ac447f52a", size = 35281163, upload-time = "2026-02-23T00:22:07.024Z" }, + { url = "https://files.pythonhosted.org/packages/84/8d/c8a5e19479554007a5632ed7529e665c315ae7492b4f946b0deb39870e39/scipy-1.17.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:a4328d245944d09fd639771de275701ccadf5f781ba0ff092ad141e017eccda4", size = 35116291, upload-time = "2026-02-23T00:22:12.585Z" }, + { url = "https://files.pythonhosted.org/packages/52/52/e57eceff0e342a1f50e274264ed47497b59e6a4e3118808ee58ddda7b74a/scipy-1.17.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a77cbd07b940d326d39a1d1b37817e2ee4d79cb30e7338f3d0cddffae70fcaa2", size = 37682317, upload-time = "2026-02-23T00:22:18.513Z" }, + { url = "https://files.pythonhosted.org/packages/11/2f/b29eafe4a3fbc3d6de9662b36e028d5f039e72d345e05c250e121a230dd4/scipy-1.17.1-cp314-cp314t-win_amd64.whl", hash = "sha256:eb092099205ef62cd1782b006658db09e2fed75bffcae7cc0d44052d8aa0f484", size = 37345327, upload-time = "2026-02-23T00:22:24.442Z" }, + { url = "https://files.pythonhosted.org/packages/07/39/338d9219c4e87f3e708f18857ecd24d22a0c3094752393319553096b98af/scipy-1.17.1-cp314-cp314t-win_arm64.whl", hash = "sha256:200e1050faffacc162be6a486a984a0497866ec54149a01270adc8a59b7c7d21", size = 25489165, upload-time = "2026-02-23T00:22:29.563Z" }, +] From 370931ffe03e7b2ccb7fa7e9228e825f6976c739 Mon Sep 17 00:00:00 2001 From: Alex Liberzon Date: Mon, 23 Feb 2026 22:10:16 +0200 Subject: [PATCH 02/27] added pre-commit --- pyproject.toml | 1 + uv.lock | 86 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 87 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 7fca336..c53e208 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,6 +21,7 @@ readme = "README.md" dependencies = [ "numba>=0.64.0", "numpy>=2.4.2", + "pre-commit>=4.5.1", "pytest>=9.0.2", "pyyaml>=6.0.3", "scipy>=1.17.1", diff --git a/uv.lock b/uv.lock index 1626878..a3e1270 100644 --- a/uv.lock +++ b/uv.lock @@ -2,6 +2,15 @@ version = 1 revision = 3 requires-python = ">=3.14" +[[package]] +name = "cfgv" +version = "3.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4e/b5/721b8799b04bf9afe054a3899c6cf4e880fcf8563cc71c15610242490a0c/cfgv-3.5.0.tar.gz", hash = "sha256:d5b1034354820651caa73ede66a6294d6e95c1b00acc5e9b098e917404669132", size = 7334, upload-time = "2025-11-19T20:55:51.612Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl", hash = "sha256:a8dc6b26ad22ff227d2634a65cb388215ce6cc96bbcc5cfde7641ae87e8dacc0", size = 7445, upload-time = "2025-11-19T20:55:50.744Z" }, +] + [[package]] name = "colorama" version = "0.4.6" @@ -11,6 +20,33 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, ] +[[package]] +name = "distlib" +version = "0.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/96/8e/709914eb2b5749865801041647dc7f4e6d00b549cfe88b65ca192995f07c/distlib-0.4.0.tar.gz", hash = "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d", size = 614605, upload-time = "2025-07-17T16:52:00.465Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16", size = 469047, upload-time = "2025-07-17T16:51:58.613Z" }, +] + +[[package]] +name = "filelock" +version = "3.24.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/73/92/a8e2479937ff39185d20dd6a851c1a63e55849e447a55e798cc2e1f49c65/filelock-3.24.3.tar.gz", hash = "sha256:011a5644dc937c22699943ebbfc46e969cdde3e171470a6e40b9533e5a72affa", size = 37935, upload-time = "2026-02-19T00:48:20.543Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9c/0f/5d0c71a1aefeb08efff26272149e07ab922b64f46c63363756224bd6872e/filelock-3.24.3-py3-none-any.whl", hash = "sha256:426e9a4660391f7f8a810d71b0555bce9008b0a1cc342ab1f6947d37639e002d", size = 24331, upload-time = "2026-02-19T00:48:18.465Z" }, +] + +[[package]] +name = "identify" +version = "2.6.16" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5b/8d/e8b97e6bd3fb6fb271346f7981362f1e04d6a7463abd0de79e1fda17c067/identify-2.6.16.tar.gz", hash = "sha256:846857203b5511bbe94d5a352a48ef2359532bc8f6727b5544077a0dcfb24980", size = 99360, upload-time = "2026-01-12T18:58:58.201Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b8/58/40fbbcefeda82364720eba5cf2270f98496bdfa19ea75b4cccae79c698e6/identify-2.6.16-py2.py3-none-any.whl", hash = "sha256:391ee4d77741d994189522896270b787aed8670389bfd60f326d677d64a6dfb0", size = 99202, upload-time = "2026-01-12T18:58:56.627Z" }, +] + [[package]] name = "iniconfig" version = "2.3.0" @@ -32,6 +68,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/30/a8/e61a8c2b3cc7a597073d9cde1fcbb567e9d827f1db30c93cf80422eac70d/llvmlite-0.46.0-cp314-cp314-win_amd64.whl", hash = "sha256:7821eda3ec1f18050f981819756631d60b6d7ab1a6cf806d9efefbe3f4082d61", size = 39153056, upload-time = "2025-12-08T18:15:33.938Z" }, ] +[[package]] +name = "nodeenv" +version = "1.10.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/24/bf/d1bda4f6168e0b2e9e5958945e01910052158313224ada5ce1fb2e1113b8/nodeenv-1.10.0.tar.gz", hash = "sha256:996c191ad80897d076bdfba80a41994c2b47c68e224c542b48feba42ba00f8bb", size = 55611, upload-time = "2025-12-20T14:08:54.006Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl", hash = "sha256:5bb13e3eed2923615535339b3c620e76779af4cb4c6a90deccc9e36b274d3827", size = 23438, upload-time = "2025-12-20T14:08:52.782Z" }, +] + [[package]] name = "numba" version = "0.64.0" @@ -83,6 +128,7 @@ source = { editable = "." } dependencies = [ { name = "numba" }, { name = "numpy" }, + { name = "pre-commit" }, { name = "pytest" }, { name = "pyyaml" }, { name = "scipy" }, @@ -92,6 +138,7 @@ dependencies = [ requires-dist = [ { name = "numba", specifier = ">=0.64.0" }, { name = "numpy", specifier = ">=2.4.2" }, + { name = "pre-commit", specifier = ">=4.5.1" }, { name = "pytest", specifier = ">=9.0.2" }, { name = "pyyaml", specifier = ">=6.0.3" }, { name = "scipy", specifier = ">=1.17.1" }, @@ -106,6 +153,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529", size = 74366, upload-time = "2026-01-21T20:50:37.788Z" }, ] +[[package]] +name = "platformdirs" +version = "4.9.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/04/fea538adf7dbbd6d186f551d595961e564a3b6715bdf276b477460858672/platformdirs-4.9.2.tar.gz", hash = "sha256:9a33809944b9db043ad67ca0db94b14bf452cc6aeaac46a88ea55b26e2e9d291", size = 28394, upload-time = "2026-02-16T03:56:10.574Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/48/31/05e764397056194206169869b50cf2fee4dbbbc71b344705b9c0d878d4d8/platformdirs-4.9.2-py3-none-any.whl", hash = "sha256:9170634f126f8efdae22fb58ae8a0eaa86f38365bc57897a6c4f781d1f5875bd", size = 21168, upload-time = "2026-02-16T03:56:08.891Z" }, +] + [[package]] name = "pluggy" version = "1.6.0" @@ -115,6 +171,22 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, ] +[[package]] +name = "pre-commit" +version = "4.5.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cfgv" }, + { name = "identify" }, + { name = "nodeenv" }, + { name = "pyyaml" }, + { name = "virtualenv" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/40/f1/6d86a29246dfd2e9b6237f0b5823717f60cad94d47ddc26afa916d21f525/pre_commit-4.5.1.tar.gz", hash = "sha256:eb545fcff725875197837263e977ea257a402056661f09dae08e4b149b030a61", size = 198232, upload-time = "2025-12-16T21:14:33.552Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5d/19/fd3ef348460c80af7bb4669ea7926651d1f95c23ff2df18b9d24bab4f3fa/pre_commit-4.5.1-py2.py3-none-any.whl", hash = "sha256:3b3afd891e97337708c1674210f8eba659b52a38ea5f822ff142d10786221f77", size = 226437, upload-time = "2025-12-16T21:14:32.409Z" }, +] + [[package]] name = "pygments" version = "2.19.2" @@ -196,3 +268,17 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/11/2f/b29eafe4a3fbc3d6de9662b36e028d5f039e72d345e05c250e121a230dd4/scipy-1.17.1-cp314-cp314t-win_amd64.whl", hash = "sha256:eb092099205ef62cd1782b006658db09e2fed75bffcae7cc0d44052d8aa0f484", size = 37345327, upload-time = "2026-02-23T00:22:24.442Z" }, { url = "https://files.pythonhosted.org/packages/07/39/338d9219c4e87f3e708f18857ecd24d22a0c3094752393319553096b98af/scipy-1.17.1-cp314-cp314t-win_arm64.whl", hash = "sha256:200e1050faffacc162be6a486a984a0497866ec54149a01270adc8a59b7c7d21", size = 25489165, upload-time = "2026-02-23T00:22:29.563Z" }, ] + +[[package]] +name = "virtualenv" +version = "20.39.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "distlib" }, + { name = "filelock" }, + { name = "platformdirs" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ed/54/809199edc537dbace273495ac0884d13df26436e910a5ed4d0ec0a69806b/virtualenv-20.39.0.tar.gz", hash = "sha256:a15f0cebd00d50074fd336a169d53422436a12dfe15149efec7072cfe817df8b", size = 5869141, upload-time = "2026-02-23T18:09:13.349Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f7/b4/8268da45f26f4fe84f6eae80a6ca1485ffb490a926afecff75fc48f61979/virtualenv-20.39.0-py3-none-any.whl", hash = "sha256:44888bba3775990a152ea1f73f8e5f566d49f11bbd1de61d426fd7732770043e", size = 5839121, upload-time = "2026-02-23T18:09:11.173Z" }, +] From 296432146d0143cbf460ba94bdc5adc76fc09676 Mon Sep 17 00:00:00 2001 From: Alex Liberzon Date: Mon, 23 Feb 2026 22:11:00 +0200 Subject: [PATCH 03/27] running make template-update --- .cruft.json | 4 +-- .github/workflows/on-push.yml | 46 +++++++++++++++++------------------ .pre-commit-config.yaml | 14 +++++------ Dockerfile | 2 +- README.md.rej | 14 +++++++++++ pyproject.toml | 1 + 6 files changed, 48 insertions(+), 33 deletions(-) create mode 100644 README.md.rej diff --git a/.cruft.json b/.cruft.json index 6fb3fda..053d3c8 100644 --- a/.cruft.json +++ b/.cruft.json @@ -1,6 +1,6 @@ { "template": "https://github.com/ecmwf-projects/cookiecutter-conda-package", - "commit": "a9c290fa0e810e9dee6c61991afac6f0c969c3b1", + "commit": "5ad0c955478c9b0fe8772545ef46291f5f314f75", "checkout": null, "context": { "cookiecutter": { @@ -13,7 +13,7 @@ "integration_tests": "True", "pypi": true, "_template": "https://github.com/ecmwf-projects/cookiecutter-conda-package", - "_commit": "a9c290fa0e810e9dee6c61991afac6f0c969c3b1" + "_commit": "5ad0c955478c9b0fe8772545ef46291f5f314f75" } }, "directory": null diff --git a/.github/workflows/on-push.yml b/.github/workflows/on-push.yml index abacbb2..b04fbfb 100644 --- a/.github/workflows/on-push.yml +++ b/.github/workflows/on-push.yml @@ -22,8 +22,8 @@ jobs: pre-commit: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 + - uses: actions/checkout@v6 + - uses: actions/setup-python@v6 with: python-version: 3.x - uses: pre-commit/action@v3.0.1 @@ -32,8 +32,8 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 + - uses: actions/checkout@v6 + - uses: actions/setup-python@v6 with: python-version: 3.x - name: Install conda-merge @@ -44,7 +44,7 @@ jobs: for SUFFIX in ci integration; do conda-merge ci/environment-$SUFFIX.yml environment.yml > ci/combined-environment-$SUFFIX.yml || exit done - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@v6 with: name: combined-environments path: ci/combined-environment-*.yml @@ -55,11 +55,11 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ['3.11'] + python-version: ['3.11', '3.12'] steps: - - uses: actions/checkout@v4 - - uses: actions/download-artifact@v4 + - uses: actions/checkout@v6 + - uses: actions/download-artifact@v7 with: name: combined-environments path: ci @@ -87,8 +87,8 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - uses: actions/download-artifact@v4 + - uses: actions/checkout@v6 + - uses: actions/download-artifact@v7 with: name: combined-environments path: ci @@ -103,7 +103,7 @@ jobs: cache-environment-key: environment-${{ steps.date.outputs.date }} cache-downloads-key: downloads-${{ steps.date.outputs.date }} create-args: >- - python=3.11 + python=3.12 - name: Install package run: | python -m pip install --no-deps -e . @@ -116,8 +116,8 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - uses: actions/download-artifact@v4 + - uses: actions/checkout@v6 + - uses: actions/download-artifact@v7 with: name: combined-environments path: ci @@ -132,7 +132,7 @@ jobs: cache-environment-key: environment-${{ steps.date.outputs.date }} cache-downloads-key: downloads-${{ steps.date.outputs.date }} create-args: >- - python=3.11 + python=3.12 - name: Install package run: | python -m pip install --no-deps -e . @@ -149,12 +149,12 @@ jobs: strategy: matrix: include: - - python-version: '3.11' + - python-version: '3.12' extra: -integration steps: - - uses: actions/checkout@v4 - - uses: actions/download-artifact@v4 + - uses: actions/checkout@v6 + - uses: actions/download-artifact@v7 with: name: combined-environments path: ci @@ -188,10 +188,10 @@ jobs: (needs.integration-tests.result == 'success' || needs.integration-tests.result == 'skipped') steps: - - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 + - uses: actions/checkout@v6 + - uses: actions/setup-python@v6 with: - python-version: '3.11' + python-version: '3.12' - name: Install package run: | python -m pip install --upgrade pip @@ -206,7 +206,7 @@ jobs: python -m twine check --strict * || exit python -c "import openptv_python" || exit cd .. - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@v6 with: name: distribution path: dist @@ -226,10 +226,10 @@ jobs: id-token: write # IMPORTANT: this permission is mandatory for trusted publish steps: - - uses: actions/download-artifact@v4 + - uses: actions/download-artifact@v7 with: name: distribution path: dist - - uses: pypa/gh-action-pypi-publish@v1.12.4 + - uses: pypa/gh-action-pypi-publish@v1.13.0 with: verbose: true diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 2922494..64c2474 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v5.0.0 + rev: v6.0.0 hooks: - id: trailing-whitespace - id: end-of-file-fixer @@ -12,28 +12,28 @@ repos: - id: debug-statements - id: mixed-line-ending - repo: https://github.com/keewis/blackdoc - rev: v0.3.9 + rev: v0.4.6 hooks: - id: blackdoc - additional_dependencies: [black==23.11.0] + additional_dependencies: [black==25.9.0] - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.11.11 + rev: v0.15.2 hooks: - id: ruff args: [--fix, --show-fixes] - id: ruff-format - repo: https://github.com/executablebooks/mdformat - rev: 0.7.22 + rev: 1.0.0 hooks: - id: mdformat - repo: https://github.com/macisamuele/language-formatters-pre-commit-hooks - rev: v2.14.0 + rev: v2.16.0 hooks: - id: pretty-format-yaml args: [--autofix, --preserve-quotes] - id: pretty-format-toml args: [--autofix] - repo: https://github.com/gitleaks/gitleaks - rev: v8.26.0 + rev: v8.30.0 hooks: - id: gitleaks diff --git a/Dockerfile b/Dockerfile index 372b11c..2fbf6f2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,7 +4,7 @@ WORKDIR /src/openptv-python COPY environment.yml /src/openptv-python/ -RUN conda install -c conda-forge gcc python=3.11 \ +RUN conda install -c conda-forge gcc python=3.12 \ && conda env update -n base -f environment.yml COPY . /src/openptv-python diff --git a/README.md.rej b/README.md.rej new file mode 100644 index 0000000..31889ea --- /dev/null +++ b/README.md.rej @@ -0,0 +1,14 @@ +diff a/README.md b/README.md (rejected hunks) +@@ -11,10 +11,10 @@ Python version of the OpenPTV library + + ## Workflow for developers/contributors + +-For best experience create a new conda environment (e.g. DEVELOP) with Python 3.11: ++For best experience create a new conda environment (e.g. DEVELOP) with Python 3.12: + + ``` +-conda create -n DEVELOP -c conda-forge python=3.11 ++conda create -n DEVELOP -c conda-forge python=3.12 + conda activate DEVELOP + ``` + diff --git a/pyproject.toml b/pyproject.toml index c53e208..33d2d18 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,6 +11,7 @@ classifiers = [ "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", "Topic :: Scientific/Engineering" ] description = "Python version of the OpenPTV library" From a1bb0dffaaf870ccd882b77106baff3c092bac46 Mon Sep 17 00:00:00 2001 From: Alex Liberzon Date: Mon, 23 Feb 2026 22:23:58 +0200 Subject: [PATCH 04/27] everything works --- README.md | 6 +- README.md.rej | 14 -- pyproject.toml | 23 ++- uv.lock | 529 +++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 547 insertions(+), 25 deletions(-) delete mode 100644 README.md.rej diff --git a/README.md b/README.md index eb512ae..36d5595 100644 --- a/README.md +++ b/README.md @@ -19,16 +19,16 @@ this structure ### Workflow for developers/contributors -For the best experience create a new conda environment (e.g. DEVELOP) with Python 3.10: +For the best experience create a new conda environment (e.g. DEVELOP) with Python 3.12: ``` -conda create -n openptv-python -c conda-forge python=3.11 +conda create -n openptv-python -c conda-forge python=3.12 conda activate openptv-python ``` Before pushing to GitHub, run the following commands: -1. Update conda environment: `make conda-env-update` +1. Update conda environment: `make conda-env-update` or `uv venv` and `source .venv/bin/activate` followed by `uv sync` 1. Install this package: `pip install -e .` 1. Sync with the latest [template](https://github.com/ecmwf-projects/cookiecutter-conda-package) (optional): `make template-update` 1. Run quality assurance checks: `make qa` diff --git a/README.md.rej b/README.md.rej deleted file mode 100644 index 31889ea..0000000 --- a/README.md.rej +++ /dev/null @@ -1,14 +0,0 @@ -diff a/README.md b/README.md (rejected hunks) -@@ -11,10 +11,10 @@ Python version of the OpenPTV library - - ## Workflow for developers/contributors - --For best experience create a new conda environment (e.g. DEVELOP) with Python 3.11: -+For best experience create a new conda environment (e.g. DEVELOP) with Python 3.12: - - ``` --conda create -n DEVELOP -c conda-forge python=3.11 -+conda create -n DEVELOP -c conda-forge python=3.12 - conda activate DEVELOP - ``` - diff --git a/pyproject.toml b/pyproject.toml index 33d2d18..f5946bd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,19 +14,26 @@ classifiers = [ "Programming Language :: Python :: 3.12", "Topic :: Scientific/Engineering" ] +dependencies = [ + "mypy>=1.19.1", + "myst-parser>=5.0.0", + "numba>=0.64.0", + "numpy>=2.4.2", + "pre-commit>=4.5.1", + "pydata-sphinx-theme>=0.16.1", + "pytest>=9.0.2", + "pytest-cov>=7.0.0", + "pyyaml>=6.0.3", + "scipy>=1.17.1", + "sphinx>=9.1.0", + "sphinx-autoapi>=3.7.0", + "types-pyyaml>=6.0.12.20250915", +] description = "Python version of the OpenPTV library" dynamic = ["version"] license = {file = "LICENSE"} name = "openptv-python" readme = "README.md" -dependencies = [ - "numba>=0.64.0", - "numpy>=2.4.2", - "pre-commit>=4.5.1", - "pytest>=9.0.2", - "pyyaml>=6.0.3", - "scipy>=1.17.1", -] [tool.coverage.run] branch = true diff --git a/uv.lock b/uv.lock index a3e1270..a4c40c8 100644 --- a/uv.lock +++ b/uv.lock @@ -2,6 +2,67 @@ version = 1 revision = 3 requires-python = ">=3.14" +[[package]] +name = "accessible-pygments" +version = "0.0.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bc/c1/bbac6a50d02774f91572938964c582fff4270eee73ab822a4aeea4d8b11b/accessible_pygments-0.0.5.tar.gz", hash = "sha256:40918d3e6a2b619ad424cb91e556bd3bd8865443d9f22f1dcdf79e33c8046872", size = 1377899, upload-time = "2024-05-10T11:23:10.216Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8d/3f/95338030883d8c8b91223b4e21744b04d11b161a3ef117295d8241f50ab4/accessible_pygments-0.0.5-py3-none-any.whl", hash = "sha256:88ae3211e68a1d0b011504b2ffc1691feafce124b845bd072ab6f9f66f34d4b7", size = 1395903, upload-time = "2024-05-10T11:23:08.421Z" }, +] + +[[package]] +name = "alabaster" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a6/f8/d9c74d0daf3f742840fd818d69cfae176fa332022fd44e3469487d5a9420/alabaster-1.0.0.tar.gz", hash = "sha256:c00dca57bca26fa62a6d7d0a9fcce65f3e026e9bfe33e9c538fd3fbb2144fd9e", size = 24210, upload-time = "2024-07-26T18:15:03.762Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/b3/6b4067be973ae96ba0d615946e314c5ae35f9f993eca561b356540bb0c2b/alabaster-1.0.0-py3-none-any.whl", hash = "sha256:fc6786402dc3fcb2de3cabd5fe455a2db534b371124f1f21de8731783dec828b", size = 13929, upload-time = "2024-07-26T18:15:02.05Z" }, +] + +[[package]] +name = "astroid" +version = "4.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/4c/569eefb533ce71bc9f4f12a4a0d7f0ba27e500681dec1312d4657e849d20/astroid-4.1.1.tar.gz", hash = "sha256:445d831fe785df8c670bbb46b900b8424b82f85b4af187103f71a63a63ebed43", size = 412522, upload-time = "2026-02-23T02:36:30.315Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/32/53/bac4724684064bfee95ece0bb6caf3887e509006845e25388a12cac26d0c/astroid-4.1.1-py3-none-any.whl", hash = "sha256:6b28522096f7e7a36ffcf3be60e77de15e2411ab3a713184beac33fb8f20c0c9", size = 279273, upload-time = "2026-02-23T02:36:28.676Z" }, +] + +[[package]] +name = "babel" +version = "2.18.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/b2/51899539b6ceeeb420d40ed3cd4b7a40519404f9baf3d4ac99dc413a834b/babel-2.18.0.tar.gz", hash = "sha256:b80b99a14bd085fcacfa15c9165f651fbb3406e66cc603abf11c5750937c992d", size = 9959554, upload-time = "2026-02-01T12:30:56.078Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl", hash = "sha256:e2b422b277c2b9a9630c1d7903c2a00d0830c409c59ac8cae9081c92f1aeba35", size = 10196845, upload-time = "2026-02-01T12:30:53.445Z" }, +] + +[[package]] +name = "beautifulsoup4" +version = "4.14.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "soupsieve" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c3/b0/1c6a16426d389813b48d95e26898aff79abbde42ad353958ad95cc8c9b21/beautifulsoup4-4.14.3.tar.gz", hash = "sha256:6292b1c5186d356bba669ef9f7f051757099565ad9ada5dd630bd9de5fa7fb86", size = 627737, upload-time = "2025-11-30T15:08:26.084Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1a/39/47f9197bdd44df24d67ac8893641e16f386c984a0619ef2ee4c51fbbc019/beautifulsoup4-4.14.3-py3-none-any.whl", hash = "sha256:0918bfe44902e6ad8d57732ba310582e98da931428d231a5ecb9e7c703a735bb", size = 107721, upload-time = "2025-11-30T15:08:24.087Z" }, +] + +[[package]] +name = "certifi" +version = "2026.1.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e0/2d/a891ca51311197f6ad14a7ef42e2399f36cf2f9bd44752b3dc4eab60fdc5/certifi-2026.1.4.tar.gz", hash = "sha256:ac726dd470482006e014ad384921ed6438c457018f4b3d204aea4281258b2120", size = 154268, upload-time = "2026-01-04T02:42:41.825Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e6/ad/3cc14f097111b4de0040c83a525973216457bbeeb63739ef1ed275c1c021/certifi-2026.1.4-py3-none-any.whl", hash = "sha256:9943707519e4add1115f44c2bc244f782c0249876bf51b6599fee1ffbedd685c", size = 152900, upload-time = "2026-01-04T02:42:40.15Z" }, +] + [[package]] name = "cfgv" version = "3.5.0" @@ -11,6 +72,31 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl", hash = "sha256:a8dc6b26ad22ff227d2634a65cb388215ce6cc96bbcc5cfde7641ae87e8dacc0", size = 7445, upload-time = "2025-11-19T20:55:50.744Z" }, ] +[[package]] +name = "charset-normalizer" +version = "3.4.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/13/69/33ddede1939fdd074bce5434295f38fae7136463422fe4fd3e0e89b98062/charset_normalizer-3.4.4.tar.gz", hash = "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a", size = 129418, upload-time = "2025-10-14T04:42:32.879Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/35/7051599bd493e62411d6ede36fd5af83a38f37c4767b92884df7301db25d/charset_normalizer-3.4.4-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:da3326d9e65ef63a817ecbcc0df6e94463713b754fe293eaa03da99befb9a5bd", size = 207746, upload-time = "2025-10-14T04:41:33.773Z" }, + { url = "https://files.pythonhosted.org/packages/10/9a/97c8d48ef10d6cd4fcead2415523221624bf58bcf68a802721a6bc807c8f/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8af65f14dc14a79b924524b1e7fffe304517b2bff5a58bf64f30b98bbc5079eb", size = 147889, upload-time = "2025-10-14T04:41:34.897Z" }, + { url = "https://files.pythonhosted.org/packages/10/bf/979224a919a1b606c82bd2c5fa49b5c6d5727aa47b4312bb27b1734f53cd/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74664978bb272435107de04e36db5a9735e78232b85b77d45cfb38f758efd33e", size = 143641, upload-time = "2025-10-14T04:41:36.116Z" }, + { url = "https://files.pythonhosted.org/packages/ba/33/0ad65587441fc730dc7bd90e9716b30b4702dc7b617e6ba4997dc8651495/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:752944c7ffbfdd10c074dc58ec2d5a8a4cd9493b314d367c14d24c17684ddd14", size = 160779, upload-time = "2025-10-14T04:41:37.229Z" }, + { url = "https://files.pythonhosted.org/packages/67/ed/331d6b249259ee71ddea93f6f2f0a56cfebd46938bde6fcc6f7b9a3d0e09/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d1f13550535ad8cff21b8d757a3257963e951d96e20ec82ab44bc64aeb62a191", size = 159035, upload-time = "2025-10-14T04:41:38.368Z" }, + { url = "https://files.pythonhosted.org/packages/67/ff/f6b948ca32e4f2a4576aa129d8bed61f2e0543bf9f5f2b7fc3758ed005c9/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ecaae4149d99b1c9e7b88bb03e3221956f68fd6d50be2ef061b2381b61d20838", size = 152542, upload-time = "2025-10-14T04:41:39.862Z" }, + { url = "https://files.pythonhosted.org/packages/16/85/276033dcbcc369eb176594de22728541a925b2632f9716428c851b149e83/charset_normalizer-3.4.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cb6254dc36b47a990e59e1068afacdcd02958bdcce30bb50cc1700a8b9d624a6", size = 149524, upload-time = "2025-10-14T04:41:41.319Z" }, + { url = "https://files.pythonhosted.org/packages/9e/f2/6a2a1f722b6aba37050e626530a46a68f74e63683947a8acff92569f979a/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c8ae8a0f02f57a6e61203a31428fa1d677cbe50c93622b4149d5c0f319c1d19e", size = 150395, upload-time = "2025-10-14T04:41:42.539Z" }, + { url = "https://files.pythonhosted.org/packages/60/bb/2186cb2f2bbaea6338cad15ce23a67f9b0672929744381e28b0592676824/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:47cc91b2f4dd2833fddaedd2893006b0106129d4b94fdb6af1f4ce5a9965577c", size = 143680, upload-time = "2025-10-14T04:41:43.661Z" }, + { url = "https://files.pythonhosted.org/packages/7d/a5/bf6f13b772fbb2a90360eb620d52ed8f796f3c5caee8398c3b2eb7b1c60d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:82004af6c302b5d3ab2cfc4cc5f29db16123b1a8417f2e25f9066f91d4411090", size = 162045, upload-time = "2025-10-14T04:41:44.821Z" }, + { url = "https://files.pythonhosted.org/packages/df/c5/d1be898bf0dc3ef9030c3825e5d3b83f2c528d207d246cbabe245966808d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b7d8f6c26245217bd2ad053761201e9f9680f8ce52f0fcd8d0755aeae5b2152", size = 149687, upload-time = "2025-10-14T04:41:46.442Z" }, + { url = "https://files.pythonhosted.org/packages/a5/42/90c1f7b9341eef50c8a1cb3f098ac43b0508413f33affd762855f67a410e/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:799a7a5e4fb2d5898c60b640fd4981d6a25f1c11790935a44ce38c54e985f828", size = 160014, upload-time = "2025-10-14T04:41:47.631Z" }, + { url = "https://files.pythonhosted.org/packages/76/be/4d3ee471e8145d12795ab655ece37baed0929462a86e72372fd25859047c/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:99ae2cffebb06e6c22bdc25801d7b30f503cc87dbd283479e7b606f70aff57ec", size = 154044, upload-time = "2025-10-14T04:41:48.81Z" }, + { url = "https://files.pythonhosted.org/packages/b0/6f/8f7af07237c34a1defe7defc565a9bc1807762f672c0fde711a4b22bf9c0/charset_normalizer-3.4.4-cp314-cp314-win32.whl", hash = "sha256:f9d332f8c2a2fcbffe1378594431458ddbef721c1769d78e2cbc06280d8155f9", size = 99940, upload-time = "2025-10-14T04:41:49.946Z" }, + { url = "https://files.pythonhosted.org/packages/4b/51/8ade005e5ca5b0d80fb4aff72a3775b325bdc3d27408c8113811a7cbe640/charset_normalizer-3.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:8a6562c3700cce886c5be75ade4a5db4214fda19fede41d9792d100288d8f94c", size = 107104, upload-time = "2025-10-14T04:41:51.051Z" }, + { url = "https://files.pythonhosted.org/packages/da/5f/6b8f83a55bb8278772c5ae54a577f3099025f9ade59d0136ac24a0df4bde/charset_normalizer-3.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:de00632ca48df9daf77a2c65a484531649261ec9f25489917f09e455cb09ddb2", size = 100743, upload-time = "2025-10-14T04:41:52.122Z" }, + { url = "https://files.pythonhosted.org/packages/0a/4c/925909008ed5a988ccbb72dcc897407e5d6d3bd72410d69e051fc0c14647/charset_normalizer-3.4.4-py3-none-any.whl", hash = "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f", size = 53402, upload-time = "2025-10-14T04:42:31.76Z" }, +] + [[package]] name = "colorama" version = "0.4.6" @@ -20,6 +106,45 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, ] +[[package]] +name = "coverage" +version = "7.13.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/24/56/95b7e30fa389756cb56630faa728da46a27b8c6eb46f9d557c68fff12b65/coverage-7.13.4.tar.gz", hash = "sha256:e5c8f6ed1e61a8b2dcdf31eb0b9bbf0130750ca79c1c49eb898e2ad86f5ccc91", size = 827239, upload-time = "2026-02-09T12:59:03.86Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/92/11/a9cf762bb83386467737d32187756a42094927150c3e107df4cb078e8590/coverage-7.13.4-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:300deaee342f90696ed186e3a00c71b5b3d27bffe9e827677954f4ee56969601", size = 219522, upload-time = "2026-02-09T12:58:08.623Z" }, + { url = "https://files.pythonhosted.org/packages/d3/28/56e6d892b7b052236d67c95f1936b6a7cf7c3e2634bf27610b8cbd7f9c60/coverage-7.13.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:29e3220258d682b6226a9b0925bc563ed9a1ebcff3cad30f043eceea7eaf2689", size = 219855, upload-time = "2026-02-09T12:58:10.176Z" }, + { url = "https://files.pythonhosted.org/packages/e5/69/233459ee9eb0c0d10fcc2fe425a029b3fa5ce0f040c966ebce851d030c70/coverage-7.13.4-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:391ee8f19bef69210978363ca930f7328081c6a0152f1166c91f0b5fdd2a773c", size = 250887, upload-time = "2026-02-09T12:58:12.503Z" }, + { url = "https://files.pythonhosted.org/packages/06/90/2cdab0974b9b5bbc1623f7876b73603aecac11b8d95b85b5b86b32de5eab/coverage-7.13.4-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0dd7ab8278f0d58a0128ba2fca25824321f05d059c1441800e934ff2efa52129", size = 253396, upload-time = "2026-02-09T12:58:14.615Z" }, + { url = "https://files.pythonhosted.org/packages/ac/15/ea4da0f85bf7d7b27635039e649e99deb8173fe551096ea15017f7053537/coverage-7.13.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:78cdf0d578b15148b009ccf18c686aa4f719d887e76e6b40c38ffb61d264a552", size = 254745, upload-time = "2026-02-09T12:58:16.162Z" }, + { url = "https://files.pythonhosted.org/packages/99/11/bb356e86920c655ca4d61daee4e2bbc7258f0a37de0be32d233b561134ff/coverage-7.13.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:48685fee12c2eb3b27c62f2658e7ea21e9c3239cba5a8a242801a0a3f6a8c62a", size = 257055, upload-time = "2026-02-09T12:58:17.892Z" }, + { url = "https://files.pythonhosted.org/packages/c9/0f/9ae1f8cb17029e09da06ca4e28c9e1d5c1c0a511c7074592e37e0836c915/coverage-7.13.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4e83efc079eb39480e6346a15a1bcb3e9b04759c5202d157e1dd4303cd619356", size = 250911, upload-time = "2026-02-09T12:58:19.495Z" }, + { url = "https://files.pythonhosted.org/packages/89/3a/adfb68558fa815cbc29747b553bc833d2150228f251b127f1ce97e48547c/coverage-7.13.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ecae9737b72408d6a950f7e525f30aca12d4bd8dd95e37342e5beb3a2a8c4f71", size = 252754, upload-time = "2026-02-09T12:58:21.064Z" }, + { url = "https://files.pythonhosted.org/packages/32/b1/540d0c27c4e748bd3cd0bd001076ee416eda993c2bae47a73b7cc9357931/coverage-7.13.4-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ae4578f8528569d3cf303fef2ea569c7f4c4059a38c8667ccef15c6e1f118aa5", size = 250720, upload-time = "2026-02-09T12:58:22.622Z" }, + { url = "https://files.pythonhosted.org/packages/c7/95/383609462b3ffb1fe133014a7c84fc0dd01ed55ac6140fa1093b5af7ebb1/coverage-7.13.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:6fdef321fdfbb30a197efa02d48fcd9981f0d8ad2ae8903ac318adc653f5df98", size = 254994, upload-time = "2026-02-09T12:58:24.548Z" }, + { url = "https://files.pythonhosted.org/packages/f7/ba/1761138e86c81680bfc3c49579d66312865457f9fe405b033184e5793cb3/coverage-7.13.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b0f6ccf3dbe577170bebfce1318707d0e8c3650003cb4b3a9dd744575daa8b5", size = 250531, upload-time = "2026-02-09T12:58:26.271Z" }, + { url = "https://files.pythonhosted.org/packages/f8/8e/05900df797a9c11837ab59c4d6fe94094e029582aab75c3309a93e6fb4e3/coverage-7.13.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:75fcd519f2a5765db3f0e391eb3b7d150cce1a771bf4c9f861aeab86c767a3c0", size = 252189, upload-time = "2026-02-09T12:58:27.807Z" }, + { url = "https://files.pythonhosted.org/packages/00/bd/29c9f2db9ea4ed2738b8a9508c35626eb205d51af4ab7bf56a21a2e49926/coverage-7.13.4-cp314-cp314-win32.whl", hash = "sha256:8e798c266c378da2bd819b0677df41ab46d78065fb2a399558f3f6cae78b2fbb", size = 222258, upload-time = "2026-02-09T12:58:29.441Z" }, + { url = "https://files.pythonhosted.org/packages/a7/4d/1f8e723f6829977410efeb88f73673d794075091c8c7c18848d273dc9d73/coverage-7.13.4-cp314-cp314-win_amd64.whl", hash = "sha256:245e37f664d89861cf2329c9afa2c1fe9e6d4e1a09d872c947e70718aeeac505", size = 223073, upload-time = "2026-02-09T12:58:31.026Z" }, + { url = "https://files.pythonhosted.org/packages/51/5b/84100025be913b44e082ea32abcf1afbf4e872f5120b7a1cab1d331b1e13/coverage-7.13.4-cp314-cp314-win_arm64.whl", hash = "sha256:ad27098a189e5838900ce4c2a99f2fe42a0bf0c2093c17c69b45a71579e8d4a2", size = 221638, upload-time = "2026-02-09T12:58:32.599Z" }, + { url = "https://files.pythonhosted.org/packages/a7/e4/c884a405d6ead1370433dad1e3720216b4f9fd8ef5b64bfd984a2a60a11a/coverage-7.13.4-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:85480adfb35ffc32d40918aad81b89c69c9cc5661a9b8a81476d3e645321a056", size = 220246, upload-time = "2026-02-09T12:58:34.181Z" }, + { url = "https://files.pythonhosted.org/packages/81/5c/4d7ed8b23b233b0fffbc9dfec53c232be2e695468523242ea9fd30f97ad2/coverage-7.13.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:79be69cf7f3bf9b0deeeb062eab7ac7f36cd4cc4c4dd694bd28921ba4d8596cc", size = 220514, upload-time = "2026-02-09T12:58:35.704Z" }, + { url = "https://files.pythonhosted.org/packages/2f/6f/3284d4203fd2f28edd73034968398cd2d4cb04ab192abc8cff007ea35679/coverage-7.13.4-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:caa421e2684e382c5d8973ac55e4f36bed6821a9bad5c953494de960c74595c9", size = 261877, upload-time = "2026-02-09T12:58:37.864Z" }, + { url = "https://files.pythonhosted.org/packages/09/aa/b672a647bbe1556a85337dc95bfd40d146e9965ead9cc2fe81bde1e5cbce/coverage-7.13.4-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:14375934243ee05f56c45393fe2ce81fe5cc503c07cee2bdf1725fb8bef3ffaf", size = 264004, upload-time = "2026-02-09T12:58:39.492Z" }, + { url = "https://files.pythonhosted.org/packages/79/a1/aa384dbe9181f98bba87dd23dda436f0c6cf2e148aecbb4e50fc51c1a656/coverage-7.13.4-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:25a41c3104d08edb094d9db0d905ca54d0cd41c928bb6be3c4c799a54753af55", size = 266408, upload-time = "2026-02-09T12:58:41.852Z" }, + { url = "https://files.pythonhosted.org/packages/53/5e/5150bf17b4019bc600799f376bb9606941e55bd5a775dc1e096b6ffea952/coverage-7.13.4-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6f01afcff62bf9a08fb32b2c1d6e924236c0383c02c790732b6537269e466a72", size = 267544, upload-time = "2026-02-09T12:58:44.093Z" }, + { url = "https://files.pythonhosted.org/packages/e0/ed/f1de5c675987a4a7a672250d2c5c9d73d289dbf13410f00ed7181d8017dd/coverage-7.13.4-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:eb9078108fbf0bcdde37c3f4779303673c2fa1fe8f7956e68d447d0dd426d38a", size = 260980, upload-time = "2026-02-09T12:58:45.721Z" }, + { url = "https://files.pythonhosted.org/packages/b3/e3/fe758d01850aa172419a6743fe76ba8b92c29d181d4f676ffe2dae2ba631/coverage-7.13.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0e086334e8537ddd17e5f16a344777c1ab8194986ec533711cbe6c41cde841b6", size = 263871, upload-time = "2026-02-09T12:58:47.334Z" }, + { url = "https://files.pythonhosted.org/packages/b6/76/b829869d464115e22499541def9796b25312b8cf235d3bb00b39f1675395/coverage-7.13.4-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:725d985c5ab621268b2edb8e50dfe57633dc69bda071abc470fed55a14935fd3", size = 261472, upload-time = "2026-02-09T12:58:48.995Z" }, + { url = "https://files.pythonhosted.org/packages/14/9e/caedb1679e73e2f6ad240173f55218488bfe043e38da577c4ec977489915/coverage-7.13.4-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:3c06f0f1337c667b971ca2f975523347e63ec5e500b9aa5882d91931cd3ef750", size = 265210, upload-time = "2026-02-09T12:58:51.178Z" }, + { url = "https://files.pythonhosted.org/packages/3a/10/0dd02cb009b16ede425b49ec344aba13a6ae1dc39600840ea6abcb085ac4/coverage-7.13.4-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:590c0ed4bf8e85f745e6b805b2e1c457b2e33d5255dd9729743165253bc9ad39", size = 260319, upload-time = "2026-02-09T12:58:53.081Z" }, + { url = "https://files.pythonhosted.org/packages/92/8e/234d2c927af27c6d7a5ffad5bd2cf31634c46a477b4c7adfbfa66baf7ebb/coverage-7.13.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:eb30bf180de3f632cd043322dad5751390e5385108b2807368997d1a92a509d0", size = 262638, upload-time = "2026-02-09T12:58:55.258Z" }, + { url = "https://files.pythonhosted.org/packages/2f/64/e5547c8ff6964e5965c35a480855911b61509cce544f4d442caa759a0702/coverage-7.13.4-cp314-cp314t-win32.whl", hash = "sha256:c4240e7eded42d131a2d2c4dec70374b781b043ddc79a9de4d55ca71f8e98aea", size = 223040, upload-time = "2026-02-09T12:58:56.936Z" }, + { url = "https://files.pythonhosted.org/packages/c7/96/38086d58a181aac86d503dfa9c47eb20715a79c3e3acbdf786e92e5c09a8/coverage-7.13.4-cp314-cp314t-win_amd64.whl", hash = "sha256:4c7d3cc01e7350f2f0f6f7036caaf5673fb56b6998889ccfe9e1c1fe75a9c932", size = 224148, upload-time = "2026-02-09T12:58:58.645Z" }, + { url = "https://files.pythonhosted.org/packages/ce/72/8d10abd3740a0beb98c305e0c3faf454366221c0f37a8bcf8f60020bb65a/coverage-7.13.4-cp314-cp314t-win_arm64.whl", hash = "sha256:23e3f687cf945070d1c90f85db66d11e3025665d8dafa831301a0e0038f3db9b", size = 222172, upload-time = "2026-02-09T12:59:00.396Z" }, + { url = "https://files.pythonhosted.org/packages/0d/4a/331fe2caf6799d591109bb9c08083080f6de90a823695d412a935622abb2/coverage-7.13.4-py3-none-any.whl", hash = "sha256:1af1641e57cf7ba1bd67d677c9abdbcd6cc2ab7da3bca7fa1e2b7e50e65f2ad0", size = 211242, upload-time = "2026-02-09T12:59:02.032Z" }, +] + [[package]] name = "distlib" version = "0.4.0" @@ -29,6 +154,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16", size = 469047, upload-time = "2025-07-17T16:51:58.613Z" }, ] +[[package]] +name = "docutils" +version = "0.22.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ae/b6/03bb70946330e88ffec97aefd3ea75ba575cb2e762061e0e62a213befee8/docutils-0.22.4.tar.gz", hash = "sha256:4db53b1fde9abecbb74d91230d32ab626d94f6badfc575d6db9194a49df29968", size = 2291750, upload-time = "2025-12-18T19:00:26.443Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/02/10/5da547df7a391dcde17f59520a231527b8571e6f46fc8efb02ccb370ab12/docutils-0.22.4-py3-none-any.whl", hash = "sha256:d0013f540772d1420576855455d050a2180186c91c15779301ac2ccb3eeb68de", size = 633196, upload-time = "2025-12-18T19:00:18.077Z" }, +] + [[package]] name = "filelock" version = "3.24.3" @@ -47,6 +181,24 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b8/58/40fbbcefeda82364720eba5cf2270f98496bdfa19ea75b4cccae79c698e6/identify-2.6.16-py2.py3-none-any.whl", hash = "sha256:391ee4d77741d994189522896270b787aed8670389bfd60f326d677d64a6dfb0", size = 99202, upload-time = "2026-01-12T18:58:56.627Z" }, ] +[[package]] +name = "idna" +version = "3.11" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, +] + +[[package]] +name = "imagesize" +version = "1.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a7/84/62473fb57d61e31fef6e36d64a179c8781605429fd927b5dd608c997be31/imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a", size = 1280026, upload-time = "2022-07-01T12:21:05.687Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ff/62/85c4c919272577931d407be5ba5d71c20f0b616d31a0befe0ae45bb79abd/imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b", size = 8769, upload-time = "2022-07-01T12:21:02.467Z" }, +] + [[package]] name = "iniconfig" version = "2.3.0" @@ -56,6 +208,52 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, ] +[[package]] +name = "jinja2" +version = "3.1.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, +] + +[[package]] +name = "librt" +version = "0.8.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/56/9c/b4b0c54d84da4a94b37bd44151e46d5e583c9534c7e02250b961b1b6d8a8/librt-0.8.1.tar.gz", hash = "sha256:be46a14693955b3bd96014ccbdb8339ee8c9346fbe11c1b78901b55125f14c73", size = 177471, upload-time = "2026-02-17T16:13:06.101Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c9/6a/907ef6800f7bca71b525a05f1839b21f708c09043b1c6aa77b6b827b3996/librt-0.8.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:6cfa7fe54fd4d1f47130017351a959fe5804bda7a0bc7e07a2cdbc3fdd28d34f", size = 66081, upload-time = "2026-02-17T16:12:12.766Z" }, + { url = "https://files.pythonhosted.org/packages/1b/18/25e991cd5640c9fb0f8d91b18797b29066b792f17bf8493da183bf5caabe/librt-0.8.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:228c2409c079f8c11fb2e5d7b277077f694cb93443eb760e00b3b83cb8b3176c", size = 68309, upload-time = "2026-02-17T16:12:13.756Z" }, + { url = "https://files.pythonhosted.org/packages/a4/36/46820d03f058cfb5a9de5940640ba03165ed8aded69e0733c417bb04df34/librt-0.8.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7aae78ab5e3206181780e56912d1b9bb9f90a7249ce12f0e8bf531d0462dd0fc", size = 196804, upload-time = "2026-02-17T16:12:14.818Z" }, + { url = "https://files.pythonhosted.org/packages/59/18/5dd0d3b87b8ff9c061849fbdb347758d1f724b9a82241aa908e0ec54ccd0/librt-0.8.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:172d57ec04346b047ca6af181e1ea4858086c80bdf455f61994c4aa6fc3f866c", size = 206907, upload-time = "2026-02-17T16:12:16.513Z" }, + { url = "https://files.pythonhosted.org/packages/d1/96/ef04902aad1424fd7299b62d1890e803e6ab4018c3044dca5922319c4b97/librt-0.8.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6b1977c4ea97ce5eb7755a78fae68d87e4102e4aaf54985e8b56806849cc06a3", size = 221217, upload-time = "2026-02-17T16:12:17.906Z" }, + { url = "https://files.pythonhosted.org/packages/6d/ff/7e01f2dda84a8f5d280637a2e5827210a8acca9a567a54507ef1c75b342d/librt-0.8.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:10c42e1f6fd06733ef65ae7bebce2872bcafd8d6e6b0a08fe0a05a23b044fb14", size = 214622, upload-time = "2026-02-17T16:12:19.108Z" }, + { url = "https://files.pythonhosted.org/packages/1e/8c/5b093d08a13946034fed57619742f790faf77058558b14ca36a6e331161e/librt-0.8.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:4c8dfa264b9193c4ee19113c985c95f876fae5e51f731494fc4e0cf594990ba7", size = 221987, upload-time = "2026-02-17T16:12:20.331Z" }, + { url = "https://files.pythonhosted.org/packages/d3/cc/86b0b3b151d40920ad45a94ce0171dec1aebba8a9d72bb3fa00c73ab25dd/librt-0.8.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:01170b6729a438f0dedc4a26ed342e3dc4f02d1000b4b19f980e1877f0c297e6", size = 215132, upload-time = "2026-02-17T16:12:21.54Z" }, + { url = "https://files.pythonhosted.org/packages/fc/be/8588164a46edf1e69858d952654e216a9a91174688eeefb9efbb38a9c799/librt-0.8.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:7b02679a0d783bdae30d443025b94465d8c3dc512f32f5b5031f93f57ac32071", size = 215195, upload-time = "2026-02-17T16:12:23.073Z" }, + { url = "https://files.pythonhosted.org/packages/f5/f2/0b9279bea735c734d69344ecfe056c1ba211694a72df10f568745c899c76/librt-0.8.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:190b109bb69592a3401fe1ffdea41a2e73370ace2ffdc4a0e8e2b39cdea81b78", size = 237946, upload-time = "2026-02-17T16:12:24.275Z" }, + { url = "https://files.pythonhosted.org/packages/e9/cc/5f2a34fbc8aeb35314a3641f9956fa9051a947424652fad9882be7a97949/librt-0.8.1-cp314-cp314-win32.whl", hash = "sha256:e70a57ecf89a0f64c24e37f38d3fe217a58169d2fe6ed6d70554964042474023", size = 50689, upload-time = "2026-02-17T16:12:25.766Z" }, + { url = "https://files.pythonhosted.org/packages/a0/76/cd4d010ab2147339ca2b93e959c3686e964edc6de66ddacc935c325883d7/librt-0.8.1-cp314-cp314-win_amd64.whl", hash = "sha256:7e2f3edca35664499fbb36e4770650c4bd4a08abc1f4458eab9df4ec56389730", size = 57875, upload-time = "2026-02-17T16:12:27.465Z" }, + { url = "https://files.pythonhosted.org/packages/84/0f/2143cb3c3ca48bd3379dcd11817163ca50781927c4537345d608b5045998/librt-0.8.1-cp314-cp314-win_arm64.whl", hash = "sha256:0d2f82168e55ddefd27c01c654ce52379c0750ddc31ee86b4b266bcf4d65f2a3", size = 48058, upload-time = "2026-02-17T16:12:28.556Z" }, + { url = "https://files.pythonhosted.org/packages/d2/0e/9b23a87e37baf00311c3efe6b48d6b6c168c29902dfc3f04c338372fd7db/librt-0.8.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2c74a2da57a094bd48d03fa5d196da83d2815678385d2978657499063709abe1", size = 68313, upload-time = "2026-02-17T16:12:29.659Z" }, + { url = "https://files.pythonhosted.org/packages/db/9a/859c41e5a4f1c84200a7d2b92f586aa27133c8243b6cac9926f6e54d01b9/librt-0.8.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a355d99c4c0d8e5b770313b8b247411ed40949ca44e33e46a4789b9293a907ee", size = 70994, upload-time = "2026-02-17T16:12:31.516Z" }, + { url = "https://files.pythonhosted.org/packages/4c/28/10605366ee599ed34223ac2bf66404c6fb59399f47108215d16d5ad751a8/librt-0.8.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:2eb345e8b33fb748227409c9f1233d4df354d6e54091f0e8fc53acdb2ffedeb7", size = 220770, upload-time = "2026-02-17T16:12:33.294Z" }, + { url = "https://files.pythonhosted.org/packages/af/8d/16ed8fd452dafae9c48d17a6bc1ee3e818fd40ef718d149a8eff2c9f4ea2/librt-0.8.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9be2f15e53ce4e83cc08adc29b26fb5978db62ef2a366fbdf716c8a6c8901040", size = 235409, upload-time = "2026-02-17T16:12:35.443Z" }, + { url = "https://files.pythonhosted.org/packages/89/1b/7bdf3e49349c134b25db816e4a3db6b94a47ac69d7d46b1e682c2c4949be/librt-0.8.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:785ae29c1f5c6e7c2cde2c7c0e148147f4503da3abc5d44d482068da5322fd9e", size = 246473, upload-time = "2026-02-17T16:12:36.656Z" }, + { url = "https://files.pythonhosted.org/packages/4e/8a/91fab8e4fd2a24930a17188c7af5380eb27b203d72101c9cc000dbdfd95a/librt-0.8.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1d3a7da44baf692f0c6aeb5b2a09c5e6fc7a703bca9ffa337ddd2e2da53f7732", size = 238866, upload-time = "2026-02-17T16:12:37.849Z" }, + { url = "https://files.pythonhosted.org/packages/b9/e0/c45a098843fc7c07e18a7f8a24ca8496aecbf7bdcd54980c6ca1aaa79a8e/librt-0.8.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5fc48998000cbc39ec0d5311312dda93ecf92b39aaf184c5e817d5d440b29624", size = 250248, upload-time = "2026-02-17T16:12:39.445Z" }, + { url = "https://files.pythonhosted.org/packages/82/30/07627de23036640c952cce0c1fe78972e77d7d2f8fd54fa5ef4554ff4a56/librt-0.8.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:e96baa6820280077a78244b2e06e416480ed859bbd8e5d641cf5742919d8beb4", size = 240629, upload-time = "2026-02-17T16:12:40.889Z" }, + { url = "https://files.pythonhosted.org/packages/fb/c1/55bfe1ee3542eba055616f9098eaf6eddb966efb0ca0f44eaa4aba327307/librt-0.8.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:31362dbfe297b23590530007062c32c6f6176f6099646bb2c95ab1b00a57c382", size = 239615, upload-time = "2026-02-17T16:12:42.446Z" }, + { url = "https://files.pythonhosted.org/packages/2b/39/191d3d28abc26c9099b19852e6c99f7f6d400b82fa5a4e80291bd3803e19/librt-0.8.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cc3656283d11540ab0ea01978378e73e10002145117055e03722417aeab30994", size = 263001, upload-time = "2026-02-17T16:12:43.627Z" }, + { url = "https://files.pythonhosted.org/packages/b9/eb/7697f60fbe7042ab4e88f4ee6af496b7f222fffb0a4e3593ef1f29f81652/librt-0.8.1-cp314-cp314t-win32.whl", hash = "sha256:738f08021b3142c2918c03692608baed43bc51144c29e35807682f8070ee2a3a", size = 51328, upload-time = "2026-02-17T16:12:45.148Z" }, + { url = "https://files.pythonhosted.org/packages/7c/72/34bf2eb7a15414a23e5e70ecb9440c1d3179f393d9349338a91e2781c0fb/librt-0.8.1-cp314-cp314t-win_amd64.whl", hash = "sha256:89815a22daf9c51884fb5dbe4f1ef65ee6a146e0b6a8df05f753e2e4a9359bf4", size = 58722, upload-time = "2026-02-17T16:12:46.85Z" }, + { url = "https://files.pythonhosted.org/packages/b2/c8/d148e041732d631fc76036f8b30fae4e77b027a1e95b7a84bb522481a940/librt-0.8.1-cp314-cp314t-win_arm64.whl", hash = "sha256:bf512a71a23504ed08103a13c941f763db13fb11177beb3d9244c98c29fb4a61", size = 48755, upload-time = "2026-02-17T16:12:47.943Z" }, +] + [[package]] name = "llvmlite" version = "0.46.0" @@ -68,6 +266,116 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/30/a8/e61a8c2b3cc7a597073d9cde1fcbb567e9d827f1db30c93cf80422eac70d/llvmlite-0.46.0-cp314-cp314-win_amd64.whl", hash = "sha256:7821eda3ec1f18050f981819756631d60b6d7ab1a6cf806d9efefbe3f4082d61", size = 39153056, upload-time = "2025-12-08T18:15:33.938Z" }, ] +[[package]] +name = "markdown-it-py" +version = "4.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mdurl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5b/f5/4ec618ed16cc4f8fb3b701563655a69816155e79e24a17b651541804721d/markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3", size = 73070, upload-time = "2025-08-11T12:57:52.854Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147", size = 87321, upload-time = "2025-08-11T12:57:51.923Z" }, +] + +[[package]] +name = "markupsafe" +version = "3.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/33/8a/8e42d4838cd89b7dde187011e97fe6c3af66d8c044997d2183fbd6d31352/markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe", size = 11619, upload-time = "2025-09-27T18:37:06.342Z" }, + { url = "https://files.pythonhosted.org/packages/b5/64/7660f8a4a8e53c924d0fa05dc3a55c9cee10bbd82b11c5afb27d44b096ce/markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026", size = 12029, upload-time = "2025-09-27T18:37:07.213Z" }, + { url = "https://files.pythonhosted.org/packages/da/ef/e648bfd021127bef5fa12e1720ffed0c6cbb8310c8d9bea7266337ff06de/markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737", size = 24408, upload-time = "2025-09-27T18:37:09.572Z" }, + { url = "https://files.pythonhosted.org/packages/41/3c/a36c2450754618e62008bf7435ccb0f88053e07592e6028a34776213d877/markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97", size = 23005, upload-time = "2025-09-27T18:37:10.58Z" }, + { url = "https://files.pythonhosted.org/packages/bc/20/b7fdf89a8456b099837cd1dc21974632a02a999ec9bf7ca3e490aacd98e7/markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d", size = 22048, upload-time = "2025-09-27T18:37:11.547Z" }, + { url = "https://files.pythonhosted.org/packages/9a/a7/591f592afdc734f47db08a75793a55d7fbcc6902a723ae4cfbab61010cc5/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda", size = 23821, upload-time = "2025-09-27T18:37:12.48Z" }, + { url = "https://files.pythonhosted.org/packages/7d/33/45b24e4f44195b26521bc6f1a82197118f74df348556594bd2262bda1038/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf", size = 21606, upload-time = "2025-09-27T18:37:13.485Z" }, + { url = "https://files.pythonhosted.org/packages/ff/0e/53dfaca23a69fbfbbf17a4b64072090e70717344c52eaaaa9c5ddff1e5f0/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe", size = 23043, upload-time = "2025-09-27T18:37:14.408Z" }, + { url = "https://files.pythonhosted.org/packages/46/11/f333a06fc16236d5238bfe74daccbca41459dcd8d1fa952e8fbd5dccfb70/markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9", size = 14747, upload-time = "2025-09-27T18:37:15.36Z" }, + { url = "https://files.pythonhosted.org/packages/28/52/182836104b33b444e400b14f797212f720cbc9ed6ba34c800639d154e821/markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581", size = 15341, upload-time = "2025-09-27T18:37:16.496Z" }, + { url = "https://files.pythonhosted.org/packages/6f/18/acf23e91bd94fd7b3031558b1f013adfa21a8e407a3fdb32745538730382/markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4", size = 14073, upload-time = "2025-09-27T18:37:17.476Z" }, + { url = "https://files.pythonhosted.org/packages/3c/f0/57689aa4076e1b43b15fdfa646b04653969d50cf30c32a102762be2485da/markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab", size = 11661, upload-time = "2025-09-27T18:37:18.453Z" }, + { url = "https://files.pythonhosted.org/packages/89/c3/2e67a7ca217c6912985ec766c6393b636fb0c2344443ff9d91404dc4c79f/markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175", size = 12069, upload-time = "2025-09-27T18:37:19.332Z" }, + { url = "https://files.pythonhosted.org/packages/f0/00/be561dce4e6ca66b15276e184ce4b8aec61fe83662cce2f7d72bd3249d28/markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634", size = 25670, upload-time = "2025-09-27T18:37:20.245Z" }, + { url = "https://files.pythonhosted.org/packages/50/09/c419f6f5a92e5fadde27efd190eca90f05e1261b10dbd8cbcb39cd8ea1dc/markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50", size = 23598, upload-time = "2025-09-27T18:37:21.177Z" }, + { url = "https://files.pythonhosted.org/packages/22/44/a0681611106e0b2921b3033fc19bc53323e0b50bc70cffdd19f7d679bb66/markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e", size = 23261, upload-time = "2025-09-27T18:37:22.167Z" }, + { url = "https://files.pythonhosted.org/packages/5f/57/1b0b3f100259dc9fffe780cfb60d4be71375510e435efec3d116b6436d43/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5", size = 24835, upload-time = "2025-09-27T18:37:23.296Z" }, + { url = "https://files.pythonhosted.org/packages/26/6a/4bf6d0c97c4920f1597cc14dd720705eca0bf7c787aebc6bb4d1bead5388/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523", size = 22733, upload-time = "2025-09-27T18:37:24.237Z" }, + { url = "https://files.pythonhosted.org/packages/14/c7/ca723101509b518797fedc2fdf79ba57f886b4aca8a7d31857ba3ee8281f/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc", size = 23672, upload-time = "2025-09-27T18:37:25.271Z" }, + { url = "https://files.pythonhosted.org/packages/fb/df/5bd7a48c256faecd1d36edc13133e51397e41b73bb77e1a69deab746ebac/markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d", size = 14819, upload-time = "2025-09-27T18:37:26.285Z" }, + { url = "https://files.pythonhosted.org/packages/1a/8a/0402ba61a2f16038b48b39bccca271134be00c5c9f0f623208399333c448/markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9", size = 15426, upload-time = "2025-09-27T18:37:27.316Z" }, + { url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z" }, +] + +[[package]] +name = "mdit-py-plugins" +version = "0.5.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown-it-py" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b2/fd/a756d36c0bfba5f6e39a1cdbdbfdd448dc02692467d83816dff4592a1ebc/mdit_py_plugins-0.5.0.tar.gz", hash = "sha256:f4918cb50119f50446560513a8e311d574ff6aaed72606ddae6d35716fe809c6", size = 44655, upload-time = "2025-08-11T07:25:49.083Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fb/86/dd6e5db36df29e76c7a7699123569a4a18c1623ce68d826ed96c62643cae/mdit_py_plugins-0.5.0-py3-none-any.whl", hash = "sha256:07a08422fc1936a5d26d146759e9155ea466e842f5ab2f7d2266dd084c8dab1f", size = 57205, upload-time = "2025-08-11T07:25:47.597Z" }, +] + +[[package]] +name = "mdurl" +version = "0.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, +] + +[[package]] +name = "mypy" +version = "1.19.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "librt", marker = "platform_python_implementation != 'PyPy'" }, + { name = "mypy-extensions" }, + { name = "pathspec" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f5/db/4efed9504bc01309ab9c2da7e352cc223569f05478012b5d9ece38fd44d2/mypy-1.19.1.tar.gz", hash = "sha256:19d88bb05303fe63f71dd2c6270daca27cb9401c4ca8255fe50d1d920e0eb9ba", size = 3582404, upload-time = "2025-12-15T05:03:48.42Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/de/eb/b83e75f4c820c4247a58580ef86fcd35165028f191e7e1ba57128c52782d/mypy-1.19.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:06e6170bd5836770e8104c8fdd58e5e725cfeb309f0a6c681a811f557e97eac1", size = 13199744, upload-time = "2025-12-15T05:03:30.823Z" }, + { url = "https://files.pythonhosted.org/packages/94/28/52785ab7bfa165f87fcbb61547a93f98bb20e7f82f90f165a1f69bce7b3d/mypy-1.19.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:804bd67b8054a85447c8954215a906d6eff9cabeabe493fb6334b24f4bfff718", size = 12215815, upload-time = "2025-12-15T05:02:42.323Z" }, + { url = "https://files.pythonhosted.org/packages/0a/c6/bdd60774a0dbfb05122e3e925f2e9e846c009e479dcec4821dad881f5b52/mypy-1.19.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:21761006a7f497cb0d4de3d8ef4ca70532256688b0523eee02baf9eec895e27b", size = 12740047, upload-time = "2025-12-15T05:03:33.168Z" }, + { url = "https://files.pythonhosted.org/packages/32/2a/66ba933fe6c76bd40d1fe916a83f04fed253152f451a877520b3c4a5e41e/mypy-1.19.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:28902ee51f12e0f19e1e16fbe2f8f06b6637f482c459dd393efddd0ec7f82045", size = 13601998, upload-time = "2025-12-15T05:03:13.056Z" }, + { url = "https://files.pythonhosted.org/packages/e3/da/5055c63e377c5c2418760411fd6a63ee2b96cf95397259038756c042574f/mypy-1.19.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:481daf36a4c443332e2ae9c137dfee878fcea781a2e3f895d54bd3002a900957", size = 13807476, upload-time = "2025-12-15T05:03:17.977Z" }, + { url = "https://files.pythonhosted.org/packages/cd/09/4ebd873390a063176f06b0dbf1f7783dd87bd120eae7727fa4ae4179b685/mypy-1.19.1-cp314-cp314-win_amd64.whl", hash = "sha256:8bb5c6f6d043655e055be9b542aa5f3bdd30e4f3589163e85f93f3640060509f", size = 10281872, upload-time = "2025-12-15T05:03:05.549Z" }, + { url = "https://files.pythonhosted.org/packages/8d/f4/4ce9a05ce5ded1de3ec1c1d96cf9f9504a04e54ce0ed55cfa38619a32b8d/mypy-1.19.1-py3-none-any.whl", hash = "sha256:f1235f5ea01b7db5468d53ece6aaddf1ad0b88d9e7462b86ef96fe04995d7247", size = 2471239, upload-time = "2025-12-15T05:03:07.248Z" }, +] + +[[package]] +name = "mypy-extensions" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343, upload-time = "2025-04-22T14:54:24.164Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" }, +] + +[[package]] +name = "myst-parser" +version = "5.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "docutils" }, + { name = "jinja2" }, + { name = "markdown-it-py" }, + { name = "mdit-py-plugins" }, + { name = "pyyaml" }, + { name = "sphinx" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/33/fa/7b45eef11b7971f0beb29d27b7bfe0d747d063aa29e170d9edd004733c8a/myst_parser-5.0.0.tar.gz", hash = "sha256:f6f231452c56e8baa662cc352c548158f6a16fcbd6e3800fc594978002b94f3a", size = 98535, upload-time = "2026-01-15T09:08:18.036Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d3/ac/686789b9145413f1a61878c407210e41bfdb097976864e0913078b24098c/myst_parser-5.0.0-py3-none-any.whl", hash = "sha256:ab31e516024918296e169139072b81592336f2fef55b8986aa31c9f04b5f7211", size = 84533, upload-time = "2026-01-15T09:08:16.788Z" }, +] + [[package]] name = "nodeenv" version = "1.10.0" @@ -126,22 +434,36 @@ wheels = [ name = "openptv-python" source = { editable = "." } dependencies = [ + { name = "mypy" }, + { name = "myst-parser" }, { name = "numba" }, { name = "numpy" }, { name = "pre-commit" }, + { name = "pydata-sphinx-theme" }, { name = "pytest" }, + { name = "pytest-cov" }, { name = "pyyaml" }, { name = "scipy" }, + { name = "sphinx" }, + { name = "sphinx-autoapi" }, + { name = "types-pyyaml" }, ] [package.metadata] requires-dist = [ + { name = "mypy", specifier = ">=1.19.1" }, + { name = "myst-parser", specifier = ">=5.0.0" }, { name = "numba", specifier = ">=0.64.0" }, { name = "numpy", specifier = ">=2.4.2" }, { name = "pre-commit", specifier = ">=4.5.1" }, + { name = "pydata-sphinx-theme", specifier = ">=0.16.1" }, { name = "pytest", specifier = ">=9.0.2" }, + { name = "pytest-cov", specifier = ">=7.0.0" }, { name = "pyyaml", specifier = ">=6.0.3" }, { name = "scipy", specifier = ">=1.17.1" }, + { name = "sphinx", specifier = ">=9.1.0" }, + { name = "sphinx-autoapi", specifier = ">=3.7.0" }, + { name = "types-pyyaml", specifier = ">=6.0.12.20250915" }, ] [[package]] @@ -153,6 +475,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529", size = 74366, upload-time = "2026-01-21T20:50:37.788Z" }, ] +[[package]] +name = "pathspec" +version = "1.0.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fa/36/e27608899f9b8d4dff0617b2d9ab17ca5608956ca44461ac14ac48b44015/pathspec-1.0.4.tar.gz", hash = "sha256:0210e2ae8a21a9137c0d470578cb0e595af87edaa6ebf12ff176f14a02e0e645", size = 131200, upload-time = "2026-01-27T03:59:46.938Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/3c/2c197d226f9ea224a9ab8d197933f9da0ae0aac5b6e0f884e2b8d9c8e9f7/pathspec-1.0.4-py3-none-any.whl", hash = "sha256:fb6ae2fd4e7c921a165808a552060e722767cfa526f99ca5156ed2ce45a5c723", size = 55206, upload-time = "2026-01-27T03:59:45.137Z" }, +] + [[package]] name = "platformdirs" version = "4.9.2" @@ -187,6 +518,24 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/5d/19/fd3ef348460c80af7bb4669ea7926651d1f95c23ff2df18b9d24bab4f3fa/pre_commit-4.5.1-py2.py3-none-any.whl", hash = "sha256:3b3afd891e97337708c1674210f8eba659b52a38ea5f822ff142d10786221f77", size = 226437, upload-time = "2025-12-16T21:14:32.409Z" }, ] +[[package]] +name = "pydata-sphinx-theme" +version = "0.16.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "accessible-pygments" }, + { name = "babel" }, + { name = "beautifulsoup4" }, + { name = "docutils" }, + { name = "pygments" }, + { name = "sphinx" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/00/20/bb50f9de3a6de69e6abd6b087b52fa2418a0418b19597601605f855ad044/pydata_sphinx_theme-0.16.1.tar.gz", hash = "sha256:a08b7f0b7f70387219dc659bff0893a7554d5eb39b59d3b8ef37b8401b7642d7", size = 2412693, upload-time = "2024-12-17T10:53:39.537Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e2/0d/8ba33fa83a7dcde13eb3c1c2a0c1cc29950a048bfed6d9b0d8b6bd710b4c/pydata_sphinx_theme-0.16.1-py3-none-any.whl", hash = "sha256:225331e8ac4b32682c18fcac5a57a6f717c4e632cea5dd0e247b55155faeccde", size = 6723264, upload-time = "2024-12-17T10:53:35.645Z" }, +] + [[package]] name = "pygments" version = "2.19.2" @@ -212,6 +561,20 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801, upload-time = "2025-12-06T21:30:49.154Z" }, ] +[[package]] +name = "pytest-cov" +version = "7.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "coverage" }, + { name = "pluggy" }, + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5e/f7/c933acc76f5208b3b00089573cf6a2bc26dc80a8aece8f52bb7d6b1855ca/pytest_cov-7.0.0.tar.gz", hash = "sha256:33c97eda2e049a0c5298e91f519302a1334c26ac65c1a483d6206fd458361af1", size = 54328, upload-time = "2025-09-09T10:57:02.113Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl", hash = "sha256:3b8e9558b16cc1479da72058bdecf8073661c7f57f7d3c5f22a1c23507f2d861", size = 22424, upload-time = "2025-09-09T10:57:00.695Z" }, +] + [[package]] name = "pyyaml" version = "6.0.3" @@ -238,6 +601,30 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" }, ] +[[package]] +name = "requests" +version = "2.32.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" }, +] + +[[package]] +name = "roman-numerals" +version = "4.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ae/f9/41dc953bbeb056c17d5f7a519f50fdf010bd0553be2d630bc69d1e022703/roman_numerals-4.1.0.tar.gz", hash = "sha256:1af8b147eb1405d5839e78aeb93131690495fe9da5c91856cb33ad55a7f1e5b2", size = 9077, upload-time = "2025-12-17T18:25:34.381Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/54/6f679c435d28e0a568d8e8a7c0a93a09010818634c3c3907fc98d8983770/roman_numerals-4.1.0-py3-none-any.whl", hash = "sha256:647ba99caddc2cc1e55a51e4360689115551bf4476d90e8162cf8c345fe233c7", size = 7676, upload-time = "2025-12-17T18:25:33.098Z" }, +] + [[package]] name = "scipy" version = "1.17.1" @@ -269,6 +656,148 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/07/39/338d9219c4e87f3e708f18857ecd24d22a0c3094752393319553096b98af/scipy-1.17.1-cp314-cp314t-win_arm64.whl", hash = "sha256:200e1050faffacc162be6a486a984a0497866ec54149a01270adc8a59b7c7d21", size = 25489165, upload-time = "2026-02-23T00:22:29.563Z" }, ] +[[package]] +name = "snowballstemmer" +version = "3.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/75/a7/9810d872919697c9d01295633f5d574fb416d47e535f258272ca1f01f447/snowballstemmer-3.0.1.tar.gz", hash = "sha256:6d5eeeec8e9f84d4d56b847692bacf79bc2c8e90c7f80ca4444ff8b6f2e52895", size = 105575, upload-time = "2025-05-09T16:34:51.843Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c8/78/3565d011c61f5a43488987ee32b6f3f656e7f107ac2782dd57bdd7d91d9a/snowballstemmer-3.0.1-py3-none-any.whl", hash = "sha256:6cd7b3897da8d6c9ffb968a6781fa6532dce9c3618a4b127d920dab764a19064", size = 103274, upload-time = "2025-05-09T16:34:50.371Z" }, +] + +[[package]] +name = "soupsieve" +version = "2.8.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7b/ae/2d9c981590ed9999a0d91755b47fc74f74de286b0f5cee14c9269041e6c4/soupsieve-2.8.3.tar.gz", hash = "sha256:3267f1eeea4251fb42728b6dfb746edc9acaffc4a45b27e19450b676586e8349", size = 118627, upload-time = "2026-01-20T04:27:02.457Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/46/2c/1462b1d0a634697ae9e55b3cecdcb64788e8b7d63f54d923fcd0bb140aed/soupsieve-2.8.3-py3-none-any.whl", hash = "sha256:ed64f2ba4eebeab06cc4962affce381647455978ffc1e36bb79a545b91f45a95", size = 37016, upload-time = "2026-01-20T04:27:01.012Z" }, +] + +[[package]] +name = "sphinx" +version = "9.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "alabaster" }, + { name = "babel" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "docutils" }, + { name = "imagesize" }, + { name = "jinja2" }, + { name = "packaging" }, + { name = "pygments" }, + { name = "requests" }, + { name = "roman-numerals" }, + { name = "snowballstemmer" }, + { name = "sphinxcontrib-applehelp" }, + { name = "sphinxcontrib-devhelp" }, + { name = "sphinxcontrib-htmlhelp" }, + { name = "sphinxcontrib-jsmath" }, + { name = "sphinxcontrib-qthelp" }, + { name = "sphinxcontrib-serializinghtml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cd/bd/f08eb0f4eed5c83f1ba2a3bd18f7745a2b1525fad70660a1c00224ec468a/sphinx-9.1.0.tar.gz", hash = "sha256:7741722357dd75f8190766926071fed3bdc211c74dd2d7d4df5404da95930ddb", size = 8718324, upload-time = "2025-12-31T15:09:27.646Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/73/f7/b1884cb3188ab181fc81fa00c266699dab600f927a964df02ec3d5d1916a/sphinx-9.1.0-py3-none-any.whl", hash = "sha256:c84fdd4e782504495fe4f2c0b3413d6c2bf388589bb352d439b2a3bb99991978", size = 3921742, upload-time = "2025-12-31T15:09:25.561Z" }, +] + +[[package]] +name = "sphinx-autoapi" +version = "3.7.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "astroid" }, + { name = "jinja2" }, + { name = "pyyaml" }, + { name = "sphinx" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/47/22/7ea4b660e98ffa271dbec5847b64738127955746d887f9596940279d30bf/sphinx_autoapi-3.7.0.tar.gz", hash = "sha256:5c8a934d788f00d11b8c8092536c7373592cce986820de8dd7daf6dfd79afd91", size = 58136, upload-time = "2026-02-11T05:24:29.86Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d8/11/9825443890d3ef6b6a0db938054e7c806e9911509e0315fe984d00a090c1/sphinx_autoapi-3.7.0-py3-none-any.whl", hash = "sha256:5ea37271b50de08538cf6e33044ef4fb6e981ddb693060ec84f297005014fdfd", size = 36021, upload-time = "2026-02-11T05:24:28.657Z" }, +] + +[[package]] +name = "sphinxcontrib-applehelp" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ba/6e/b837e84a1a704953c62ef8776d45c3e8d759876b4a84fe14eba2859106fe/sphinxcontrib_applehelp-2.0.0.tar.gz", hash = "sha256:2f29ef331735ce958efa4734873f084941970894c6090408b079c61b2e1c06d1", size = 20053, upload-time = "2024-07-29T01:09:00.465Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5d/85/9ebeae2f76e9e77b952f4b274c27238156eae7979c5421fba91a28f4970d/sphinxcontrib_applehelp-2.0.0-py3-none-any.whl", hash = "sha256:4cd3f0ec4ac5dd9c17ec65e9ab272c9b867ea77425228e68ecf08d6b28ddbdb5", size = 119300, upload-time = "2024-07-29T01:08:58.99Z" }, +] + +[[package]] +name = "sphinxcontrib-devhelp" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/d2/5beee64d3e4e747f316bae86b55943f51e82bb86ecd325883ef65741e7da/sphinxcontrib_devhelp-2.0.0.tar.gz", hash = "sha256:411f5d96d445d1d73bb5d52133377b4248ec79db5c793ce7dbe59e074b4dd1ad", size = 12967, upload-time = "2024-07-29T01:09:23.417Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/35/7a/987e583882f985fe4d7323774889ec58049171828b58c2217e7f79cdf44e/sphinxcontrib_devhelp-2.0.0-py3-none-any.whl", hash = "sha256:aefb8b83854e4b0998877524d1029fd3e6879210422ee3780459e28a1f03a8a2", size = 82530, upload-time = "2024-07-29T01:09:21.945Z" }, +] + +[[package]] +name = "sphinxcontrib-htmlhelp" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/93/983afd9aa001e5201eab16b5a444ed5b9b0a7a010541e0ddfbbfd0b2470c/sphinxcontrib_htmlhelp-2.1.0.tar.gz", hash = "sha256:c9e2916ace8aad64cc13a0d233ee22317f2b9025b9cf3295249fa985cc7082e9", size = 22617, upload-time = "2024-07-29T01:09:37.889Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0a/7b/18a8c0bcec9182c05a0b3ec2a776bba4ead82750a55ff798e8d406dae604/sphinxcontrib_htmlhelp-2.1.0-py3-none-any.whl", hash = "sha256:166759820b47002d22914d64a075ce08f4c46818e17cfc9470a9786b759b19f8", size = 98705, upload-time = "2024-07-29T01:09:36.407Z" }, +] + +[[package]] +name = "sphinxcontrib-jsmath" +version = "1.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b2/e8/9ed3830aeed71f17c026a07a5097edcf44b692850ef215b161b8ad875729/sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8", size = 5787, upload-time = "2019-01-21T16:10:16.347Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c2/42/4c8646762ee83602e3fb3fbe774c2fac12f317deb0b5dbeeedd2d3ba4b77/sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178", size = 5071, upload-time = "2019-01-21T16:10:14.333Z" }, +] + +[[package]] +name = "sphinxcontrib-qthelp" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/68/bc/9104308fc285eb3e0b31b67688235db556cd5b0ef31d96f30e45f2e51cae/sphinxcontrib_qthelp-2.0.0.tar.gz", hash = "sha256:4fe7d0ac8fc171045be623aba3e2a8f613f8682731f9153bb2e40ece16b9bbab", size = 17165, upload-time = "2024-07-29T01:09:56.435Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/27/83/859ecdd180cacc13b1f7e857abf8582a64552ea7a061057a6c716e790fce/sphinxcontrib_qthelp-2.0.0-py3-none-any.whl", hash = "sha256:b18a828cdba941ccd6ee8445dbe72ffa3ef8cbe7505d8cd1fa0d42d3f2d5f3eb", size = 88743, upload-time = "2024-07-29T01:09:54.885Z" }, +] + +[[package]] +name = "sphinxcontrib-serializinghtml" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3b/44/6716b257b0aa6bfd51a1b31665d1c205fb12cb5ad56de752dfa15657de2f/sphinxcontrib_serializinghtml-2.0.0.tar.gz", hash = "sha256:e9d912827f872c029017a53f0ef2180b327c3f7fd23c87229f7a8e8b70031d4d", size = 16080, upload-time = "2024-07-29T01:10:09.332Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/52/a7/d2782e4e3f77c8450f727ba74a8f12756d5ba823d81b941f1b04da9d033a/sphinxcontrib_serializinghtml-2.0.0-py3-none-any.whl", hash = "sha256:6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331", size = 92072, upload-time = "2024-07-29T01:10:08.203Z" }, +] + +[[package]] +name = "types-pyyaml" +version = "6.0.12.20250915" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7e/69/3c51b36d04da19b92f9e815be12753125bd8bc247ba0470a982e6979e71c/types_pyyaml-6.0.12.20250915.tar.gz", hash = "sha256:0f8b54a528c303f0e6f7165687dd33fafa81c807fcac23f632b63aa624ced1d3", size = 17522, upload-time = "2025-09-15T03:01:00.728Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bd/e0/1eed384f02555dde685fff1a1ac805c1c7dcb6dd019c916fe659b1c1f9ec/types_pyyaml-6.0.12.20250915-py3-none-any.whl", hash = "sha256:e7d4d9e064e89a3b3cae120b4990cd370874d2bf12fa5f46c97018dd5d3c9ab6", size = 20338, upload-time = "2025-09-15T03:00:59.218Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, +] + +[[package]] +name = "urllib3" +version = "2.6.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c7/24/5f1b3bdffd70275f6661c76461e25f024d5a38a46f04aaca912426a2b1d3/urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed", size = 435556, upload-time = "2026-01-07T16:24:43.925Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4", size = 131584, upload-time = "2026-01-07T16:24:42.685Z" }, +] + [[package]] name = "virtualenv" version = "20.39.0" From 493f4bd03f94069cdf72d2976337b9a51b32b925 Mon Sep 17 00:00:00 2001 From: Alex Liberzon Date: Fri, 6 Mar 2026 17:54:44 +0200 Subject: [PATCH 05/27] working on bundle adjustment or volume self-calibration --- README.md | 2 +- openptv_python/calibration_compare.py | 150 +++++++ openptv_python/orientation.py | 425 +++++++++++++++++- pyproject.toml | 2 +- tests/test_bundle_adjustment.py | 204 +++++++++ tests/test_calibration_compare.py | 64 +++ .../test_cavity/parameters_Run1.yaml | 174 +++++++ uv.lock | 358 +++++++++++++-- 8 files changed, 1335 insertions(+), 44 deletions(-) create mode 100644 openptv_python/calibration_compare.py create mode 100644 tests/test_bundle_adjustment.py create mode 100644 tests/test_calibration_compare.py create mode 100644 tests/testing_fodder/test_cavity/parameters_Run1.yaml diff --git a/README.md b/README.md index 36d5595..1a873da 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ conda activate openptv-python Before pushing to GitHub, run the following commands: -1. Update conda environment: `make conda-env-update` or `uv venv` and `source .venv/bin/activate` followed by `uv sync` +1. Update conda environment: `make conda-env-update` or `uv venv` and `source .venv/bin/activate` followed by `uv sync --upgrade` 1. Install this package: `pip install -e .` 1. Sync with the latest [template](https://github.com/ecmwf-projects/cookiecutter-conda-package) (optional): `make template-update` 1. Run quality assurance checks: `make qa` diff --git a/openptv_python/calibration_compare.py b/openptv_python/calibration_compare.py new file mode 100644 index 0000000..28e79b0 --- /dev/null +++ b/openptv_python/calibration_compare.py @@ -0,0 +1,150 @@ +"""Utilities for comparing calibration folders camera by camera.""" + +from __future__ import annotations + +import argparse +from dataclasses import dataclass +from pathlib import Path +from typing import Dict, Iterable + +import numpy as np + +from .calibration import Calibration, read_calibration + + +@dataclass +class CalibrationDelta: + """Numeric parameter deltas for one camera calibration pair.""" + + camera_key: str + position_delta: np.ndarray + angle_delta: np.ndarray + primary_point_delta: np.ndarray + glass_delta: np.ndarray + added_par_delta: np.ndarray + + +def _camera_key_from_ori_path(ori_path: Path) -> str: + """Return the camera key shared by the .ori and .addpar file pair.""" + if not ori_path.name.endswith(".ori"): + raise ValueError(f"Expected a .ori file, got {ori_path}") + return ori_path.name[: -len(".ori")] + + +def _discover_calibration_pairs(folder: Path) -> Dict[str, tuple[Path, Path | None]]: + """Discover calibration files in a folder keyed by camera base name.""" + if not folder.exists(): + raise FileNotFoundError(folder) + + pairs: Dict[str, tuple[Path, Path | None]] = {} + for ori_path in sorted(folder.glob("*.ori")): + key = _camera_key_from_ori_path(ori_path) + addpar_path = folder / f"{key}.addpar" + pairs[key] = (ori_path, addpar_path if addpar_path.exists() else None) + + if not pairs: + raise ValueError(f"No calibration .ori files found in {folder}") + + return pairs + + +def _load_calibration_pair(pair: tuple[Path, Path | None]) -> Calibration: + """Load one calibration pair from disk.""" + ori_path, addpar_path = pair + return read_calibration(ori_path, addpar_path) + + +def compare_calibration_folders( + reference_dir: Path | str, candidate_dir: Path | str +) -> Dict[str, CalibrationDelta]: + """Compare two calibration folders and return numeric deltas per camera.""" + reference_dir = Path(reference_dir) + candidate_dir = Path(candidate_dir) + + reference_pairs = _discover_calibration_pairs(reference_dir) + candidate_pairs = _discover_calibration_pairs(candidate_dir) + + if set(reference_pairs) != set(candidate_pairs): + missing_from_candidate = sorted(set(reference_pairs) - set(candidate_pairs)) + missing_from_reference = sorted(set(candidate_pairs) - set(reference_pairs)) + raise ValueError( + "Calibration folders contain different camera sets: " + f"missing_from_candidate={missing_from_candidate}, " + f"missing_from_reference={missing_from_reference}" + ) + + deltas: Dict[str, CalibrationDelta] = {} + for camera_key in sorted(reference_pairs): + reference_cal = _load_calibration_pair(reference_pairs[camera_key]) + candidate_cal = _load_calibration_pair(candidate_pairs[camera_key]) + + deltas[camera_key] = CalibrationDelta( + camera_key=camera_key, + position_delta=candidate_cal.get_pos() - reference_cal.get_pos(), + angle_delta=candidate_cal.get_angles() - reference_cal.get_angles(), + primary_point_delta=( + candidate_cal.get_primary_point() - reference_cal.get_primary_point() + ), + glass_delta=candidate_cal.glass_par - reference_cal.glass_par, + added_par_delta=candidate_cal.added_par - reference_cal.added_par, + ) + + return deltas + + +def format_calibration_comparison( + deltas: Dict[str, CalibrationDelta], + reference_dir: Path | str | None = None, + candidate_dir: Path | str | None = None, +) -> str: + """Format camera-by-camera calibration deltas as readable text.""" + lines: list[str] = [] + if reference_dir is not None and candidate_dir is not None: + lines.append(f"Reference: {Path(reference_dir)}") + lines.append(f"Candidate: {Path(candidate_dir)}") + + for camera_key in sorted(deltas): + delta = deltas[camera_key] + lines.append(f"{camera_key}:") + lines.append( + " position_delta: " + " ".join(f"{value:+.9f}" for value in delta.position_delta) + ) + lines.append( + " angle_delta: " + " ".join(f"{value:+.9f}" for value in delta.angle_delta) + ) + lines.append( + " primary_point_delta: " + + " ".join(f"{value:+.9f}" for value in delta.primary_point_delta) + ) + lines.append( + " glass_delta: " + " ".join(f"{value:+.9f}" for value in delta.glass_delta) + ) + lines.append( + " addpar_delta: " + " ".join(f"{value:+.9f}" for value in delta.added_par_delta) + ) + + return "\n".join(lines) + + +def main(argv: Iterable[str] | None = None) -> int: + """Command-line entry point for comparing calibration folders.""" + parser = argparse.ArgumentParser( + description="Compare two calibration folders camera by camera." + ) + parser.add_argument("reference_dir", type=Path) + parser.add_argument("candidate_dir", type=Path) + args = parser.parse_args(list(argv) if argv is not None else None) + + deltas = compare_calibration_folders(args.reference_dir, args.candidate_dir) + print( + format_calibration_comparison( + deltas, + reference_dir=args.reference_dir, + candidate_dir=args.candidate_dir, + ) + ) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) \ No newline at end of file diff --git a/openptv_python/orientation.py b/openptv_python/orientation.py index 3b7b1a8..d92cb9b 100644 --- a/openptv_python/orientation.py +++ b/openptv_python/orientation.py @@ -1,6 +1,6 @@ """Functions for the orientation of the camera.""" -from typing import List, Optional, Tuple +from typing import Dict, List, Optional, Tuple import numpy as np import scipy @@ -18,7 +18,7 @@ from .ray_tracing import ray_tracing from .sortgrid import sortgrid from .tracking_frame_buf import Target -from .trafo import correct_brown_affine, pixel_to_metric +from .trafo import correct_brown_affine, dist_to_flat, metric_to_pixel, pixel_to_metric from .vec_utils import unit_vector, vec_norm, vec_set @@ -1060,6 +1060,425 @@ def multi_cam_point_positions( rcm = np.empty(num_targets) for pt in range(num_targets): - rcm[pt], res = point_position(targets[pt], num_cams, mm_par, cals) + rcm[pt], res[pt] = point_position(targets[pt], num_cams, mm_par, cals) return res, rcm + + +def _clone_calibration(cal: Calibration) -> Calibration: + """Create an isolated calibration copy for optimization updates.""" + return Calibration( + ext_par=cal.ext_par.copy(), + int_par=cal.int_par.copy(), + glass_par=cal.glass_par.copy(), + added_par=cal.added_par.copy(), + mmlut=cal.mmlut, + mmlut_data=cal.mmlut_data, + ) + + +def _bundle_optional_parameter_names(orient_par: OrientPar) -> List[str]: + """Return optional calibration parameter names enabled for optimization.""" + names = [] + mapping = ( + ("ccflag", "cc"), + ("xhflag", "xh"), + ("yhflag", "yh"), + ("k1flag", "k1"), + ("k2flag", "k2"), + ("k3flag", "k3"), + ("p1flag", "p1"), + ("p2flag", "p2"), + ("scxflag", "scx"), + ("sheflag", "she"), + ) + for flag_name, param_name in mapping: + if getattr(orient_par, flag_name): + names.append(param_name) + return names + + +def _get_optional_parameter(cal: Calibration, name: str) -> float: + """Read one optional calibration parameter from a camera model.""" + if name == "cc": + return float(cal.int_par.cc) + if name == "xh": + return float(cal.int_par.xh) + if name == "yh": + return float(cal.int_par.yh) + + added_map = { + "k1": 0, + "k2": 1, + "k3": 2, + "p1": 3, + "p2": 4, + "scx": 5, + "she": 6, + } + if name not in added_map: + raise ValueError(f"Unknown optional camera parameter: {name}") + return float(cal.added_par[added_map[name]]) + + +def _set_optional_parameter(cal: Calibration, name: str, value: float) -> None: + """Write one optional calibration parameter into a camera model.""" + if name == "cc": + cal.int_par.cc = value + return + if name == "xh": + cal.int_par.xh = value + return + if name == "yh": + cal.int_par.yh = value + return + + added_map = { + "k1": 0, + "k2": 1, + "k3": 2, + "p1": 3, + "p2": 4, + "scx": 5, + "she": 6, + } + if name not in added_map: + raise ValueError(f"Unknown optional camera parameter: {name}") + cal.added_par[added_map[name]] = value + + +def _glass_basis(glass_vec: np.ndarray) -> Tuple[np.ndarray, np.ndarray, float]: + """Build a stable tangent basis around the current glass vector.""" + norm = float(np.linalg.norm(glass_vec)) + if norm == 0.0: + raise ValueError("Glass vector norm must be non-zero") + + normal = glass_vec / norm + helper = ( + np.array([1.0, 0.0, 0.0]) if abs(normal[0]) < 0.9 else np.array([0.0, 1.0, 0.0]) + ) + e1 = np.cross(normal, helper) + e1_norm = float(np.linalg.norm(e1)) + if e1_norm == 0.0: + helper = np.array([0.0, 0.0, 1.0]) + e1 = np.cross(normal, helper) + e1_norm = float(np.linalg.norm(e1)) + e1 /= e1_norm + e2 = np.cross(normal, e1) + e2 /= float(np.linalg.norm(e2)) + return e1, e2, norm + + +def _camera_parameter_block( + cal: Calibration, optional_names: List[str], include_interface: bool +) -> np.ndarray: + """Pack one camera block for bundle adjustment.""" + values = [ + float(cal.ext_par.x0), + float(cal.ext_par.y0), + float(cal.ext_par.z0), + float(cal.ext_par.omega), + float(cal.ext_par.phi), + float(cal.ext_par.kappa), + ] + values.extend(_get_optional_parameter(cal, name) for name in optional_names) + if include_interface: + values.extend([0.0, 0.0]) + return np.asarray(values, dtype=np.float64) + + +def _apply_camera_parameter_block( + cal: Calibration, + block: np.ndarray, + optional_names: List[str], + include_interface: bool, + base_glass: Optional[np.ndarray], +) -> None: + """Apply one bundle-adjustment camera block to a calibration object.""" + cal.set_pos(block[:3]) + cal.set_angles(block[3:6]) + + offset = 6 + for name in optional_names: + _set_optional_parameter(cal, name, float(block[offset])) + offset += 1 + + if include_interface: + if base_glass is None: + raise ValueError("Base glass vector is required when interfflag is enabled") + e1, e2, scale = _glass_basis(base_glass) + cal.glass_par = base_glass + scale * ( + block[offset] * e1 + block[offset + 1] * e2 + ) + + +def metric_observations_from_pixels( + observed_pixels: np.ndarray, + cals: List[Calibration], + cpar: ControlPar, +) -> np.ndarray: + """Convert pixel observations to flat metric coordinates for ray intersection.""" + num_points, num_cams, _ = observed_pixels.shape + metric_obs = np.full((num_points, num_cams, 2), COORD_UNUSED, dtype=np.float64) + + for pt in range(num_points): + for cam in range(num_cams): + obs = observed_pixels[pt, cam] + if not np.all(np.isfinite(obs)): + continue + x_metric, y_metric = pixel_to_metric(float(obs[0]), float(obs[1]), cpar) + metric_obs[pt, cam] = dist_to_flat(x_metric, y_metric, cals[cam]) + + return metric_obs + + +def initialize_bundle_adjustment_points( + observed_pixels: np.ndarray, + cals: List[Calibration], + cpar: ControlPar, +) -> Tuple[np.ndarray, np.ndarray]: + """Triangulate 3D starting points from pixel observations and current cameras.""" + metric_obs = metric_observations_from_pixels(observed_pixels, cals, cpar) + num_points, num_cams, _ = metric_obs.shape + points = np.empty((num_points, 3), dtype=np.float64) + ray_convergence = np.empty(num_points, dtype=np.float64) + + for pt in range(num_points): + if np.count_nonzero(metric_obs[pt, :, 0] != COORD_UNUSED) < 2: + raise ValueError( + "Each point must be observed by at least two cameras for bundle adjustment" + ) + ray_convergence[pt], points[pt] = point_position( + metric_obs[pt], num_cams, cpar.mm, cals + ) + + return points, ray_convergence + + +def reprojection_errors( + observed_pixels: np.ndarray, + points_3d: np.ndarray, + cals: List[Calibration], + cpar: ControlPar, +) -> np.ndarray: + """Return per-observation reprojection errors in pixels.""" + if observed_pixels.ndim != 3 or observed_pixels.shape[2] != 2: + raise ValueError("observed_pixels must have shape (num_points, num_cams, 2)") + if points_3d.shape != (observed_pixels.shape[0], 3): + raise ValueError("points_3d must have shape (num_points, 3)") + if observed_pixels.shape[1] != len(cals): + raise ValueError( + "Number of cameras in observations and calibrations must match" + ) + + residuals = np.full_like(observed_pixels, np.nan, dtype=np.float64) + for pt in range(observed_pixels.shape[0]): + for cam in range(observed_pixels.shape[1]): + obs = observed_pixels[pt, cam] + if not np.all(np.isfinite(obs)): + continue + x_metric, y_metric = img_coord(points_3d[pt], cals[cam], cpar.mm) + proj = metric_to_pixel(x_metric, y_metric, cpar) + residuals[pt, cam] = np.asarray(proj, dtype=np.float64) - obs + + return residuals + + +def reprojection_rms( + observed_pixels: np.ndarray, + points_3d: np.ndarray, + cals: List[Calibration], + cpar: ControlPar, +) -> float: + """Return the global RMS reprojection error in pixels.""" + residuals = reprojection_errors(observed_pixels, points_3d, cals, cpar) + valid = np.isfinite(residuals) + if not np.any(valid): + raise ValueError("No valid observations available for reprojection RMS") + return float(np.sqrt(np.mean(np.square(residuals[valid])))) + + +def reprojection_rms_per_camera( + observed_pixels: np.ndarray, + points_3d: np.ndarray, + cals: List[Calibration], + cpar: ControlPar, +) -> np.ndarray: + """Return per-camera RMS reprojection errors in pixels.""" + residuals = reprojection_errors(observed_pixels, points_3d, cals, cpar) + per_camera = np.full(observed_pixels.shape[1], np.nan, dtype=np.float64) + for cam in range(observed_pixels.shape[1]): + valid = np.isfinite(residuals[:, cam, :]) + if np.any(valid): + per_camera[cam] = float( + np.sqrt(np.mean(np.square(residuals[:, cam, :][valid]))) + ) + return per_camera + + +def multi_camera_bundle_adjustment( + observed_pixels: np.ndarray, + cals: List[Calibration], + cpar: ControlPar, + orient_par: OrientPar, + point_init: Optional[np.ndarray] = None, + fix_first_camera: bool = True, + fixed_camera_indices: Optional[List[int]] = None, + loss: str = "soft_l1", + f_scale: float = 1.0, + method: str = "trf", + prior_sigmas: Optional[Dict[str, float]] = None, + max_nfev: Optional[int] = None, +) -> Tuple[List[Calibration], np.ndarray, scipy.optimize.OptimizeResult]: + """Jointly refine multi-camera calibration and 3D points by reprojection error.""" + observed_pixels = np.asarray(observed_pixels, dtype=np.float64) + if observed_pixels.ndim != 3 or observed_pixels.shape[2] != 2: + raise ValueError("observed_pixels must have shape (num_points, num_cams, 2)") + if len(cals) < 2: + raise ValueError("Bundle adjustment requires at least two cameras") + if observed_pixels.shape[1] != len(cals): + raise ValueError( + "Number of cameras in observations and calibrations must match" + ) + + num_points, num_cams, _ = observed_pixels.shape + if num_points == 0: + raise ValueError("At least one 3D point is required for bundle adjustment") + + obs_counts = np.count_nonzero(np.all(np.isfinite(observed_pixels), axis=2), axis=1) + if np.any(obs_counts < 2): + raise ValueError("Each point must be observed by at least two cameras") + + base_cals = [_clone_calibration(cal) for cal in cals] + optional_names = _bundle_optional_parameter_names(orient_par) + include_interface = bool(orient_par.interfflag) + prior_sigmas = {} if prior_sigmas is None else prior_sigmas + + if fixed_camera_indices is None: + fixed_camera_indices = [0] if fix_first_camera else [] + + fixed_camera_indices = sorted(set(fixed_camera_indices)) + if any(cam < 0 or cam >= num_cams for cam in fixed_camera_indices): + raise ValueError("fixed_camera_indices contains an out-of-range camera index") + + optimized_cam_indices = [ + cam for cam in range(num_cams) if cam not in fixed_camera_indices + ] + + # Refining both 3D points and camera poses from image observations has a similarity + # gauge. One fixed camera removes global translation/rotation, but scale still drifts + # unless another camera is fixed or translation priors are applied. + has_translation_priors = all( + prior_sigmas.get(name, 0) > 0 for name in ("x0", "y0", "z0") + ) + if optimized_cam_indices and len(fixed_camera_indices) < 2 and not has_translation_priors: + raise ValueError( + "Bundle adjustment with free 3D points and only one fixed camera is scale-ambiguous. " + "Fix at least two cameras via fixed_camera_indices or provide translation priors " + "for x0, y0, and z0." + ) + + if point_init is None: + points0, _ = initialize_bundle_adjustment_points( + observed_pixels, base_cals, cpar + ) + else: + points0 = np.asarray(point_init, dtype=np.float64) + if points0.shape != (num_points, 3): + raise ValueError("point_init must have shape (num_points, 3)") + + base_camera_blocks = [] + base_glass_vectors: Dict[int, np.ndarray] = {} + parameter_names = ["x0", "y0", "z0", "omega", "phi", "kappa"] + optional_names + if include_interface: + parameter_names.extend(["glass_e1", "glass_e2"]) + + initial_blocks = [] + for cam in optimized_cam_indices: + initial_block = _camera_parameter_block( + base_cals[cam], optional_names, include_interface + ) + initial_blocks.append(initial_block) + base_camera_blocks.append(initial_block.copy()) + if include_interface: + base_glass_vectors[cam] = base_cals[cam].glass_par.copy() + + if initial_blocks: + x0 = np.concatenate(initial_blocks + [points0.ravel()]) + else: + x0 = points0.ravel().copy() + + camera_block_size = 6 + len(optional_names) + (2 if include_interface else 0) + point_offset = camera_block_size * len(optimized_cam_indices) + + def unpack_parameters(params: np.ndarray) -> Tuple[List[Calibration], np.ndarray]: + trial_cals = [_clone_calibration(cal) for cal in base_cals] + offset = 0 + for cam in optimized_cam_indices: + block = params[offset : offset + camera_block_size] + _apply_camera_parameter_block( + trial_cals[cam], + block, + optional_names, + include_interface, + base_glass_vectors.get(cam), + ) + offset += camera_block_size + + points = params[point_offset:].reshape(num_points, 3) + return trial_cals, points + + def residual_vector(params: np.ndarray) -> np.ndarray: + trial_cals, points = unpack_parameters(params) + residuals = [] + for pt in range(num_points): + for cam in range(num_cams): + obs = observed_pixels[pt, cam] + if not np.all(np.isfinite(obs)): + continue + x_metric, y_metric = img_coord(points[pt], trial_cals[cam], cpar.mm) + proj_x, proj_y = metric_to_pixel(x_metric, y_metric, cpar) + residuals.append((proj_x - obs[0]) / cpar.pix_x) + residuals.append((proj_y - obs[1]) / cpar.pix_y) + + if prior_sigmas: + offset = 0 + for cam_index, cam in enumerate(optimized_cam_indices): + block = params[offset : offset + camera_block_size] + base_block = base_camera_blocks[cam_index] + for name, value, base_value in zip(parameter_names, block, base_block): + sigma = prior_sigmas.get(name) + if sigma is None or sigma <= 0: + continue + residuals.append((value - base_value) / sigma) + offset += camera_block_size + + return np.asarray(residuals, dtype=np.float64) + + initial_cals, initial_points = unpack_parameters(x0) + initial_rms = reprojection_rms(observed_pixels, initial_points, initial_cals, cpar) + initial_per_camera = reprojection_rms_per_camera( + observed_pixels, initial_points, initial_cals, cpar + ) + + result = scipy.optimize.least_squares( + residual_vector, + x0, + method=method, + loss=loss, + f_scale=f_scale, + max_nfev=max_nfev, + ) + + refined_cals, refined_points = unpack_parameters(result.x) + result["initial_reprojection_rms"] = initial_rms + result["final_reprojection_rms"] = reprojection_rms( + observed_pixels, refined_points, refined_cals, cpar + ) + result["initial_reprojection_rms_per_camera"] = initial_per_camera + result["final_reprojection_rms_per_camera"] = reprojection_rms_per_camera( + observed_pixels, refined_points, refined_cals, cpar + ) + result["optimized_camera_indices"] = optimized_cam_indices + + return refined_cals, refined_points, result diff --git a/pyproject.toml b/pyproject.toml index f5946bd..7ddceb8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,7 +27,7 @@ dependencies = [ "scipy>=1.17.1", "sphinx>=9.1.0", "sphinx-autoapi>=3.7.0", - "types-pyyaml>=6.0.12.20250915", + "types-pyyaml>=6.0.12.20250915" ] description = "Python version of the OpenPTV library" dynamic = ["version"] diff --git a/tests/test_bundle_adjustment.py b/tests/test_bundle_adjustment.py new file mode 100644 index 0000000..2215192 --- /dev/null +++ b/tests/test_bundle_adjustment.py @@ -0,0 +1,204 @@ +import unittest +from pathlib import Path + +import numpy as np + +from openptv_python.calibration import Calibration, read_calibration +from openptv_python.imgcoord import image_coordinates +from openptv_python.orientation import ( + multi_camera_bundle_adjustment, + reprojection_rms, +) +from openptv_python.parameters import ControlPar, OrientPar +from openptv_python.tracking_frame_buf import read_path_frame, read_targets +from openptv_python.trafo import arr_metric_to_pixel + + +class TestBundleAdjustment(unittest.TestCase): + def setUp(self): + self.control = ControlPar(4).from_file( + Path("tests/testing_folder/control_parameters/control.par") + ) + self.add_file = Path("tests/testing_folder/calibration/cam1.tif.addpar") + self.true_cals = [ + read_calibration( + Path( + f"tests/testing_folder/calibration/sym_cam{cam_num}.tif.ori" + ), + self.add_file, + ) + for cam_num in range(1, 5) + ] + + @staticmethod + def clone_calibration(cal: Calibration) -> Calibration: + return Calibration( + ext_par=cal.ext_par.copy(), + int_par=cal.int_par.copy(), + glass_par=cal.glass_par.copy(), + added_par=cal.added_par.copy(), + mmlut=cal.mmlut, + mmlut_data=cal.mmlut_data, + ) + + def perturb_calibrations(self, true_cals: list[Calibration]) -> list[Calibration]: + perturbed = [self.clone_calibration(cal) for cal in true_cals] + deltas = [ + (np.array([0.0, 0.0, 0.0]), np.array([0.0, 0.0, 0.0])), + (np.array([1.2, -0.8, 0.7]), np.array([0.012, -0.010, 0.006])), + (np.array([-0.9, 0.7, -0.6]), np.array([-0.009, 0.008, -0.005])), + (np.array([0.8, 1.0, -0.5]), np.array([0.011, 0.007, -0.004])), + ] + for cal, (pos_delta, angle_delta) in zip(perturbed, deltas): + cal.set_pos(cal.get_pos() + pos_delta) + cal.set_angles(cal.get_angles() + angle_delta) + return perturbed + + def lightly_perturb_calibrations(self, true_cals: list[Calibration]) -> list[Calibration]: + perturbed = [self.clone_calibration(cal) for cal in true_cals] + deltas = [ + (np.array([0.0, 0.0, 0.0]), np.array([0.0, 0.0, 0.0])), + (np.array([0.5, -0.3, 0.2]), np.array([0.004, -0.003, 0.002])), + (np.array([-0.4, 0.3, -0.2]), np.array([-0.003, 0.003, -0.002])), + (np.array([0.3, 0.4, -0.2]), np.array([0.003, 0.002, -0.002])), + ] + for cal, (pos_delta, angle_delta) in zip(perturbed, deltas): + cal.set_pos(cal.get_pos() + pos_delta) + cal.set_angles(cal.get_angles() + angle_delta) + return perturbed + + def test_multi_camera_bundle_adjustment_improves_synthetic_reprojection(self): + xs = np.linspace(-20.0, 20.0, 3) + ys = np.linspace(-15.0, 15.0, 2) + zs = np.array([-4.0, 5.0]) + points = np.array([[x, y, z] for x in xs for y in ys for z in zs], dtype=float) + + observed_pixels = np.empty((len(points), 4, 2), dtype=float) + for cam_num, cal in enumerate(self.true_cals): + observed_pixels[:, cam_num, :] = arr_metric_to_pixel( + image_coordinates(points, cal, self.control.mm), self.control + ) + + start_cals = self.perturb_calibrations(self.true_cals) + refined_cals, refined_points, result = multi_camera_bundle_adjustment( + observed_pixels, + start_cals, + self.control, + OrientPar(), + point_init=None, + fixed_camera_indices=[0, 1], + loss="linear", + method="lm", + prior_sigmas={ + "x0": 1.0, + "y0": 1.0, + "z0": 1.0, + "omega": 0.01, + "phi": 0.01, + "kappa": 0.01, + }, + max_nfev=50, + ) + + self.assertTrue(result.success, msg=result.message) + before_rms = result["initial_reprojection_rms"] + after_rms = result["final_reprojection_rms"] + self.assertLess(after_rms, before_rms * 0.4) + self.assertLess(after_rms, 3e-2) + + def test_cavity_reprojection_improves(self): + cavity_dir = Path("tests/testing_fodder/test_cavity") + control = ControlPar(4).from_file(cavity_dir / "parameters/ptv.par") + true_cals = [ + read_calibration( + cavity_dir / f"cal/cam{cam_num}.tif.ori", + cavity_dir / f"cal/cam{cam_num}.tif.addpar", + ) + for cam_num in range(1, 5) + ] + start_cals = self.lightly_perturb_calibrations(true_cals) + + cor_buf, path_buf = read_path_frame( + str(cavity_dir / "res_orig/rt_is"), + "", + "", + 10001, + ) + targets = [ + read_targets(str(cavity_dir / f"img_orig/cam{cam_num}.%05d"), 10001) + for cam_num in range(1, 5) + ] + + subset = [ + pt_num + for pt_num, corres in enumerate(cor_buf) + if np.all(corres.p >= 0) + ][:12] + observed_pixels = np.full((len(subset), 4, 2), np.nan, dtype=float) + point_init = np.empty((len(subset), 3), dtype=float) + for out_num, pt_num in enumerate(subset): + path_info = path_buf[pt_num] + point_init[out_num] = path_info.x + for cam in range(4): + target_index = cor_buf[pt_num].p[cam] + if target_index < 0: + continue + observed_pixels[out_num, cam, 0] = targets[cam][target_index].x + observed_pixels[out_num, cam, 1] = targets[cam][target_index].y + + before_rms = reprojection_rms(observed_pixels, point_init, start_cals, control) + refined_cals, refined_points, result = multi_camera_bundle_adjustment( + observed_pixels, + start_cals, + control, + OrientPar(), + point_init=point_init, + fixed_camera_indices=[0, 1], + loss="linear", + method="lm", + max_nfev=80, + ) + after_rms = reprojection_rms(observed_pixels, refined_points, refined_cals, control) + + print( + f"test_cavity reprojection RMS improved from {before_rms:.6f} px to {after_rms:.6f} px" + ) + + self.assertTrue(result.success, msg=result.message) + self.assertLess(after_rms, before_rms * 0.6) + self.assertLess(after_rms, before_rms - 0.2) + np.testing.assert_allclose(after_rms, result["final_reprojection_rms"]) + + def test_bundle_adjustment_rejects_scale_ambiguous_configuration(self): + points = np.array( + [ + [-10.0, -10.0, 0.0], + [10.0, -10.0, 1.0], + [-10.0, 10.0, -1.0], + [10.0, 10.0, 0.5], + ], + dtype=float, + ) + + observed_pixels = np.empty((len(points), 4, 2), dtype=float) + for cam_num, cal in enumerate(self.true_cals): + observed_pixels[:, cam_num, :] = arr_metric_to_pixel( + image_coordinates(points, cal, self.control.mm), self.control + ) + + with self.assertRaises(ValueError): + multi_camera_bundle_adjustment( + observed_pixels, + self.perturb_calibrations(self.true_cals), + self.control, + OrientPar(), + point_init=None, + fix_first_camera=True, + loss="linear", + method="lm", + max_nfev=10, + ) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_calibration_compare.py b/tests/test_calibration_compare.py new file mode 100644 index 0000000..ff5dfc9 --- /dev/null +++ b/tests/test_calibration_compare.py @@ -0,0 +1,64 @@ +import tempfile +import unittest +from pathlib import Path + +import numpy as np + +from openptv_python.calibration import read_calibration, write_calibration +from openptv_python.calibration_compare import ( + compare_calibration_folders, + format_calibration_comparison, +) + + +class TestCalibrationCompare(unittest.TestCase): + def test_compare_same_folder_is_zero(self): + cavity_cal_dir = Path("tests/testing_fodder/test_cavity/cal") + deltas = compare_calibration_folders(cavity_cal_dir, cavity_cal_dir) + + self.assertEqual(sorted(deltas.keys()), [f"cam{i}.tif" for i in range(1, 5)]) + for delta in deltas.values(): + np.testing.assert_allclose(delta.position_delta, 0.0) + np.testing.assert_allclose(delta.angle_delta, 0.0) + np.testing.assert_allclose(delta.primary_point_delta, 0.0) + np.testing.assert_allclose(delta.glass_delta, 0.0) + np.testing.assert_allclose(delta.added_par_delta, 0.0) + + def test_compare_modified_folder_reports_numeric_deltas(self): + cavity_cal_dir = Path("tests/testing_fodder/test_cavity/cal") + + with tempfile.TemporaryDirectory() as tmp_dir_name: + tmp_dir = Path(tmp_dir_name) + for cam_num in range(1, 5): + cal = read_calibration( + cavity_cal_dir / f"cam{cam_num}.tif.ori", + cavity_cal_dir / f"cam{cam_num}.tif.addpar", + ) + if cam_num == 2: + cal.set_pos(cal.get_pos() + np.array([1.0, -2.0, 3.0])) + cal.set_angles(cal.get_angles() + np.array([0.01, -0.02, 0.03])) + cal.added_par[0] += 1.5e-4 + cal.added_par[3] -= 2.5e-4 + write_calibration( + cal, + tmp_dir / f"cam{cam_num}.tif.ori", + tmp_dir / f"cam{cam_num}.tif.addpar", + ) + + deltas = compare_calibration_folders(cavity_cal_dir, tmp_dir) + cam2 = deltas["cam2.tif"] + np.testing.assert_allclose(cam2.position_delta, [1.0, -2.0, 3.0]) + np.testing.assert_allclose(cam2.angle_delta, [0.01, -0.02, 0.03]) + np.testing.assert_allclose( + cam2.added_par_delta, + [1.5e-4, 0.0, 0.0, -2.5e-4, 0.0, 0.0, 0.0], + ) + + rendered = format_calibration_comparison(deltas) + self.assertIn("cam2.tif:", rendered) + self.assertIn("+1.000000000 -2.000000000 +3.000000000", rendered) + self.assertIn("+0.000150000", rendered) + + +if __name__ == "__main__": + unittest.main() \ No newline at end of file diff --git a/tests/testing_fodder/test_cavity/parameters_Run1.yaml b/tests/testing_fodder/test_cavity/parameters_Run1.yaml new file mode 100644 index 0000000..9c58258 --- /dev/null +++ b/tests/testing_fodder/test_cavity/parameters_Run1.yaml @@ -0,0 +1,174 @@ +num_cams: 4 +plugins: + available_tracking: + - default + available_sequence: + - default + selected_tracking: default + selected_sequence: default +cal_ori: + chfield: 0 + fixp_name: cal/target_on_a_side.txt + img_cal_name: + - cal/cam1.tif + - cal/cam2.tif + - cal/cam3.tif + - cal/cam4.tif + img_ori: + - cal/cam1.tif.ori + - cal/cam2.tif.ori + - cal/cam3.tif.ori + - cal/cam4.tif.ori + pair_flag: true + tiff_flag: true + cal_splitter: false +criteria: + X_lay: + - -40 + - 40 + Zmax_lay: + - 20 + - 20 + Zmin_lay: + - -20 + - -20 + cn: 0.02 + cnx: 0.02 + cny: 0.02 + corrmin: 33.0 + csumg: 0.02 + eps0: 0.2 +detect_plate: + gvth_1: 40 + gvth_2: 40 + gvth_3: 40 + gvth_4: 40 + max_npix: 400 + max_npix_x: 50 + max_npix_y: 50 + min_npix: 25 + min_npix_x: 5 + min_npix_y: 5 + size_cross: 3 + sum_grey: 100 + tol_dis: 500 +dumbbell: + dumbbell_eps: 3.0 + dumbbell_gradient_descent: 0.05 + dumbbell_niter: 500 + dumbbell_penalty_weight: 1.0 + dumbbell_scale: 25.0 + dumbbell_step: 1 +examine: + Combine_Flag: false + Examine_Flag: false +man_ori: + nr: + - 3 + - 5 + - 72 + - 73 + - 3 + - 5 + - 72 + - 73 + - 1 + - 5 + - 71 + - 73 + - 1 + - 5 + - 71 + - 73 +multi_planes: + n_planes: 3 + plane_name: + - img/calib_a_cam + - img/calib_b_cam + - img/calib_c_cam +orient: + cc: 0 + interf: 0 + k1: 0 + k2: 0 + k3: 0 + p1: 0 + p2: 0 + pnfo: 0 + scale: 0 + shear: 0 + xh: 0 + yh: 0 +pft_version: + Existing_Target: 0 +ptv: + allcam_flag: false + chfield: 0 + hp_flag: true + img_cal: + - cal/cam1.tif + - cal/cam2.tif + - cal/cam3.tif + - cal/cam4.tif + img_name: + - img/cam1.10003 + - img/cam2.10003 + - img/cam3.10003 + - img/cam4.10003 + imx: 1280 + imy: 1024 + mmp_d: 6.0 + mmp_n1: 1.0 + mmp_n2: 1.33 + mmp_n3: 1.46 + pix_x: 0.012 + pix_y: 0.012 + tiff_flag: true + splitter: false +sequence: + base_name: + - img/cam1.%05d + - img/cam2.%05d + - img/cam3.%05d + - img/cam4.%05d + first: 10001 + last: 10004 +shaking: + shaking_first_frame: 10000 + shaking_last_frame: 10004 + shaking_max_num_frames: 5 + shaking_max_num_points: 10 +sortgrid: + radius: 20 +targ_rec: + cr_sz: 2 + disco: 100 + gvthres: + - 9 + - 9 + - 9 + - 11 + nnmax: 500 + nnmin: 4 + nxmax: 100 + nxmin: 2 + nymax: 100 + nymin: 2 + sumg_min: 150 +track: + angle: 110.0 + dacc: 1.0 + dvxmax: 5.5 + dvxmin: -5.5 + dvymax: 5.5 + dvymin: -5.5 + dvzmax: 5.5 + dvzmin: -5.5 + flagNewParticles: false +masking: + mask_flag: false + mask_base_name: '' +unsharp_mask: + flag: false + size: 3 + strength: 1.0 diff --git a/uv.lock b/uv.lock index a4c40c8..6cb65f9 100644 --- a/uv.lock +++ b/uv.lock @@ -23,6 +23,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/7e/b3/6b4067be973ae96ba0d615946e314c5ae35f9f993eca561b356540bb0c2b/alabaster-1.0.0-py3-none-any.whl", hash = "sha256:fc6786402dc3fcb2de3cabd5fe455a2db534b371124f1f21de8731783dec828b", size = 13929, upload-time = "2024-07-26T18:15:02.05Z" }, ] +[[package]] +name = "anyio" +version = "4.12.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/96/f0/5eb65b2bb0d09ac6776f2eb54adee6abe8228ea05b20a5ad0e4945de8aac/anyio-4.12.1.tar.gz", hash = "sha256:41cfcc3a4c85d3f05c932da7c26d0201ac36f72abd4435ba90d0464a3ffed703", size = 228685, upload-time = "2026-01-06T11:45:21.246Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/38/0e/27be9fdef66e72d64c0cdc3cc2823101b80585f8119b5c112c2e8f5f7dab/anyio-4.12.1-py3-none-any.whl", hash = "sha256:d405828884fc140aa80a3c667b8beed277f1dfedec42ba031bd6ac3db606ab6c", size = 113592, upload-time = "2026-01-06T11:45:19.497Z" }, +] + [[package]] name = "astroid" version = "4.1.1" @@ -56,11 +68,11 @@ wheels = [ [[package]] name = "certifi" -version = "2026.1.4" +version = "2026.2.25" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e0/2d/a891ca51311197f6ad14a7ef42e2399f36cf2f9bd44752b3dc4eab60fdc5/certifi-2026.1.4.tar.gz", hash = "sha256:ac726dd470482006e014ad384921ed6438c457018f4b3d204aea4281258b2120", size = 154268, upload-time = "2026-01-04T02:42:41.825Z" } +sdist = { url = "https://files.pythonhosted.org/packages/af/2d/7bf41579a8986e348fa033a31cdd0e4121114f6bce2457e8876010b092dd/certifi-2026.2.25.tar.gz", hash = "sha256:e887ab5cee78ea814d3472169153c2d12cd43b14bd03329a39a9c6e2e80bfba7", size = 155029, upload-time = "2026-02-25T02:54:17.342Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e6/ad/3cc14f097111b4de0040c83a525973216457bbeeb63739ef1ed275c1c021/certifi-2026.1.4-py3-none-any.whl", hash = "sha256:9943707519e4add1115f44c2bc244f782c0249876bf51b6599fee1ffbedd685c", size = 152900, upload-time = "2026-01-04T02:42:40.15Z" }, + { url = "https://files.pythonhosted.org/packages/9a/3c/c17fb3ca2d9c3acff52e30b309f538586f9f5b9c9cf454f3845fc9af4881/certifi-2026.2.25-py3-none-any.whl", hash = "sha256:027692e4402ad994f1c42e52a4997a9763c646b73e4096e4d5d6db8af1d6f0fa", size = 153684, upload-time = "2026-02-25T02:54:15.766Z" }, ] [[package]] @@ -74,27 +86,39 @@ wheels = [ [[package]] name = "charset-normalizer" -version = "3.4.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/13/69/33ddede1939fdd074bce5434295f38fae7136463422fe4fd3e0e89b98062/charset_normalizer-3.4.4.tar.gz", hash = "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a", size = 129418, upload-time = "2025-10-14T04:42:32.879Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2a/35/7051599bd493e62411d6ede36fd5af83a38f37c4767b92884df7301db25d/charset_normalizer-3.4.4-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:da3326d9e65ef63a817ecbcc0df6e94463713b754fe293eaa03da99befb9a5bd", size = 207746, upload-time = "2025-10-14T04:41:33.773Z" }, - { url = "https://files.pythonhosted.org/packages/10/9a/97c8d48ef10d6cd4fcead2415523221624bf58bcf68a802721a6bc807c8f/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8af65f14dc14a79b924524b1e7fffe304517b2bff5a58bf64f30b98bbc5079eb", size = 147889, upload-time = "2025-10-14T04:41:34.897Z" }, - { url = "https://files.pythonhosted.org/packages/10/bf/979224a919a1b606c82bd2c5fa49b5c6d5727aa47b4312bb27b1734f53cd/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74664978bb272435107de04e36db5a9735e78232b85b77d45cfb38f758efd33e", size = 143641, upload-time = "2025-10-14T04:41:36.116Z" }, - { url = "https://files.pythonhosted.org/packages/ba/33/0ad65587441fc730dc7bd90e9716b30b4702dc7b617e6ba4997dc8651495/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:752944c7ffbfdd10c074dc58ec2d5a8a4cd9493b314d367c14d24c17684ddd14", size = 160779, upload-time = "2025-10-14T04:41:37.229Z" }, - { url = "https://files.pythonhosted.org/packages/67/ed/331d6b249259ee71ddea93f6f2f0a56cfebd46938bde6fcc6f7b9a3d0e09/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d1f13550535ad8cff21b8d757a3257963e951d96e20ec82ab44bc64aeb62a191", size = 159035, upload-time = "2025-10-14T04:41:38.368Z" }, - { url = "https://files.pythonhosted.org/packages/67/ff/f6b948ca32e4f2a4576aa129d8bed61f2e0543bf9f5f2b7fc3758ed005c9/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ecaae4149d99b1c9e7b88bb03e3221956f68fd6d50be2ef061b2381b61d20838", size = 152542, upload-time = "2025-10-14T04:41:39.862Z" }, - { url = "https://files.pythonhosted.org/packages/16/85/276033dcbcc369eb176594de22728541a925b2632f9716428c851b149e83/charset_normalizer-3.4.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cb6254dc36b47a990e59e1068afacdcd02958bdcce30bb50cc1700a8b9d624a6", size = 149524, upload-time = "2025-10-14T04:41:41.319Z" }, - { url = "https://files.pythonhosted.org/packages/9e/f2/6a2a1f722b6aba37050e626530a46a68f74e63683947a8acff92569f979a/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c8ae8a0f02f57a6e61203a31428fa1d677cbe50c93622b4149d5c0f319c1d19e", size = 150395, upload-time = "2025-10-14T04:41:42.539Z" }, - { url = "https://files.pythonhosted.org/packages/60/bb/2186cb2f2bbaea6338cad15ce23a67f9b0672929744381e28b0592676824/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:47cc91b2f4dd2833fddaedd2893006b0106129d4b94fdb6af1f4ce5a9965577c", size = 143680, upload-time = "2025-10-14T04:41:43.661Z" }, - { url = "https://files.pythonhosted.org/packages/7d/a5/bf6f13b772fbb2a90360eb620d52ed8f796f3c5caee8398c3b2eb7b1c60d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:82004af6c302b5d3ab2cfc4cc5f29db16123b1a8417f2e25f9066f91d4411090", size = 162045, upload-time = "2025-10-14T04:41:44.821Z" }, - { url = "https://files.pythonhosted.org/packages/df/c5/d1be898bf0dc3ef9030c3825e5d3b83f2c528d207d246cbabe245966808d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b7d8f6c26245217bd2ad053761201e9f9680f8ce52f0fcd8d0755aeae5b2152", size = 149687, upload-time = "2025-10-14T04:41:46.442Z" }, - { url = "https://files.pythonhosted.org/packages/a5/42/90c1f7b9341eef50c8a1cb3f098ac43b0508413f33affd762855f67a410e/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:799a7a5e4fb2d5898c60b640fd4981d6a25f1c11790935a44ce38c54e985f828", size = 160014, upload-time = "2025-10-14T04:41:47.631Z" }, - { url = "https://files.pythonhosted.org/packages/76/be/4d3ee471e8145d12795ab655ece37baed0929462a86e72372fd25859047c/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:99ae2cffebb06e6c22bdc25801d7b30f503cc87dbd283479e7b606f70aff57ec", size = 154044, upload-time = "2025-10-14T04:41:48.81Z" }, - { url = "https://files.pythonhosted.org/packages/b0/6f/8f7af07237c34a1defe7defc565a9bc1807762f672c0fde711a4b22bf9c0/charset_normalizer-3.4.4-cp314-cp314-win32.whl", hash = "sha256:f9d332f8c2a2fcbffe1378594431458ddbef721c1769d78e2cbc06280d8155f9", size = 99940, upload-time = "2025-10-14T04:41:49.946Z" }, - { url = "https://files.pythonhosted.org/packages/4b/51/8ade005e5ca5b0d80fb4aff72a3775b325bdc3d27408c8113811a7cbe640/charset_normalizer-3.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:8a6562c3700cce886c5be75ade4a5db4214fda19fede41d9792d100288d8f94c", size = 107104, upload-time = "2025-10-14T04:41:51.051Z" }, - { url = "https://files.pythonhosted.org/packages/da/5f/6b8f83a55bb8278772c5ae54a577f3099025f9ade59d0136ac24a0df4bde/charset_normalizer-3.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:de00632ca48df9daf77a2c65a484531649261ec9f25489917f09e455cb09ddb2", size = 100743, upload-time = "2025-10-14T04:41:52.122Z" }, - { url = "https://files.pythonhosted.org/packages/0a/4c/925909008ed5a988ccbb72dcc897407e5d6d3bd72410d69e051fc0c14647/charset_normalizer-3.4.4-py3-none-any.whl", hash = "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f", size = 53402, upload-time = "2025-10-14T04:42:31.76Z" }, +version = "3.4.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1d/35/02daf95b9cd686320bb622eb148792655c9412dbb9b67abb5694e5910a24/charset_normalizer-3.4.5.tar.gz", hash = "sha256:95adae7b6c42a6c5b5b559b1a99149f090a57128155daeea91732c8d970d8644", size = 134804, upload-time = "2026-03-06T06:03:19.46Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/43/be/0f0fd9bb4a7fa4fb5067fb7d9ac693d4e928d306f80a0d02bde43a7c4aee/charset_normalizer-3.4.5-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8197abe5ca1ffb7d91e78360f915eef5addff270f8a71c1fc5be24a56f3e4873", size = 280232, upload-time = "2026-03-06T06:02:01.508Z" }, + { url = "https://files.pythonhosted.org/packages/28/02/983b5445e4bef49cd8c9da73a8e029f0825f39b74a06d201bfaa2e55142a/charset_normalizer-3.4.5-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a2aecdb364b8a1802afdc7f9327d55dad5366bc97d8502d0f5854e50712dbc5f", size = 189688, upload-time = "2026-03-06T06:02:02.857Z" }, + { url = "https://files.pythonhosted.org/packages/d0/88/152745c5166437687028027dc080e2daed6fe11cfa95a22f4602591c42db/charset_normalizer-3.4.5-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a66aa5022bf81ab4b1bebfb009db4fd68e0c6d4307a1ce5ef6a26e5878dfc9e4", size = 206833, upload-time = "2026-03-06T06:02:05.127Z" }, + { url = "https://files.pythonhosted.org/packages/cb/0f/ebc15c8b02af2f19be9678d6eed115feeeccc45ce1f4b098d986c13e8769/charset_normalizer-3.4.5-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d77f97e515688bd615c1d1f795d540f32542d514242067adcb8ef532504cb9ee", size = 202879, upload-time = "2026-03-06T06:02:06.446Z" }, + { url = "https://files.pythonhosted.org/packages/38/9c/71336bff6934418dc8d1e8a1644176ac9088068bc571da612767619c97b3/charset_normalizer-3.4.5-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01a1ed54b953303ca7e310fafe0fe347aab348bd81834a0bcd602eb538f89d66", size = 195764, upload-time = "2026-03-06T06:02:08.763Z" }, + { url = "https://files.pythonhosted.org/packages/b7/95/ce92fde4f98615661871bc282a856cf9b8a15f686ba0af012984660d480b/charset_normalizer-3.4.5-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:b2d37d78297b39a9eb9eb92c0f6df98c706467282055419df141389b23f93362", size = 183728, upload-time = "2026-03-06T06:02:10.137Z" }, + { url = "https://files.pythonhosted.org/packages/1c/e7/f5b4588d94e747ce45ae680f0f242bc2d98dbd4eccfab73e6160b6893893/charset_normalizer-3.4.5-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e71bbb595973622b817c042bd943c3f3667e9c9983ce3d205f973f486fec98a7", size = 192937, upload-time = "2026-03-06T06:02:11.663Z" }, + { url = "https://files.pythonhosted.org/packages/f9/29/9d94ed6b929bf9f48bf6ede6e7474576499f07c4c5e878fb186083622716/charset_normalizer-3.4.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:4cd966c2559f501c6fd69294d082c2934c8dd4719deb32c22961a5ac6db0df1d", size = 192040, upload-time = "2026-03-06T06:02:13.489Z" }, + { url = "https://files.pythonhosted.org/packages/15/d2/1a093a1cf827957f9445f2fe7298bcc16f8fc5e05c1ed2ad1af0b239035e/charset_normalizer-3.4.5-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:d5e52d127045d6ae01a1e821acfad2f3a1866c54d0e837828538fabe8d9d1bd6", size = 184107, upload-time = "2026-03-06T06:02:14.83Z" }, + { url = "https://files.pythonhosted.org/packages/0f/7d/82068ce16bd36135df7b97f6333c5d808b94e01d4599a682e2337ed5fd14/charset_normalizer-3.4.5-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:30a2b1a48478c3428d047ed9690d57c23038dac838a87ad624c85c0a78ebeb39", size = 208310, upload-time = "2026-03-06T06:02:16.165Z" }, + { url = "https://files.pythonhosted.org/packages/84/4e/4dfb52307bb6af4a5c9e73e482d171b81d36f522b21ccd28a49656baa680/charset_normalizer-3.4.5-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:d8ed79b8f6372ca4254955005830fd61c1ccdd8c0fac6603e2c145c61dd95db6", size = 192918, upload-time = "2026-03-06T06:02:18.144Z" }, + { url = "https://files.pythonhosted.org/packages/08/a4/159ff7da662cf7201502ca89980b8f06acf3e887b278956646a8aeb178ab/charset_normalizer-3.4.5-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:c5af897b45fa606b12464ccbe0014bbf8c09191e0a66aab6aa9d5cf6e77e0c94", size = 204615, upload-time = "2026-03-06T06:02:19.821Z" }, + { url = "https://files.pythonhosted.org/packages/d6/62/0dd6172203cb6b429ffffc9935001fde42e5250d57f07b0c28c6046deb6b/charset_normalizer-3.4.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:1088345bcc93c58d8d8f3d783eca4a6e7a7752bbff26c3eee7e73c597c191c2e", size = 197784, upload-time = "2026-03-06T06:02:21.86Z" }, + { url = "https://files.pythonhosted.org/packages/c7/5e/1aab5cb737039b9c59e63627dc8bbc0d02562a14f831cc450e5f91d84ce1/charset_normalizer-3.4.5-cp314-cp314-win32.whl", hash = "sha256:ee57b926940ba00bca7ba7041e665cc956e55ef482f851b9b65acb20d867e7a2", size = 133009, upload-time = "2026-03-06T06:02:23.289Z" }, + { url = "https://files.pythonhosted.org/packages/40/65/e7c6c77d7aaa4c0d7974f2e403e17f0ed2cb0fc135f77d686b916bf1eead/charset_normalizer-3.4.5-cp314-cp314-win_amd64.whl", hash = "sha256:4481e6da1830c8a1cc0b746b47f603b653dadb690bcd851d039ffaefe70533aa", size = 143511, upload-time = "2026-03-06T06:02:26.195Z" }, + { url = "https://files.pythonhosted.org/packages/ba/91/52b0841c71f152f563b8e072896c14e3d83b195c188b338d3cc2e582d1d4/charset_normalizer-3.4.5-cp314-cp314-win_arm64.whl", hash = "sha256:97ab7787092eb9b50fb47fa04f24c75b768a606af1bcba1957f07f128a7219e4", size = 133775, upload-time = "2026-03-06T06:02:27.473Z" }, + { url = "https://files.pythonhosted.org/packages/c5/60/3a621758945513adfd4db86827a5bafcc615f913dbd0b4c2ed64a65731be/charset_normalizer-3.4.5-py3-none-any.whl", hash = "sha256:9db5e3fcdcee89a78c04dffb3fe33c79f77bd741a624946db2591c81b2fc85b0", size = 55455, upload-time = "2026-03-06T06:03:17.827Z" }, +] + +[[package]] +name = "click" +version = "8.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3d/fa/656b739db8587d7b5dfa22e22ed02566950fbfbcdc20311993483657a5c0/click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a", size = 295065, upload-time = "2025-11-15T20:45:42.706Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6", size = 108274, upload-time = "2025-11-15T20:45:41.139Z" }, ] [[package]] @@ -165,20 +189,29 @@ wheels = [ [[package]] name = "filelock" -version = "3.24.3" +version = "3.25.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/77/18/a1fd2231c679dcb9726204645721b12498aeac28e1ad0601038f94b42556/filelock-3.25.0.tar.gz", hash = "sha256:8f00faf3abf9dc730a1ffe9c354ae5c04e079ab7d3a683b7c32da5dd05f26af3", size = 40158, upload-time = "2026-03-01T15:08:45.916Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f9/0b/de6f54d4a8bedfe8645c41497f3c18d749f0bd3218170c667bf4b81d0cdd/filelock-3.25.0-py3-none-any.whl", hash = "sha256:5ccf8069f7948f494968fc0713c10e5c182a9c9d9eef3a636307a20c2490f047", size = 26427, upload-time = "2026-03-01T15:08:44.593Z" }, +] + +[[package]] +name = "h11" +version = "0.16.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/73/92/a8e2479937ff39185d20dd6a851c1a63e55849e447a55e798cc2e1f49c65/filelock-3.24.3.tar.gz", hash = "sha256:011a5644dc937c22699943ebbfc46e969cdde3e171470a6e40b9533e5a72affa", size = 37935, upload-time = "2026-02-19T00:48:20.543Z" } +sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9c/0f/5d0c71a1aefeb08efff26272149e07ab922b64f46c63363756224bd6872e/filelock-3.24.3-py3-none-any.whl", hash = "sha256:426e9a4660391f7f8a810d71b0555bce9008b0a1cc342ab1f6947d37639e002d", size = 24331, upload-time = "2026-02-19T00:48:18.465Z" }, + { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, ] [[package]] name = "identify" -version = "2.6.16" +version = "2.6.17" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/5b/8d/e8b97e6bd3fb6fb271346f7981362f1e04d6a7463abd0de79e1fda17c067/identify-2.6.16.tar.gz", hash = "sha256:846857203b5511bbe94d5a352a48ef2359532bc8f6727b5544077a0dcfb24980", size = 99360, upload-time = "2026-01-12T18:58:58.201Z" } +sdist = { url = "https://files.pythonhosted.org/packages/57/84/376a3b96e5a8d33a7aa2c5b3b31a4b3c364117184bf0b17418055f6ace66/identify-2.6.17.tar.gz", hash = "sha256:f816b0b596b204c9fdf076ded172322f2723cf958d02f9c3587504834c8ff04d", size = 99579, upload-time = "2026-03-01T20:04:12.702Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b8/58/40fbbcefeda82364720eba5cf2270f98496bdfa19ea75b4cccae79c698e6/identify-2.6.16-py2.py3-none-any.whl", hash = "sha256:391ee4d77741d994189522896270b787aed8670389bfd60f326d677d64a6dfb0", size = 99202, upload-time = "2026-01-12T18:58:56.627Z" }, + { url = "https://files.pythonhosted.org/packages/40/66/71c1227dff78aaeb942fed29dd5651f2aec166cc7c9aeea3e8b26a539b7d/identify-2.6.17-py2.py3-none-any.whl", hash = "sha256:be5f8412d5ed4b20f2bd41a65f920990bdccaa6a4a18a08f1eefdcd0bdd885f0", size = 99382, upload-time = "2026-03-01T20:04:11.439Z" }, ] [[package]] @@ -192,11 +225,11 @@ wheels = [ [[package]] name = "imagesize" -version = "1.4.1" +version = "2.0.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a7/84/62473fb57d61e31fef6e36d64a179c8781605429fd927b5dd608c997be31/imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a", size = 1280026, upload-time = "2022-07-01T12:21:05.687Z" } +sdist = { url = "https://files.pythonhosted.org/packages/6c/e6/7bf14eeb8f8b7251141944835abd42eb20a658d89084b7e1f3e5fe394090/imagesize-2.0.0.tar.gz", hash = "sha256:8e8358c4a05c304f1fccf7ff96f036e7243a189e9e42e90851993c558cfe9ee3", size = 1773045, upload-time = "2026-03-03T14:18:29.941Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ff/62/85c4c919272577931d407be5ba5d71c20f0b616d31a0befe0ae45bb79abd/imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b", size = 8769, upload-time = "2022-07-01T12:21:02.467Z" }, + { url = "https://files.pythonhosted.org/packages/5f/53/fb7122b71361a0d121b669dcf3d31244ef75badbbb724af388948de543e2/imagesize-2.0.0-py2.py3-none-any.whl", hash = "sha256:5667c5bbb57ab3f1fa4bc366f4fbc971db3d5ed011fd2715fd8001f782718d96", size = 9441, upload-time = "2026-03-03T14:18:27.892Z" }, ] [[package]] @@ -208,6 +241,27 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, ] +[[package]] +name = "itsdangerous" +version = "2.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9c/cb/8ac0172223afbccb63986cc25049b154ecfb5e85932587206f42317be31d/itsdangerous-2.2.0.tar.gz", hash = "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173", size = 54410, upload-time = "2024-04-16T21:28:15.614Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/96/92447566d16df59b2a776c0fb82dbc4d9e07cd95062562af01e408583fc4/itsdangerous-2.2.0-py3-none-any.whl", hash = "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef", size = 16234, upload-time = "2024-04-16T21:28:14.499Z" }, +] + +[[package]] +name = "jedi" +version = "0.19.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "parso" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/72/3a/79a912fbd4d8dd6fbb02bf69afd3bb72cf0c729bb3063c6f4498603db17a/jedi-0.19.2.tar.gz", hash = "sha256:4770dc3de41bde3966b02eb84fbcf557fb33cce26ad23da12c742fb50ecb11f0", size = 1231287, upload-time = "2024-11-11T01:41:42.873Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl", hash = "sha256:a8ef22bde8490f57fe5c7681a3c83cb58874daf72b4784de3cce5b6ef6edb5b9", size = 1572278, upload-time = "2024-11-11T01:41:40.175Z" }, +] + [[package]] name = "jinja2" version = "3.1.6" @@ -266,6 +320,73 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/30/a8/e61a8c2b3cc7a597073d9cde1fcbb567e9d827f1db30c93cf80422eac70d/llvmlite-0.46.0-cp314-cp314-win_amd64.whl", hash = "sha256:7821eda3ec1f18050f981819756631d60b6d7ab1a6cf806d9efefbe3f4082d61", size = 39153056, upload-time = "2025-12-08T18:15:33.938Z" }, ] +[[package]] +name = "loro" +version = "1.10.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/27/ea6f3298fc87ea5f2d60ebfbca088e7d9b2ceb3993f67c83bfb81778ec01/loro-1.10.3.tar.gz", hash = "sha256:68184ab1c2ab94af6ad4aaba416d22f579cabee0b26cbb09a1f67858207bbce8", size = 68833, upload-time = "2025-12-09T10:14:06.644Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8d/4a/a5340b6fdf4cd34d758bed23bd1f64063b3b1b41ff4ecc94ee39259ee9a7/loro-1.10.3-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:623cf7df17626aa55bc6ca54e89177dbe71a5f1c293e102d6153f43991a1a041", size = 3213589, upload-time = "2025-12-09T10:11:35.377Z" }, + { url = "https://files.pythonhosted.org/packages/00/93/5164e93a77e365a92def77c1258386daef233516a29fb674a3b9d973b8b8/loro-1.10.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:d8e715d475f32a1462969aca27eeb3f998f309182978f55bc37ce5c515d92e90", size = 3029557, upload-time = "2025-12-09T10:11:20.076Z" }, + { url = "https://files.pythonhosted.org/packages/6c/30/94592d7c01f480ce99e1783b0d9203eb20ba2eab42575dabd384e3c9d1fa/loro-1.10.3-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:61e012a80e8c9fe248b9d0a76e91664c9479a72d976eaeed78f87b15b5d1d732", size = 3282335, upload-time = "2025-12-09T10:08:18.168Z" }, + { url = "https://files.pythonhosted.org/packages/e9/a8/7ae3c0b955aa638fa7dbd2d194c7759749a0d0d96a94805d5dec9b30eaea/loro-1.10.3-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:686ece56756acbaf80c986848915e9126a29a06d7a62209747e3ef1efc0bd8f6", size = 3333071, upload-time = "2025-12-09T10:08:55.314Z" }, + { url = "https://files.pythonhosted.org/packages/f7/10/151edebdb2bca626ad50911b761164ced16984b25b0b37b34b674ded8b29/loro-1.10.3-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3aa821c8871deca98f4605eb0c40fb26bcf82bd29c9e7fa33b183516c5395b11", size = 3698226, upload-time = "2025-12-09T10:09:30.474Z" }, + { url = "https://files.pythonhosted.org/packages/f4/ac/02a490e38466506b1003df4910d2a8ae582265023dae9e2217c98b56ea3f/loro-1.10.3-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:507d34137adb4148f79e1da7f89a21a4aab18565621a5dc2b389773fe98ac25b", size = 3407322, upload-time = "2025-12-09T10:10:04.199Z" }, + { url = "https://files.pythonhosted.org/packages/81/db/da51f2bcad81ca3733bc21e83f3b6752446436b565b90f5c350ad227ad01/loro-1.10.3-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:91d3b2e187ccfe2b14118a6e5617266fedcdf3435f6fa0a3db7b4afce8afa687", size = 3330268, upload-time = "2025-12-09T10:10:58.61Z" }, + { url = "https://files.pythonhosted.org/packages/4e/af/50d136c83d504a3a1f4ad33a6bf38b6933985a82741302255cf446a5f7ad/loro-1.10.3-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c0016f834fd1626710081334400aed8494380b55ef131f7133d21c3bd22d892a", size = 3673582, upload-time = "2025-12-09T10:10:35.849Z" }, + { url = "https://files.pythonhosted.org/packages/63/4d/53288aae777218e05c43af9c080652bcdbbc8d97c031607eedd3fc15617d/loro-1.10.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:71c4275dca5a8a86219d60545d4f60e081b4af44b490ac912c0481906934bfc6", size = 3463731, upload-time = "2025-12-09T10:11:54.102Z" }, + { url = "https://files.pythonhosted.org/packages/75/01/2389f26ffe8bc3ffe48a0a578f610dd49c709bbcf0d5d2642c6e2b52f490/loro-1.10.3-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:490f12571b2ed1a8eaf1edd3a7fffc55adac5010b1875fe1bb9e9af9a3907c38", size = 3602334, upload-time = "2025-12-09T10:12:30.082Z" }, + { url = "https://files.pythonhosted.org/packages/a7/16/07b64af13f5fcea025e003ca27bbd6f748217abbd4803dad88ea0900526c/loro-1.10.3-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:a374a43cadaa48528a5411496481df9ae52bf01e513f4509e37d6c986f199c0e", size = 3657896, upload-time = "2025-12-09T10:13:04.86Z" }, + { url = "https://files.pythonhosted.org/packages/c9/2f/4050770d7675ceced71651fe76971d5c27456b7098c0de03a4ecdbb0a02d/loro-1.10.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:1a93b2ee59f1fa8d98dd552211fd5693551893b34c1dd2ba0324806d6d14022f", size = 3544339, upload-time = "2025-12-09T10:13:40.396Z" }, + { url = "https://files.pythonhosted.org/packages/c9/21/67e27cb404c968fc19a841d5c6277f13a17c69a56f49e3c15ea1c92a28eb/loro-1.10.3-cp314-cp314-win32.whl", hash = "sha256:baa863e3d869422e3320e822c0b1f87f5dc44cda903d1bd3b7a16f8413ce3d92", size = 2706731, upload-time = "2025-12-09T10:14:31.604Z" }, + { url = "https://files.pythonhosted.org/packages/08/54/6770cf36aeb994489375e9ab9c01201e70ab7cc286fa97e907aa41b1bae6/loro-1.10.3-cp314-cp314-win_amd64.whl", hash = "sha256:f10ed3ca89485f942b8b2de796ed9783edb990e7e570605232de77489e9f3548", size = 2933563, upload-time = "2025-12-09T10:14:13.805Z" }, + { url = "https://files.pythonhosted.org/packages/24/f5/eb089fd25eb428709dbe79fd4d36b82a00572aa54badd1dff62511a38fe3/loro-1.10.3-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2b4d049efb1953aebfc16fa0b445ff5a37d4d08a1ab93f3b5a577a454b7a5ded", size = 3282369, upload-time = "2025-12-09T10:08:20.011Z" }, + { url = "https://files.pythonhosted.org/packages/30/d7/692cb87c908f6a8af6cbfc10ebab69e16780e3796e11454c2b481b5c3817/loro-1.10.3-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:56ecad7fbac58aa8bee52bb261a764aeef6c7b39c20f0d69e8fad908ab2ca7d8", size = 3332530, upload-time = "2025-12-09T10:08:57.07Z" }, + { url = "https://files.pythonhosted.org/packages/54/46/ed3afbf749288b6f70f3b859a6762538818bf6a557ca873b07d6b036946b/loro-1.10.3-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5d8d1be349d08b3a95592c6a17b80b1ea6aef892b1b8e2b93b540062d04e34e0", size = 3702599, upload-time = "2025-12-09T10:09:31.779Z" }, + { url = "https://files.pythonhosted.org/packages/fe/30/6cb616939c12bfe96a71a01a6e3551febf1c34bf9de114fafadbcfb65064/loro-1.10.3-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1ec0a0b9bc4e32c46f14710062ec5b536c72110318aaf85632a4f8b37e9a470a", size = 3404412, upload-time = "2025-12-09T10:10:05.448Z" }, + { url = "https://files.pythonhosted.org/packages/02/a2/3d4006d3333589f9158ac6d403979bf5c985be8b461b18e7a2ea23b05414/loro-1.10.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c5d4437987f7a4a4ff5927f39d0f43ded5b34295dfb0a3c8e150687e25c3d6b8", size = 3462948, upload-time = "2025-12-09T10:11:55.405Z" }, + { url = "https://files.pythonhosted.org/packages/41/30/c640ccd3e570b08770a9f459decc2d8e7ceefdc34ac28a745418fb9cb5ba/loro-1.10.3-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:86d4f0c631ca274ad2fa2c0bdb8e1e141882d94339b7284a8bef5bf73fa6957d", size = 3599851, upload-time = "2025-12-09T10:12:31.759Z" }, + { url = "https://files.pythonhosted.org/packages/59/8f/062ea50554c47ae30e98b1f0442a458c0edecc6d4edc7fcfc4d901734dd0/loro-1.10.3-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:15e03084ff1b472e14623183ed6e1e43e0f717c2112697beda5e69b5bd0ff236", size = 3655558, upload-time = "2025-12-09T10:13:06.529Z" }, + { url = "https://files.pythonhosted.org/packages/f3/f5/c7dd8cdbd57454b23d89799c22cd42b6d2dda283cd87d7b198dc424a462c/loro-1.10.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:42d6a5ce5bc518eaa682413e82d597299650eeb03e8bc39341752d6e0d22503e", size = 3541282, upload-time = "2025-12-09T10:13:42.189Z" }, +] + +[[package]] +name = "marimo" +version = "0.20.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "docutils" }, + { name = "itsdangerous" }, + { name = "jedi" }, + { name = "loro" }, + { name = "markdown" }, + { name = "msgspec" }, + { name = "narwhals" }, + { name = "packaging" }, + { name = "psutil" }, + { name = "pygments" }, + { name = "pymdown-extensions" }, + { name = "pyyaml" }, + { name = "starlette" }, + { name = "tomlkit" }, + { name = "uvicorn" }, + { name = "websockets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9c/eb/3c19ce54be3440cece96bffbb2027c57d618ab336d53ef6a962f570ce468/marimo-0.20.4.tar.gz", hash = "sha256:7f46ce837953717504673e33e47fb42a619bf5f9d2000d1a3a3b1663a47c5498", size = 38336283, upload-time = "2026-03-04T01:15:13.641Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/56/62/bf2c92cc7aab69edfb3907e9bf102c37c1e16b8f4bc6ddd4258ae4c8063c/marimo-0.20.4-py3-none-any.whl", hash = "sha256:3200edd7209f470821b58deb4a45e0577889fab1052748d77088f18bef7d9dd8", size = 38752562, upload-time = "2026-03-04T01:15:09.608Z" }, +] + +[[package]] +name = "markdown" +version = "3.10.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2b/f4/69fa6ed85ae003c2378ffa8f6d2e3234662abd02c10d216c0ba96081a238/markdown-3.10.2.tar.gz", hash = "sha256:994d51325d25ad8aa7ce4ebaec003febcce822c3f8c911e3b17c52f7f589f950", size = 368805, upload-time = "2026-02-09T14:57:26.942Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.2-py3-none-any.whl", hash = "sha256:e91464b71ae3ee7afd3017d9f358ef0baf158fd9a298db92f1d4761133824c36", size = 108180, upload-time = "2026-02-09T14:57:25.787Z" }, +] + [[package]] name = "markdown-it-py" version = "4.0.0" @@ -329,6 +450,30 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, ] +[[package]] +name = "msgspec" +version = "0.20.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ea/9c/bfbd12955a49180cbd234c5d29ec6f74fe641698f0cd9df154a854fc8a15/msgspec-0.20.0.tar.gz", hash = "sha256:692349e588fde322875f8d3025ac01689fead5901e7fb18d6870a44519d62a29", size = 317862, upload-time = "2025-11-24T03:56:28.934Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bb/18/62dc13ab0260c7d741dda8dc7f481495b93ac9168cd887dda5929880eef8/msgspec-0.20.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:eead16538db1b3f7ec6e3ed1f6f7c5dec67e90f76e76b610e1ffb5671815633a", size = 196407, upload-time = "2025-11-24T03:55:55.001Z" }, + { url = "https://files.pythonhosted.org/packages/dd/1d/b9949e4ad6953e9f9a142c7997b2f7390c81e03e93570c7c33caf65d27e1/msgspec-0.20.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:703c3bb47bf47801627fb1438f106adbfa2998fe586696d1324586a375fca238", size = 188889, upload-time = "2025-11-24T03:55:56.311Z" }, + { url = "https://files.pythonhosted.org/packages/1e/19/f8bb2dc0f1bfe46cc7d2b6b61c5e9b5a46c62298e8f4d03bbe499c926180/msgspec-0.20.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6cdb227dc585fb109305cee0fd304c2896f02af93ecf50a9c84ee54ee67dbb42", size = 219691, upload-time = "2025-11-24T03:55:57.908Z" }, + { url = "https://files.pythonhosted.org/packages/b8/8e/6b17e43f6eb9369d9858ee32c97959fcd515628a1df376af96c11606cf70/msgspec-0.20.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:27d35044dd8818ac1bd0fedb2feb4fbdff4e3508dd7c5d14316a12a2d96a0de0", size = 224918, upload-time = "2025-11-24T03:55:59.322Z" }, + { url = "https://files.pythonhosted.org/packages/1c/db/0e833a177db1a4484797adba7f429d4242585980b90882cc38709e1b62df/msgspec-0.20.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b4296393a29ee42dd25947981c65506fd4ad39beaf816f614146fa0c5a6c91ae", size = 223436, upload-time = "2025-11-24T03:56:00.716Z" }, + { url = "https://files.pythonhosted.org/packages/c3/30/d2ee787f4c918fd2b123441d49a7707ae9015e0e8e1ab51aa7967a97b90e/msgspec-0.20.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:205fbdadd0d8d861d71c8f3399fe1a82a2caf4467bc8ff9a626df34c12176980", size = 227190, upload-time = "2025-11-24T03:56:02.371Z" }, + { url = "https://files.pythonhosted.org/packages/ff/37/9c4b58ff11d890d788e700b827db2366f4d11b3313bf136780da7017278b/msgspec-0.20.0-cp314-cp314-win_amd64.whl", hash = "sha256:7dfebc94fe7d3feec6bc6c9df4f7e9eccc1160bb5b811fbf3e3a56899e398a6b", size = 193950, upload-time = "2025-11-24T03:56:03.668Z" }, + { url = "https://files.pythonhosted.org/packages/e9/4e/cab707bf2fa57408e2934e5197fc3560079db34a1e3cd2675ff2e47e07de/msgspec-0.20.0-cp314-cp314-win_arm64.whl", hash = "sha256:2ad6ae36e4a602b24b4bf4eaf8ab5a441fec03e1f1b5931beca8ebda68f53fc0", size = 179018, upload-time = "2025-11-24T03:56:05.038Z" }, + { url = "https://files.pythonhosted.org/packages/4c/06/3da3fc9aaa55618a8f43eb9052453cfe01f82930bca3af8cea63a89f3a11/msgspec-0.20.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:f84703e0e6ef025663dd1de828ca028774797b8155e070e795c548f76dde65d5", size = 200389, upload-time = "2025-11-24T03:56:06.375Z" }, + { url = "https://files.pythonhosted.org/packages/83/3b/cc4270a5ceab40dfe1d1745856951b0a24fd16ac8539a66ed3004a60c91e/msgspec-0.20.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7c83fc24dd09cf1275934ff300e3951b3adc5573f0657a643515cc16c7dee131", size = 193198, upload-time = "2025-11-24T03:56:07.742Z" }, + { url = "https://files.pythonhosted.org/packages/cd/ae/4c7905ac53830c8e3c06fdd60e3cdcfedc0bbc993872d1549b84ea21a1bd/msgspec-0.20.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f13ccb1c335a124e80c4562573b9b90f01ea9521a1a87f7576c2e281d547f56", size = 225973, upload-time = "2025-11-24T03:56:09.18Z" }, + { url = "https://files.pythonhosted.org/packages/d9/da/032abac1de4d0678d99eaeadb1323bd9d247f4711c012404ba77ed6f15ca/msgspec-0.20.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:17c2b5ca19f19306fc83c96d85e606d2cc107e0caeea85066b5389f664e04846", size = 229509, upload-time = "2025-11-24T03:56:10.898Z" }, + { url = "https://files.pythonhosted.org/packages/69/52/fdc7bdb7057a166f309e0b44929e584319e625aaba4771b60912a9321ccd/msgspec-0.20.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:d931709355edabf66c2dd1a756b2d658593e79882bc81aae5964969d5a291b63", size = 230434, upload-time = "2025-11-24T03:56:12.48Z" }, + { url = "https://files.pythonhosted.org/packages/cb/fe/1dfd5f512b26b53043884e4f34710c73e294e7cc54278c3fe28380e42c37/msgspec-0.20.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:565f915d2e540e8a0c93a01ff67f50aebe1f7e22798c6a25873f9fda8d1325f8", size = 231758, upload-time = "2025-11-24T03:56:13.765Z" }, + { url = "https://files.pythonhosted.org/packages/97/f6/9ba7121b8e0c4e0beee49575d1dbc804e2e72467692f0428cf39ceba1ea5/msgspec-0.20.0-cp314-cp314t-win_amd64.whl", hash = "sha256:726f3e6c3c323f283f6021ebb6c8ccf58d7cd7baa67b93d73bfbe9a15c34ab8d", size = 206540, upload-time = "2025-11-24T03:56:15.029Z" }, + { url = "https://files.pythonhosted.org/packages/c8/3e/c5187de84bb2c2ca334ab163fcacf19a23ebb1d876c837f81a1b324a15bf/msgspec-0.20.0-cp314-cp314t-win_arm64.whl", hash = "sha256:93f23528edc51d9f686808a361728e903d6f2be55c901d6f5c92e44c6d546bfc", size = 183011, upload-time = "2025-11-24T03:56:16.442Z" }, +] + [[package]] name = "mypy" version = "1.19.1" @@ -376,6 +521,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d3/ac/686789b9145413f1a61878c407210e41bfdb097976864e0913078b24098c/myst_parser-5.0.0-py3-none-any.whl", hash = "sha256:ab31e516024918296e169139072b81592336f2fef55b8986aa31c9f04b5f7211", size = 84533, upload-time = "2026-01-15T09:08:16.788Z" }, ] +[[package]] +name = "narwhals" +version = "2.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/75/59/81d0f4cad21484083466f278e6b392addd9f4205b48d45b5c8771670ebf8/narwhals-2.17.0.tar.gz", hash = "sha256:ebd5bc95bcfa2f8e89a8ac09e2765a63055162837208e67b42d6eeb6651d5e67", size = 620306, upload-time = "2026-02-23T09:44:34.142Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4b/27/20770bd6bf8fbe1e16f848ba21da9df061f38d2e6483952c29d2bb5d1d8b/narwhals-2.17.0-py3-none-any.whl", hash = "sha256:2ac5307b7c2b275a7d66eeda906b8605e3d7a760951e188dcfff86e8ebe083dd", size = 444897, upload-time = "2026-02-23T09:44:32.006Z" }, +] + [[package]] name = "nodeenv" version = "1.10.0" @@ -449,8 +603,14 @@ dependencies = [ { name = "types-pyyaml" }, ] +[package.optional-dependencies] +marimo = [ + { name = "marimo" }, +] + [package.metadata] requires-dist = [ + { name = "marimo", marker = "extra == 'marimo'" }, { name = "mypy", specifier = ">=1.19.1" }, { name = "myst-parser", specifier = ">=5.0.0" }, { name = "numba", specifier = ">=0.64.0" }, @@ -465,6 +625,7 @@ requires-dist = [ { name = "sphinx-autoapi", specifier = ">=3.7.0" }, { name = "types-pyyaml", specifier = ">=6.0.12.20250915" }, ] +provides-extras = ["marimo"] [[package]] name = "packaging" @@ -475,6 +636,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529", size = 74366, upload-time = "2026-01-21T20:50:37.788Z" }, ] +[[package]] +name = "parso" +version = "0.8.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/81/76/a1e769043c0c0c9fe391b702539d594731a4362334cdf4dc25d0c09761e7/parso-0.8.6.tar.gz", hash = "sha256:2b9a0332696df97d454fa67b81618fd69c35a7b90327cbe6ba5c92d2c68a7bfd", size = 401621, upload-time = "2026-02-09T15:45:24.425Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b6/61/fae042894f4296ec49e3f193aff5d7c18440da9e48102c3315e1bc4519a7/parso-0.8.6-py2.py3-none-any.whl", hash = "sha256:2c549f800b70a5c4952197248825584cb00f033b29c692671d3bf08bf380baff", size = 106894, upload-time = "2026-02-09T15:45:21.391Z" }, +] + [[package]] name = "pathspec" version = "1.0.4" @@ -486,11 +656,11 @@ wheels = [ [[package]] name = "platformdirs" -version = "4.9.2" +version = "4.9.4" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1b/04/fea538adf7dbbd6d186f551d595961e564a3b6715bdf276b477460858672/platformdirs-4.9.2.tar.gz", hash = "sha256:9a33809944b9db043ad67ca0db94b14bf452cc6aeaac46a88ea55b26e2e9d291", size = 28394, upload-time = "2026-02-16T03:56:10.574Z" } +sdist = { url = "https://files.pythonhosted.org/packages/19/56/8d4c30c8a1d07013911a8fdbd8f89440ef9f08d07a1b50ab8ca8be5a20f9/platformdirs-4.9.4.tar.gz", hash = "sha256:1ec356301b7dc906d83f371c8f487070e99d3ccf9e501686456394622a01a934", size = 28737, upload-time = "2026-03-05T18:34:13.271Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/48/31/05e764397056194206169869b50cf2fee4dbbbc71b344705b9c0d878d4d8/platformdirs-4.9.2-py3-none-any.whl", hash = "sha256:9170634f126f8efdae22fb58ae8a0eaa86f38365bc57897a6c4f781d1f5875bd", size = 21168, upload-time = "2026-02-16T03:56:08.891Z" }, + { url = "https://files.pythonhosted.org/packages/63/d7/97f7e3a6abb67d8080dd406fd4df842c2be0efaf712d1c899c32a075027c/platformdirs-4.9.4-py3-none-any.whl", hash = "sha256:68a9a4619a666ea6439f2ff250c12a853cd1cbd5158d258bd824a7df6be2f868", size = 21216, upload-time = "2026-03-05T18:34:12.172Z" }, ] [[package]] @@ -518,6 +688,28 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/5d/19/fd3ef348460c80af7bb4669ea7926651d1f95c23ff2df18b9d24bab4f3fa/pre_commit-4.5.1-py2.py3-none-any.whl", hash = "sha256:3b3afd891e97337708c1674210f8eba659b52a38ea5f822ff142d10786221f77", size = 226437, upload-time = "2025-12-16T21:14:32.409Z" }, ] +[[package]] +name = "psutil" +version = "7.2.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/aa/c6/d1ddf4abb55e93cebc4f2ed8b5d6dbad109ecb8d63748dd2b20ab5e57ebe/psutil-7.2.2.tar.gz", hash = "sha256:0746f5f8d406af344fd547f1c8daa5f5c33dbc293bb8d6a16d80b4bb88f59372", size = 493740, upload-time = "2026-01-28T18:14:54.428Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/69/ef179ab5ca24f32acc1dac0c247fd6a13b501fd5534dbae0e05a1c48b66d/psutil-7.2.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:eed63d3b4d62449571547b60578c5b2c4bcccc5387148db46e0c2313dad0ee00", size = 130664, upload-time = "2026-01-28T18:15:09.469Z" }, + { url = "https://files.pythonhosted.org/packages/7b/64/665248b557a236d3fa9efc378d60d95ef56dd0a490c2cd37dafc7660d4a9/psutil-7.2.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7b6d09433a10592ce39b13d7be5a54fbac1d1228ed29abc880fb23df7cb694c9", size = 131087, upload-time = "2026-01-28T18:15:11.724Z" }, + { url = "https://files.pythonhosted.org/packages/d5/2e/e6782744700d6759ebce3043dcfa661fb61e2fb752b91cdeae9af12c2178/psutil-7.2.2-cp314-cp314t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1fa4ecf83bcdf6e6c8f4449aff98eefb5d0604bf88cb883d7da3d8d2d909546a", size = 182383, upload-time = "2026-01-28T18:15:13.445Z" }, + { url = "https://files.pythonhosted.org/packages/57/49/0a41cefd10cb7505cdc04dab3eacf24c0c2cb158a998b8c7b1d27ee2c1f5/psutil-7.2.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e452c464a02e7dc7822a05d25db4cde564444a67e58539a00f929c51eddda0cf", size = 185210, upload-time = "2026-01-28T18:15:16.002Z" }, + { url = "https://files.pythonhosted.org/packages/dd/2c/ff9bfb544f283ba5f83ba725a3c5fec6d6b10b8f27ac1dc641c473dc390d/psutil-7.2.2-cp314-cp314t-win_amd64.whl", hash = "sha256:c7663d4e37f13e884d13994247449e9f8f574bc4655d509c3b95e9ec9e2b9dc1", size = 141228, upload-time = "2026-01-28T18:15:18.385Z" }, + { url = "https://files.pythonhosted.org/packages/f2/fc/f8d9c31db14fcec13748d373e668bc3bed94d9077dbc17fb0eebc073233c/psutil-7.2.2-cp314-cp314t-win_arm64.whl", hash = "sha256:11fe5a4f613759764e79c65cf11ebdf26e33d6dd34336f8a337aa2996d71c841", size = 136284, upload-time = "2026-01-28T18:15:19.912Z" }, + { url = "https://files.pythonhosted.org/packages/e7/36/5ee6e05c9bd427237b11b3937ad82bb8ad2752d72c6969314590dd0c2f6e/psutil-7.2.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ed0cace939114f62738d808fdcecd4c869222507e266e574799e9c0faa17d486", size = 129090, upload-time = "2026-01-28T18:15:22.168Z" }, + { url = "https://files.pythonhosted.org/packages/80/c4/f5af4c1ca8c1eeb2e92ccca14ce8effdeec651d5ab6053c589b074eda6e1/psutil-7.2.2-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:1a7b04c10f32cc88ab39cbf606e117fd74721c831c98a27dc04578deb0c16979", size = 129859, upload-time = "2026-01-28T18:15:23.795Z" }, + { url = "https://files.pythonhosted.org/packages/b5/70/5d8df3b09e25bce090399cf48e452d25c935ab72dad19406c77f4e828045/psutil-7.2.2-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:076a2d2f923fd4821644f5ba89f059523da90dc9014e85f8e45a5774ca5bc6f9", size = 155560, upload-time = "2026-01-28T18:15:25.976Z" }, + { url = "https://files.pythonhosted.org/packages/63/65/37648c0c158dc222aba51c089eb3bdfa238e621674dc42d48706e639204f/psutil-7.2.2-cp36-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b0726cecd84f9474419d67252add4ac0cd9811b04d61123054b9fb6f57df6e9e", size = 156997, upload-time = "2026-01-28T18:15:27.794Z" }, + { url = "https://files.pythonhosted.org/packages/8e/13/125093eadae863ce03c6ffdbae9929430d116a246ef69866dad94da3bfbc/psutil-7.2.2-cp36-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:fd04ef36b4a6d599bbdb225dd1d3f51e00105f6d48a28f006da7f9822f2606d8", size = 148972, upload-time = "2026-01-28T18:15:29.342Z" }, + { url = "https://files.pythonhosted.org/packages/04/78/0acd37ca84ce3ddffaa92ef0f571e073faa6d8ff1f0559ab1272188ea2be/psutil-7.2.2-cp36-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b58fabe35e80b264a4e3bb23e6b96f9e45a3df7fb7eed419ac0e5947c61e47cc", size = 148266, upload-time = "2026-01-28T18:15:31.597Z" }, + { url = "https://files.pythonhosted.org/packages/b4/90/e2159492b5426be0c1fef7acba807a03511f97c5f86b3caeda6ad92351a7/psutil-7.2.2-cp37-abi3-win_amd64.whl", hash = "sha256:eb7e81434c8d223ec4a219b5fc1c47d0417b12be7ea866e24fb5ad6e84b3d988", size = 137737, upload-time = "2026-01-28T18:15:33.849Z" }, + { url = "https://files.pythonhosted.org/packages/8c/c7/7bb2e321574b10df20cbde462a94e2b71d05f9bbda251ef27d104668306a/psutil-7.2.2-cp37-abi3-win_arm64.whl", hash = "sha256:8c233660f575a5a89e6d4cb65d9f938126312bca76d8fe087b947b3a1aaac9ee", size = 134617, upload-time = "2026-01-28T18:15:36.514Z" }, +] + [[package]] name = "pydata-sphinx-theme" version = "0.16.1" @@ -545,6 +737,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, ] +[[package]] +name = "pymdown-extensions" +version = "10.21" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown" }, + { name = "pyyaml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ba/63/06673d1eb6d8f83c0ea1f677d770e12565fb516928b4109c9e2055656a9e/pymdown_extensions-10.21.tar.gz", hash = "sha256:39f4a020f40773f6b2ff31d2cd2546c2c04d0a6498c31d9c688d2be07e1767d5", size = 853363, upload-time = "2026-02-15T20:44:06.748Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6f/2c/5b079febdc65e1c3fb2729bf958d18b45be7113828528e8a0b5850dd819a/pymdown_extensions-10.21-py3-none-any.whl", hash = "sha256:91b879f9f864d49794c2d9534372b10150e6141096c3908a455e45ca72ad9d3f", size = 268877, upload-time = "2026-02-15T20:44:05.464Z" }, +] + [[package]] name = "pytest" version = "9.0.2" @@ -575,6 +780,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl", hash = "sha256:3b8e9558b16cc1479da72058bdecf8073661c7f57f7d3c5f22a1c23507f2d861", size = 22424, upload-time = "2025-09-09T10:57:00.695Z" }, ] +[[package]] +name = "python-discovery" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "filelock" }, + { name = "platformdirs" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/82/bb/93a3e83bdf9322c7e21cafd092e56a4a17c4d8ef4277b6eb01af1a540a6f/python_discovery-1.1.0.tar.gz", hash = "sha256:447941ba1aed8cc2ab7ee3cb91be5fc137c5bdbb05b7e6ea62fbdcb66e50b268", size = 55674, upload-time = "2026-02-26T09:42:49.668Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/06/54/82a6e2ef37f0f23dccac604b9585bdcbd0698604feb64807dcb72853693e/python_discovery-1.1.0-py3-none-any.whl", hash = "sha256:a162893b8809727f54594a99ad2179d2ede4bf953e12d4c7abc3cc9cdbd1437b", size = 30687, upload-time = "2026-02-26T09:42:48.548Z" }, +] + [[package]] name = "pyyaml" version = "6.0.3" @@ -771,6 +989,27 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/52/a7/d2782e4e3f77c8450f727ba74a8f12756d5ba823d81b941f1b04da9d033a/sphinxcontrib_serializinghtml-2.0.0-py3-none-any.whl", hash = "sha256:6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331", size = 92072, upload-time = "2024-07-29T01:10:08.203Z" }, ] +[[package]] +name = "starlette" +version = "0.52.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c4/68/79977123bb7be889ad680d79a40f339082c1978b5cfcf62c2d8d196873ac/starlette-0.52.1.tar.gz", hash = "sha256:834edd1b0a23167694292e94f597773bc3f89f362be6effee198165a35d62933", size = 2653702, upload-time = "2026-01-18T13:34:11.062Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/0d/13d1d239a25cbfb19e740db83143e95c772a1fe10202dda4b76792b114dd/starlette-0.52.1-py3-none-any.whl", hash = "sha256:0029d43eb3d273bc4f83a08720b4912ea4b071087a3b48db01b7c839f7954d74", size = 74272, upload-time = "2026-01-18T13:34:09.188Z" }, +] + +[[package]] +name = "tomlkit" +version = "0.14.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c3/af/14b24e41977adb296d6bd1fb59402cf7d60ce364f90c890bd2ec65c43b5a/tomlkit-0.14.0.tar.gz", hash = "sha256:cf00efca415dbd57575befb1f6634c4f42d2d87dbba376128adb42c121b87064", size = 187167, upload-time = "2026-01-13T01:14:53.304Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b5/11/87d6d29fb5d237229d67973a6c9e06e048f01cf4994dee194ab0ea841814/tomlkit-0.14.0-py3-none-any.whl", hash = "sha256:592064ed85b40fa213469f81ac584f67a4f2992509a7c3ea2d632208623a3680", size = 39310, upload-time = "2026-01-13T01:14:51.965Z" }, +] + [[package]] name = "types-pyyaml" version = "6.0.12.20250915" @@ -798,16 +1037,57 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4", size = 131584, upload-time = "2026-01-07T16:24:42.685Z" }, ] +[[package]] +name = "uvicorn" +version = "0.41.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/32/ce/eeb58ae4ac36fe09e3842eb02e0eb676bf2c53ae062b98f1b2531673efdd/uvicorn-0.41.0.tar.gz", hash = "sha256:09d11cf7008da33113824ee5a1c6422d89fbc2ff476540d69a34c87fab8b571a", size = 82633, upload-time = "2026-02-16T23:07:24.1Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/83/e4/d04a086285c20886c0daad0e026f250869201013d18f81d9ff5eada73a88/uvicorn-0.41.0-py3-none-any.whl", hash = "sha256:29e35b1d2c36a04b9e180d4007ede3bcb32a85fbdfd6c6aeb3f26839de088187", size = 68783, upload-time = "2026-02-16T23:07:22.357Z" }, +] + [[package]] name = "virtualenv" -version = "20.39.0" +version = "21.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "distlib" }, { name = "filelock" }, { name = "platformdirs" }, + { name = "python-discovery" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2f/c9/18d4b36606d6091844daa3bd93cf7dc78e6f5da21d9f21d06c221104b684/virtualenv-21.1.0.tar.gz", hash = "sha256:1990a0188c8f16b6b9cf65c9183049007375b26aad415514d377ccacf1e4fb44", size = 5840471, upload-time = "2026-02-27T08:49:29.702Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/55/896b06bf93a49bec0f4ae2a6f1ed12bd05c8860744ac3a70eda041064e4d/virtualenv-21.1.0-py3-none-any.whl", hash = "sha256:164f5e14c5587d170cf98e60378eb91ea35bf037be313811905d3a24ea33cc07", size = 5825072, upload-time = "2026-02-27T08:49:27.516Z" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ed/54/809199edc537dbace273495ac0884d13df26436e910a5ed4d0ec0a69806b/virtualenv-20.39.0.tar.gz", hash = "sha256:a15f0cebd00d50074fd336a169d53422436a12dfe15149efec7072cfe817df8b", size = 5869141, upload-time = "2026-02-23T18:09:13.349Z" } + +[[package]] +name = "websockets" +version = "16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/04/24/4b2031d72e840ce4c1ccb255f693b15c334757fc50023e4db9537080b8c4/websockets-16.0.tar.gz", hash = "sha256:5f6261a5e56e8d5c42a4497b364ea24d94d9563e8fbd44e78ac40879c60179b5", size = 179346, upload-time = "2026-01-10T09:23:47.181Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f7/b4/8268da45f26f4fe84f6eae80a6ca1485ffb490a926afecff75fc48f61979/virtualenv-20.39.0-py3-none-any.whl", hash = "sha256:44888bba3775990a152ea1f73f8e5f566d49f11bbd1de61d426fd7732770043e", size = 5839121, upload-time = "2026-02-23T18:09:11.173Z" }, + { url = "https://files.pythonhosted.org/packages/f3/1d/e88022630271f5bd349ed82417136281931e558d628dd52c4d8621b4a0b2/websockets-16.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8cc451a50f2aee53042ac52d2d053d08bf89bcb31ae799cb4487587661c038a0", size = 177406, upload-time = "2026-01-10T09:23:12.178Z" }, + { url = "https://files.pythonhosted.org/packages/f2/78/e63be1bf0724eeb4616efb1ae1c9044f7c3953b7957799abb5915bffd38e/websockets-16.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:daa3b6ff70a9241cf6c7fc9e949d41232d9d7d26fd3522b1ad2b4d62487e9904", size = 175085, upload-time = "2026-01-10T09:23:13.511Z" }, + { url = "https://files.pythonhosted.org/packages/bb/f4/d3c9220d818ee955ae390cf319a7c7a467beceb24f05ee7aaaa2414345ba/websockets-16.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:fd3cb4adb94a2a6e2b7c0d8d05cb94e6f1c81a0cf9dc2694fb65c7e8d94c42e4", size = 175328, upload-time = "2026-01-10T09:23:14.727Z" }, + { url = "https://files.pythonhosted.org/packages/63/bc/d3e208028de777087e6fb2b122051a6ff7bbcca0d6df9d9c2bf1dd869ae9/websockets-16.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:781caf5e8eee67f663126490c2f96f40906594cb86b408a703630f95550a8c3e", size = 185044, upload-time = "2026-01-10T09:23:15.939Z" }, + { url = "https://files.pythonhosted.org/packages/ad/6e/9a0927ac24bd33a0a9af834d89e0abc7cfd8e13bed17a86407a66773cc0e/websockets-16.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:caab51a72c51973ca21fa8a18bd8165e1a0183f1ac7066a182ff27107b71e1a4", size = 186279, upload-time = "2026-01-10T09:23:17.148Z" }, + { url = "https://files.pythonhosted.org/packages/b9/ca/bf1c68440d7a868180e11be653c85959502efd3a709323230314fda6e0b3/websockets-16.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:19c4dc84098e523fd63711e563077d39e90ec6702aff4b5d9e344a60cb3c0cb1", size = 185711, upload-time = "2026-01-10T09:23:18.372Z" }, + { url = "https://files.pythonhosted.org/packages/c4/f8/fdc34643a989561f217bb477cbc47a3a07212cbda91c0e4389c43c296ebf/websockets-16.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:a5e18a238a2b2249c9a9235466b90e96ae4795672598a58772dd806edc7ac6d3", size = 184982, upload-time = "2026-01-10T09:23:19.652Z" }, + { url = "https://files.pythonhosted.org/packages/dd/d1/574fa27e233764dbac9c52730d63fcf2823b16f0856b3329fc6268d6ae4f/websockets-16.0-cp314-cp314-win32.whl", hash = "sha256:a069d734c4a043182729edd3e9f247c3b2a4035415a9172fd0f1b71658a320a8", size = 177915, upload-time = "2026-01-10T09:23:21.458Z" }, + { url = "https://files.pythonhosted.org/packages/8a/f1/ae6b937bf3126b5134ce1f482365fde31a357c784ac51852978768b5eff4/websockets-16.0-cp314-cp314-win_amd64.whl", hash = "sha256:c0ee0e63f23914732c6d7e0cce24915c48f3f1512ec1d079ed01fc629dab269d", size = 178381, upload-time = "2026-01-10T09:23:22.715Z" }, + { url = "https://files.pythonhosted.org/packages/06/9b/f791d1db48403e1f0a27577a6beb37afae94254a8c6f08be4a23e4930bc0/websockets-16.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:a35539cacc3febb22b8f4d4a99cc79b104226a756aa7400adc722e83b0d03244", size = 177737, upload-time = "2026-01-10T09:23:24.523Z" }, + { url = "https://files.pythonhosted.org/packages/bd/40/53ad02341fa33b3ce489023f635367a4ac98b73570102ad2cdd770dacc9a/websockets-16.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:b784ca5de850f4ce93ec85d3269d24d4c82f22b7212023c974c401d4980ebc5e", size = 175268, upload-time = "2026-01-10T09:23:25.781Z" }, + { url = "https://files.pythonhosted.org/packages/74/9b/6158d4e459b984f949dcbbb0c5d270154c7618e11c01029b9bbd1bb4c4f9/websockets-16.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:569d01a4e7fba956c5ae4fc988f0d4e187900f5497ce46339c996dbf24f17641", size = 175486, upload-time = "2026-01-10T09:23:27.033Z" }, + { url = "https://files.pythonhosted.org/packages/e5/2d/7583b30208b639c8090206f95073646c2c9ffd66f44df967981a64f849ad/websockets-16.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:50f23cdd8343b984957e4077839841146f67a3d31ab0d00e6b824e74c5b2f6e8", size = 185331, upload-time = "2026-01-10T09:23:28.259Z" }, + { url = "https://files.pythonhosted.org/packages/45/b0/cce3784eb519b7b5ad680d14b9673a31ab8dcb7aad8b64d81709d2430aa8/websockets-16.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:152284a83a00c59b759697b7f9e9cddf4e3c7861dd0d964b472b70f78f89e80e", size = 186501, upload-time = "2026-01-10T09:23:29.449Z" }, + { url = "https://files.pythonhosted.org/packages/19/60/b8ebe4c7e89fb5f6cdf080623c9d92789a53636950f7abacfc33fe2b3135/websockets-16.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:bc59589ab64b0022385f429b94697348a6a234e8ce22544e3681b2e9331b5944", size = 186062, upload-time = "2026-01-10T09:23:31.368Z" }, + { url = "https://files.pythonhosted.org/packages/88/a8/a080593f89b0138b6cba1b28f8df5673b5506f72879322288b031337c0b8/websockets-16.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:32da954ffa2814258030e5a57bc73a3635463238e797c7375dc8091327434206", size = 185356, upload-time = "2026-01-10T09:23:32.627Z" }, + { url = "https://files.pythonhosted.org/packages/c2/b6/b9afed2afadddaf5ebb2afa801abf4b0868f42f8539bfe4b071b5266c9fe/websockets-16.0-cp314-cp314t-win32.whl", hash = "sha256:5a4b4cc550cb665dd8a47f868c8d04c8230f857363ad3c9caf7a0c3bf8c61ca6", size = 178085, upload-time = "2026-01-10T09:23:33.816Z" }, + { url = "https://files.pythonhosted.org/packages/9f/3e/28135a24e384493fa804216b79a6a6759a38cc4ff59118787b9fb693df93/websockets-16.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b14dc141ed6d2dde437cddb216004bcac6a1df0935d79656387bd41632ba0bbd", size = 178531, upload-time = "2026-01-10T09:23:35.016Z" }, + { url = "https://files.pythonhosted.org/packages/6f/28/258ebab549c2bf3e64d2b0217b973467394a9cea8c42f70418ca2c5d0d2e/websockets-16.0-py3-none-any.whl", hash = "sha256:1637db62fad1dc833276dded54215f2c7fa46912301a24bd94d45d46a011ceec", size = 171598, upload-time = "2026-01-10T09:23:45.395Z" }, ] From 55a837f252db22214b188cb18afe248ce64f9457 Mon Sep 17 00:00:00 2001 From: Alex Liberzon Date: Fri, 6 Mar 2026 20:54:25 +0200 Subject: [PATCH 06/27] added bundle adjustment --- openptv_python/calibration_compare.py | 8 +- openptv_python/orientation.py | 268 ++++- tests/test_bundle_adjustment.py | 219 ++++- tests/test_calibration_compare.py | 2 +- uv.lock | 1294 ++++++++++++------------- 5 files changed, 1118 insertions(+), 673 deletions(-) diff --git a/openptv_python/calibration_compare.py b/openptv_python/calibration_compare.py index 28e79b0..2490c9d 100644 --- a/openptv_python/calibration_compare.py +++ b/openptv_python/calibration_compare.py @@ -107,7 +107,8 @@ def format_calibration_comparison( delta = deltas[camera_key] lines.append(f"{camera_key}:") lines.append( - " position_delta: " + " ".join(f"{value:+.9f}" for value in delta.position_delta) + " position_delta: " + + " ".join(f"{value:+.9f}" for value in delta.position_delta) ) lines.append( " angle_delta: " + " ".join(f"{value:+.9f}" for value in delta.angle_delta) @@ -120,7 +121,8 @@ def format_calibration_comparison( " glass_delta: " + " ".join(f"{value:+.9f}" for value in delta.glass_delta) ) lines.append( - " addpar_delta: " + " ".join(f"{value:+.9f}" for value in delta.added_par_delta) + " addpar_delta: " + + " ".join(f"{value:+.9f}" for value in delta.added_par_delta) ) return "\n".join(lines) @@ -147,4 +149,4 @@ def main(argv: Iterable[str] | None = None) -> int: if __name__ == "__main__": - raise SystemExit(main()) \ No newline at end of file + raise SystemExit(main()) diff --git a/openptv_python/orientation.py b/openptv_python/orientation.py index d92cb9b..1f914df 100644 --- a/openptv_python/orientation.py +++ b/openptv_python/orientation.py @@ -1316,6 +1316,45 @@ def reprojection_rms_per_camera( return per_camera +def mean_ray_convergence( + observed_pixels: np.ndarray, + cals: List[Calibration], + cpar: ControlPar, +) -> float: + """Return the mean ray convergence from triangulating observed pixels.""" + _, ray_convergence = initialize_bundle_adjustment_points( + observed_pixels, cals, cpar + ) + return float(np.mean(ray_convergence)) + + +def _expand_parameter_limits( + parameter_names: List[str], + limits: Optional[Dict[str, Tuple[float, float]]], + base_blocks: List[np.ndarray], + repeat: int, +) -> Tuple[np.ndarray, np.ndarray]: + """Expand per-parameter bounds for each optimized camera block.""" + lower = [] + upper = [] + + if repeat <= 0: + return np.empty(0, dtype=np.float64), np.empty(0, dtype=np.float64) + + for block_index in range(repeat): + for param_index, name in enumerate(parameter_names): + if limits is None or name not in limits: + lower.append(-np.inf) + upper.append(np.inf) + continue + low_delta, high_delta = limits[name] + base_value = base_blocks[block_index][param_index] + lower.append(base_value + low_delta) + upper.append(base_value + high_delta) + + return np.asarray(lower, dtype=np.float64), np.asarray(upper, dtype=np.float64) + + def multi_camera_bundle_adjustment( observed_pixels: np.ndarray, cals: List[Calibration], @@ -1328,7 +1367,13 @@ def multi_camera_bundle_adjustment( f_scale: float = 1.0, method: str = "trf", prior_sigmas: Optional[Dict[str, float]] = None, + parameter_bounds: Optional[Dict[str, Tuple[float, float]]] = None, max_nfev: Optional[int] = None, + optimize_extrinsics: bool = True, + optimize_points: bool = True, + ftol: Optional[float] = None, + xtol: Optional[float] = None, + gtol: Optional[float] = None, ) -> Tuple[List[Calibration], np.ndarray, scipy.optimize.OptimizeResult]: """Jointly refine multi-camera calibration and 3D points by reprojection error.""" observed_pixels = np.asarray(observed_pixels, dtype=np.float64) @@ -1361,9 +1406,20 @@ def multi_camera_bundle_adjustment( if any(cam < 0 or cam >= num_cams for cam in fixed_camera_indices): raise ValueError("fixed_camera_indices contains an out-of-range camera index") - optimized_cam_indices = [ - cam for cam in range(num_cams) if cam not in fixed_camera_indices - ] + if optimize_extrinsics: + optimized_cam_indices = [ + cam for cam in range(num_cams) if cam not in fixed_camera_indices + ] + else: + optimized_cam_indices = list(range(num_cams)) + + if not optimize_extrinsics and not optional_names and not include_interface: + raise ValueError("No camera parameters are enabled for optimization") + + if not optimize_points and not optimized_cam_indices: + raise ValueError( + "Bundle adjustment must optimize points or at least one camera" + ) # Refining both 3D points and camera poses from image observations has a similarity # gauge. One fixed camera removes global translation/rotation, but scale still drifts @@ -1371,7 +1427,13 @@ def multi_camera_bundle_adjustment( has_translation_priors = all( prior_sigmas.get(name, 0) > 0 for name in ("x0", "y0", "z0") ) - if optimized_cam_indices and len(fixed_camera_indices) < 2 and not has_translation_priors: + if ( + optimize_points + and optimized_cam_indices + and optimize_extrinsics + and len(fixed_camera_indices) < 2 + and not has_translation_priors + ): raise ValueError( "Bundle adjustment with free 3D points and only one fixed camera is scale-ambiguous. " "Fix at least two cameras via fixed_camera_indices or provide translation priors " @@ -1389,7 +1451,10 @@ def multi_camera_bundle_adjustment( base_camera_blocks = [] base_glass_vectors: Dict[int, np.ndarray] = {} - parameter_names = ["x0", "y0", "z0", "omega", "phi", "kappa"] + optional_names + parameter_names = [] + if optimize_extrinsics: + parameter_names.extend(["x0", "y0", "z0", "omega", "phi", "kappa"]) + parameter_names.extend(optional_names) if include_interface: parameter_names.extend(["glass_e1", "glass_e2"]) @@ -1398,24 +1463,50 @@ def multi_camera_bundle_adjustment( initial_block = _camera_parameter_block( base_cals[cam], optional_names, include_interface ) + if not optimize_extrinsics: + initial_block = initial_block[6:] initial_blocks.append(initial_block) base_camera_blocks.append(initial_block.copy()) if include_interface: base_glass_vectors[cam] = base_cals[cam].glass_par.copy() + camera_block_size = len(parameter_names) + payloads = [] if initial_blocks: - x0 = np.concatenate(initial_blocks + [points0.ravel()]) + payloads.extend(initial_blocks) + if optimize_points: + payloads.append(points0.ravel()) + if payloads: + x0 = np.concatenate(payloads) else: - x0 = points0.ravel().copy() + x0 = np.empty(0, dtype=np.float64) - camera_block_size = 6 + len(optional_names) + (2 if include_interface else 0) point_offset = camera_block_size * len(optimized_cam_indices) + camera_lower, camera_upper = _expand_parameter_limits( + parameter_names, + parameter_bounds, + base_camera_blocks, + len(optimized_cam_indices), + ) + if optimize_points: + point_lower = np.full(points0.size, -np.inf, dtype=np.float64) + point_upper = np.full(points0.size, np.inf, dtype=np.float64) + bounds = ( + np.concatenate([camera_lower, point_lower]), + np.concatenate([camera_upper, point_upper]), + ) + else: + bounds = (camera_lower, camera_upper) + def unpack_parameters(params: np.ndarray) -> Tuple[List[Calibration], np.ndarray]: trial_cals = [_clone_calibration(cal) for cal in base_cals] offset = 0 for cam in optimized_cam_indices: block = params[offset : offset + camera_block_size] + if not optimize_extrinsics: + base_pose = _camera_parameter_block(base_cals[cam], [], False)[:6] + block = np.concatenate([base_pose, block]) _apply_camera_parameter_block( trial_cals[cam], block, @@ -1425,7 +1516,10 @@ def unpack_parameters(params: np.ndarray) -> Tuple[List[Calibration], np.ndarray ) offset += camera_block_size - points = params[point_offset:].reshape(num_points, 3) + if optimize_points: + points = params[point_offset:].reshape(num_points, 3) + else: + points = points0.copy() return trial_cals, points def residual_vector(params: np.ndarray) -> np.ndarray: @@ -1461,13 +1555,24 @@ def residual_vector(params: np.ndarray) -> np.ndarray: observed_pixels, initial_points, initial_cals, cpar ) + least_squares_kwargs = { + "method": method, + "loss": loss, + "f_scale": f_scale, + "max_nfev": max_nfev, + "bounds": bounds, + } + if ftol is not None: + least_squares_kwargs["ftol"] = ftol + if xtol is not None: + least_squares_kwargs["xtol"] = xtol + if gtol is not None: + least_squares_kwargs["gtol"] = gtol + result = scipy.optimize.least_squares( residual_vector, x0, - method=method, - loss=loss, - f_scale=f_scale, - max_nfev=max_nfev, + **least_squares_kwargs, ) refined_cals, refined_points = unpack_parameters(result.x) @@ -1482,3 +1587,140 @@ def residual_vector(params: np.ndarray) -> np.ndarray: result["optimized_camera_indices"] = optimized_cam_indices return refined_cals, refined_points, result + + +def guarded_two_step_bundle_adjustment( + observed_pixels: np.ndarray, + cals: List[Calibration], + cpar: ControlPar, + pose_orient_par: OrientPar, + intrinsic_orient_par: OrientPar, + *, + point_init: Optional[np.ndarray] = None, + fixed_camera_indices: Optional[List[int]] = None, + pose_prior_sigmas: Optional[Dict[str, float]] = None, + pose_parameter_bounds: Optional[Dict[str, Tuple[float, float]]] = None, + pose_loss: str = "linear", + pose_method: str = "trf", + pose_max_nfev: Optional[int] = None, + intrinsic_prior_sigmas: Optional[Dict[str, float]] = None, + intrinsic_parameter_bounds: Optional[Dict[str, Tuple[float, float]]] = None, + intrinsic_loss: str = "linear", + intrinsic_method: str = "trf", + intrinsic_max_nfev: Optional[int] = None, + intrinsic_ftol: Optional[float] = 1e-12, + intrinsic_xtol: Optional[float] = 1e-12, + intrinsic_gtol: Optional[float] = 1e-12, + pose_optimize_points: bool = True, + intrinsic_optimize_points: bool = True, + reject_worse_solution: bool = True, + reject_on_ray_convergence: bool = True, +) -> Tuple[List[Calibration], np.ndarray, Dict[str, object]]: + """Run pose-only BA then tightly constrained intrinsics BA with acceptance checks.""" + base_cals = [_clone_calibration(cal) for cal in cals] + if point_init is None: + base_points, _ = initialize_bundle_adjustment_points( + observed_pixels, base_cals, cpar + ) + else: + base_points = np.asarray(point_init, dtype=np.float64) + if base_points.shape != (observed_pixels.shape[0], 3): + raise ValueError("point_init must have shape (num_points, 3)") + + baseline_rms = reprojection_rms(observed_pixels, base_points, base_cals, cpar) + baseline_ray_convergence = mean_ray_convergence(observed_pixels, base_cals, cpar) + + pose_cals, pose_points, pose_result = multi_camera_bundle_adjustment( + observed_pixels, + base_cals, + cpar, + pose_orient_par, + point_init=base_points, + fixed_camera_indices=fixed_camera_indices, + loss=pose_loss, + method=pose_method, + prior_sigmas=pose_prior_sigmas, + parameter_bounds=pose_parameter_bounds, + max_nfev=pose_max_nfev, + optimize_extrinsics=True, + optimize_points=pose_optimize_points, + ) + pose_rms = reprojection_rms(observed_pixels, pose_points, pose_cals, cpar) + pose_ray_convergence = mean_ray_convergence(observed_pixels, pose_cals, cpar) + + intrinsic_fixed = list(range(len(cals))) + intrinsic_cals, intrinsic_points, intrinsic_result = multi_camera_bundle_adjustment( + observed_pixels, + pose_cals, + cpar, + intrinsic_orient_par, + point_init=pose_points, + fixed_camera_indices=intrinsic_fixed, + loss=intrinsic_loss, + method=intrinsic_method, + prior_sigmas=intrinsic_prior_sigmas, + parameter_bounds=intrinsic_parameter_bounds, + max_nfev=intrinsic_max_nfev, + optimize_extrinsics=False, + optimize_points=intrinsic_optimize_points, + ftol=intrinsic_ftol, + xtol=intrinsic_xtol, + gtol=intrinsic_gtol, + ) + intrinsic_rms = reprojection_rms( + observed_pixels, intrinsic_points, intrinsic_cals, cpar + ) + intrinsic_ray_convergence = mean_ray_convergence( + observed_pixels, intrinsic_cals, cpar + ) + + accepted_stage = "intrinsics" + final_cals = intrinsic_cals + final_points = intrinsic_points + final_rms = intrinsic_rms + final_ray_convergence = intrinsic_ray_convergence + + pose_ok = pose_rms <= baseline_rms and ( + not reject_on_ray_convergence + or pose_ray_convergence <= baseline_ray_convergence + ) + intrinsic_ok = intrinsic_rms <= pose_rms and ( + not reject_on_ray_convergence + or intrinsic_ray_convergence <= pose_ray_convergence + ) + + if reject_worse_solution: + if not pose_ok: + accepted_stage = "baseline" + final_cals = base_cals + final_points = base_points + final_rms = baseline_rms + final_ray_convergence = baseline_ray_convergence + elif not intrinsic_ok: + accepted_stage = "pose" + final_cals = pose_cals + final_points = pose_points + final_rms = pose_rms + final_ray_convergence = pose_ray_convergence + + summary = { + "baseline_reprojection_rms": baseline_rms, + "baseline_mean_ray_convergence": baseline_ray_convergence, + "baseline_cals": base_cals, + "baseline_points": base_points, + "pose_reprojection_rms": pose_rms, + "pose_mean_ray_convergence": pose_ray_convergence, + "pose_cals": pose_cals, + "pose_points": pose_points, + "intrinsic_reprojection_rms": intrinsic_rms, + "intrinsic_mean_ray_convergence": intrinsic_ray_convergence, + "intrinsic_cals": intrinsic_cals, + "intrinsic_points": intrinsic_points, + "accepted_stage": accepted_stage, + "final_reprojection_rms": final_rms, + "final_mean_ray_convergence": final_ray_convergence, + "pose_result": pose_result, + "intrinsic_result": intrinsic_result, + } + + return final_cals, final_points, summary diff --git a/tests/test_bundle_adjustment.py b/tests/test_bundle_adjustment.py index 2215192..cb84caf 100644 --- a/tests/test_bundle_adjustment.py +++ b/tests/test_bundle_adjustment.py @@ -6,10 +6,12 @@ from openptv_python.calibration import Calibration, read_calibration from openptv_python.imgcoord import image_coordinates from openptv_python.orientation import ( + guarded_two_step_bundle_adjustment, + mean_ray_convergence, multi_camera_bundle_adjustment, reprojection_rms, ) -from openptv_python.parameters import ControlPar, OrientPar +from openptv_python.parameters import ControlPar, OrientPar, SequencePar from openptv_python.tracking_frame_buf import read_path_frame, read_targets from openptv_python.trafo import arr_metric_to_pixel @@ -22,9 +24,7 @@ def setUp(self): self.add_file = Path("tests/testing_folder/calibration/cam1.tif.addpar") self.true_cals = [ read_calibration( - Path( - f"tests/testing_folder/calibration/sym_cam{cam_num}.tif.ori" - ), + Path(f"tests/testing_folder/calibration/sym_cam{cam_num}.tif.ori"), self.add_file, ) for cam_num in range(1, 5) @@ -54,7 +54,9 @@ def perturb_calibrations(self, true_cals: list[Calibration]) -> list[Calibration cal.set_angles(cal.get_angles() + angle_delta) return perturbed - def lightly_perturb_calibrations(self, true_cals: list[Calibration]) -> list[Calibration]: + def lightly_perturb_calibrations( + self, true_cals: list[Calibration] + ) -> list[Calibration]: perturbed = [self.clone_calibration(cal) for cal in true_cals] deltas = [ (np.array([0.0, 0.0, 0.0]), np.array([0.0, 0.0, 0.0])), @@ -67,6 +69,41 @@ def lightly_perturb_calibrations(self, true_cals: list[Calibration]) -> list[Cal cal.set_angles(cal.get_angles() + angle_delta) return perturbed + @staticmethod + def cavity_quadruplet_observations(cavity_dir: Path, control: ControlPar): + seq = SequencePar.from_file(cavity_dir / "parameters/sequence.par", 4) + observed_batches = [] + point_batches = [] + + for frame in range(seq.first, seq.last + 1): + cor_buf, path_buf = read_path_frame( + str(cavity_dir / "res_orig/rt_is"), + "", + "", + frame, + ) + targets = [ + read_targets(str(cavity_dir / f"img_orig/cam{cam_num}.%05d"), frame) + for cam_num in range(1, 5) + ] + subset = [ + pt_num for pt_num, corres in enumerate(cor_buf) if np.all(corres.p >= 0) + ] + observed_pixels = np.full((len(subset), 4, 2), np.nan, dtype=float) + point_init = np.empty((len(subset), 3), dtype=float) + for out_num, pt_num in enumerate(subset): + point_init[out_num] = path_buf[pt_num].x + for cam in range(4): + target_index = cor_buf[pt_num].p[cam] + observed_pixels[out_num, cam, 0] = targets[cam][target_index].x + observed_pixels[out_num, cam, 1] = targets[cam][target_index].y + observed_batches.append(observed_pixels) + point_batches.append(point_init) + + return np.concatenate(observed_batches, axis=0), np.concatenate( + point_batches, axis=0 + ) + def test_multi_camera_bundle_adjustment_improves_synthetic_reprojection(self): xs = np.linspace(-20.0, 20.0, 3) ys = np.linspace(-15.0, 15.0, 2) @@ -130,9 +167,7 @@ def test_cavity_reprojection_improves(self): ] subset = [ - pt_num - for pt_num, corres in enumerate(cor_buf) - if np.all(corres.p >= 0) + pt_num for pt_num, corres in enumerate(cor_buf) if np.all(corres.p >= 0) ][:12] observed_pixels = np.full((len(subset), 4, 2), np.nan, dtype=float) point_init = np.empty((len(subset), 3), dtype=float) @@ -158,7 +193,9 @@ def test_cavity_reprojection_improves(self): method="lm", max_nfev=80, ) - after_rms = reprojection_rms(observed_pixels, refined_points, refined_cals, control) + after_rms = reprojection_rms( + observed_pixels, refined_points, refined_cals, control + ) print( f"test_cavity reprojection RMS improved from {before_rms:.6f} px to {after_rms:.6f} px" @@ -199,6 +236,170 @@ def test_bundle_adjustment_rejects_scale_ambiguous_configuration(self): max_nfev=10, ) + def test_guarded_two_step_bundle_adjustment_rejects_bad_intrinsics_stage(self): + cavity_dir = Path("tests/testing_fodder/test_cavity") + control = ControlPar(4).from_file(cavity_dir / "parameters/ptv.par") + true_cals = [ + read_calibration( + cavity_dir / f"cal/cam{cam_num}.tif.ori", + cavity_dir / f"cal/cam{cam_num}.tif.addpar", + ) + for cam_num in range(1, 5) + ] + observed_pixels, point_init = self.cavity_quadruplet_observations( + cavity_dir, control + ) + start_cals = self.lightly_perturb_calibrations(true_cals) + + intrinsics = OrientPar() + intrinsics.k1flag = 1 + intrinsics.p1flag = 1 + intrinsics.p2flag = 1 + + final_cals, final_points, summary = guarded_two_step_bundle_adjustment( + observed_pixels, + start_cals, + control, + OrientPar(), + intrinsics, + point_init=point_init, + fixed_camera_indices=[0, 1], + pose_prior_sigmas={ + "x0": 0.5, + "y0": 0.5, + "z0": 0.5, + "omega": 0.005, + "phi": 0.005, + "kappa": 0.005, + }, + pose_parameter_bounds={ + "x0": (-2.0, 2.0), + "y0": (-2.0, 2.0), + "z0": (-2.0, 2.0), + "omega": (-0.02, 0.02), + "phi": (-0.02, 0.02), + "kappa": (-0.02, 0.02), + }, + pose_max_nfev=60, + intrinsic_prior_sigmas={ + "k1": 1e-12, + "k2": 1e-12, + "k3": 1e-12, + "p1": 1e-12, + "p2": 1e-12, + "scx": 1e-12, + "she": 1e-12, + "cc": 1e-12, + "xh": 1e-12, + "yh": 1e-12, + }, + intrinsic_parameter_bounds={ + "k1": (-1e-10, 1e-10), + "k2": (-1e-10, 1e-10), + "k3": (-1e-10, 1e-10), + "p1": (-1e-10, 1e-10), + "p2": (-1e-10, 1e-10), + "scx": (-1e-12, 1e-12), + "she": (-1e-12, 1e-12), + "cc": (-1e-12, 1e-12), + "xh": (-1e-12, 1e-12), + "yh": (-1e-12, 1e-12), + }, + intrinsic_max_nfev=20, + ) + + self.assertIn(summary["accepted_stage"], {"baseline", "pose"}) + self.assertLessEqual( + summary["final_reprojection_rms"], summary["baseline_reprojection_rms"] + ) + self.assertLessEqual( + summary["final_mean_ray_convergence"], + summary["baseline_mean_ray_convergence"], + ) + np.testing.assert_allclose( + reprojection_rms(observed_pixels, final_points, final_cals, control), + summary["final_reprojection_rms"], + ) + + def test_guarded_two_step_bundle_adjustment_preserves_pose_when_intrinsics_are_tight( + self, + ): + points = np.array( + [ + [-10.0, -10.0, 0.0], + [10.0, -10.0, 1.0], + [-10.0, 10.0, -1.0], + [10.0, 10.0, 0.5], + [0.0, 0.0, 3.0], + [6.0, -4.0, -2.0], + ], + dtype=float, + ) + observed_pixels = np.empty((len(points), 4, 2), dtype=float) + for cam_num, cal in enumerate(self.true_cals): + observed_pixels[:, cam_num, :] = arr_metric_to_pixel( + image_coordinates(points, cal, self.control.mm), self.control + ) + + start_cals = self.perturb_calibrations(self.true_cals) + intrinsics = OrientPar() + intrinsics.k1flag = 1 + intrinsics.p1flag = 1 + intrinsics.p2flag = 1 + final_cals, final_points, summary = guarded_two_step_bundle_adjustment( + observed_pixels, + start_cals, + self.control, + OrientPar(), + intrinsics, + point_init=points, + fixed_camera_indices=[0, 1], + pose_prior_sigmas={ + "x0": 1.0, + "y0": 1.0, + "z0": 1.0, + "omega": 0.01, + "phi": 0.01, + "kappa": 0.01, + }, + pose_max_nfev=50, + intrinsic_prior_sigmas={ + "k1": 1e-12, + "k2": 1e-12, + "k3": 1e-12, + "p1": 1e-12, + "p2": 1e-12, + "scx": 1e-12, + "she": 1e-12, + "cc": 1e-12, + "xh": 1e-12, + "yh": 1e-12, + }, + intrinsic_parameter_bounds={ + "k1": (-1e-10, 1e-10), + "k2": (-1e-10, 1e-10), + "k3": (-1e-10, 1e-10), + "p1": (-1e-10, 1e-10), + "p2": (-1e-10, 1e-10), + "scx": (-1e-12, 1e-12), + "she": (-1e-12, 1e-12), + "cc": (-1e-12, 1e-12), + "xh": (-1e-12, 1e-12), + "yh": (-1e-12, 1e-12), + }, + intrinsic_max_nfev=20, + ) + + self.assertIn(summary["accepted_stage"], {"pose", "intrinsics"}) + self.assertLess( + summary["final_reprojection_rms"], summary["baseline_reprojection_rms"] + ) + self.assertLessEqual( + mean_ray_convergence(observed_pixels, final_cals, self.control), + summary["baseline_mean_ray_convergence"], + ) + self.assertEqual(final_points.shape, points.shape) + if __name__ == "__main__": unittest.main() diff --git a/tests/test_calibration_compare.py b/tests/test_calibration_compare.py index ff5dfc9..7ef910f 100644 --- a/tests/test_calibration_compare.py +++ b/tests/test_calibration_compare.py @@ -61,4 +61,4 @@ def test_compare_modified_folder_reports_numeric_deltas(self): if __name__ == "__main__": - unittest.main() \ No newline at end of file + unittest.main() diff --git a/uv.lock b/uv.lock index 6cb65f9..fd02693 100644 --- a/uv.lock +++ b/uv.lock @@ -1,1093 +1,1093 @@ -version = 1 -revision = 3 requires-python = ">=3.14" +revision = 3 +version = 1 [[package]] -name = "accessible-pygments" -version = "0.0.5" -source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pygments" }, + {name = "pygments"} ] -sdist = { url = "https://files.pythonhosted.org/packages/bc/c1/bbac6a50d02774f91572938964c582fff4270eee73ab822a4aeea4d8b11b/accessible_pygments-0.0.5.tar.gz", hash = "sha256:40918d3e6a2b619ad424cb91e556bd3bd8865443d9f22f1dcdf79e33c8046872", size = 1377899, upload-time = "2024-05-10T11:23:10.216Z" } +name = "accessible-pygments" +sdist = {url = "https://files.pythonhosted.org/packages/bc/c1/bbac6a50d02774f91572938964c582fff4270eee73ab822a4aeea4d8b11b/accessible_pygments-0.0.5.tar.gz", hash = "sha256:40918d3e6a2b619ad424cb91e556bd3bd8865443d9f22f1dcdf79e33c8046872", size = 1377899, upload-time = "2024-05-10T11:23:10.216Z"} +source = {registry = "https://pypi.org/simple"} +version = "0.0.5" wheels = [ - { url = "https://files.pythonhosted.org/packages/8d/3f/95338030883d8c8b91223b4e21744b04d11b161a3ef117295d8241f50ab4/accessible_pygments-0.0.5-py3-none-any.whl", hash = "sha256:88ae3211e68a1d0b011504b2ffc1691feafce124b845bd072ab6f9f66f34d4b7", size = 1395903, upload-time = "2024-05-10T11:23:08.421Z" }, + {url = "https://files.pythonhosted.org/packages/8d/3f/95338030883d8c8b91223b4e21744b04d11b161a3ef117295d8241f50ab4/accessible_pygments-0.0.5-py3-none-any.whl", hash = "sha256:88ae3211e68a1d0b011504b2ffc1691feafce124b845bd072ab6f9f66f34d4b7", size = 1395903, upload-time = "2024-05-10T11:23:08.421Z"} ] [[package]] name = "alabaster" +sdist = {url = "https://files.pythonhosted.org/packages/a6/f8/d9c74d0daf3f742840fd818d69cfae176fa332022fd44e3469487d5a9420/alabaster-1.0.0.tar.gz", hash = "sha256:c00dca57bca26fa62a6d7d0a9fcce65f3e026e9bfe33e9c538fd3fbb2144fd9e", size = 24210, upload-time = "2024-07-26T18:15:03.762Z"} +source = {registry = "https://pypi.org/simple"} version = "1.0.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a6/f8/d9c74d0daf3f742840fd818d69cfae176fa332022fd44e3469487d5a9420/alabaster-1.0.0.tar.gz", hash = "sha256:c00dca57bca26fa62a6d7d0a9fcce65f3e026e9bfe33e9c538fd3fbb2144fd9e", size = 24210, upload-time = "2024-07-26T18:15:03.762Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7e/b3/6b4067be973ae96ba0d615946e314c5ae35f9f993eca561b356540bb0c2b/alabaster-1.0.0-py3-none-any.whl", hash = "sha256:fc6786402dc3fcb2de3cabd5fe455a2db534b371124f1f21de8731783dec828b", size = 13929, upload-time = "2024-07-26T18:15:02.05Z" }, + {url = "https://files.pythonhosted.org/packages/7e/b3/6b4067be973ae96ba0d615946e314c5ae35f9f993eca561b356540bb0c2b/alabaster-1.0.0-py3-none-any.whl", hash = "sha256:fc6786402dc3fcb2de3cabd5fe455a2db534b371124f1f21de8731783dec828b", size = 13929, upload-time = "2024-07-26T18:15:02.05Z"} ] [[package]] -name = "anyio" -version = "4.12.1" -source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "idna" }, + {name = "idna"} ] -sdist = { url = "https://files.pythonhosted.org/packages/96/f0/5eb65b2bb0d09ac6776f2eb54adee6abe8228ea05b20a5ad0e4945de8aac/anyio-4.12.1.tar.gz", hash = "sha256:41cfcc3a4c85d3f05c932da7c26d0201ac36f72abd4435ba90d0464a3ffed703", size = 228685, upload-time = "2026-01-06T11:45:21.246Z" } +name = "anyio" +sdist = {url = "https://files.pythonhosted.org/packages/96/f0/5eb65b2bb0d09ac6776f2eb54adee6abe8228ea05b20a5ad0e4945de8aac/anyio-4.12.1.tar.gz", hash = "sha256:41cfcc3a4c85d3f05c932da7c26d0201ac36f72abd4435ba90d0464a3ffed703", size = 228685, upload-time = "2026-01-06T11:45:21.246Z"} +source = {registry = "https://pypi.org/simple"} +version = "4.12.1" wheels = [ - { url = "https://files.pythonhosted.org/packages/38/0e/27be9fdef66e72d64c0cdc3cc2823101b80585f8119b5c112c2e8f5f7dab/anyio-4.12.1-py3-none-any.whl", hash = "sha256:d405828884fc140aa80a3c667b8beed277f1dfedec42ba031bd6ac3db606ab6c", size = 113592, upload-time = "2026-01-06T11:45:19.497Z" }, + {url = "https://files.pythonhosted.org/packages/38/0e/27be9fdef66e72d64c0cdc3cc2823101b80585f8119b5c112c2e8f5f7dab/anyio-4.12.1-py3-none-any.whl", hash = "sha256:d405828884fc140aa80a3c667b8beed277f1dfedec42ba031bd6ac3db606ab6c", size = 113592, upload-time = "2026-01-06T11:45:19.497Z"} ] [[package]] name = "astroid" +sdist = {url = "https://files.pythonhosted.org/packages/a2/4c/569eefb533ce71bc9f4f12a4a0d7f0ba27e500681dec1312d4657e849d20/astroid-4.1.1.tar.gz", hash = "sha256:445d831fe785df8c670bbb46b900b8424b82f85b4af187103f71a63a63ebed43", size = 412522, upload-time = "2026-02-23T02:36:30.315Z"} +source = {registry = "https://pypi.org/simple"} version = "4.1.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a2/4c/569eefb533ce71bc9f4f12a4a0d7f0ba27e500681dec1312d4657e849d20/astroid-4.1.1.tar.gz", hash = "sha256:445d831fe785df8c670bbb46b900b8424b82f85b4af187103f71a63a63ebed43", size = 412522, upload-time = "2026-02-23T02:36:30.315Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/32/53/bac4724684064bfee95ece0bb6caf3887e509006845e25388a12cac26d0c/astroid-4.1.1-py3-none-any.whl", hash = "sha256:6b28522096f7e7a36ffcf3be60e77de15e2411ab3a713184beac33fb8f20c0c9", size = 279273, upload-time = "2026-02-23T02:36:28.676Z" }, + {url = "https://files.pythonhosted.org/packages/32/53/bac4724684064bfee95ece0bb6caf3887e509006845e25388a12cac26d0c/astroid-4.1.1-py3-none-any.whl", hash = "sha256:6b28522096f7e7a36ffcf3be60e77de15e2411ab3a713184beac33fb8f20c0c9", size = 279273, upload-time = "2026-02-23T02:36:28.676Z"} ] [[package]] name = "babel" +sdist = {url = "https://files.pythonhosted.org/packages/7d/b2/51899539b6ceeeb420d40ed3cd4b7a40519404f9baf3d4ac99dc413a834b/babel-2.18.0.tar.gz", hash = "sha256:b80b99a14bd085fcacfa15c9165f651fbb3406e66cc603abf11c5750937c992d", size = 9959554, upload-time = "2026-02-01T12:30:56.078Z"} +source = {registry = "https://pypi.org/simple"} version = "2.18.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7d/b2/51899539b6ceeeb420d40ed3cd4b7a40519404f9baf3d4ac99dc413a834b/babel-2.18.0.tar.gz", hash = "sha256:b80b99a14bd085fcacfa15c9165f651fbb3406e66cc603abf11c5750937c992d", size = 9959554, upload-time = "2026-02-01T12:30:56.078Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl", hash = "sha256:e2b422b277c2b9a9630c1d7903c2a00d0830c409c59ac8cae9081c92f1aeba35", size = 10196845, upload-time = "2026-02-01T12:30:53.445Z" }, + {url = "https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl", hash = "sha256:e2b422b277c2b9a9630c1d7903c2a00d0830c409c59ac8cae9081c92f1aeba35", size = 10196845, upload-time = "2026-02-01T12:30:53.445Z"} ] [[package]] -name = "beautifulsoup4" -version = "4.14.3" -source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "soupsieve" }, - { name = "typing-extensions" }, + {name = "soupsieve"}, + {name = "typing-extensions"} ] -sdist = { url = "https://files.pythonhosted.org/packages/c3/b0/1c6a16426d389813b48d95e26898aff79abbde42ad353958ad95cc8c9b21/beautifulsoup4-4.14.3.tar.gz", hash = "sha256:6292b1c5186d356bba669ef9f7f051757099565ad9ada5dd630bd9de5fa7fb86", size = 627737, upload-time = "2025-11-30T15:08:26.084Z" } +name = "beautifulsoup4" +sdist = {url = "https://files.pythonhosted.org/packages/c3/b0/1c6a16426d389813b48d95e26898aff79abbde42ad353958ad95cc8c9b21/beautifulsoup4-4.14.3.tar.gz", hash = "sha256:6292b1c5186d356bba669ef9f7f051757099565ad9ada5dd630bd9de5fa7fb86", size = 627737, upload-time = "2025-11-30T15:08:26.084Z"} +source = {registry = "https://pypi.org/simple"} +version = "4.14.3" wheels = [ - { url = "https://files.pythonhosted.org/packages/1a/39/47f9197bdd44df24d67ac8893641e16f386c984a0619ef2ee4c51fbbc019/beautifulsoup4-4.14.3-py3-none-any.whl", hash = "sha256:0918bfe44902e6ad8d57732ba310582e98da931428d231a5ecb9e7c703a735bb", size = 107721, upload-time = "2025-11-30T15:08:24.087Z" }, + {url = "https://files.pythonhosted.org/packages/1a/39/47f9197bdd44df24d67ac8893641e16f386c984a0619ef2ee4c51fbbc019/beautifulsoup4-4.14.3-py3-none-any.whl", hash = "sha256:0918bfe44902e6ad8d57732ba310582e98da931428d231a5ecb9e7c703a735bb", size = 107721, upload-time = "2025-11-30T15:08:24.087Z"} ] [[package]] name = "certifi" +sdist = {url = "https://files.pythonhosted.org/packages/af/2d/7bf41579a8986e348fa033a31cdd0e4121114f6bce2457e8876010b092dd/certifi-2026.2.25.tar.gz", hash = "sha256:e887ab5cee78ea814d3472169153c2d12cd43b14bd03329a39a9c6e2e80bfba7", size = 155029, upload-time = "2026-02-25T02:54:17.342Z"} +source = {registry = "https://pypi.org/simple"} version = "2026.2.25" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/af/2d/7bf41579a8986e348fa033a31cdd0e4121114f6bce2457e8876010b092dd/certifi-2026.2.25.tar.gz", hash = "sha256:e887ab5cee78ea814d3472169153c2d12cd43b14bd03329a39a9c6e2e80bfba7", size = 155029, upload-time = "2026-02-25T02:54:17.342Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9a/3c/c17fb3ca2d9c3acff52e30b309f538586f9f5b9c9cf454f3845fc9af4881/certifi-2026.2.25-py3-none-any.whl", hash = "sha256:027692e4402ad994f1c42e52a4997a9763c646b73e4096e4d5d6db8af1d6f0fa", size = 153684, upload-time = "2026-02-25T02:54:15.766Z" }, + {url = "https://files.pythonhosted.org/packages/9a/3c/c17fb3ca2d9c3acff52e30b309f538586f9f5b9c9cf454f3845fc9af4881/certifi-2026.2.25-py3-none-any.whl", hash = "sha256:027692e4402ad994f1c42e52a4997a9763c646b73e4096e4d5d6db8af1d6f0fa", size = 153684, upload-time = "2026-02-25T02:54:15.766Z"} ] [[package]] name = "cfgv" +sdist = {url = "https://files.pythonhosted.org/packages/4e/b5/721b8799b04bf9afe054a3899c6cf4e880fcf8563cc71c15610242490a0c/cfgv-3.5.0.tar.gz", hash = "sha256:d5b1034354820651caa73ede66a6294d6e95c1b00acc5e9b098e917404669132", size = 7334, upload-time = "2025-11-19T20:55:51.612Z"} +source = {registry = "https://pypi.org/simple"} version = "3.5.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/4e/b5/721b8799b04bf9afe054a3899c6cf4e880fcf8563cc71c15610242490a0c/cfgv-3.5.0.tar.gz", hash = "sha256:d5b1034354820651caa73ede66a6294d6e95c1b00acc5e9b098e917404669132", size = 7334, upload-time = "2025-11-19T20:55:51.612Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl", hash = "sha256:a8dc6b26ad22ff227d2634a65cb388215ce6cc96bbcc5cfde7641ae87e8dacc0", size = 7445, upload-time = "2025-11-19T20:55:50.744Z" }, + {url = "https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl", hash = "sha256:a8dc6b26ad22ff227d2634a65cb388215ce6cc96bbcc5cfde7641ae87e8dacc0", size = 7445, upload-time = "2025-11-19T20:55:50.744Z"} ] [[package]] name = "charset-normalizer" +sdist = {url = "https://files.pythonhosted.org/packages/1d/35/02daf95b9cd686320bb622eb148792655c9412dbb9b67abb5694e5910a24/charset_normalizer-3.4.5.tar.gz", hash = "sha256:95adae7b6c42a6c5b5b559b1a99149f090a57128155daeea91732c8d970d8644", size = 134804, upload-time = "2026-03-06T06:03:19.46Z"} +source = {registry = "https://pypi.org/simple"} version = "3.4.5" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1d/35/02daf95b9cd686320bb622eb148792655c9412dbb9b67abb5694e5910a24/charset_normalizer-3.4.5.tar.gz", hash = "sha256:95adae7b6c42a6c5b5b559b1a99149f090a57128155daeea91732c8d970d8644", size = 134804, upload-time = "2026-03-06T06:03:19.46Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/43/be/0f0fd9bb4a7fa4fb5067fb7d9ac693d4e928d306f80a0d02bde43a7c4aee/charset_normalizer-3.4.5-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8197abe5ca1ffb7d91e78360f915eef5addff270f8a71c1fc5be24a56f3e4873", size = 280232, upload-time = "2026-03-06T06:02:01.508Z" }, - { url = "https://files.pythonhosted.org/packages/28/02/983b5445e4bef49cd8c9da73a8e029f0825f39b74a06d201bfaa2e55142a/charset_normalizer-3.4.5-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a2aecdb364b8a1802afdc7f9327d55dad5366bc97d8502d0f5854e50712dbc5f", size = 189688, upload-time = "2026-03-06T06:02:02.857Z" }, - { url = "https://files.pythonhosted.org/packages/d0/88/152745c5166437687028027dc080e2daed6fe11cfa95a22f4602591c42db/charset_normalizer-3.4.5-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a66aa5022bf81ab4b1bebfb009db4fd68e0c6d4307a1ce5ef6a26e5878dfc9e4", size = 206833, upload-time = "2026-03-06T06:02:05.127Z" }, - { url = "https://files.pythonhosted.org/packages/cb/0f/ebc15c8b02af2f19be9678d6eed115feeeccc45ce1f4b098d986c13e8769/charset_normalizer-3.4.5-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d77f97e515688bd615c1d1f795d540f32542d514242067adcb8ef532504cb9ee", size = 202879, upload-time = "2026-03-06T06:02:06.446Z" }, - { url = "https://files.pythonhosted.org/packages/38/9c/71336bff6934418dc8d1e8a1644176ac9088068bc571da612767619c97b3/charset_normalizer-3.4.5-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01a1ed54b953303ca7e310fafe0fe347aab348bd81834a0bcd602eb538f89d66", size = 195764, upload-time = "2026-03-06T06:02:08.763Z" }, - { url = "https://files.pythonhosted.org/packages/b7/95/ce92fde4f98615661871bc282a856cf9b8a15f686ba0af012984660d480b/charset_normalizer-3.4.5-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:b2d37d78297b39a9eb9eb92c0f6df98c706467282055419df141389b23f93362", size = 183728, upload-time = "2026-03-06T06:02:10.137Z" }, - { url = "https://files.pythonhosted.org/packages/1c/e7/f5b4588d94e747ce45ae680f0f242bc2d98dbd4eccfab73e6160b6893893/charset_normalizer-3.4.5-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e71bbb595973622b817c042bd943c3f3667e9c9983ce3d205f973f486fec98a7", size = 192937, upload-time = "2026-03-06T06:02:11.663Z" }, - { url = "https://files.pythonhosted.org/packages/f9/29/9d94ed6b929bf9f48bf6ede6e7474576499f07c4c5e878fb186083622716/charset_normalizer-3.4.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:4cd966c2559f501c6fd69294d082c2934c8dd4719deb32c22961a5ac6db0df1d", size = 192040, upload-time = "2026-03-06T06:02:13.489Z" }, - { url = "https://files.pythonhosted.org/packages/15/d2/1a093a1cf827957f9445f2fe7298bcc16f8fc5e05c1ed2ad1af0b239035e/charset_normalizer-3.4.5-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:d5e52d127045d6ae01a1e821acfad2f3a1866c54d0e837828538fabe8d9d1bd6", size = 184107, upload-time = "2026-03-06T06:02:14.83Z" }, - { url = "https://files.pythonhosted.org/packages/0f/7d/82068ce16bd36135df7b97f6333c5d808b94e01d4599a682e2337ed5fd14/charset_normalizer-3.4.5-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:30a2b1a48478c3428d047ed9690d57c23038dac838a87ad624c85c0a78ebeb39", size = 208310, upload-time = "2026-03-06T06:02:16.165Z" }, - { url = "https://files.pythonhosted.org/packages/84/4e/4dfb52307bb6af4a5c9e73e482d171b81d36f522b21ccd28a49656baa680/charset_normalizer-3.4.5-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:d8ed79b8f6372ca4254955005830fd61c1ccdd8c0fac6603e2c145c61dd95db6", size = 192918, upload-time = "2026-03-06T06:02:18.144Z" }, - { url = "https://files.pythonhosted.org/packages/08/a4/159ff7da662cf7201502ca89980b8f06acf3e887b278956646a8aeb178ab/charset_normalizer-3.4.5-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:c5af897b45fa606b12464ccbe0014bbf8c09191e0a66aab6aa9d5cf6e77e0c94", size = 204615, upload-time = "2026-03-06T06:02:19.821Z" }, - { url = "https://files.pythonhosted.org/packages/d6/62/0dd6172203cb6b429ffffc9935001fde42e5250d57f07b0c28c6046deb6b/charset_normalizer-3.4.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:1088345bcc93c58d8d8f3d783eca4a6e7a7752bbff26c3eee7e73c597c191c2e", size = 197784, upload-time = "2026-03-06T06:02:21.86Z" }, - { url = "https://files.pythonhosted.org/packages/c7/5e/1aab5cb737039b9c59e63627dc8bbc0d02562a14f831cc450e5f91d84ce1/charset_normalizer-3.4.5-cp314-cp314-win32.whl", hash = "sha256:ee57b926940ba00bca7ba7041e665cc956e55ef482f851b9b65acb20d867e7a2", size = 133009, upload-time = "2026-03-06T06:02:23.289Z" }, - { url = "https://files.pythonhosted.org/packages/40/65/e7c6c77d7aaa4c0d7974f2e403e17f0ed2cb0fc135f77d686b916bf1eead/charset_normalizer-3.4.5-cp314-cp314-win_amd64.whl", hash = "sha256:4481e6da1830c8a1cc0b746b47f603b653dadb690bcd851d039ffaefe70533aa", size = 143511, upload-time = "2026-03-06T06:02:26.195Z" }, - { url = "https://files.pythonhosted.org/packages/ba/91/52b0841c71f152f563b8e072896c14e3d83b195c188b338d3cc2e582d1d4/charset_normalizer-3.4.5-cp314-cp314-win_arm64.whl", hash = "sha256:97ab7787092eb9b50fb47fa04f24c75b768a606af1bcba1957f07f128a7219e4", size = 133775, upload-time = "2026-03-06T06:02:27.473Z" }, - { url = "https://files.pythonhosted.org/packages/c5/60/3a621758945513adfd4db86827a5bafcc615f913dbd0b4c2ed64a65731be/charset_normalizer-3.4.5-py3-none-any.whl", hash = "sha256:9db5e3fcdcee89a78c04dffb3fe33c79f77bd741a624946db2591c81b2fc85b0", size = 55455, upload-time = "2026-03-06T06:03:17.827Z" }, +wheels = [ + {url = "https://files.pythonhosted.org/packages/43/be/0f0fd9bb4a7fa4fb5067fb7d9ac693d4e928d306f80a0d02bde43a7c4aee/charset_normalizer-3.4.5-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8197abe5ca1ffb7d91e78360f915eef5addff270f8a71c1fc5be24a56f3e4873", size = 280232, upload-time = "2026-03-06T06:02:01.508Z"}, + {url = "https://files.pythonhosted.org/packages/28/02/983b5445e4bef49cd8c9da73a8e029f0825f39b74a06d201bfaa2e55142a/charset_normalizer-3.4.5-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a2aecdb364b8a1802afdc7f9327d55dad5366bc97d8502d0f5854e50712dbc5f", size = 189688, upload-time = "2026-03-06T06:02:02.857Z"}, + {url = "https://files.pythonhosted.org/packages/d0/88/152745c5166437687028027dc080e2daed6fe11cfa95a22f4602591c42db/charset_normalizer-3.4.5-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a66aa5022bf81ab4b1bebfb009db4fd68e0c6d4307a1ce5ef6a26e5878dfc9e4", size = 206833, upload-time = "2026-03-06T06:02:05.127Z"}, + {url = "https://files.pythonhosted.org/packages/cb/0f/ebc15c8b02af2f19be9678d6eed115feeeccc45ce1f4b098d986c13e8769/charset_normalizer-3.4.5-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d77f97e515688bd615c1d1f795d540f32542d514242067adcb8ef532504cb9ee", size = 202879, upload-time = "2026-03-06T06:02:06.446Z"}, + {url = "https://files.pythonhosted.org/packages/38/9c/71336bff6934418dc8d1e8a1644176ac9088068bc571da612767619c97b3/charset_normalizer-3.4.5-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01a1ed54b953303ca7e310fafe0fe347aab348bd81834a0bcd602eb538f89d66", size = 195764, upload-time = "2026-03-06T06:02:08.763Z"}, + {url = "https://files.pythonhosted.org/packages/b7/95/ce92fde4f98615661871bc282a856cf9b8a15f686ba0af012984660d480b/charset_normalizer-3.4.5-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:b2d37d78297b39a9eb9eb92c0f6df98c706467282055419df141389b23f93362", size = 183728, upload-time = "2026-03-06T06:02:10.137Z"}, + {url = "https://files.pythonhosted.org/packages/1c/e7/f5b4588d94e747ce45ae680f0f242bc2d98dbd4eccfab73e6160b6893893/charset_normalizer-3.4.5-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e71bbb595973622b817c042bd943c3f3667e9c9983ce3d205f973f486fec98a7", size = 192937, upload-time = "2026-03-06T06:02:11.663Z"}, + {url = "https://files.pythonhosted.org/packages/f9/29/9d94ed6b929bf9f48bf6ede6e7474576499f07c4c5e878fb186083622716/charset_normalizer-3.4.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:4cd966c2559f501c6fd69294d082c2934c8dd4719deb32c22961a5ac6db0df1d", size = 192040, upload-time = "2026-03-06T06:02:13.489Z"}, + {url = "https://files.pythonhosted.org/packages/15/d2/1a093a1cf827957f9445f2fe7298bcc16f8fc5e05c1ed2ad1af0b239035e/charset_normalizer-3.4.5-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:d5e52d127045d6ae01a1e821acfad2f3a1866c54d0e837828538fabe8d9d1bd6", size = 184107, upload-time = "2026-03-06T06:02:14.83Z"}, + {url = "https://files.pythonhosted.org/packages/0f/7d/82068ce16bd36135df7b97f6333c5d808b94e01d4599a682e2337ed5fd14/charset_normalizer-3.4.5-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:30a2b1a48478c3428d047ed9690d57c23038dac838a87ad624c85c0a78ebeb39", size = 208310, upload-time = "2026-03-06T06:02:16.165Z"}, + {url = "https://files.pythonhosted.org/packages/84/4e/4dfb52307bb6af4a5c9e73e482d171b81d36f522b21ccd28a49656baa680/charset_normalizer-3.4.5-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:d8ed79b8f6372ca4254955005830fd61c1ccdd8c0fac6603e2c145c61dd95db6", size = 192918, upload-time = "2026-03-06T06:02:18.144Z"}, + {url = "https://files.pythonhosted.org/packages/08/a4/159ff7da662cf7201502ca89980b8f06acf3e887b278956646a8aeb178ab/charset_normalizer-3.4.5-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:c5af897b45fa606b12464ccbe0014bbf8c09191e0a66aab6aa9d5cf6e77e0c94", size = 204615, upload-time = "2026-03-06T06:02:19.821Z"}, + {url = "https://files.pythonhosted.org/packages/d6/62/0dd6172203cb6b429ffffc9935001fde42e5250d57f07b0c28c6046deb6b/charset_normalizer-3.4.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:1088345bcc93c58d8d8f3d783eca4a6e7a7752bbff26c3eee7e73c597c191c2e", size = 197784, upload-time = "2026-03-06T06:02:21.86Z"}, + {url = "https://files.pythonhosted.org/packages/c7/5e/1aab5cb737039b9c59e63627dc8bbc0d02562a14f831cc450e5f91d84ce1/charset_normalizer-3.4.5-cp314-cp314-win32.whl", hash = "sha256:ee57b926940ba00bca7ba7041e665cc956e55ef482f851b9b65acb20d867e7a2", size = 133009, upload-time = "2026-03-06T06:02:23.289Z"}, + {url = "https://files.pythonhosted.org/packages/40/65/e7c6c77d7aaa4c0d7974f2e403e17f0ed2cb0fc135f77d686b916bf1eead/charset_normalizer-3.4.5-cp314-cp314-win_amd64.whl", hash = "sha256:4481e6da1830c8a1cc0b746b47f603b653dadb690bcd851d039ffaefe70533aa", size = 143511, upload-time = "2026-03-06T06:02:26.195Z"}, + {url = "https://files.pythonhosted.org/packages/ba/91/52b0841c71f152f563b8e072896c14e3d83b195c188b338d3cc2e582d1d4/charset_normalizer-3.4.5-cp314-cp314-win_arm64.whl", hash = "sha256:97ab7787092eb9b50fb47fa04f24c75b768a606af1bcba1957f07f128a7219e4", size = 133775, upload-time = "2026-03-06T06:02:27.473Z"}, + {url = "https://files.pythonhosted.org/packages/c5/60/3a621758945513adfd4db86827a5bafcc615f913dbd0b4c2ed64a65731be/charset_normalizer-3.4.5-py3-none-any.whl", hash = "sha256:9db5e3fcdcee89a78c04dffb3fe33c79f77bd741a624946db2591c81b2fc85b0", size = 55455, upload-time = "2026-03-06T06:03:17.827Z"} ] [[package]] -name = "click" -version = "8.3.1" -source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, + {name = "colorama", marker = "sys_platform == 'win32'"} ] -sdist = { url = "https://files.pythonhosted.org/packages/3d/fa/656b739db8587d7b5dfa22e22ed02566950fbfbcdc20311993483657a5c0/click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a", size = 295065, upload-time = "2025-11-15T20:45:42.706Z" } +name = "click" +sdist = {url = "https://files.pythonhosted.org/packages/3d/fa/656b739db8587d7b5dfa22e22ed02566950fbfbcdc20311993483657a5c0/click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a", size = 295065, upload-time = "2025-11-15T20:45:42.706Z"} +source = {registry = "https://pypi.org/simple"} +version = "8.3.1" wheels = [ - { url = "https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6", size = 108274, upload-time = "2025-11-15T20:45:41.139Z" }, + {url = "https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6", size = 108274, upload-time = "2025-11-15T20:45:41.139Z"} ] [[package]] name = "colorama" +sdist = {url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z"} +source = {registry = "https://pypi.org/simple"} version = "0.4.6" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, + {url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z"} ] [[package]] name = "coverage" +sdist = {url = "https://files.pythonhosted.org/packages/24/56/95b7e30fa389756cb56630faa728da46a27b8c6eb46f9d557c68fff12b65/coverage-7.13.4.tar.gz", hash = "sha256:e5c8f6ed1e61a8b2dcdf31eb0b9bbf0130750ca79c1c49eb898e2ad86f5ccc91", size = 827239, upload-time = "2026-02-09T12:59:03.86Z"} +source = {registry = "https://pypi.org/simple"} version = "7.13.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/24/56/95b7e30fa389756cb56630faa728da46a27b8c6eb46f9d557c68fff12b65/coverage-7.13.4.tar.gz", hash = "sha256:e5c8f6ed1e61a8b2dcdf31eb0b9bbf0130750ca79c1c49eb898e2ad86f5ccc91", size = 827239, upload-time = "2026-02-09T12:59:03.86Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/92/11/a9cf762bb83386467737d32187756a42094927150c3e107df4cb078e8590/coverage-7.13.4-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:300deaee342f90696ed186e3a00c71b5b3d27bffe9e827677954f4ee56969601", size = 219522, upload-time = "2026-02-09T12:58:08.623Z" }, - { url = "https://files.pythonhosted.org/packages/d3/28/56e6d892b7b052236d67c95f1936b6a7cf7c3e2634bf27610b8cbd7f9c60/coverage-7.13.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:29e3220258d682b6226a9b0925bc563ed9a1ebcff3cad30f043eceea7eaf2689", size = 219855, upload-time = "2026-02-09T12:58:10.176Z" }, - { url = "https://files.pythonhosted.org/packages/e5/69/233459ee9eb0c0d10fcc2fe425a029b3fa5ce0f040c966ebce851d030c70/coverage-7.13.4-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:391ee8f19bef69210978363ca930f7328081c6a0152f1166c91f0b5fdd2a773c", size = 250887, upload-time = "2026-02-09T12:58:12.503Z" }, - { url = "https://files.pythonhosted.org/packages/06/90/2cdab0974b9b5bbc1623f7876b73603aecac11b8d95b85b5b86b32de5eab/coverage-7.13.4-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0dd7ab8278f0d58a0128ba2fca25824321f05d059c1441800e934ff2efa52129", size = 253396, upload-time = "2026-02-09T12:58:14.615Z" }, - { url = "https://files.pythonhosted.org/packages/ac/15/ea4da0f85bf7d7b27635039e649e99deb8173fe551096ea15017f7053537/coverage-7.13.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:78cdf0d578b15148b009ccf18c686aa4f719d887e76e6b40c38ffb61d264a552", size = 254745, upload-time = "2026-02-09T12:58:16.162Z" }, - { url = "https://files.pythonhosted.org/packages/99/11/bb356e86920c655ca4d61daee4e2bbc7258f0a37de0be32d233b561134ff/coverage-7.13.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:48685fee12c2eb3b27c62f2658e7ea21e9c3239cba5a8a242801a0a3f6a8c62a", size = 257055, upload-time = "2026-02-09T12:58:17.892Z" }, - { url = "https://files.pythonhosted.org/packages/c9/0f/9ae1f8cb17029e09da06ca4e28c9e1d5c1c0a511c7074592e37e0836c915/coverage-7.13.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4e83efc079eb39480e6346a15a1bcb3e9b04759c5202d157e1dd4303cd619356", size = 250911, upload-time = "2026-02-09T12:58:19.495Z" }, - { url = "https://files.pythonhosted.org/packages/89/3a/adfb68558fa815cbc29747b553bc833d2150228f251b127f1ce97e48547c/coverage-7.13.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ecae9737b72408d6a950f7e525f30aca12d4bd8dd95e37342e5beb3a2a8c4f71", size = 252754, upload-time = "2026-02-09T12:58:21.064Z" }, - { url = "https://files.pythonhosted.org/packages/32/b1/540d0c27c4e748bd3cd0bd001076ee416eda993c2bae47a73b7cc9357931/coverage-7.13.4-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ae4578f8528569d3cf303fef2ea569c7f4c4059a38c8667ccef15c6e1f118aa5", size = 250720, upload-time = "2026-02-09T12:58:22.622Z" }, - { url = "https://files.pythonhosted.org/packages/c7/95/383609462b3ffb1fe133014a7c84fc0dd01ed55ac6140fa1093b5af7ebb1/coverage-7.13.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:6fdef321fdfbb30a197efa02d48fcd9981f0d8ad2ae8903ac318adc653f5df98", size = 254994, upload-time = "2026-02-09T12:58:24.548Z" }, - { url = "https://files.pythonhosted.org/packages/f7/ba/1761138e86c81680bfc3c49579d66312865457f9fe405b033184e5793cb3/coverage-7.13.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b0f6ccf3dbe577170bebfce1318707d0e8c3650003cb4b3a9dd744575daa8b5", size = 250531, upload-time = "2026-02-09T12:58:26.271Z" }, - { url = "https://files.pythonhosted.org/packages/f8/8e/05900df797a9c11837ab59c4d6fe94094e029582aab75c3309a93e6fb4e3/coverage-7.13.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:75fcd519f2a5765db3f0e391eb3b7d150cce1a771bf4c9f861aeab86c767a3c0", size = 252189, upload-time = "2026-02-09T12:58:27.807Z" }, - { url = "https://files.pythonhosted.org/packages/00/bd/29c9f2db9ea4ed2738b8a9508c35626eb205d51af4ab7bf56a21a2e49926/coverage-7.13.4-cp314-cp314-win32.whl", hash = "sha256:8e798c266c378da2bd819b0677df41ab46d78065fb2a399558f3f6cae78b2fbb", size = 222258, upload-time = "2026-02-09T12:58:29.441Z" }, - { url = "https://files.pythonhosted.org/packages/a7/4d/1f8e723f6829977410efeb88f73673d794075091c8c7c18848d273dc9d73/coverage-7.13.4-cp314-cp314-win_amd64.whl", hash = "sha256:245e37f664d89861cf2329c9afa2c1fe9e6d4e1a09d872c947e70718aeeac505", size = 223073, upload-time = "2026-02-09T12:58:31.026Z" }, - { url = "https://files.pythonhosted.org/packages/51/5b/84100025be913b44e082ea32abcf1afbf4e872f5120b7a1cab1d331b1e13/coverage-7.13.4-cp314-cp314-win_arm64.whl", hash = "sha256:ad27098a189e5838900ce4c2a99f2fe42a0bf0c2093c17c69b45a71579e8d4a2", size = 221638, upload-time = "2026-02-09T12:58:32.599Z" }, - { url = "https://files.pythonhosted.org/packages/a7/e4/c884a405d6ead1370433dad1e3720216b4f9fd8ef5b64bfd984a2a60a11a/coverage-7.13.4-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:85480adfb35ffc32d40918aad81b89c69c9cc5661a9b8a81476d3e645321a056", size = 220246, upload-time = "2026-02-09T12:58:34.181Z" }, - { url = "https://files.pythonhosted.org/packages/81/5c/4d7ed8b23b233b0fffbc9dfec53c232be2e695468523242ea9fd30f97ad2/coverage-7.13.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:79be69cf7f3bf9b0deeeb062eab7ac7f36cd4cc4c4dd694bd28921ba4d8596cc", size = 220514, upload-time = "2026-02-09T12:58:35.704Z" }, - { url = "https://files.pythonhosted.org/packages/2f/6f/3284d4203fd2f28edd73034968398cd2d4cb04ab192abc8cff007ea35679/coverage-7.13.4-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:caa421e2684e382c5d8973ac55e4f36bed6821a9bad5c953494de960c74595c9", size = 261877, upload-time = "2026-02-09T12:58:37.864Z" }, - { url = "https://files.pythonhosted.org/packages/09/aa/b672a647bbe1556a85337dc95bfd40d146e9965ead9cc2fe81bde1e5cbce/coverage-7.13.4-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:14375934243ee05f56c45393fe2ce81fe5cc503c07cee2bdf1725fb8bef3ffaf", size = 264004, upload-time = "2026-02-09T12:58:39.492Z" }, - { url = "https://files.pythonhosted.org/packages/79/a1/aa384dbe9181f98bba87dd23dda436f0c6cf2e148aecbb4e50fc51c1a656/coverage-7.13.4-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:25a41c3104d08edb094d9db0d905ca54d0cd41c928bb6be3c4c799a54753af55", size = 266408, upload-time = "2026-02-09T12:58:41.852Z" }, - { url = "https://files.pythonhosted.org/packages/53/5e/5150bf17b4019bc600799f376bb9606941e55bd5a775dc1e096b6ffea952/coverage-7.13.4-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6f01afcff62bf9a08fb32b2c1d6e924236c0383c02c790732b6537269e466a72", size = 267544, upload-time = "2026-02-09T12:58:44.093Z" }, - { url = "https://files.pythonhosted.org/packages/e0/ed/f1de5c675987a4a7a672250d2c5c9d73d289dbf13410f00ed7181d8017dd/coverage-7.13.4-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:eb9078108fbf0bcdde37c3f4779303673c2fa1fe8f7956e68d447d0dd426d38a", size = 260980, upload-time = "2026-02-09T12:58:45.721Z" }, - { url = "https://files.pythonhosted.org/packages/b3/e3/fe758d01850aa172419a6743fe76ba8b92c29d181d4f676ffe2dae2ba631/coverage-7.13.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0e086334e8537ddd17e5f16a344777c1ab8194986ec533711cbe6c41cde841b6", size = 263871, upload-time = "2026-02-09T12:58:47.334Z" }, - { url = "https://files.pythonhosted.org/packages/b6/76/b829869d464115e22499541def9796b25312b8cf235d3bb00b39f1675395/coverage-7.13.4-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:725d985c5ab621268b2edb8e50dfe57633dc69bda071abc470fed55a14935fd3", size = 261472, upload-time = "2026-02-09T12:58:48.995Z" }, - { url = "https://files.pythonhosted.org/packages/14/9e/caedb1679e73e2f6ad240173f55218488bfe043e38da577c4ec977489915/coverage-7.13.4-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:3c06f0f1337c667b971ca2f975523347e63ec5e500b9aa5882d91931cd3ef750", size = 265210, upload-time = "2026-02-09T12:58:51.178Z" }, - { url = "https://files.pythonhosted.org/packages/3a/10/0dd02cb009b16ede425b49ec344aba13a6ae1dc39600840ea6abcb085ac4/coverage-7.13.4-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:590c0ed4bf8e85f745e6b805b2e1c457b2e33d5255dd9729743165253bc9ad39", size = 260319, upload-time = "2026-02-09T12:58:53.081Z" }, - { url = "https://files.pythonhosted.org/packages/92/8e/234d2c927af27c6d7a5ffad5bd2cf31634c46a477b4c7adfbfa66baf7ebb/coverage-7.13.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:eb30bf180de3f632cd043322dad5751390e5385108b2807368997d1a92a509d0", size = 262638, upload-time = "2026-02-09T12:58:55.258Z" }, - { url = "https://files.pythonhosted.org/packages/2f/64/e5547c8ff6964e5965c35a480855911b61509cce544f4d442caa759a0702/coverage-7.13.4-cp314-cp314t-win32.whl", hash = "sha256:c4240e7eded42d131a2d2c4dec70374b781b043ddc79a9de4d55ca71f8e98aea", size = 223040, upload-time = "2026-02-09T12:58:56.936Z" }, - { url = "https://files.pythonhosted.org/packages/c7/96/38086d58a181aac86d503dfa9c47eb20715a79c3e3acbdf786e92e5c09a8/coverage-7.13.4-cp314-cp314t-win_amd64.whl", hash = "sha256:4c7d3cc01e7350f2f0f6f7036caaf5673fb56b6998889ccfe9e1c1fe75a9c932", size = 224148, upload-time = "2026-02-09T12:58:58.645Z" }, - { url = "https://files.pythonhosted.org/packages/ce/72/8d10abd3740a0beb98c305e0c3faf454366221c0f37a8bcf8f60020bb65a/coverage-7.13.4-cp314-cp314t-win_arm64.whl", hash = "sha256:23e3f687cf945070d1c90f85db66d11e3025665d8dafa831301a0e0038f3db9b", size = 222172, upload-time = "2026-02-09T12:59:00.396Z" }, - { url = "https://files.pythonhosted.org/packages/0d/4a/331fe2caf6799d591109bb9c08083080f6de90a823695d412a935622abb2/coverage-7.13.4-py3-none-any.whl", hash = "sha256:1af1641e57cf7ba1bd67d677c9abdbcd6cc2ab7da3bca7fa1e2b7e50e65f2ad0", size = 211242, upload-time = "2026-02-09T12:59:02.032Z" }, +wheels = [ + {url = "https://files.pythonhosted.org/packages/92/11/a9cf762bb83386467737d32187756a42094927150c3e107df4cb078e8590/coverage-7.13.4-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:300deaee342f90696ed186e3a00c71b5b3d27bffe9e827677954f4ee56969601", size = 219522, upload-time = "2026-02-09T12:58:08.623Z"}, + {url = "https://files.pythonhosted.org/packages/d3/28/56e6d892b7b052236d67c95f1936b6a7cf7c3e2634bf27610b8cbd7f9c60/coverage-7.13.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:29e3220258d682b6226a9b0925bc563ed9a1ebcff3cad30f043eceea7eaf2689", size = 219855, upload-time = "2026-02-09T12:58:10.176Z"}, + {url = "https://files.pythonhosted.org/packages/e5/69/233459ee9eb0c0d10fcc2fe425a029b3fa5ce0f040c966ebce851d030c70/coverage-7.13.4-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:391ee8f19bef69210978363ca930f7328081c6a0152f1166c91f0b5fdd2a773c", size = 250887, upload-time = "2026-02-09T12:58:12.503Z"}, + {url = "https://files.pythonhosted.org/packages/06/90/2cdab0974b9b5bbc1623f7876b73603aecac11b8d95b85b5b86b32de5eab/coverage-7.13.4-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0dd7ab8278f0d58a0128ba2fca25824321f05d059c1441800e934ff2efa52129", size = 253396, upload-time = "2026-02-09T12:58:14.615Z"}, + {url = "https://files.pythonhosted.org/packages/ac/15/ea4da0f85bf7d7b27635039e649e99deb8173fe551096ea15017f7053537/coverage-7.13.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:78cdf0d578b15148b009ccf18c686aa4f719d887e76e6b40c38ffb61d264a552", size = 254745, upload-time = "2026-02-09T12:58:16.162Z"}, + {url = "https://files.pythonhosted.org/packages/99/11/bb356e86920c655ca4d61daee4e2bbc7258f0a37de0be32d233b561134ff/coverage-7.13.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:48685fee12c2eb3b27c62f2658e7ea21e9c3239cba5a8a242801a0a3f6a8c62a", size = 257055, upload-time = "2026-02-09T12:58:17.892Z"}, + {url = "https://files.pythonhosted.org/packages/c9/0f/9ae1f8cb17029e09da06ca4e28c9e1d5c1c0a511c7074592e37e0836c915/coverage-7.13.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4e83efc079eb39480e6346a15a1bcb3e9b04759c5202d157e1dd4303cd619356", size = 250911, upload-time = "2026-02-09T12:58:19.495Z"}, + {url = "https://files.pythonhosted.org/packages/89/3a/adfb68558fa815cbc29747b553bc833d2150228f251b127f1ce97e48547c/coverage-7.13.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ecae9737b72408d6a950f7e525f30aca12d4bd8dd95e37342e5beb3a2a8c4f71", size = 252754, upload-time = "2026-02-09T12:58:21.064Z"}, + {url = "https://files.pythonhosted.org/packages/32/b1/540d0c27c4e748bd3cd0bd001076ee416eda993c2bae47a73b7cc9357931/coverage-7.13.4-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ae4578f8528569d3cf303fef2ea569c7f4c4059a38c8667ccef15c6e1f118aa5", size = 250720, upload-time = "2026-02-09T12:58:22.622Z"}, + {url = "https://files.pythonhosted.org/packages/c7/95/383609462b3ffb1fe133014a7c84fc0dd01ed55ac6140fa1093b5af7ebb1/coverage-7.13.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:6fdef321fdfbb30a197efa02d48fcd9981f0d8ad2ae8903ac318adc653f5df98", size = 254994, upload-time = "2026-02-09T12:58:24.548Z"}, + {url = "https://files.pythonhosted.org/packages/f7/ba/1761138e86c81680bfc3c49579d66312865457f9fe405b033184e5793cb3/coverage-7.13.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b0f6ccf3dbe577170bebfce1318707d0e8c3650003cb4b3a9dd744575daa8b5", size = 250531, upload-time = "2026-02-09T12:58:26.271Z"}, + {url = "https://files.pythonhosted.org/packages/f8/8e/05900df797a9c11837ab59c4d6fe94094e029582aab75c3309a93e6fb4e3/coverage-7.13.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:75fcd519f2a5765db3f0e391eb3b7d150cce1a771bf4c9f861aeab86c767a3c0", size = 252189, upload-time = "2026-02-09T12:58:27.807Z"}, + {url = "https://files.pythonhosted.org/packages/00/bd/29c9f2db9ea4ed2738b8a9508c35626eb205d51af4ab7bf56a21a2e49926/coverage-7.13.4-cp314-cp314-win32.whl", hash = "sha256:8e798c266c378da2bd819b0677df41ab46d78065fb2a399558f3f6cae78b2fbb", size = 222258, upload-time = "2026-02-09T12:58:29.441Z"}, + {url = "https://files.pythonhosted.org/packages/a7/4d/1f8e723f6829977410efeb88f73673d794075091c8c7c18848d273dc9d73/coverage-7.13.4-cp314-cp314-win_amd64.whl", hash = "sha256:245e37f664d89861cf2329c9afa2c1fe9e6d4e1a09d872c947e70718aeeac505", size = 223073, upload-time = "2026-02-09T12:58:31.026Z"}, + {url = "https://files.pythonhosted.org/packages/51/5b/84100025be913b44e082ea32abcf1afbf4e872f5120b7a1cab1d331b1e13/coverage-7.13.4-cp314-cp314-win_arm64.whl", hash = "sha256:ad27098a189e5838900ce4c2a99f2fe42a0bf0c2093c17c69b45a71579e8d4a2", size = 221638, upload-time = "2026-02-09T12:58:32.599Z"}, + {url = "https://files.pythonhosted.org/packages/a7/e4/c884a405d6ead1370433dad1e3720216b4f9fd8ef5b64bfd984a2a60a11a/coverage-7.13.4-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:85480adfb35ffc32d40918aad81b89c69c9cc5661a9b8a81476d3e645321a056", size = 220246, upload-time = "2026-02-09T12:58:34.181Z"}, + {url = "https://files.pythonhosted.org/packages/81/5c/4d7ed8b23b233b0fffbc9dfec53c232be2e695468523242ea9fd30f97ad2/coverage-7.13.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:79be69cf7f3bf9b0deeeb062eab7ac7f36cd4cc4c4dd694bd28921ba4d8596cc", size = 220514, upload-time = "2026-02-09T12:58:35.704Z"}, + {url = "https://files.pythonhosted.org/packages/2f/6f/3284d4203fd2f28edd73034968398cd2d4cb04ab192abc8cff007ea35679/coverage-7.13.4-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:caa421e2684e382c5d8973ac55e4f36bed6821a9bad5c953494de960c74595c9", size = 261877, upload-time = "2026-02-09T12:58:37.864Z"}, + {url = "https://files.pythonhosted.org/packages/09/aa/b672a647bbe1556a85337dc95bfd40d146e9965ead9cc2fe81bde1e5cbce/coverage-7.13.4-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:14375934243ee05f56c45393fe2ce81fe5cc503c07cee2bdf1725fb8bef3ffaf", size = 264004, upload-time = "2026-02-09T12:58:39.492Z"}, + {url = "https://files.pythonhosted.org/packages/79/a1/aa384dbe9181f98bba87dd23dda436f0c6cf2e148aecbb4e50fc51c1a656/coverage-7.13.4-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:25a41c3104d08edb094d9db0d905ca54d0cd41c928bb6be3c4c799a54753af55", size = 266408, upload-time = "2026-02-09T12:58:41.852Z"}, + {url = "https://files.pythonhosted.org/packages/53/5e/5150bf17b4019bc600799f376bb9606941e55bd5a775dc1e096b6ffea952/coverage-7.13.4-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6f01afcff62bf9a08fb32b2c1d6e924236c0383c02c790732b6537269e466a72", size = 267544, upload-time = "2026-02-09T12:58:44.093Z"}, + {url = "https://files.pythonhosted.org/packages/e0/ed/f1de5c675987a4a7a672250d2c5c9d73d289dbf13410f00ed7181d8017dd/coverage-7.13.4-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:eb9078108fbf0bcdde37c3f4779303673c2fa1fe8f7956e68d447d0dd426d38a", size = 260980, upload-time = "2026-02-09T12:58:45.721Z"}, + {url = "https://files.pythonhosted.org/packages/b3/e3/fe758d01850aa172419a6743fe76ba8b92c29d181d4f676ffe2dae2ba631/coverage-7.13.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0e086334e8537ddd17e5f16a344777c1ab8194986ec533711cbe6c41cde841b6", size = 263871, upload-time = "2026-02-09T12:58:47.334Z"}, + {url = "https://files.pythonhosted.org/packages/b6/76/b829869d464115e22499541def9796b25312b8cf235d3bb00b39f1675395/coverage-7.13.4-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:725d985c5ab621268b2edb8e50dfe57633dc69bda071abc470fed55a14935fd3", size = 261472, upload-time = "2026-02-09T12:58:48.995Z"}, + {url = "https://files.pythonhosted.org/packages/14/9e/caedb1679e73e2f6ad240173f55218488bfe043e38da577c4ec977489915/coverage-7.13.4-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:3c06f0f1337c667b971ca2f975523347e63ec5e500b9aa5882d91931cd3ef750", size = 265210, upload-time = "2026-02-09T12:58:51.178Z"}, + {url = "https://files.pythonhosted.org/packages/3a/10/0dd02cb009b16ede425b49ec344aba13a6ae1dc39600840ea6abcb085ac4/coverage-7.13.4-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:590c0ed4bf8e85f745e6b805b2e1c457b2e33d5255dd9729743165253bc9ad39", size = 260319, upload-time = "2026-02-09T12:58:53.081Z"}, + {url = "https://files.pythonhosted.org/packages/92/8e/234d2c927af27c6d7a5ffad5bd2cf31634c46a477b4c7adfbfa66baf7ebb/coverage-7.13.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:eb30bf180de3f632cd043322dad5751390e5385108b2807368997d1a92a509d0", size = 262638, upload-time = "2026-02-09T12:58:55.258Z"}, + {url = "https://files.pythonhosted.org/packages/2f/64/e5547c8ff6964e5965c35a480855911b61509cce544f4d442caa759a0702/coverage-7.13.4-cp314-cp314t-win32.whl", hash = "sha256:c4240e7eded42d131a2d2c4dec70374b781b043ddc79a9de4d55ca71f8e98aea", size = 223040, upload-time = "2026-02-09T12:58:56.936Z"}, + {url = "https://files.pythonhosted.org/packages/c7/96/38086d58a181aac86d503dfa9c47eb20715a79c3e3acbdf786e92e5c09a8/coverage-7.13.4-cp314-cp314t-win_amd64.whl", hash = "sha256:4c7d3cc01e7350f2f0f6f7036caaf5673fb56b6998889ccfe9e1c1fe75a9c932", size = 224148, upload-time = "2026-02-09T12:58:58.645Z"}, + {url = "https://files.pythonhosted.org/packages/ce/72/8d10abd3740a0beb98c305e0c3faf454366221c0f37a8bcf8f60020bb65a/coverage-7.13.4-cp314-cp314t-win_arm64.whl", hash = "sha256:23e3f687cf945070d1c90f85db66d11e3025665d8dafa831301a0e0038f3db9b", size = 222172, upload-time = "2026-02-09T12:59:00.396Z"}, + {url = "https://files.pythonhosted.org/packages/0d/4a/331fe2caf6799d591109bb9c08083080f6de90a823695d412a935622abb2/coverage-7.13.4-py3-none-any.whl", hash = "sha256:1af1641e57cf7ba1bd67d677c9abdbcd6cc2ab7da3bca7fa1e2b7e50e65f2ad0", size = 211242, upload-time = "2026-02-09T12:59:02.032Z"} ] [[package]] name = "distlib" +sdist = {url = "https://files.pythonhosted.org/packages/96/8e/709914eb2b5749865801041647dc7f4e6d00b549cfe88b65ca192995f07c/distlib-0.4.0.tar.gz", hash = "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d", size = 614605, upload-time = "2025-07-17T16:52:00.465Z"} +source = {registry = "https://pypi.org/simple"} version = "0.4.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/96/8e/709914eb2b5749865801041647dc7f4e6d00b549cfe88b65ca192995f07c/distlib-0.4.0.tar.gz", hash = "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d", size = 614605, upload-time = "2025-07-17T16:52:00.465Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16", size = 469047, upload-time = "2025-07-17T16:51:58.613Z" }, + {url = "https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16", size = 469047, upload-time = "2025-07-17T16:51:58.613Z"} ] [[package]] name = "docutils" +sdist = {url = "https://files.pythonhosted.org/packages/ae/b6/03bb70946330e88ffec97aefd3ea75ba575cb2e762061e0e62a213befee8/docutils-0.22.4.tar.gz", hash = "sha256:4db53b1fde9abecbb74d91230d32ab626d94f6badfc575d6db9194a49df29968", size = 2291750, upload-time = "2025-12-18T19:00:26.443Z"} +source = {registry = "https://pypi.org/simple"} version = "0.22.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ae/b6/03bb70946330e88ffec97aefd3ea75ba575cb2e762061e0e62a213befee8/docutils-0.22.4.tar.gz", hash = "sha256:4db53b1fde9abecbb74d91230d32ab626d94f6badfc575d6db9194a49df29968", size = 2291750, upload-time = "2025-12-18T19:00:26.443Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/02/10/5da547df7a391dcde17f59520a231527b8571e6f46fc8efb02ccb370ab12/docutils-0.22.4-py3-none-any.whl", hash = "sha256:d0013f540772d1420576855455d050a2180186c91c15779301ac2ccb3eeb68de", size = 633196, upload-time = "2025-12-18T19:00:18.077Z" }, + {url = "https://files.pythonhosted.org/packages/02/10/5da547df7a391dcde17f59520a231527b8571e6f46fc8efb02ccb370ab12/docutils-0.22.4-py3-none-any.whl", hash = "sha256:d0013f540772d1420576855455d050a2180186c91c15779301ac2ccb3eeb68de", size = 633196, upload-time = "2025-12-18T19:00:18.077Z"} ] [[package]] name = "filelock" +sdist = {url = "https://files.pythonhosted.org/packages/77/18/a1fd2231c679dcb9726204645721b12498aeac28e1ad0601038f94b42556/filelock-3.25.0.tar.gz", hash = "sha256:8f00faf3abf9dc730a1ffe9c354ae5c04e079ab7d3a683b7c32da5dd05f26af3", size = 40158, upload-time = "2026-03-01T15:08:45.916Z"} +source = {registry = "https://pypi.org/simple"} version = "3.25.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/77/18/a1fd2231c679dcb9726204645721b12498aeac28e1ad0601038f94b42556/filelock-3.25.0.tar.gz", hash = "sha256:8f00faf3abf9dc730a1ffe9c354ae5c04e079ab7d3a683b7c32da5dd05f26af3", size = 40158, upload-time = "2026-03-01T15:08:45.916Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f9/0b/de6f54d4a8bedfe8645c41497f3c18d749f0bd3218170c667bf4b81d0cdd/filelock-3.25.0-py3-none-any.whl", hash = "sha256:5ccf8069f7948f494968fc0713c10e5c182a9c9d9eef3a636307a20c2490f047", size = 26427, upload-time = "2026-03-01T15:08:44.593Z" }, + {url = "https://files.pythonhosted.org/packages/f9/0b/de6f54d4a8bedfe8645c41497f3c18d749f0bd3218170c667bf4b81d0cdd/filelock-3.25.0-py3-none-any.whl", hash = "sha256:5ccf8069f7948f494968fc0713c10e5c182a9c9d9eef3a636307a20c2490f047", size = 26427, upload-time = "2026-03-01T15:08:44.593Z"} ] [[package]] name = "h11" +sdist = {url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z"} +source = {registry = "https://pypi.org/simple"} version = "0.16.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, + {url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z"} ] [[package]] name = "identify" +sdist = {url = "https://files.pythonhosted.org/packages/57/84/376a3b96e5a8d33a7aa2c5b3b31a4b3c364117184bf0b17418055f6ace66/identify-2.6.17.tar.gz", hash = "sha256:f816b0b596b204c9fdf076ded172322f2723cf958d02f9c3587504834c8ff04d", size = 99579, upload-time = "2026-03-01T20:04:12.702Z"} +source = {registry = "https://pypi.org/simple"} version = "2.6.17" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/57/84/376a3b96e5a8d33a7aa2c5b3b31a4b3c364117184bf0b17418055f6ace66/identify-2.6.17.tar.gz", hash = "sha256:f816b0b596b204c9fdf076ded172322f2723cf958d02f9c3587504834c8ff04d", size = 99579, upload-time = "2026-03-01T20:04:12.702Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/40/66/71c1227dff78aaeb942fed29dd5651f2aec166cc7c9aeea3e8b26a539b7d/identify-2.6.17-py2.py3-none-any.whl", hash = "sha256:be5f8412d5ed4b20f2bd41a65f920990bdccaa6a4a18a08f1eefdcd0bdd885f0", size = 99382, upload-time = "2026-03-01T20:04:11.439Z" }, + {url = "https://files.pythonhosted.org/packages/40/66/71c1227dff78aaeb942fed29dd5651f2aec166cc7c9aeea3e8b26a539b7d/identify-2.6.17-py2.py3-none-any.whl", hash = "sha256:be5f8412d5ed4b20f2bd41a65f920990bdccaa6a4a18a08f1eefdcd0bdd885f0", size = 99382, upload-time = "2026-03-01T20:04:11.439Z"} ] [[package]] name = "idna" +sdist = {url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z"} +source = {registry = "https://pypi.org/simple"} version = "3.11" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, + {url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z"} ] [[package]] name = "imagesize" +sdist = {url = "https://files.pythonhosted.org/packages/6c/e6/7bf14eeb8f8b7251141944835abd42eb20a658d89084b7e1f3e5fe394090/imagesize-2.0.0.tar.gz", hash = "sha256:8e8358c4a05c304f1fccf7ff96f036e7243a189e9e42e90851993c558cfe9ee3", size = 1773045, upload-time = "2026-03-03T14:18:29.941Z"} +source = {registry = "https://pypi.org/simple"} version = "2.0.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6c/e6/7bf14eeb8f8b7251141944835abd42eb20a658d89084b7e1f3e5fe394090/imagesize-2.0.0.tar.gz", hash = "sha256:8e8358c4a05c304f1fccf7ff96f036e7243a189e9e42e90851993c558cfe9ee3", size = 1773045, upload-time = "2026-03-03T14:18:29.941Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5f/53/fb7122b71361a0d121b669dcf3d31244ef75badbbb724af388948de543e2/imagesize-2.0.0-py2.py3-none-any.whl", hash = "sha256:5667c5bbb57ab3f1fa4bc366f4fbc971db3d5ed011fd2715fd8001f782718d96", size = 9441, upload-time = "2026-03-03T14:18:27.892Z" }, + {url = "https://files.pythonhosted.org/packages/5f/53/fb7122b71361a0d121b669dcf3d31244ef75badbbb724af388948de543e2/imagesize-2.0.0-py2.py3-none-any.whl", hash = "sha256:5667c5bbb57ab3f1fa4bc366f4fbc971db3d5ed011fd2715fd8001f782718d96", size = 9441, upload-time = "2026-03-03T14:18:27.892Z"} ] [[package]] name = "iniconfig" +sdist = {url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z"} +source = {registry = "https://pypi.org/simple"} version = "2.3.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, + {url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z"} ] [[package]] name = "itsdangerous" +sdist = {url = "https://files.pythonhosted.org/packages/9c/cb/8ac0172223afbccb63986cc25049b154ecfb5e85932587206f42317be31d/itsdangerous-2.2.0.tar.gz", hash = "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173", size = 54410, upload-time = "2024-04-16T21:28:15.614Z"} +source = {registry = "https://pypi.org/simple"} version = "2.2.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/9c/cb/8ac0172223afbccb63986cc25049b154ecfb5e85932587206f42317be31d/itsdangerous-2.2.0.tar.gz", hash = "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173", size = 54410, upload-time = "2024-04-16T21:28:15.614Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/04/96/92447566d16df59b2a776c0fb82dbc4d9e07cd95062562af01e408583fc4/itsdangerous-2.2.0-py3-none-any.whl", hash = "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef", size = 16234, upload-time = "2024-04-16T21:28:14.499Z" }, + {url = "https://files.pythonhosted.org/packages/04/96/92447566d16df59b2a776c0fb82dbc4d9e07cd95062562af01e408583fc4/itsdangerous-2.2.0-py3-none-any.whl", hash = "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef", size = 16234, upload-time = "2024-04-16T21:28:14.499Z"} ] [[package]] -name = "jedi" -version = "0.19.2" -source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "parso" }, + {name = "parso"} ] -sdist = { url = "https://files.pythonhosted.org/packages/72/3a/79a912fbd4d8dd6fbb02bf69afd3bb72cf0c729bb3063c6f4498603db17a/jedi-0.19.2.tar.gz", hash = "sha256:4770dc3de41bde3966b02eb84fbcf557fb33cce26ad23da12c742fb50ecb11f0", size = 1231287, upload-time = "2024-11-11T01:41:42.873Z" } +name = "jedi" +sdist = {url = "https://files.pythonhosted.org/packages/72/3a/79a912fbd4d8dd6fbb02bf69afd3bb72cf0c729bb3063c6f4498603db17a/jedi-0.19.2.tar.gz", hash = "sha256:4770dc3de41bde3966b02eb84fbcf557fb33cce26ad23da12c742fb50ecb11f0", size = 1231287, upload-time = "2024-11-11T01:41:42.873Z"} +source = {registry = "https://pypi.org/simple"} +version = "0.19.2" wheels = [ - { url = "https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl", hash = "sha256:a8ef22bde8490f57fe5c7681a3c83cb58874daf72b4784de3cce5b6ef6edb5b9", size = 1572278, upload-time = "2024-11-11T01:41:40.175Z" }, + {url = "https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl", hash = "sha256:a8ef22bde8490f57fe5c7681a3c83cb58874daf72b4784de3cce5b6ef6edb5b9", size = 1572278, upload-time = "2024-11-11T01:41:40.175Z"} ] [[package]] -name = "jinja2" -version = "3.1.6" -source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "markupsafe" }, + {name = "markupsafe"} ] -sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" } +name = "jinja2" +sdist = {url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z"} +source = {registry = "https://pypi.org/simple"} +version = "3.1.6" wheels = [ - { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, + {url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z"} ] [[package]] name = "librt" +sdist = {url = "https://files.pythonhosted.org/packages/56/9c/b4b0c54d84da4a94b37bd44151e46d5e583c9534c7e02250b961b1b6d8a8/librt-0.8.1.tar.gz", hash = "sha256:be46a14693955b3bd96014ccbdb8339ee8c9346fbe11c1b78901b55125f14c73", size = 177471, upload-time = "2026-02-17T16:13:06.101Z"} +source = {registry = "https://pypi.org/simple"} version = "0.8.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/56/9c/b4b0c54d84da4a94b37bd44151e46d5e583c9534c7e02250b961b1b6d8a8/librt-0.8.1.tar.gz", hash = "sha256:be46a14693955b3bd96014ccbdb8339ee8c9346fbe11c1b78901b55125f14c73", size = 177471, upload-time = "2026-02-17T16:13:06.101Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c9/6a/907ef6800f7bca71b525a05f1839b21f708c09043b1c6aa77b6b827b3996/librt-0.8.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:6cfa7fe54fd4d1f47130017351a959fe5804bda7a0bc7e07a2cdbc3fdd28d34f", size = 66081, upload-time = "2026-02-17T16:12:12.766Z" }, - { url = "https://files.pythonhosted.org/packages/1b/18/25e991cd5640c9fb0f8d91b18797b29066b792f17bf8493da183bf5caabe/librt-0.8.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:228c2409c079f8c11fb2e5d7b277077f694cb93443eb760e00b3b83cb8b3176c", size = 68309, upload-time = "2026-02-17T16:12:13.756Z" }, - { url = "https://files.pythonhosted.org/packages/a4/36/46820d03f058cfb5a9de5940640ba03165ed8aded69e0733c417bb04df34/librt-0.8.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7aae78ab5e3206181780e56912d1b9bb9f90a7249ce12f0e8bf531d0462dd0fc", size = 196804, upload-time = "2026-02-17T16:12:14.818Z" }, - { url = "https://files.pythonhosted.org/packages/59/18/5dd0d3b87b8ff9c061849fbdb347758d1f724b9a82241aa908e0ec54ccd0/librt-0.8.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:172d57ec04346b047ca6af181e1ea4858086c80bdf455f61994c4aa6fc3f866c", size = 206907, upload-time = "2026-02-17T16:12:16.513Z" }, - { url = "https://files.pythonhosted.org/packages/d1/96/ef04902aad1424fd7299b62d1890e803e6ab4018c3044dca5922319c4b97/librt-0.8.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6b1977c4ea97ce5eb7755a78fae68d87e4102e4aaf54985e8b56806849cc06a3", size = 221217, upload-time = "2026-02-17T16:12:17.906Z" }, - { url = "https://files.pythonhosted.org/packages/6d/ff/7e01f2dda84a8f5d280637a2e5827210a8acca9a567a54507ef1c75b342d/librt-0.8.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:10c42e1f6fd06733ef65ae7bebce2872bcafd8d6e6b0a08fe0a05a23b044fb14", size = 214622, upload-time = "2026-02-17T16:12:19.108Z" }, - { url = "https://files.pythonhosted.org/packages/1e/8c/5b093d08a13946034fed57619742f790faf77058558b14ca36a6e331161e/librt-0.8.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:4c8dfa264b9193c4ee19113c985c95f876fae5e51f731494fc4e0cf594990ba7", size = 221987, upload-time = "2026-02-17T16:12:20.331Z" }, - { url = "https://files.pythonhosted.org/packages/d3/cc/86b0b3b151d40920ad45a94ce0171dec1aebba8a9d72bb3fa00c73ab25dd/librt-0.8.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:01170b6729a438f0dedc4a26ed342e3dc4f02d1000b4b19f980e1877f0c297e6", size = 215132, upload-time = "2026-02-17T16:12:21.54Z" }, - { url = "https://files.pythonhosted.org/packages/fc/be/8588164a46edf1e69858d952654e216a9a91174688eeefb9efbb38a9c799/librt-0.8.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:7b02679a0d783bdae30d443025b94465d8c3dc512f32f5b5031f93f57ac32071", size = 215195, upload-time = "2026-02-17T16:12:23.073Z" }, - { url = "https://files.pythonhosted.org/packages/f5/f2/0b9279bea735c734d69344ecfe056c1ba211694a72df10f568745c899c76/librt-0.8.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:190b109bb69592a3401fe1ffdea41a2e73370ace2ffdc4a0e8e2b39cdea81b78", size = 237946, upload-time = "2026-02-17T16:12:24.275Z" }, - { url = "https://files.pythonhosted.org/packages/e9/cc/5f2a34fbc8aeb35314a3641f9956fa9051a947424652fad9882be7a97949/librt-0.8.1-cp314-cp314-win32.whl", hash = "sha256:e70a57ecf89a0f64c24e37f38d3fe217a58169d2fe6ed6d70554964042474023", size = 50689, upload-time = "2026-02-17T16:12:25.766Z" }, - { url = "https://files.pythonhosted.org/packages/a0/76/cd4d010ab2147339ca2b93e959c3686e964edc6de66ddacc935c325883d7/librt-0.8.1-cp314-cp314-win_amd64.whl", hash = "sha256:7e2f3edca35664499fbb36e4770650c4bd4a08abc1f4458eab9df4ec56389730", size = 57875, upload-time = "2026-02-17T16:12:27.465Z" }, - { url = "https://files.pythonhosted.org/packages/84/0f/2143cb3c3ca48bd3379dcd11817163ca50781927c4537345d608b5045998/librt-0.8.1-cp314-cp314-win_arm64.whl", hash = "sha256:0d2f82168e55ddefd27c01c654ce52379c0750ddc31ee86b4b266bcf4d65f2a3", size = 48058, upload-time = "2026-02-17T16:12:28.556Z" }, - { url = "https://files.pythonhosted.org/packages/d2/0e/9b23a87e37baf00311c3efe6b48d6b6c168c29902dfc3f04c338372fd7db/librt-0.8.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2c74a2da57a094bd48d03fa5d196da83d2815678385d2978657499063709abe1", size = 68313, upload-time = "2026-02-17T16:12:29.659Z" }, - { url = "https://files.pythonhosted.org/packages/db/9a/859c41e5a4f1c84200a7d2b92f586aa27133c8243b6cac9926f6e54d01b9/librt-0.8.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a355d99c4c0d8e5b770313b8b247411ed40949ca44e33e46a4789b9293a907ee", size = 70994, upload-time = "2026-02-17T16:12:31.516Z" }, - { url = "https://files.pythonhosted.org/packages/4c/28/10605366ee599ed34223ac2bf66404c6fb59399f47108215d16d5ad751a8/librt-0.8.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:2eb345e8b33fb748227409c9f1233d4df354d6e54091f0e8fc53acdb2ffedeb7", size = 220770, upload-time = "2026-02-17T16:12:33.294Z" }, - { url = "https://files.pythonhosted.org/packages/af/8d/16ed8fd452dafae9c48d17a6bc1ee3e818fd40ef718d149a8eff2c9f4ea2/librt-0.8.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9be2f15e53ce4e83cc08adc29b26fb5978db62ef2a366fbdf716c8a6c8901040", size = 235409, upload-time = "2026-02-17T16:12:35.443Z" }, - { url = "https://files.pythonhosted.org/packages/89/1b/7bdf3e49349c134b25db816e4a3db6b94a47ac69d7d46b1e682c2c4949be/librt-0.8.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:785ae29c1f5c6e7c2cde2c7c0e148147f4503da3abc5d44d482068da5322fd9e", size = 246473, upload-time = "2026-02-17T16:12:36.656Z" }, - { url = "https://files.pythonhosted.org/packages/4e/8a/91fab8e4fd2a24930a17188c7af5380eb27b203d72101c9cc000dbdfd95a/librt-0.8.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1d3a7da44baf692f0c6aeb5b2a09c5e6fc7a703bca9ffa337ddd2e2da53f7732", size = 238866, upload-time = "2026-02-17T16:12:37.849Z" }, - { url = "https://files.pythonhosted.org/packages/b9/e0/c45a098843fc7c07e18a7f8a24ca8496aecbf7bdcd54980c6ca1aaa79a8e/librt-0.8.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5fc48998000cbc39ec0d5311312dda93ecf92b39aaf184c5e817d5d440b29624", size = 250248, upload-time = "2026-02-17T16:12:39.445Z" }, - { url = "https://files.pythonhosted.org/packages/82/30/07627de23036640c952cce0c1fe78972e77d7d2f8fd54fa5ef4554ff4a56/librt-0.8.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:e96baa6820280077a78244b2e06e416480ed859bbd8e5d641cf5742919d8beb4", size = 240629, upload-time = "2026-02-17T16:12:40.889Z" }, - { url = "https://files.pythonhosted.org/packages/fb/c1/55bfe1ee3542eba055616f9098eaf6eddb966efb0ca0f44eaa4aba327307/librt-0.8.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:31362dbfe297b23590530007062c32c6f6176f6099646bb2c95ab1b00a57c382", size = 239615, upload-time = "2026-02-17T16:12:42.446Z" }, - { url = "https://files.pythonhosted.org/packages/2b/39/191d3d28abc26c9099b19852e6c99f7f6d400b82fa5a4e80291bd3803e19/librt-0.8.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cc3656283d11540ab0ea01978378e73e10002145117055e03722417aeab30994", size = 263001, upload-time = "2026-02-17T16:12:43.627Z" }, - { url = "https://files.pythonhosted.org/packages/b9/eb/7697f60fbe7042ab4e88f4ee6af496b7f222fffb0a4e3593ef1f29f81652/librt-0.8.1-cp314-cp314t-win32.whl", hash = "sha256:738f08021b3142c2918c03692608baed43bc51144c29e35807682f8070ee2a3a", size = 51328, upload-time = "2026-02-17T16:12:45.148Z" }, - { url = "https://files.pythonhosted.org/packages/7c/72/34bf2eb7a15414a23e5e70ecb9440c1d3179f393d9349338a91e2781c0fb/librt-0.8.1-cp314-cp314t-win_amd64.whl", hash = "sha256:89815a22daf9c51884fb5dbe4f1ef65ee6a146e0b6a8df05f753e2e4a9359bf4", size = 58722, upload-time = "2026-02-17T16:12:46.85Z" }, - { url = "https://files.pythonhosted.org/packages/b2/c8/d148e041732d631fc76036f8b30fae4e77b027a1e95b7a84bb522481a940/librt-0.8.1-cp314-cp314t-win_arm64.whl", hash = "sha256:bf512a71a23504ed08103a13c941f763db13fb11177beb3d9244c98c29fb4a61", size = 48755, upload-time = "2026-02-17T16:12:47.943Z" }, +wheels = [ + {url = "https://files.pythonhosted.org/packages/c9/6a/907ef6800f7bca71b525a05f1839b21f708c09043b1c6aa77b6b827b3996/librt-0.8.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:6cfa7fe54fd4d1f47130017351a959fe5804bda7a0bc7e07a2cdbc3fdd28d34f", size = 66081, upload-time = "2026-02-17T16:12:12.766Z"}, + {url = "https://files.pythonhosted.org/packages/1b/18/25e991cd5640c9fb0f8d91b18797b29066b792f17bf8493da183bf5caabe/librt-0.8.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:228c2409c079f8c11fb2e5d7b277077f694cb93443eb760e00b3b83cb8b3176c", size = 68309, upload-time = "2026-02-17T16:12:13.756Z"}, + {url = "https://files.pythonhosted.org/packages/a4/36/46820d03f058cfb5a9de5940640ba03165ed8aded69e0733c417bb04df34/librt-0.8.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7aae78ab5e3206181780e56912d1b9bb9f90a7249ce12f0e8bf531d0462dd0fc", size = 196804, upload-time = "2026-02-17T16:12:14.818Z"}, + {url = "https://files.pythonhosted.org/packages/59/18/5dd0d3b87b8ff9c061849fbdb347758d1f724b9a82241aa908e0ec54ccd0/librt-0.8.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:172d57ec04346b047ca6af181e1ea4858086c80bdf455f61994c4aa6fc3f866c", size = 206907, upload-time = "2026-02-17T16:12:16.513Z"}, + {url = "https://files.pythonhosted.org/packages/d1/96/ef04902aad1424fd7299b62d1890e803e6ab4018c3044dca5922319c4b97/librt-0.8.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6b1977c4ea97ce5eb7755a78fae68d87e4102e4aaf54985e8b56806849cc06a3", size = 221217, upload-time = "2026-02-17T16:12:17.906Z"}, + {url = "https://files.pythonhosted.org/packages/6d/ff/7e01f2dda84a8f5d280637a2e5827210a8acca9a567a54507ef1c75b342d/librt-0.8.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:10c42e1f6fd06733ef65ae7bebce2872bcafd8d6e6b0a08fe0a05a23b044fb14", size = 214622, upload-time = "2026-02-17T16:12:19.108Z"}, + {url = "https://files.pythonhosted.org/packages/1e/8c/5b093d08a13946034fed57619742f790faf77058558b14ca36a6e331161e/librt-0.8.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:4c8dfa264b9193c4ee19113c985c95f876fae5e51f731494fc4e0cf594990ba7", size = 221987, upload-time = "2026-02-17T16:12:20.331Z"}, + {url = "https://files.pythonhosted.org/packages/d3/cc/86b0b3b151d40920ad45a94ce0171dec1aebba8a9d72bb3fa00c73ab25dd/librt-0.8.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:01170b6729a438f0dedc4a26ed342e3dc4f02d1000b4b19f980e1877f0c297e6", size = 215132, upload-time = "2026-02-17T16:12:21.54Z"}, + {url = "https://files.pythonhosted.org/packages/fc/be/8588164a46edf1e69858d952654e216a9a91174688eeefb9efbb38a9c799/librt-0.8.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:7b02679a0d783bdae30d443025b94465d8c3dc512f32f5b5031f93f57ac32071", size = 215195, upload-time = "2026-02-17T16:12:23.073Z"}, + {url = "https://files.pythonhosted.org/packages/f5/f2/0b9279bea735c734d69344ecfe056c1ba211694a72df10f568745c899c76/librt-0.8.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:190b109bb69592a3401fe1ffdea41a2e73370ace2ffdc4a0e8e2b39cdea81b78", size = 237946, upload-time = "2026-02-17T16:12:24.275Z"}, + {url = "https://files.pythonhosted.org/packages/e9/cc/5f2a34fbc8aeb35314a3641f9956fa9051a947424652fad9882be7a97949/librt-0.8.1-cp314-cp314-win32.whl", hash = "sha256:e70a57ecf89a0f64c24e37f38d3fe217a58169d2fe6ed6d70554964042474023", size = 50689, upload-time = "2026-02-17T16:12:25.766Z"}, + {url = "https://files.pythonhosted.org/packages/a0/76/cd4d010ab2147339ca2b93e959c3686e964edc6de66ddacc935c325883d7/librt-0.8.1-cp314-cp314-win_amd64.whl", hash = "sha256:7e2f3edca35664499fbb36e4770650c4bd4a08abc1f4458eab9df4ec56389730", size = 57875, upload-time = "2026-02-17T16:12:27.465Z"}, + {url = "https://files.pythonhosted.org/packages/84/0f/2143cb3c3ca48bd3379dcd11817163ca50781927c4537345d608b5045998/librt-0.8.1-cp314-cp314-win_arm64.whl", hash = "sha256:0d2f82168e55ddefd27c01c654ce52379c0750ddc31ee86b4b266bcf4d65f2a3", size = 48058, upload-time = "2026-02-17T16:12:28.556Z"}, + {url = "https://files.pythonhosted.org/packages/d2/0e/9b23a87e37baf00311c3efe6b48d6b6c168c29902dfc3f04c338372fd7db/librt-0.8.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2c74a2da57a094bd48d03fa5d196da83d2815678385d2978657499063709abe1", size = 68313, upload-time = "2026-02-17T16:12:29.659Z"}, + {url = "https://files.pythonhosted.org/packages/db/9a/859c41e5a4f1c84200a7d2b92f586aa27133c8243b6cac9926f6e54d01b9/librt-0.8.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a355d99c4c0d8e5b770313b8b247411ed40949ca44e33e46a4789b9293a907ee", size = 70994, upload-time = "2026-02-17T16:12:31.516Z"}, + {url = "https://files.pythonhosted.org/packages/4c/28/10605366ee599ed34223ac2bf66404c6fb59399f47108215d16d5ad751a8/librt-0.8.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:2eb345e8b33fb748227409c9f1233d4df354d6e54091f0e8fc53acdb2ffedeb7", size = 220770, upload-time = "2026-02-17T16:12:33.294Z"}, + {url = "https://files.pythonhosted.org/packages/af/8d/16ed8fd452dafae9c48d17a6bc1ee3e818fd40ef718d149a8eff2c9f4ea2/librt-0.8.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9be2f15e53ce4e83cc08adc29b26fb5978db62ef2a366fbdf716c8a6c8901040", size = 235409, upload-time = "2026-02-17T16:12:35.443Z"}, + {url = "https://files.pythonhosted.org/packages/89/1b/7bdf3e49349c134b25db816e4a3db6b94a47ac69d7d46b1e682c2c4949be/librt-0.8.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:785ae29c1f5c6e7c2cde2c7c0e148147f4503da3abc5d44d482068da5322fd9e", size = 246473, upload-time = "2026-02-17T16:12:36.656Z"}, + {url = "https://files.pythonhosted.org/packages/4e/8a/91fab8e4fd2a24930a17188c7af5380eb27b203d72101c9cc000dbdfd95a/librt-0.8.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1d3a7da44baf692f0c6aeb5b2a09c5e6fc7a703bca9ffa337ddd2e2da53f7732", size = 238866, upload-time = "2026-02-17T16:12:37.849Z"}, + {url = "https://files.pythonhosted.org/packages/b9/e0/c45a098843fc7c07e18a7f8a24ca8496aecbf7bdcd54980c6ca1aaa79a8e/librt-0.8.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5fc48998000cbc39ec0d5311312dda93ecf92b39aaf184c5e817d5d440b29624", size = 250248, upload-time = "2026-02-17T16:12:39.445Z"}, + {url = "https://files.pythonhosted.org/packages/82/30/07627de23036640c952cce0c1fe78972e77d7d2f8fd54fa5ef4554ff4a56/librt-0.8.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:e96baa6820280077a78244b2e06e416480ed859bbd8e5d641cf5742919d8beb4", size = 240629, upload-time = "2026-02-17T16:12:40.889Z"}, + {url = "https://files.pythonhosted.org/packages/fb/c1/55bfe1ee3542eba055616f9098eaf6eddb966efb0ca0f44eaa4aba327307/librt-0.8.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:31362dbfe297b23590530007062c32c6f6176f6099646bb2c95ab1b00a57c382", size = 239615, upload-time = "2026-02-17T16:12:42.446Z"}, + {url = "https://files.pythonhosted.org/packages/2b/39/191d3d28abc26c9099b19852e6c99f7f6d400b82fa5a4e80291bd3803e19/librt-0.8.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cc3656283d11540ab0ea01978378e73e10002145117055e03722417aeab30994", size = 263001, upload-time = "2026-02-17T16:12:43.627Z"}, + {url = "https://files.pythonhosted.org/packages/b9/eb/7697f60fbe7042ab4e88f4ee6af496b7f222fffb0a4e3593ef1f29f81652/librt-0.8.1-cp314-cp314t-win32.whl", hash = "sha256:738f08021b3142c2918c03692608baed43bc51144c29e35807682f8070ee2a3a", size = 51328, upload-time = "2026-02-17T16:12:45.148Z"}, + {url = "https://files.pythonhosted.org/packages/7c/72/34bf2eb7a15414a23e5e70ecb9440c1d3179f393d9349338a91e2781c0fb/librt-0.8.1-cp314-cp314t-win_amd64.whl", hash = "sha256:89815a22daf9c51884fb5dbe4f1ef65ee6a146e0b6a8df05f753e2e4a9359bf4", size = 58722, upload-time = "2026-02-17T16:12:46.85Z"}, + {url = "https://files.pythonhosted.org/packages/b2/c8/d148e041732d631fc76036f8b30fae4e77b027a1e95b7a84bb522481a940/librt-0.8.1-cp314-cp314t-win_arm64.whl", hash = "sha256:bf512a71a23504ed08103a13c941f763db13fb11177beb3d9244c98c29fb4a61", size = 48755, upload-time = "2026-02-17T16:12:47.943Z"} ] [[package]] name = "llvmlite" +sdist = {url = "https://files.pythonhosted.org/packages/74/cd/08ae687ba099c7e3d21fe2ea536500563ef1943c5105bf6ab4ee3829f68e/llvmlite-0.46.0.tar.gz", hash = "sha256:227c9fd6d09dce2783c18b754b7cd9d9b3b3515210c46acc2d3c5badd9870ceb", size = 193456, upload-time = "2025-12-08T18:15:36.295Z"} +source = {registry = "https://pypi.org/simple"} version = "0.46.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/74/cd/08ae687ba099c7e3d21fe2ea536500563ef1943c5105bf6ab4ee3829f68e/llvmlite-0.46.0.tar.gz", hash = "sha256:227c9fd6d09dce2783c18b754b7cd9d9b3b3515210c46acc2d3c5badd9870ceb", size = 193456, upload-time = "2025-12-08T18:15:36.295Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/95/ae/af0ffb724814cc2ea64445acad05f71cff5f799bb7efb22e47ee99340dbc/llvmlite-0.46.0-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:d252edfb9f4ac1fcf20652258e3f102b26b03eef738dc8a6ffdab7d7d341d547", size = 37232768, upload-time = "2025-12-08T18:15:25.055Z" }, - { url = "https://files.pythonhosted.org/packages/c9/19/5018e5352019be753b7b07f7759cdabb69ca5779fea2494be8839270df4c/llvmlite-0.46.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:379fdd1c59badeff8982cb47e4694a6143bec3bb49aa10a466e095410522064d", size = 56275173, upload-time = "2025-12-08T18:15:28.109Z" }, - { url = "https://files.pythonhosted.org/packages/9f/c9/d57877759d707e84c082163c543853245f91b70c804115a5010532890f18/llvmlite-0.46.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2e8cbfff7f6db0fa2c771ad24154e2a7e457c2444d7673e6de06b8b698c3b269", size = 55128628, upload-time = "2025-12-08T18:15:31.098Z" }, - { url = "https://files.pythonhosted.org/packages/30/a8/e61a8c2b3cc7a597073d9cde1fcbb567e9d827f1db30c93cf80422eac70d/llvmlite-0.46.0-cp314-cp314-win_amd64.whl", hash = "sha256:7821eda3ec1f18050f981819756631d60b6d7ab1a6cf806d9efefbe3f4082d61", size = 39153056, upload-time = "2025-12-08T18:15:33.938Z" }, + {url = "https://files.pythonhosted.org/packages/95/ae/af0ffb724814cc2ea64445acad05f71cff5f799bb7efb22e47ee99340dbc/llvmlite-0.46.0-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:d252edfb9f4ac1fcf20652258e3f102b26b03eef738dc8a6ffdab7d7d341d547", size = 37232768, upload-time = "2025-12-08T18:15:25.055Z"}, + {url = "https://files.pythonhosted.org/packages/c9/19/5018e5352019be753b7b07f7759cdabb69ca5779fea2494be8839270df4c/llvmlite-0.46.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:379fdd1c59badeff8982cb47e4694a6143bec3bb49aa10a466e095410522064d", size = 56275173, upload-time = "2025-12-08T18:15:28.109Z"}, + {url = "https://files.pythonhosted.org/packages/9f/c9/d57877759d707e84c082163c543853245f91b70c804115a5010532890f18/llvmlite-0.46.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2e8cbfff7f6db0fa2c771ad24154e2a7e457c2444d7673e6de06b8b698c3b269", size = 55128628, upload-time = "2025-12-08T18:15:31.098Z"}, + {url = "https://files.pythonhosted.org/packages/30/a8/e61a8c2b3cc7a597073d9cde1fcbb567e9d827f1db30c93cf80422eac70d/llvmlite-0.46.0-cp314-cp314-win_amd64.whl", hash = "sha256:7821eda3ec1f18050f981819756631d60b6d7ab1a6cf806d9efefbe3f4082d61", size = 39153056, upload-time = "2025-12-08T18:15:33.938Z"} ] [[package]] name = "loro" +sdist = {url = "https://files.pythonhosted.org/packages/7d/27/ea6f3298fc87ea5f2d60ebfbca088e7d9b2ceb3993f67c83bfb81778ec01/loro-1.10.3.tar.gz", hash = "sha256:68184ab1c2ab94af6ad4aaba416d22f579cabee0b26cbb09a1f67858207bbce8", size = 68833, upload-time = "2025-12-09T10:14:06.644Z"} +source = {registry = "https://pypi.org/simple"} version = "1.10.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7d/27/ea6f3298fc87ea5f2d60ebfbca088e7d9b2ceb3993f67c83bfb81778ec01/loro-1.10.3.tar.gz", hash = "sha256:68184ab1c2ab94af6ad4aaba416d22f579cabee0b26cbb09a1f67858207bbce8", size = 68833, upload-time = "2025-12-09T10:14:06.644Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/8d/4a/a5340b6fdf4cd34d758bed23bd1f64063b3b1b41ff4ecc94ee39259ee9a7/loro-1.10.3-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:623cf7df17626aa55bc6ca54e89177dbe71a5f1c293e102d6153f43991a1a041", size = 3213589, upload-time = "2025-12-09T10:11:35.377Z" }, - { url = "https://files.pythonhosted.org/packages/00/93/5164e93a77e365a92def77c1258386daef233516a29fb674a3b9d973b8b8/loro-1.10.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:d8e715d475f32a1462969aca27eeb3f998f309182978f55bc37ce5c515d92e90", size = 3029557, upload-time = "2025-12-09T10:11:20.076Z" }, - { url = "https://files.pythonhosted.org/packages/6c/30/94592d7c01f480ce99e1783b0d9203eb20ba2eab42575dabd384e3c9d1fa/loro-1.10.3-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:61e012a80e8c9fe248b9d0a76e91664c9479a72d976eaeed78f87b15b5d1d732", size = 3282335, upload-time = "2025-12-09T10:08:18.168Z" }, - { url = "https://files.pythonhosted.org/packages/e9/a8/7ae3c0b955aa638fa7dbd2d194c7759749a0d0d96a94805d5dec9b30eaea/loro-1.10.3-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:686ece56756acbaf80c986848915e9126a29a06d7a62209747e3ef1efc0bd8f6", size = 3333071, upload-time = "2025-12-09T10:08:55.314Z" }, - { url = "https://files.pythonhosted.org/packages/f7/10/151edebdb2bca626ad50911b761164ced16984b25b0b37b34b674ded8b29/loro-1.10.3-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3aa821c8871deca98f4605eb0c40fb26bcf82bd29c9e7fa33b183516c5395b11", size = 3698226, upload-time = "2025-12-09T10:09:30.474Z" }, - { url = "https://files.pythonhosted.org/packages/f4/ac/02a490e38466506b1003df4910d2a8ae582265023dae9e2217c98b56ea3f/loro-1.10.3-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:507d34137adb4148f79e1da7f89a21a4aab18565621a5dc2b389773fe98ac25b", size = 3407322, upload-time = "2025-12-09T10:10:04.199Z" }, - { url = "https://files.pythonhosted.org/packages/81/db/da51f2bcad81ca3733bc21e83f3b6752446436b565b90f5c350ad227ad01/loro-1.10.3-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:91d3b2e187ccfe2b14118a6e5617266fedcdf3435f6fa0a3db7b4afce8afa687", size = 3330268, upload-time = "2025-12-09T10:10:58.61Z" }, - { url = "https://files.pythonhosted.org/packages/4e/af/50d136c83d504a3a1f4ad33a6bf38b6933985a82741302255cf446a5f7ad/loro-1.10.3-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c0016f834fd1626710081334400aed8494380b55ef131f7133d21c3bd22d892a", size = 3673582, upload-time = "2025-12-09T10:10:35.849Z" }, - { url = "https://files.pythonhosted.org/packages/63/4d/53288aae777218e05c43af9c080652bcdbbc8d97c031607eedd3fc15617d/loro-1.10.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:71c4275dca5a8a86219d60545d4f60e081b4af44b490ac912c0481906934bfc6", size = 3463731, upload-time = "2025-12-09T10:11:54.102Z" }, - { url = "https://files.pythonhosted.org/packages/75/01/2389f26ffe8bc3ffe48a0a578f610dd49c709bbcf0d5d2642c6e2b52f490/loro-1.10.3-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:490f12571b2ed1a8eaf1edd3a7fffc55adac5010b1875fe1bb9e9af9a3907c38", size = 3602334, upload-time = "2025-12-09T10:12:30.082Z" }, - { url = "https://files.pythonhosted.org/packages/a7/16/07b64af13f5fcea025e003ca27bbd6f748217abbd4803dad88ea0900526c/loro-1.10.3-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:a374a43cadaa48528a5411496481df9ae52bf01e513f4509e37d6c986f199c0e", size = 3657896, upload-time = "2025-12-09T10:13:04.86Z" }, - { url = "https://files.pythonhosted.org/packages/c9/2f/4050770d7675ceced71651fe76971d5c27456b7098c0de03a4ecdbb0a02d/loro-1.10.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:1a93b2ee59f1fa8d98dd552211fd5693551893b34c1dd2ba0324806d6d14022f", size = 3544339, upload-time = "2025-12-09T10:13:40.396Z" }, - { url = "https://files.pythonhosted.org/packages/c9/21/67e27cb404c968fc19a841d5c6277f13a17c69a56f49e3c15ea1c92a28eb/loro-1.10.3-cp314-cp314-win32.whl", hash = "sha256:baa863e3d869422e3320e822c0b1f87f5dc44cda903d1bd3b7a16f8413ce3d92", size = 2706731, upload-time = "2025-12-09T10:14:31.604Z" }, - { url = "https://files.pythonhosted.org/packages/08/54/6770cf36aeb994489375e9ab9c01201e70ab7cc286fa97e907aa41b1bae6/loro-1.10.3-cp314-cp314-win_amd64.whl", hash = "sha256:f10ed3ca89485f942b8b2de796ed9783edb990e7e570605232de77489e9f3548", size = 2933563, upload-time = "2025-12-09T10:14:13.805Z" }, - { url = "https://files.pythonhosted.org/packages/24/f5/eb089fd25eb428709dbe79fd4d36b82a00572aa54badd1dff62511a38fe3/loro-1.10.3-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2b4d049efb1953aebfc16fa0b445ff5a37d4d08a1ab93f3b5a577a454b7a5ded", size = 3282369, upload-time = "2025-12-09T10:08:20.011Z" }, - { url = "https://files.pythonhosted.org/packages/30/d7/692cb87c908f6a8af6cbfc10ebab69e16780e3796e11454c2b481b5c3817/loro-1.10.3-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:56ecad7fbac58aa8bee52bb261a764aeef6c7b39c20f0d69e8fad908ab2ca7d8", size = 3332530, upload-time = "2025-12-09T10:08:57.07Z" }, - { url = "https://files.pythonhosted.org/packages/54/46/ed3afbf749288b6f70f3b859a6762538818bf6a557ca873b07d6b036946b/loro-1.10.3-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5d8d1be349d08b3a95592c6a17b80b1ea6aef892b1b8e2b93b540062d04e34e0", size = 3702599, upload-time = "2025-12-09T10:09:31.779Z" }, - { url = "https://files.pythonhosted.org/packages/fe/30/6cb616939c12bfe96a71a01a6e3551febf1c34bf9de114fafadbcfb65064/loro-1.10.3-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1ec0a0b9bc4e32c46f14710062ec5b536c72110318aaf85632a4f8b37e9a470a", size = 3404412, upload-time = "2025-12-09T10:10:05.448Z" }, - { url = "https://files.pythonhosted.org/packages/02/a2/3d4006d3333589f9158ac6d403979bf5c985be8b461b18e7a2ea23b05414/loro-1.10.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c5d4437987f7a4a4ff5927f39d0f43ded5b34295dfb0a3c8e150687e25c3d6b8", size = 3462948, upload-time = "2025-12-09T10:11:55.405Z" }, - { url = "https://files.pythonhosted.org/packages/41/30/c640ccd3e570b08770a9f459decc2d8e7ceefdc34ac28a745418fb9cb5ba/loro-1.10.3-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:86d4f0c631ca274ad2fa2c0bdb8e1e141882d94339b7284a8bef5bf73fa6957d", size = 3599851, upload-time = "2025-12-09T10:12:31.759Z" }, - { url = "https://files.pythonhosted.org/packages/59/8f/062ea50554c47ae30e98b1f0442a458c0edecc6d4edc7fcfc4d901734dd0/loro-1.10.3-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:15e03084ff1b472e14623183ed6e1e43e0f717c2112697beda5e69b5bd0ff236", size = 3655558, upload-time = "2025-12-09T10:13:06.529Z" }, - { url = "https://files.pythonhosted.org/packages/f3/f5/c7dd8cdbd57454b23d89799c22cd42b6d2dda283cd87d7b198dc424a462c/loro-1.10.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:42d6a5ce5bc518eaa682413e82d597299650eeb03e8bc39341752d6e0d22503e", size = 3541282, upload-time = "2025-12-09T10:13:42.189Z" }, +wheels = [ + {url = "https://files.pythonhosted.org/packages/8d/4a/a5340b6fdf4cd34d758bed23bd1f64063b3b1b41ff4ecc94ee39259ee9a7/loro-1.10.3-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:623cf7df17626aa55bc6ca54e89177dbe71a5f1c293e102d6153f43991a1a041", size = 3213589, upload-time = "2025-12-09T10:11:35.377Z"}, + {url = "https://files.pythonhosted.org/packages/00/93/5164e93a77e365a92def77c1258386daef233516a29fb674a3b9d973b8b8/loro-1.10.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:d8e715d475f32a1462969aca27eeb3f998f309182978f55bc37ce5c515d92e90", size = 3029557, upload-time = "2025-12-09T10:11:20.076Z"}, + {url = "https://files.pythonhosted.org/packages/6c/30/94592d7c01f480ce99e1783b0d9203eb20ba2eab42575dabd384e3c9d1fa/loro-1.10.3-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:61e012a80e8c9fe248b9d0a76e91664c9479a72d976eaeed78f87b15b5d1d732", size = 3282335, upload-time = "2025-12-09T10:08:18.168Z"}, + {url = "https://files.pythonhosted.org/packages/e9/a8/7ae3c0b955aa638fa7dbd2d194c7759749a0d0d96a94805d5dec9b30eaea/loro-1.10.3-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:686ece56756acbaf80c986848915e9126a29a06d7a62209747e3ef1efc0bd8f6", size = 3333071, upload-time = "2025-12-09T10:08:55.314Z"}, + {url = "https://files.pythonhosted.org/packages/f7/10/151edebdb2bca626ad50911b761164ced16984b25b0b37b34b674ded8b29/loro-1.10.3-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3aa821c8871deca98f4605eb0c40fb26bcf82bd29c9e7fa33b183516c5395b11", size = 3698226, upload-time = "2025-12-09T10:09:30.474Z"}, + {url = "https://files.pythonhosted.org/packages/f4/ac/02a490e38466506b1003df4910d2a8ae582265023dae9e2217c98b56ea3f/loro-1.10.3-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:507d34137adb4148f79e1da7f89a21a4aab18565621a5dc2b389773fe98ac25b", size = 3407322, upload-time = "2025-12-09T10:10:04.199Z"}, + {url = "https://files.pythonhosted.org/packages/81/db/da51f2bcad81ca3733bc21e83f3b6752446436b565b90f5c350ad227ad01/loro-1.10.3-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:91d3b2e187ccfe2b14118a6e5617266fedcdf3435f6fa0a3db7b4afce8afa687", size = 3330268, upload-time = "2025-12-09T10:10:58.61Z"}, + {url = "https://files.pythonhosted.org/packages/4e/af/50d136c83d504a3a1f4ad33a6bf38b6933985a82741302255cf446a5f7ad/loro-1.10.3-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c0016f834fd1626710081334400aed8494380b55ef131f7133d21c3bd22d892a", size = 3673582, upload-time = "2025-12-09T10:10:35.849Z"}, + {url = "https://files.pythonhosted.org/packages/63/4d/53288aae777218e05c43af9c080652bcdbbc8d97c031607eedd3fc15617d/loro-1.10.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:71c4275dca5a8a86219d60545d4f60e081b4af44b490ac912c0481906934bfc6", size = 3463731, upload-time = "2025-12-09T10:11:54.102Z"}, + {url = "https://files.pythonhosted.org/packages/75/01/2389f26ffe8bc3ffe48a0a578f610dd49c709bbcf0d5d2642c6e2b52f490/loro-1.10.3-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:490f12571b2ed1a8eaf1edd3a7fffc55adac5010b1875fe1bb9e9af9a3907c38", size = 3602334, upload-time = "2025-12-09T10:12:30.082Z"}, + {url = "https://files.pythonhosted.org/packages/a7/16/07b64af13f5fcea025e003ca27bbd6f748217abbd4803dad88ea0900526c/loro-1.10.3-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:a374a43cadaa48528a5411496481df9ae52bf01e513f4509e37d6c986f199c0e", size = 3657896, upload-time = "2025-12-09T10:13:04.86Z"}, + {url = "https://files.pythonhosted.org/packages/c9/2f/4050770d7675ceced71651fe76971d5c27456b7098c0de03a4ecdbb0a02d/loro-1.10.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:1a93b2ee59f1fa8d98dd552211fd5693551893b34c1dd2ba0324806d6d14022f", size = 3544339, upload-time = "2025-12-09T10:13:40.396Z"}, + {url = "https://files.pythonhosted.org/packages/c9/21/67e27cb404c968fc19a841d5c6277f13a17c69a56f49e3c15ea1c92a28eb/loro-1.10.3-cp314-cp314-win32.whl", hash = "sha256:baa863e3d869422e3320e822c0b1f87f5dc44cda903d1bd3b7a16f8413ce3d92", size = 2706731, upload-time = "2025-12-09T10:14:31.604Z"}, + {url = "https://files.pythonhosted.org/packages/08/54/6770cf36aeb994489375e9ab9c01201e70ab7cc286fa97e907aa41b1bae6/loro-1.10.3-cp314-cp314-win_amd64.whl", hash = "sha256:f10ed3ca89485f942b8b2de796ed9783edb990e7e570605232de77489e9f3548", size = 2933563, upload-time = "2025-12-09T10:14:13.805Z"}, + {url = "https://files.pythonhosted.org/packages/24/f5/eb089fd25eb428709dbe79fd4d36b82a00572aa54badd1dff62511a38fe3/loro-1.10.3-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2b4d049efb1953aebfc16fa0b445ff5a37d4d08a1ab93f3b5a577a454b7a5ded", size = 3282369, upload-time = "2025-12-09T10:08:20.011Z"}, + {url = "https://files.pythonhosted.org/packages/30/d7/692cb87c908f6a8af6cbfc10ebab69e16780e3796e11454c2b481b5c3817/loro-1.10.3-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:56ecad7fbac58aa8bee52bb261a764aeef6c7b39c20f0d69e8fad908ab2ca7d8", size = 3332530, upload-time = "2025-12-09T10:08:57.07Z"}, + {url = "https://files.pythonhosted.org/packages/54/46/ed3afbf749288b6f70f3b859a6762538818bf6a557ca873b07d6b036946b/loro-1.10.3-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5d8d1be349d08b3a95592c6a17b80b1ea6aef892b1b8e2b93b540062d04e34e0", size = 3702599, upload-time = "2025-12-09T10:09:31.779Z"}, + {url = "https://files.pythonhosted.org/packages/fe/30/6cb616939c12bfe96a71a01a6e3551febf1c34bf9de114fafadbcfb65064/loro-1.10.3-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1ec0a0b9bc4e32c46f14710062ec5b536c72110318aaf85632a4f8b37e9a470a", size = 3404412, upload-time = "2025-12-09T10:10:05.448Z"}, + {url = "https://files.pythonhosted.org/packages/02/a2/3d4006d3333589f9158ac6d403979bf5c985be8b461b18e7a2ea23b05414/loro-1.10.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c5d4437987f7a4a4ff5927f39d0f43ded5b34295dfb0a3c8e150687e25c3d6b8", size = 3462948, upload-time = "2025-12-09T10:11:55.405Z"}, + {url = "https://files.pythonhosted.org/packages/41/30/c640ccd3e570b08770a9f459decc2d8e7ceefdc34ac28a745418fb9cb5ba/loro-1.10.3-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:86d4f0c631ca274ad2fa2c0bdb8e1e141882d94339b7284a8bef5bf73fa6957d", size = 3599851, upload-time = "2025-12-09T10:12:31.759Z"}, + {url = "https://files.pythonhosted.org/packages/59/8f/062ea50554c47ae30e98b1f0442a458c0edecc6d4edc7fcfc4d901734dd0/loro-1.10.3-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:15e03084ff1b472e14623183ed6e1e43e0f717c2112697beda5e69b5bd0ff236", size = 3655558, upload-time = "2025-12-09T10:13:06.529Z"}, + {url = "https://files.pythonhosted.org/packages/f3/f5/c7dd8cdbd57454b23d89799c22cd42b6d2dda283cd87d7b198dc424a462c/loro-1.10.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:42d6a5ce5bc518eaa682413e82d597299650eeb03e8bc39341752d6e0d22503e", size = 3541282, upload-time = "2025-12-09T10:13:42.189Z"} ] [[package]] +dependencies = [ + {name = "click"}, + {name = "docutils"}, + {name = "itsdangerous"}, + {name = "jedi"}, + {name = "loro"}, + {name = "markdown"}, + {name = "msgspec"}, + {name = "narwhals"}, + {name = "packaging"}, + {name = "psutil"}, + {name = "pygments"}, + {name = "pymdown-extensions"}, + {name = "pyyaml"}, + {name = "starlette"}, + {name = "tomlkit"}, + {name = "uvicorn"}, + {name = "websockets"} +] name = "marimo" +sdist = {url = "https://files.pythonhosted.org/packages/9c/eb/3c19ce54be3440cece96bffbb2027c57d618ab336d53ef6a962f570ce468/marimo-0.20.4.tar.gz", hash = "sha256:7f46ce837953717504673e33e47fb42a619bf5f9d2000d1a3a3b1663a47c5498", size = 38336283, upload-time = "2026-03-04T01:15:13.641Z"} +source = {registry = "https://pypi.org/simple"} version = "0.20.4" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "click" }, - { name = "docutils" }, - { name = "itsdangerous" }, - { name = "jedi" }, - { name = "loro" }, - { name = "markdown" }, - { name = "msgspec" }, - { name = "narwhals" }, - { name = "packaging" }, - { name = "psutil" }, - { name = "pygments" }, - { name = "pymdown-extensions" }, - { name = "pyyaml" }, - { name = "starlette" }, - { name = "tomlkit" }, - { name = "uvicorn" }, - { name = "websockets" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/9c/eb/3c19ce54be3440cece96bffbb2027c57d618ab336d53ef6a962f570ce468/marimo-0.20.4.tar.gz", hash = "sha256:7f46ce837953717504673e33e47fb42a619bf5f9d2000d1a3a3b1663a47c5498", size = 38336283, upload-time = "2026-03-04T01:15:13.641Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/56/62/bf2c92cc7aab69edfb3907e9bf102c37c1e16b8f4bc6ddd4258ae4c8063c/marimo-0.20.4-py3-none-any.whl", hash = "sha256:3200edd7209f470821b58deb4a45e0577889fab1052748d77088f18bef7d9dd8", size = 38752562, upload-time = "2026-03-04T01:15:09.608Z" }, +wheels = [ + {url = "https://files.pythonhosted.org/packages/56/62/bf2c92cc7aab69edfb3907e9bf102c37c1e16b8f4bc6ddd4258ae4c8063c/marimo-0.20.4-py3-none-any.whl", hash = "sha256:3200edd7209f470821b58deb4a45e0577889fab1052748d77088f18bef7d9dd8", size = 38752562, upload-time = "2026-03-04T01:15:09.608Z"} ] [[package]] name = "markdown" +sdist = {url = "https://files.pythonhosted.org/packages/2b/f4/69fa6ed85ae003c2378ffa8f6d2e3234662abd02c10d216c0ba96081a238/markdown-3.10.2.tar.gz", hash = "sha256:994d51325d25ad8aa7ce4ebaec003febcce822c3f8c911e3b17c52f7f589f950", size = 368805, upload-time = "2026-02-09T14:57:26.942Z"} +source = {registry = "https://pypi.org/simple"} version = "3.10.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/2b/f4/69fa6ed85ae003c2378ffa8f6d2e3234662abd02c10d216c0ba96081a238/markdown-3.10.2.tar.gz", hash = "sha256:994d51325d25ad8aa7ce4ebaec003febcce822c3f8c911e3b17c52f7f589f950", size = 368805, upload-time = "2026-02-09T14:57:26.942Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.2-py3-none-any.whl", hash = "sha256:e91464b71ae3ee7afd3017d9f358ef0baf158fd9a298db92f1d4761133824c36", size = 108180, upload-time = "2026-02-09T14:57:25.787Z" }, + {url = "https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.2-py3-none-any.whl", hash = "sha256:e91464b71ae3ee7afd3017d9f358ef0baf158fd9a298db92f1d4761133824c36", size = 108180, upload-time = "2026-02-09T14:57:25.787Z"} ] [[package]] -name = "markdown-it-py" -version = "4.0.0" -source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "mdurl" }, + {name = "mdurl"} ] -sdist = { url = "https://files.pythonhosted.org/packages/5b/f5/4ec618ed16cc4f8fb3b701563655a69816155e79e24a17b651541804721d/markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3", size = 73070, upload-time = "2025-08-11T12:57:52.854Z" } +name = "markdown-it-py" +sdist = {url = "https://files.pythonhosted.org/packages/5b/f5/4ec618ed16cc4f8fb3b701563655a69816155e79e24a17b651541804721d/markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3", size = 73070, upload-time = "2025-08-11T12:57:52.854Z"} +source = {registry = "https://pypi.org/simple"} +version = "4.0.0" wheels = [ - { url = "https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147", size = 87321, upload-time = "2025-08-11T12:57:51.923Z" }, + {url = "https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147", size = 87321, upload-time = "2025-08-11T12:57:51.923Z"} ] [[package]] name = "markupsafe" +sdist = {url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z"} +source = {registry = "https://pypi.org/simple"} version = "3.0.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/33/8a/8e42d4838cd89b7dde187011e97fe6c3af66d8c044997d2183fbd6d31352/markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe", size = 11619, upload-time = "2025-09-27T18:37:06.342Z" }, - { url = "https://files.pythonhosted.org/packages/b5/64/7660f8a4a8e53c924d0fa05dc3a55c9cee10bbd82b11c5afb27d44b096ce/markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026", size = 12029, upload-time = "2025-09-27T18:37:07.213Z" }, - { url = "https://files.pythonhosted.org/packages/da/ef/e648bfd021127bef5fa12e1720ffed0c6cbb8310c8d9bea7266337ff06de/markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737", size = 24408, upload-time = "2025-09-27T18:37:09.572Z" }, - { url = "https://files.pythonhosted.org/packages/41/3c/a36c2450754618e62008bf7435ccb0f88053e07592e6028a34776213d877/markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97", size = 23005, upload-time = "2025-09-27T18:37:10.58Z" }, - { url = "https://files.pythonhosted.org/packages/bc/20/b7fdf89a8456b099837cd1dc21974632a02a999ec9bf7ca3e490aacd98e7/markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d", size = 22048, upload-time = "2025-09-27T18:37:11.547Z" }, - { url = "https://files.pythonhosted.org/packages/9a/a7/591f592afdc734f47db08a75793a55d7fbcc6902a723ae4cfbab61010cc5/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda", size = 23821, upload-time = "2025-09-27T18:37:12.48Z" }, - { url = "https://files.pythonhosted.org/packages/7d/33/45b24e4f44195b26521bc6f1a82197118f74df348556594bd2262bda1038/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf", size = 21606, upload-time = "2025-09-27T18:37:13.485Z" }, - { url = "https://files.pythonhosted.org/packages/ff/0e/53dfaca23a69fbfbbf17a4b64072090e70717344c52eaaaa9c5ddff1e5f0/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe", size = 23043, upload-time = "2025-09-27T18:37:14.408Z" }, - { url = "https://files.pythonhosted.org/packages/46/11/f333a06fc16236d5238bfe74daccbca41459dcd8d1fa952e8fbd5dccfb70/markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9", size = 14747, upload-time = "2025-09-27T18:37:15.36Z" }, - { url = "https://files.pythonhosted.org/packages/28/52/182836104b33b444e400b14f797212f720cbc9ed6ba34c800639d154e821/markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581", size = 15341, upload-time = "2025-09-27T18:37:16.496Z" }, - { url = "https://files.pythonhosted.org/packages/6f/18/acf23e91bd94fd7b3031558b1f013adfa21a8e407a3fdb32745538730382/markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4", size = 14073, upload-time = "2025-09-27T18:37:17.476Z" }, - { url = "https://files.pythonhosted.org/packages/3c/f0/57689aa4076e1b43b15fdfa646b04653969d50cf30c32a102762be2485da/markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab", size = 11661, upload-time = "2025-09-27T18:37:18.453Z" }, - { url = "https://files.pythonhosted.org/packages/89/c3/2e67a7ca217c6912985ec766c6393b636fb0c2344443ff9d91404dc4c79f/markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175", size = 12069, upload-time = "2025-09-27T18:37:19.332Z" }, - { url = "https://files.pythonhosted.org/packages/f0/00/be561dce4e6ca66b15276e184ce4b8aec61fe83662cce2f7d72bd3249d28/markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634", size = 25670, upload-time = "2025-09-27T18:37:20.245Z" }, - { url = "https://files.pythonhosted.org/packages/50/09/c419f6f5a92e5fadde27efd190eca90f05e1261b10dbd8cbcb39cd8ea1dc/markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50", size = 23598, upload-time = "2025-09-27T18:37:21.177Z" }, - { url = "https://files.pythonhosted.org/packages/22/44/a0681611106e0b2921b3033fc19bc53323e0b50bc70cffdd19f7d679bb66/markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e", size = 23261, upload-time = "2025-09-27T18:37:22.167Z" }, - { url = "https://files.pythonhosted.org/packages/5f/57/1b0b3f100259dc9fffe780cfb60d4be71375510e435efec3d116b6436d43/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5", size = 24835, upload-time = "2025-09-27T18:37:23.296Z" }, - { url = "https://files.pythonhosted.org/packages/26/6a/4bf6d0c97c4920f1597cc14dd720705eca0bf7c787aebc6bb4d1bead5388/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523", size = 22733, upload-time = "2025-09-27T18:37:24.237Z" }, - { url = "https://files.pythonhosted.org/packages/14/c7/ca723101509b518797fedc2fdf79ba57f886b4aca8a7d31857ba3ee8281f/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc", size = 23672, upload-time = "2025-09-27T18:37:25.271Z" }, - { url = "https://files.pythonhosted.org/packages/fb/df/5bd7a48c256faecd1d36edc13133e51397e41b73bb77e1a69deab746ebac/markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d", size = 14819, upload-time = "2025-09-27T18:37:26.285Z" }, - { url = "https://files.pythonhosted.org/packages/1a/8a/0402ba61a2f16038b48b39bccca271134be00c5c9f0f623208399333c448/markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9", size = 15426, upload-time = "2025-09-27T18:37:27.316Z" }, - { url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z" }, +wheels = [ + {url = "https://files.pythonhosted.org/packages/33/8a/8e42d4838cd89b7dde187011e97fe6c3af66d8c044997d2183fbd6d31352/markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe", size = 11619, upload-time = "2025-09-27T18:37:06.342Z"}, + {url = "https://files.pythonhosted.org/packages/b5/64/7660f8a4a8e53c924d0fa05dc3a55c9cee10bbd82b11c5afb27d44b096ce/markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026", size = 12029, upload-time = "2025-09-27T18:37:07.213Z"}, + {url = "https://files.pythonhosted.org/packages/da/ef/e648bfd021127bef5fa12e1720ffed0c6cbb8310c8d9bea7266337ff06de/markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737", size = 24408, upload-time = "2025-09-27T18:37:09.572Z"}, + {url = "https://files.pythonhosted.org/packages/41/3c/a36c2450754618e62008bf7435ccb0f88053e07592e6028a34776213d877/markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97", size = 23005, upload-time = "2025-09-27T18:37:10.58Z"}, + {url = "https://files.pythonhosted.org/packages/bc/20/b7fdf89a8456b099837cd1dc21974632a02a999ec9bf7ca3e490aacd98e7/markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d", size = 22048, upload-time = "2025-09-27T18:37:11.547Z"}, + {url = "https://files.pythonhosted.org/packages/9a/a7/591f592afdc734f47db08a75793a55d7fbcc6902a723ae4cfbab61010cc5/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda", size = 23821, upload-time = "2025-09-27T18:37:12.48Z"}, + {url = "https://files.pythonhosted.org/packages/7d/33/45b24e4f44195b26521bc6f1a82197118f74df348556594bd2262bda1038/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf", size = 21606, upload-time = "2025-09-27T18:37:13.485Z"}, + {url = "https://files.pythonhosted.org/packages/ff/0e/53dfaca23a69fbfbbf17a4b64072090e70717344c52eaaaa9c5ddff1e5f0/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe", size = 23043, upload-time = "2025-09-27T18:37:14.408Z"}, + {url = "https://files.pythonhosted.org/packages/46/11/f333a06fc16236d5238bfe74daccbca41459dcd8d1fa952e8fbd5dccfb70/markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9", size = 14747, upload-time = "2025-09-27T18:37:15.36Z"}, + {url = "https://files.pythonhosted.org/packages/28/52/182836104b33b444e400b14f797212f720cbc9ed6ba34c800639d154e821/markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581", size = 15341, upload-time = "2025-09-27T18:37:16.496Z"}, + {url = "https://files.pythonhosted.org/packages/6f/18/acf23e91bd94fd7b3031558b1f013adfa21a8e407a3fdb32745538730382/markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4", size = 14073, upload-time = "2025-09-27T18:37:17.476Z"}, + {url = "https://files.pythonhosted.org/packages/3c/f0/57689aa4076e1b43b15fdfa646b04653969d50cf30c32a102762be2485da/markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab", size = 11661, upload-time = "2025-09-27T18:37:18.453Z"}, + {url = "https://files.pythonhosted.org/packages/89/c3/2e67a7ca217c6912985ec766c6393b636fb0c2344443ff9d91404dc4c79f/markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175", size = 12069, upload-time = "2025-09-27T18:37:19.332Z"}, + {url = "https://files.pythonhosted.org/packages/f0/00/be561dce4e6ca66b15276e184ce4b8aec61fe83662cce2f7d72bd3249d28/markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634", size = 25670, upload-time = "2025-09-27T18:37:20.245Z"}, + {url = "https://files.pythonhosted.org/packages/50/09/c419f6f5a92e5fadde27efd190eca90f05e1261b10dbd8cbcb39cd8ea1dc/markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50", size = 23598, upload-time = "2025-09-27T18:37:21.177Z"}, + {url = "https://files.pythonhosted.org/packages/22/44/a0681611106e0b2921b3033fc19bc53323e0b50bc70cffdd19f7d679bb66/markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e", size = 23261, upload-time = "2025-09-27T18:37:22.167Z"}, + {url = "https://files.pythonhosted.org/packages/5f/57/1b0b3f100259dc9fffe780cfb60d4be71375510e435efec3d116b6436d43/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5", size = 24835, upload-time = "2025-09-27T18:37:23.296Z"}, + {url = "https://files.pythonhosted.org/packages/26/6a/4bf6d0c97c4920f1597cc14dd720705eca0bf7c787aebc6bb4d1bead5388/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523", size = 22733, upload-time = "2025-09-27T18:37:24.237Z"}, + {url = "https://files.pythonhosted.org/packages/14/c7/ca723101509b518797fedc2fdf79ba57f886b4aca8a7d31857ba3ee8281f/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc", size = 23672, upload-time = "2025-09-27T18:37:25.271Z"}, + {url = "https://files.pythonhosted.org/packages/fb/df/5bd7a48c256faecd1d36edc13133e51397e41b73bb77e1a69deab746ebac/markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d", size = 14819, upload-time = "2025-09-27T18:37:26.285Z"}, + {url = "https://files.pythonhosted.org/packages/1a/8a/0402ba61a2f16038b48b39bccca271134be00c5c9f0f623208399333c448/markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9", size = 15426, upload-time = "2025-09-27T18:37:27.316Z"}, + {url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z"} ] [[package]] -name = "mdit-py-plugins" -version = "0.5.0" -source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "markdown-it-py" }, + {name = "markdown-it-py"} ] -sdist = { url = "https://files.pythonhosted.org/packages/b2/fd/a756d36c0bfba5f6e39a1cdbdbfdd448dc02692467d83816dff4592a1ebc/mdit_py_plugins-0.5.0.tar.gz", hash = "sha256:f4918cb50119f50446560513a8e311d574ff6aaed72606ddae6d35716fe809c6", size = 44655, upload-time = "2025-08-11T07:25:49.083Z" } +name = "mdit-py-plugins" +sdist = {url = "https://files.pythonhosted.org/packages/b2/fd/a756d36c0bfba5f6e39a1cdbdbfdd448dc02692467d83816dff4592a1ebc/mdit_py_plugins-0.5.0.tar.gz", hash = "sha256:f4918cb50119f50446560513a8e311d574ff6aaed72606ddae6d35716fe809c6", size = 44655, upload-time = "2025-08-11T07:25:49.083Z"} +source = {registry = "https://pypi.org/simple"} +version = "0.5.0" wheels = [ - { url = "https://files.pythonhosted.org/packages/fb/86/dd6e5db36df29e76c7a7699123569a4a18c1623ce68d826ed96c62643cae/mdit_py_plugins-0.5.0-py3-none-any.whl", hash = "sha256:07a08422fc1936a5d26d146759e9155ea466e842f5ab2f7d2266dd084c8dab1f", size = 57205, upload-time = "2025-08-11T07:25:47.597Z" }, + {url = "https://files.pythonhosted.org/packages/fb/86/dd6e5db36df29e76c7a7699123569a4a18c1623ce68d826ed96c62643cae/mdit_py_plugins-0.5.0-py3-none-any.whl", hash = "sha256:07a08422fc1936a5d26d146759e9155ea466e842f5ab2f7d2266dd084c8dab1f", size = 57205, upload-time = "2025-08-11T07:25:47.597Z"} ] [[package]] name = "mdurl" +sdist = {url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z"} +source = {registry = "https://pypi.org/simple"} version = "0.1.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, + {url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z"} ] [[package]] name = "msgspec" +sdist = {url = "https://files.pythonhosted.org/packages/ea/9c/bfbd12955a49180cbd234c5d29ec6f74fe641698f0cd9df154a854fc8a15/msgspec-0.20.0.tar.gz", hash = "sha256:692349e588fde322875f8d3025ac01689fead5901e7fb18d6870a44519d62a29", size = 317862, upload-time = "2025-11-24T03:56:28.934Z"} +source = {registry = "https://pypi.org/simple"} version = "0.20.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ea/9c/bfbd12955a49180cbd234c5d29ec6f74fe641698f0cd9df154a854fc8a15/msgspec-0.20.0.tar.gz", hash = "sha256:692349e588fde322875f8d3025ac01689fead5901e7fb18d6870a44519d62a29", size = 317862, upload-time = "2025-11-24T03:56:28.934Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/bb/18/62dc13ab0260c7d741dda8dc7f481495b93ac9168cd887dda5929880eef8/msgspec-0.20.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:eead16538db1b3f7ec6e3ed1f6f7c5dec67e90f76e76b610e1ffb5671815633a", size = 196407, upload-time = "2025-11-24T03:55:55.001Z" }, - { url = "https://files.pythonhosted.org/packages/dd/1d/b9949e4ad6953e9f9a142c7997b2f7390c81e03e93570c7c33caf65d27e1/msgspec-0.20.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:703c3bb47bf47801627fb1438f106adbfa2998fe586696d1324586a375fca238", size = 188889, upload-time = "2025-11-24T03:55:56.311Z" }, - { url = "https://files.pythonhosted.org/packages/1e/19/f8bb2dc0f1bfe46cc7d2b6b61c5e9b5a46c62298e8f4d03bbe499c926180/msgspec-0.20.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6cdb227dc585fb109305cee0fd304c2896f02af93ecf50a9c84ee54ee67dbb42", size = 219691, upload-time = "2025-11-24T03:55:57.908Z" }, - { url = "https://files.pythonhosted.org/packages/b8/8e/6b17e43f6eb9369d9858ee32c97959fcd515628a1df376af96c11606cf70/msgspec-0.20.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:27d35044dd8818ac1bd0fedb2feb4fbdff4e3508dd7c5d14316a12a2d96a0de0", size = 224918, upload-time = "2025-11-24T03:55:59.322Z" }, - { url = "https://files.pythonhosted.org/packages/1c/db/0e833a177db1a4484797adba7f429d4242585980b90882cc38709e1b62df/msgspec-0.20.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b4296393a29ee42dd25947981c65506fd4ad39beaf816f614146fa0c5a6c91ae", size = 223436, upload-time = "2025-11-24T03:56:00.716Z" }, - { url = "https://files.pythonhosted.org/packages/c3/30/d2ee787f4c918fd2b123441d49a7707ae9015e0e8e1ab51aa7967a97b90e/msgspec-0.20.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:205fbdadd0d8d861d71c8f3399fe1a82a2caf4467bc8ff9a626df34c12176980", size = 227190, upload-time = "2025-11-24T03:56:02.371Z" }, - { url = "https://files.pythonhosted.org/packages/ff/37/9c4b58ff11d890d788e700b827db2366f4d11b3313bf136780da7017278b/msgspec-0.20.0-cp314-cp314-win_amd64.whl", hash = "sha256:7dfebc94fe7d3feec6bc6c9df4f7e9eccc1160bb5b811fbf3e3a56899e398a6b", size = 193950, upload-time = "2025-11-24T03:56:03.668Z" }, - { url = "https://files.pythonhosted.org/packages/e9/4e/cab707bf2fa57408e2934e5197fc3560079db34a1e3cd2675ff2e47e07de/msgspec-0.20.0-cp314-cp314-win_arm64.whl", hash = "sha256:2ad6ae36e4a602b24b4bf4eaf8ab5a441fec03e1f1b5931beca8ebda68f53fc0", size = 179018, upload-time = "2025-11-24T03:56:05.038Z" }, - { url = "https://files.pythonhosted.org/packages/4c/06/3da3fc9aaa55618a8f43eb9052453cfe01f82930bca3af8cea63a89f3a11/msgspec-0.20.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:f84703e0e6ef025663dd1de828ca028774797b8155e070e795c548f76dde65d5", size = 200389, upload-time = "2025-11-24T03:56:06.375Z" }, - { url = "https://files.pythonhosted.org/packages/83/3b/cc4270a5ceab40dfe1d1745856951b0a24fd16ac8539a66ed3004a60c91e/msgspec-0.20.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7c83fc24dd09cf1275934ff300e3951b3adc5573f0657a643515cc16c7dee131", size = 193198, upload-time = "2025-11-24T03:56:07.742Z" }, - { url = "https://files.pythonhosted.org/packages/cd/ae/4c7905ac53830c8e3c06fdd60e3cdcfedc0bbc993872d1549b84ea21a1bd/msgspec-0.20.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f13ccb1c335a124e80c4562573b9b90f01ea9521a1a87f7576c2e281d547f56", size = 225973, upload-time = "2025-11-24T03:56:09.18Z" }, - { url = "https://files.pythonhosted.org/packages/d9/da/032abac1de4d0678d99eaeadb1323bd9d247f4711c012404ba77ed6f15ca/msgspec-0.20.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:17c2b5ca19f19306fc83c96d85e606d2cc107e0caeea85066b5389f664e04846", size = 229509, upload-time = "2025-11-24T03:56:10.898Z" }, - { url = "https://files.pythonhosted.org/packages/69/52/fdc7bdb7057a166f309e0b44929e584319e625aaba4771b60912a9321ccd/msgspec-0.20.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:d931709355edabf66c2dd1a756b2d658593e79882bc81aae5964969d5a291b63", size = 230434, upload-time = "2025-11-24T03:56:12.48Z" }, - { url = "https://files.pythonhosted.org/packages/cb/fe/1dfd5f512b26b53043884e4f34710c73e294e7cc54278c3fe28380e42c37/msgspec-0.20.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:565f915d2e540e8a0c93a01ff67f50aebe1f7e22798c6a25873f9fda8d1325f8", size = 231758, upload-time = "2025-11-24T03:56:13.765Z" }, - { url = "https://files.pythonhosted.org/packages/97/f6/9ba7121b8e0c4e0beee49575d1dbc804e2e72467692f0428cf39ceba1ea5/msgspec-0.20.0-cp314-cp314t-win_amd64.whl", hash = "sha256:726f3e6c3c323f283f6021ebb6c8ccf58d7cd7baa67b93d73bfbe9a15c34ab8d", size = 206540, upload-time = "2025-11-24T03:56:15.029Z" }, - { url = "https://files.pythonhosted.org/packages/c8/3e/c5187de84bb2c2ca334ab163fcacf19a23ebb1d876c837f81a1b324a15bf/msgspec-0.20.0-cp314-cp314t-win_arm64.whl", hash = "sha256:93f23528edc51d9f686808a361728e903d6f2be55c901d6f5c92e44c6d546bfc", size = 183011, upload-time = "2025-11-24T03:56:16.442Z" }, +wheels = [ + {url = "https://files.pythonhosted.org/packages/bb/18/62dc13ab0260c7d741dda8dc7f481495b93ac9168cd887dda5929880eef8/msgspec-0.20.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:eead16538db1b3f7ec6e3ed1f6f7c5dec67e90f76e76b610e1ffb5671815633a", size = 196407, upload-time = "2025-11-24T03:55:55.001Z"}, + {url = "https://files.pythonhosted.org/packages/dd/1d/b9949e4ad6953e9f9a142c7997b2f7390c81e03e93570c7c33caf65d27e1/msgspec-0.20.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:703c3bb47bf47801627fb1438f106adbfa2998fe586696d1324586a375fca238", size = 188889, upload-time = "2025-11-24T03:55:56.311Z"}, + {url = "https://files.pythonhosted.org/packages/1e/19/f8bb2dc0f1bfe46cc7d2b6b61c5e9b5a46c62298e8f4d03bbe499c926180/msgspec-0.20.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6cdb227dc585fb109305cee0fd304c2896f02af93ecf50a9c84ee54ee67dbb42", size = 219691, upload-time = "2025-11-24T03:55:57.908Z"}, + {url = "https://files.pythonhosted.org/packages/b8/8e/6b17e43f6eb9369d9858ee32c97959fcd515628a1df376af96c11606cf70/msgspec-0.20.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:27d35044dd8818ac1bd0fedb2feb4fbdff4e3508dd7c5d14316a12a2d96a0de0", size = 224918, upload-time = "2025-11-24T03:55:59.322Z"}, + {url = "https://files.pythonhosted.org/packages/1c/db/0e833a177db1a4484797adba7f429d4242585980b90882cc38709e1b62df/msgspec-0.20.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b4296393a29ee42dd25947981c65506fd4ad39beaf816f614146fa0c5a6c91ae", size = 223436, upload-time = "2025-11-24T03:56:00.716Z"}, + {url = "https://files.pythonhosted.org/packages/c3/30/d2ee787f4c918fd2b123441d49a7707ae9015e0e8e1ab51aa7967a97b90e/msgspec-0.20.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:205fbdadd0d8d861d71c8f3399fe1a82a2caf4467bc8ff9a626df34c12176980", size = 227190, upload-time = "2025-11-24T03:56:02.371Z"}, + {url = "https://files.pythonhosted.org/packages/ff/37/9c4b58ff11d890d788e700b827db2366f4d11b3313bf136780da7017278b/msgspec-0.20.0-cp314-cp314-win_amd64.whl", hash = "sha256:7dfebc94fe7d3feec6bc6c9df4f7e9eccc1160bb5b811fbf3e3a56899e398a6b", size = 193950, upload-time = "2025-11-24T03:56:03.668Z"}, + {url = "https://files.pythonhosted.org/packages/e9/4e/cab707bf2fa57408e2934e5197fc3560079db34a1e3cd2675ff2e47e07de/msgspec-0.20.0-cp314-cp314-win_arm64.whl", hash = "sha256:2ad6ae36e4a602b24b4bf4eaf8ab5a441fec03e1f1b5931beca8ebda68f53fc0", size = 179018, upload-time = "2025-11-24T03:56:05.038Z"}, + {url = "https://files.pythonhosted.org/packages/4c/06/3da3fc9aaa55618a8f43eb9052453cfe01f82930bca3af8cea63a89f3a11/msgspec-0.20.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:f84703e0e6ef025663dd1de828ca028774797b8155e070e795c548f76dde65d5", size = 200389, upload-time = "2025-11-24T03:56:06.375Z"}, + {url = "https://files.pythonhosted.org/packages/83/3b/cc4270a5ceab40dfe1d1745856951b0a24fd16ac8539a66ed3004a60c91e/msgspec-0.20.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7c83fc24dd09cf1275934ff300e3951b3adc5573f0657a643515cc16c7dee131", size = 193198, upload-time = "2025-11-24T03:56:07.742Z"}, + {url = "https://files.pythonhosted.org/packages/cd/ae/4c7905ac53830c8e3c06fdd60e3cdcfedc0bbc993872d1549b84ea21a1bd/msgspec-0.20.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f13ccb1c335a124e80c4562573b9b90f01ea9521a1a87f7576c2e281d547f56", size = 225973, upload-time = "2025-11-24T03:56:09.18Z"}, + {url = "https://files.pythonhosted.org/packages/d9/da/032abac1de4d0678d99eaeadb1323bd9d247f4711c012404ba77ed6f15ca/msgspec-0.20.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:17c2b5ca19f19306fc83c96d85e606d2cc107e0caeea85066b5389f664e04846", size = 229509, upload-time = "2025-11-24T03:56:10.898Z"}, + {url = "https://files.pythonhosted.org/packages/69/52/fdc7bdb7057a166f309e0b44929e584319e625aaba4771b60912a9321ccd/msgspec-0.20.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:d931709355edabf66c2dd1a756b2d658593e79882bc81aae5964969d5a291b63", size = 230434, upload-time = "2025-11-24T03:56:12.48Z"}, + {url = "https://files.pythonhosted.org/packages/cb/fe/1dfd5f512b26b53043884e4f34710c73e294e7cc54278c3fe28380e42c37/msgspec-0.20.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:565f915d2e540e8a0c93a01ff67f50aebe1f7e22798c6a25873f9fda8d1325f8", size = 231758, upload-time = "2025-11-24T03:56:13.765Z"}, + {url = "https://files.pythonhosted.org/packages/97/f6/9ba7121b8e0c4e0beee49575d1dbc804e2e72467692f0428cf39ceba1ea5/msgspec-0.20.0-cp314-cp314t-win_amd64.whl", hash = "sha256:726f3e6c3c323f283f6021ebb6c8ccf58d7cd7baa67b93d73bfbe9a15c34ab8d", size = 206540, upload-time = "2025-11-24T03:56:15.029Z"}, + {url = "https://files.pythonhosted.org/packages/c8/3e/c5187de84bb2c2ca334ab163fcacf19a23ebb1d876c837f81a1b324a15bf/msgspec-0.20.0-cp314-cp314t-win_arm64.whl", hash = "sha256:93f23528edc51d9f686808a361728e903d6f2be55c901d6f5c92e44c6d546bfc", size = 183011, upload-time = "2025-11-24T03:56:16.442Z"} ] [[package]] -name = "mypy" -version = "1.19.1" -source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "librt", marker = "platform_python_implementation != 'PyPy'" }, - { name = "mypy-extensions" }, - { name = "pathspec" }, - { name = "typing-extensions" }, + {name = "librt", marker = "platform_python_implementation != 'PyPy'"}, + {name = "mypy-extensions"}, + {name = "pathspec"}, + {name = "typing-extensions"} ] -sdist = { url = "https://files.pythonhosted.org/packages/f5/db/4efed9504bc01309ab9c2da7e352cc223569f05478012b5d9ece38fd44d2/mypy-1.19.1.tar.gz", hash = "sha256:19d88bb05303fe63f71dd2c6270daca27cb9401c4ca8255fe50d1d920e0eb9ba", size = 3582404, upload-time = "2025-12-15T05:03:48.42Z" } +name = "mypy" +sdist = {url = "https://files.pythonhosted.org/packages/f5/db/4efed9504bc01309ab9c2da7e352cc223569f05478012b5d9ece38fd44d2/mypy-1.19.1.tar.gz", hash = "sha256:19d88bb05303fe63f71dd2c6270daca27cb9401c4ca8255fe50d1d920e0eb9ba", size = 3582404, upload-time = "2025-12-15T05:03:48.42Z"} +source = {registry = "https://pypi.org/simple"} +version = "1.19.1" wheels = [ - { url = "https://files.pythonhosted.org/packages/de/eb/b83e75f4c820c4247a58580ef86fcd35165028f191e7e1ba57128c52782d/mypy-1.19.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:06e6170bd5836770e8104c8fdd58e5e725cfeb309f0a6c681a811f557e97eac1", size = 13199744, upload-time = "2025-12-15T05:03:30.823Z" }, - { url = "https://files.pythonhosted.org/packages/94/28/52785ab7bfa165f87fcbb61547a93f98bb20e7f82f90f165a1f69bce7b3d/mypy-1.19.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:804bd67b8054a85447c8954215a906d6eff9cabeabe493fb6334b24f4bfff718", size = 12215815, upload-time = "2025-12-15T05:02:42.323Z" }, - { url = "https://files.pythonhosted.org/packages/0a/c6/bdd60774a0dbfb05122e3e925f2e9e846c009e479dcec4821dad881f5b52/mypy-1.19.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:21761006a7f497cb0d4de3d8ef4ca70532256688b0523eee02baf9eec895e27b", size = 12740047, upload-time = "2025-12-15T05:03:33.168Z" }, - { url = "https://files.pythonhosted.org/packages/32/2a/66ba933fe6c76bd40d1fe916a83f04fed253152f451a877520b3c4a5e41e/mypy-1.19.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:28902ee51f12e0f19e1e16fbe2f8f06b6637f482c459dd393efddd0ec7f82045", size = 13601998, upload-time = "2025-12-15T05:03:13.056Z" }, - { url = "https://files.pythonhosted.org/packages/e3/da/5055c63e377c5c2418760411fd6a63ee2b96cf95397259038756c042574f/mypy-1.19.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:481daf36a4c443332e2ae9c137dfee878fcea781a2e3f895d54bd3002a900957", size = 13807476, upload-time = "2025-12-15T05:03:17.977Z" }, - { url = "https://files.pythonhosted.org/packages/cd/09/4ebd873390a063176f06b0dbf1f7783dd87bd120eae7727fa4ae4179b685/mypy-1.19.1-cp314-cp314-win_amd64.whl", hash = "sha256:8bb5c6f6d043655e055be9b542aa5f3bdd30e4f3589163e85f93f3640060509f", size = 10281872, upload-time = "2025-12-15T05:03:05.549Z" }, - { url = "https://files.pythonhosted.org/packages/8d/f4/4ce9a05ce5ded1de3ec1c1d96cf9f9504a04e54ce0ed55cfa38619a32b8d/mypy-1.19.1-py3-none-any.whl", hash = "sha256:f1235f5ea01b7db5468d53ece6aaddf1ad0b88d9e7462b86ef96fe04995d7247", size = 2471239, upload-time = "2025-12-15T05:03:07.248Z" }, + {url = "https://files.pythonhosted.org/packages/de/eb/b83e75f4c820c4247a58580ef86fcd35165028f191e7e1ba57128c52782d/mypy-1.19.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:06e6170bd5836770e8104c8fdd58e5e725cfeb309f0a6c681a811f557e97eac1", size = 13199744, upload-time = "2025-12-15T05:03:30.823Z"}, + {url = "https://files.pythonhosted.org/packages/94/28/52785ab7bfa165f87fcbb61547a93f98bb20e7f82f90f165a1f69bce7b3d/mypy-1.19.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:804bd67b8054a85447c8954215a906d6eff9cabeabe493fb6334b24f4bfff718", size = 12215815, upload-time = "2025-12-15T05:02:42.323Z"}, + {url = "https://files.pythonhosted.org/packages/0a/c6/bdd60774a0dbfb05122e3e925f2e9e846c009e479dcec4821dad881f5b52/mypy-1.19.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:21761006a7f497cb0d4de3d8ef4ca70532256688b0523eee02baf9eec895e27b", size = 12740047, upload-time = "2025-12-15T05:03:33.168Z"}, + {url = "https://files.pythonhosted.org/packages/32/2a/66ba933fe6c76bd40d1fe916a83f04fed253152f451a877520b3c4a5e41e/mypy-1.19.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:28902ee51f12e0f19e1e16fbe2f8f06b6637f482c459dd393efddd0ec7f82045", size = 13601998, upload-time = "2025-12-15T05:03:13.056Z"}, + {url = "https://files.pythonhosted.org/packages/e3/da/5055c63e377c5c2418760411fd6a63ee2b96cf95397259038756c042574f/mypy-1.19.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:481daf36a4c443332e2ae9c137dfee878fcea781a2e3f895d54bd3002a900957", size = 13807476, upload-time = "2025-12-15T05:03:17.977Z"}, + {url = "https://files.pythonhosted.org/packages/cd/09/4ebd873390a063176f06b0dbf1f7783dd87bd120eae7727fa4ae4179b685/mypy-1.19.1-cp314-cp314-win_amd64.whl", hash = "sha256:8bb5c6f6d043655e055be9b542aa5f3bdd30e4f3589163e85f93f3640060509f", size = 10281872, upload-time = "2025-12-15T05:03:05.549Z"}, + {url = "https://files.pythonhosted.org/packages/8d/f4/4ce9a05ce5ded1de3ec1c1d96cf9f9504a04e54ce0ed55cfa38619a32b8d/mypy-1.19.1-py3-none-any.whl", hash = "sha256:f1235f5ea01b7db5468d53ece6aaddf1ad0b88d9e7462b86ef96fe04995d7247", size = 2471239, upload-time = "2025-12-15T05:03:07.248Z"} ] [[package]] name = "mypy-extensions" +sdist = {url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343, upload-time = "2025-04-22T14:54:24.164Z"} +source = {registry = "https://pypi.org/simple"} version = "1.1.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343, upload-time = "2025-04-22T14:54:24.164Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" }, + {url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z"} ] [[package]] -name = "myst-parser" -version = "5.0.0" -source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "docutils" }, - { name = "jinja2" }, - { name = "markdown-it-py" }, - { name = "mdit-py-plugins" }, - { name = "pyyaml" }, - { name = "sphinx" }, + {name = "docutils"}, + {name = "jinja2"}, + {name = "markdown-it-py"}, + {name = "mdit-py-plugins"}, + {name = "pyyaml"}, + {name = "sphinx"} ] -sdist = { url = "https://files.pythonhosted.org/packages/33/fa/7b45eef11b7971f0beb29d27b7bfe0d747d063aa29e170d9edd004733c8a/myst_parser-5.0.0.tar.gz", hash = "sha256:f6f231452c56e8baa662cc352c548158f6a16fcbd6e3800fc594978002b94f3a", size = 98535, upload-time = "2026-01-15T09:08:18.036Z" } +name = "myst-parser" +sdist = {url = "https://files.pythonhosted.org/packages/33/fa/7b45eef11b7971f0beb29d27b7bfe0d747d063aa29e170d9edd004733c8a/myst_parser-5.0.0.tar.gz", hash = "sha256:f6f231452c56e8baa662cc352c548158f6a16fcbd6e3800fc594978002b94f3a", size = 98535, upload-time = "2026-01-15T09:08:18.036Z"} +source = {registry = "https://pypi.org/simple"} +version = "5.0.0" wheels = [ - { url = "https://files.pythonhosted.org/packages/d3/ac/686789b9145413f1a61878c407210e41bfdb097976864e0913078b24098c/myst_parser-5.0.0-py3-none-any.whl", hash = "sha256:ab31e516024918296e169139072b81592336f2fef55b8986aa31c9f04b5f7211", size = 84533, upload-time = "2026-01-15T09:08:16.788Z" }, + {url = "https://files.pythonhosted.org/packages/d3/ac/686789b9145413f1a61878c407210e41bfdb097976864e0913078b24098c/myst_parser-5.0.0-py3-none-any.whl", hash = "sha256:ab31e516024918296e169139072b81592336f2fef55b8986aa31c9f04b5f7211", size = 84533, upload-time = "2026-01-15T09:08:16.788Z"} ] [[package]] name = "narwhals" +sdist = {url = "https://files.pythonhosted.org/packages/75/59/81d0f4cad21484083466f278e6b392addd9f4205b48d45b5c8771670ebf8/narwhals-2.17.0.tar.gz", hash = "sha256:ebd5bc95bcfa2f8e89a8ac09e2765a63055162837208e67b42d6eeb6651d5e67", size = 620306, upload-time = "2026-02-23T09:44:34.142Z"} +source = {registry = "https://pypi.org/simple"} version = "2.17.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/75/59/81d0f4cad21484083466f278e6b392addd9f4205b48d45b5c8771670ebf8/narwhals-2.17.0.tar.gz", hash = "sha256:ebd5bc95bcfa2f8e89a8ac09e2765a63055162837208e67b42d6eeb6651d5e67", size = 620306, upload-time = "2026-02-23T09:44:34.142Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/4b/27/20770bd6bf8fbe1e16f848ba21da9df061f38d2e6483952c29d2bb5d1d8b/narwhals-2.17.0-py3-none-any.whl", hash = "sha256:2ac5307b7c2b275a7d66eeda906b8605e3d7a760951e188dcfff86e8ebe083dd", size = 444897, upload-time = "2026-02-23T09:44:32.006Z" }, + {url = "https://files.pythonhosted.org/packages/4b/27/20770bd6bf8fbe1e16f848ba21da9df061f38d2e6483952c29d2bb5d1d8b/narwhals-2.17.0-py3-none-any.whl", hash = "sha256:2ac5307b7c2b275a7d66eeda906b8605e3d7a760951e188dcfff86e8ebe083dd", size = 444897, upload-time = "2026-02-23T09:44:32.006Z"} ] [[package]] name = "nodeenv" +sdist = {url = "https://files.pythonhosted.org/packages/24/bf/d1bda4f6168e0b2e9e5958945e01910052158313224ada5ce1fb2e1113b8/nodeenv-1.10.0.tar.gz", hash = "sha256:996c191ad80897d076bdfba80a41994c2b47c68e224c542b48feba42ba00f8bb", size = 55611, upload-time = "2025-12-20T14:08:54.006Z"} +source = {registry = "https://pypi.org/simple"} version = "1.10.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/24/bf/d1bda4f6168e0b2e9e5958945e01910052158313224ada5ce1fb2e1113b8/nodeenv-1.10.0.tar.gz", hash = "sha256:996c191ad80897d076bdfba80a41994c2b47c68e224c542b48feba42ba00f8bb", size = 55611, upload-time = "2025-12-20T14:08:54.006Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl", hash = "sha256:5bb13e3eed2923615535339b3c620e76779af4cb4c6a90deccc9e36b274d3827", size = 23438, upload-time = "2025-12-20T14:08:52.782Z" }, + {url = "https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl", hash = "sha256:5bb13e3eed2923615535339b3c620e76779af4cb4c6a90deccc9e36b274d3827", size = 23438, upload-time = "2025-12-20T14:08:52.782Z"} ] [[package]] -name = "numba" -version = "0.64.0" -source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "llvmlite" }, - { name = "numpy" }, + {name = "llvmlite"}, + {name = "numpy"} ] -sdist = { url = "https://files.pythonhosted.org/packages/23/c9/a0fb41787d01d621046138da30f6c2100d80857bf34b3390dd68040f27a3/numba-0.64.0.tar.gz", hash = "sha256:95e7300af648baa3308127b1955b52ce6d11889d16e8cfe637b4f85d2fca52b1", size = 2765679, upload-time = "2026-02-18T18:41:20.974Z" } +name = "numba" +sdist = {url = "https://files.pythonhosted.org/packages/23/c9/a0fb41787d01d621046138da30f6c2100d80857bf34b3390dd68040f27a3/numba-0.64.0.tar.gz", hash = "sha256:95e7300af648baa3308127b1955b52ce6d11889d16e8cfe637b4f85d2fca52b1", size = 2765679, upload-time = "2026-02-18T18:41:20.974Z"} +source = {registry = "https://pypi.org/simple"} +version = "0.64.0" wheels = [ - { url = "https://files.pythonhosted.org/packages/3d/8a/77d26afe0988c592dd97cb8d4e80bfb3dfc7dbdacfca7d74a7c5c81dd8c2/numba-0.64.0-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:f565d55eaeff382cbc86c63c8c610347453af3d1e7afb2b6569aac1c9b5c93ce", size = 2683590, upload-time = "2026-02-18T18:41:12.897Z" }, - { url = "https://files.pythonhosted.org/packages/8e/4b/600b8b7cdbc7f9cebee9ea3d13bb70052a79baf28944024ffcb59f0712e3/numba-0.64.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:9b55169b18892c783f85e9ad9e6f5297a6d12967e4414e6b71361086025ff0bb", size = 3781163, upload-time = "2026-02-18T18:41:15.377Z" }, - { url = "https://files.pythonhosted.org/packages/ff/73/53f2d32bfa45b7175e9944f6b816d8c32840178c3eee9325033db5bf838e/numba-0.64.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:196bcafa02c9dd1707e068434f6d5cedde0feb787e3432f7f1f0e993cc336c4c", size = 3481172, upload-time = "2026-02-18T18:41:17.281Z" }, - { url = "https://files.pythonhosted.org/packages/b5/00/aebd2f7f1e11e38814bb96e95a27580817a7b340608d3ac085fdbab83174/numba-0.64.0-cp314-cp314-win_amd64.whl", hash = "sha256:213e9acbe7f1c05090592e79020315c1749dd52517b90e94c517dca3f014d4a1", size = 2754700, upload-time = "2026-02-18T18:41:19.277Z" }, + {url = "https://files.pythonhosted.org/packages/3d/8a/77d26afe0988c592dd97cb8d4e80bfb3dfc7dbdacfca7d74a7c5c81dd8c2/numba-0.64.0-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:f565d55eaeff382cbc86c63c8c610347453af3d1e7afb2b6569aac1c9b5c93ce", size = 2683590, upload-time = "2026-02-18T18:41:12.897Z"}, + {url = "https://files.pythonhosted.org/packages/8e/4b/600b8b7cdbc7f9cebee9ea3d13bb70052a79baf28944024ffcb59f0712e3/numba-0.64.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:9b55169b18892c783f85e9ad9e6f5297a6d12967e4414e6b71361086025ff0bb", size = 3781163, upload-time = "2026-02-18T18:41:15.377Z"}, + {url = "https://files.pythonhosted.org/packages/ff/73/53f2d32bfa45b7175e9944f6b816d8c32840178c3eee9325033db5bf838e/numba-0.64.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:196bcafa02c9dd1707e068434f6d5cedde0feb787e3432f7f1f0e993cc336c4c", size = 3481172, upload-time = "2026-02-18T18:41:17.281Z"}, + {url = "https://files.pythonhosted.org/packages/b5/00/aebd2f7f1e11e38814bb96e95a27580817a7b340608d3ac085fdbab83174/numba-0.64.0-cp314-cp314-win_amd64.whl", hash = "sha256:213e9acbe7f1c05090592e79020315c1749dd52517b90e94c517dca3f014d4a1", size = 2754700, upload-time = "2026-02-18T18:41:19.277Z"} ] [[package]] name = "numpy" +sdist = {url = "https://files.pythonhosted.org/packages/57/fd/0005efbd0af48e55eb3c7208af93f2862d4b1a56cd78e84309a2d959208d/numpy-2.4.2.tar.gz", hash = "sha256:659a6107e31a83c4e33f763942275fd278b21d095094044eb35569e86a21ddae", size = 20723651, upload-time = "2026-01-31T23:13:10.135Z"} +source = {registry = "https://pypi.org/simple"} version = "2.4.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/57/fd/0005efbd0af48e55eb3c7208af93f2862d4b1a56cd78e84309a2d959208d/numpy-2.4.2.tar.gz", hash = "sha256:659a6107e31a83c4e33f763942275fd278b21d095094044eb35569e86a21ddae", size = 20723651, upload-time = "2026-01-31T23:13:10.135Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/18/88/b7df6050bf18fdcfb7046286c6535cabbdd2064a3440fca3f069d319c16e/numpy-2.4.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:444be170853f1f9d528428eceb55f12918e4fda5d8805480f36a002f1415e09b", size = 16663092, upload-time = "2026-01-31T23:12:04.521Z" }, - { url = "https://files.pythonhosted.org/packages/25/7a/1fee4329abc705a469a4afe6e69b1ef7e915117747886327104a8493a955/numpy-2.4.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:d1240d50adff70c2a88217698ca844723068533f3f5c5fa6ee2e3220e3bdb000", size = 14698770, upload-time = "2026-01-31T23:12:06.96Z" }, - { url = "https://files.pythonhosted.org/packages/fb/0b/f9e49ba6c923678ad5bc38181c08ac5e53b7a5754dbca8e581aa1a56b1ff/numpy-2.4.2-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:7cdde6de52fb6664b00b056341265441192d1291c130e99183ec0d4b110ff8b1", size = 5208562, upload-time = "2026-01-31T23:12:09.632Z" }, - { url = "https://files.pythonhosted.org/packages/7d/12/d7de8f6f53f9bb76997e5e4c069eda2051e3fe134e9181671c4391677bb2/numpy-2.4.2-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:cda077c2e5b780200b6b3e09d0b42205a3d1c68f30c6dceb90401c13bff8fe74", size = 6543710, upload-time = "2026-01-31T23:12:11.969Z" }, - { url = "https://files.pythonhosted.org/packages/09/63/c66418c2e0268a31a4cf8a8b512685748200f8e8e8ec6c507ce14e773529/numpy-2.4.2-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d30291931c915b2ab5717c2974bb95ee891a1cf22ebc16a8006bd59cd210d40a", size = 15677205, upload-time = "2026-01-31T23:12:14.33Z" }, - { url = "https://files.pythonhosted.org/packages/5d/6c/7f237821c9642fb2a04d2f1e88b4295677144ca93285fd76eff3bcba858d/numpy-2.4.2-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bba37bc29d4d85761deed3954a1bc62be7cf462b9510b51d367b769a8c8df325", size = 16611738, upload-time = "2026-01-31T23:12:16.525Z" }, - { url = "https://files.pythonhosted.org/packages/c2/a7/39c4cdda9f019b609b5c473899d87abff092fc908cfe4d1ecb2fcff453b0/numpy-2.4.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b2f0073ed0868db1dcd86e052d37279eef185b9c8db5bf61f30f46adac63c909", size = 17028888, upload-time = "2026-01-31T23:12:19.306Z" }, - { url = "https://files.pythonhosted.org/packages/da/b3/e84bb64bdfea967cc10950d71090ec2d84b49bc691df0025dddb7c26e8e3/numpy-2.4.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7f54844851cdb630ceb623dcec4db3240d1ac13d4990532446761baede94996a", size = 18339556, upload-time = "2026-01-31T23:12:21.816Z" }, - { url = "https://files.pythonhosted.org/packages/88/f5/954a291bc1192a27081706862ac62bb5920fbecfbaa302f64682aa90beed/numpy-2.4.2-cp314-cp314-win32.whl", hash = "sha256:12e26134a0331d8dbd9351620f037ec470b7c75929cb8a1537f6bfe411152a1a", size = 6006899, upload-time = "2026-01-31T23:12:24.14Z" }, - { url = "https://files.pythonhosted.org/packages/05/cb/eff72a91b2efdd1bc98b3b8759f6a1654aa87612fc86e3d87d6fe4f948c4/numpy-2.4.2-cp314-cp314-win_amd64.whl", hash = "sha256:068cdb2d0d644cdb45670810894f6a0600797a69c05f1ac478e8d31670b8ee75", size = 12443072, upload-time = "2026-01-31T23:12:26.33Z" }, - { url = "https://files.pythonhosted.org/packages/37/75/62726948db36a56428fce4ba80a115716dc4fad6a3a4352487f8bb950966/numpy-2.4.2-cp314-cp314-win_arm64.whl", hash = "sha256:6ed0be1ee58eef41231a5c943d7d1375f093142702d5723ca2eb07db9b934b05", size = 10494886, upload-time = "2026-01-31T23:12:28.488Z" }, - { url = "https://files.pythonhosted.org/packages/36/2f/ee93744f1e0661dc267e4b21940870cabfae187c092e1433b77b09b50ac4/numpy-2.4.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:98f16a80e917003a12c0580f97b5f875853ebc33e2eaa4bccfc8201ac6869308", size = 14818567, upload-time = "2026-01-31T23:12:30.709Z" }, - { url = "https://files.pythonhosted.org/packages/a7/24/6535212add7d76ff938d8bdc654f53f88d35cddedf807a599e180dcb8e66/numpy-2.4.2-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:20abd069b9cda45874498b245c8015b18ace6de8546bf50dfa8cea1696ed06ef", size = 5328372, upload-time = "2026-01-31T23:12:32.962Z" }, - { url = "https://files.pythonhosted.org/packages/5e/9d/c48f0a035725f925634bf6b8994253b43f2047f6778a54147d7e213bc5a7/numpy-2.4.2-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:e98c97502435b53741540a5717a6749ac2ada901056c7db951d33e11c885cc7d", size = 6649306, upload-time = "2026-01-31T23:12:34.797Z" }, - { url = "https://files.pythonhosted.org/packages/81/05/7c73a9574cd4a53a25907bad38b59ac83919c0ddc8234ec157f344d57d9a/numpy-2.4.2-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:da6cad4e82cb893db4b69105c604d805e0c3ce11501a55b5e9f9083b47d2ffe8", size = 15722394, upload-time = "2026-01-31T23:12:36.565Z" }, - { url = "https://files.pythonhosted.org/packages/35/fa/4de10089f21fc7d18442c4a767ab156b25c2a6eaf187c0db6d9ecdaeb43f/numpy-2.4.2-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e4424677ce4b47fe73c8b5556d876571f7c6945d264201180db2dc34f676ab5", size = 16653343, upload-time = "2026-01-31T23:12:39.188Z" }, - { url = "https://files.pythonhosted.org/packages/b8/f9/d33e4ffc857f3763a57aa85650f2e82486832d7492280ac21ba9efda80da/numpy-2.4.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:2b8f157c8a6f20eb657e240f8985cc135598b2b46985c5bccbde7616dc9c6b1e", size = 17078045, upload-time = "2026-01-31T23:12:42.041Z" }, - { url = "https://files.pythonhosted.org/packages/c8/b8/54bdb43b6225badbea6389fa038c4ef868c44f5890f95dd530a218706da3/numpy-2.4.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5daf6f3914a733336dab21a05cdec343144600e964d2fcdabaac0c0269874b2a", size = 18380024, upload-time = "2026-01-31T23:12:44.331Z" }, - { url = "https://files.pythonhosted.org/packages/a5/55/6e1a61ded7af8df04016d81b5b02daa59f2ea9252ee0397cb9f631efe9e5/numpy-2.4.2-cp314-cp314t-win32.whl", hash = "sha256:8c50dd1fc8826f5b26a5ee4d77ca55d88a895f4e4819c7ecc2a9f5905047a443", size = 6153937, upload-time = "2026-01-31T23:12:47.229Z" }, - { url = "https://files.pythonhosted.org/packages/45/aa/fa6118d1ed6d776b0983f3ceac9b1a5558e80df9365b1c3aa6d42bf9eee4/numpy-2.4.2-cp314-cp314t-win_amd64.whl", hash = "sha256:fcf92bee92742edd401ba41135185866f7026c502617f422eb432cfeca4fe236", size = 12631844, upload-time = "2026-01-31T23:12:48.997Z" }, - { url = "https://files.pythonhosted.org/packages/32/0a/2ec5deea6dcd158f254a7b372fb09cfba5719419c8d66343bab35237b3fb/numpy-2.4.2-cp314-cp314t-win_arm64.whl", hash = "sha256:1f92f53998a17265194018d1cc321b2e96e900ca52d54c7c77837b71b9465181", size = 10565379, upload-time = "2026-01-31T23:12:51.345Z" }, +wheels = [ + {url = "https://files.pythonhosted.org/packages/18/88/b7df6050bf18fdcfb7046286c6535cabbdd2064a3440fca3f069d319c16e/numpy-2.4.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:444be170853f1f9d528428eceb55f12918e4fda5d8805480f36a002f1415e09b", size = 16663092, upload-time = "2026-01-31T23:12:04.521Z"}, + {url = "https://files.pythonhosted.org/packages/25/7a/1fee4329abc705a469a4afe6e69b1ef7e915117747886327104a8493a955/numpy-2.4.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:d1240d50adff70c2a88217698ca844723068533f3f5c5fa6ee2e3220e3bdb000", size = 14698770, upload-time = "2026-01-31T23:12:06.96Z"}, + {url = "https://files.pythonhosted.org/packages/fb/0b/f9e49ba6c923678ad5bc38181c08ac5e53b7a5754dbca8e581aa1a56b1ff/numpy-2.4.2-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:7cdde6de52fb6664b00b056341265441192d1291c130e99183ec0d4b110ff8b1", size = 5208562, upload-time = "2026-01-31T23:12:09.632Z"}, + {url = "https://files.pythonhosted.org/packages/7d/12/d7de8f6f53f9bb76997e5e4c069eda2051e3fe134e9181671c4391677bb2/numpy-2.4.2-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:cda077c2e5b780200b6b3e09d0b42205a3d1c68f30c6dceb90401c13bff8fe74", size = 6543710, upload-time = "2026-01-31T23:12:11.969Z"}, + {url = "https://files.pythonhosted.org/packages/09/63/c66418c2e0268a31a4cf8a8b512685748200f8e8e8ec6c507ce14e773529/numpy-2.4.2-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d30291931c915b2ab5717c2974bb95ee891a1cf22ebc16a8006bd59cd210d40a", size = 15677205, upload-time = "2026-01-31T23:12:14.33Z"}, + {url = "https://files.pythonhosted.org/packages/5d/6c/7f237821c9642fb2a04d2f1e88b4295677144ca93285fd76eff3bcba858d/numpy-2.4.2-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bba37bc29d4d85761deed3954a1bc62be7cf462b9510b51d367b769a8c8df325", size = 16611738, upload-time = "2026-01-31T23:12:16.525Z"}, + {url = "https://files.pythonhosted.org/packages/c2/a7/39c4cdda9f019b609b5c473899d87abff092fc908cfe4d1ecb2fcff453b0/numpy-2.4.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b2f0073ed0868db1dcd86e052d37279eef185b9c8db5bf61f30f46adac63c909", size = 17028888, upload-time = "2026-01-31T23:12:19.306Z"}, + {url = "https://files.pythonhosted.org/packages/da/b3/e84bb64bdfea967cc10950d71090ec2d84b49bc691df0025dddb7c26e8e3/numpy-2.4.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7f54844851cdb630ceb623dcec4db3240d1ac13d4990532446761baede94996a", size = 18339556, upload-time = "2026-01-31T23:12:21.816Z"}, + {url = "https://files.pythonhosted.org/packages/88/f5/954a291bc1192a27081706862ac62bb5920fbecfbaa302f64682aa90beed/numpy-2.4.2-cp314-cp314-win32.whl", hash = "sha256:12e26134a0331d8dbd9351620f037ec470b7c75929cb8a1537f6bfe411152a1a", size = 6006899, upload-time = "2026-01-31T23:12:24.14Z"}, + {url = "https://files.pythonhosted.org/packages/05/cb/eff72a91b2efdd1bc98b3b8759f6a1654aa87612fc86e3d87d6fe4f948c4/numpy-2.4.2-cp314-cp314-win_amd64.whl", hash = "sha256:068cdb2d0d644cdb45670810894f6a0600797a69c05f1ac478e8d31670b8ee75", size = 12443072, upload-time = "2026-01-31T23:12:26.33Z"}, + {url = "https://files.pythonhosted.org/packages/37/75/62726948db36a56428fce4ba80a115716dc4fad6a3a4352487f8bb950966/numpy-2.4.2-cp314-cp314-win_arm64.whl", hash = "sha256:6ed0be1ee58eef41231a5c943d7d1375f093142702d5723ca2eb07db9b934b05", size = 10494886, upload-time = "2026-01-31T23:12:28.488Z"}, + {url = "https://files.pythonhosted.org/packages/36/2f/ee93744f1e0661dc267e4b21940870cabfae187c092e1433b77b09b50ac4/numpy-2.4.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:98f16a80e917003a12c0580f97b5f875853ebc33e2eaa4bccfc8201ac6869308", size = 14818567, upload-time = "2026-01-31T23:12:30.709Z"}, + {url = "https://files.pythonhosted.org/packages/a7/24/6535212add7d76ff938d8bdc654f53f88d35cddedf807a599e180dcb8e66/numpy-2.4.2-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:20abd069b9cda45874498b245c8015b18ace6de8546bf50dfa8cea1696ed06ef", size = 5328372, upload-time = "2026-01-31T23:12:32.962Z"}, + {url = "https://files.pythonhosted.org/packages/5e/9d/c48f0a035725f925634bf6b8994253b43f2047f6778a54147d7e213bc5a7/numpy-2.4.2-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:e98c97502435b53741540a5717a6749ac2ada901056c7db951d33e11c885cc7d", size = 6649306, upload-time = "2026-01-31T23:12:34.797Z"}, + {url = "https://files.pythonhosted.org/packages/81/05/7c73a9574cd4a53a25907bad38b59ac83919c0ddc8234ec157f344d57d9a/numpy-2.4.2-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:da6cad4e82cb893db4b69105c604d805e0c3ce11501a55b5e9f9083b47d2ffe8", size = 15722394, upload-time = "2026-01-31T23:12:36.565Z"}, + {url = "https://files.pythonhosted.org/packages/35/fa/4de10089f21fc7d18442c4a767ab156b25c2a6eaf187c0db6d9ecdaeb43f/numpy-2.4.2-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e4424677ce4b47fe73c8b5556d876571f7c6945d264201180db2dc34f676ab5", size = 16653343, upload-time = "2026-01-31T23:12:39.188Z"}, + {url = "https://files.pythonhosted.org/packages/b8/f9/d33e4ffc857f3763a57aa85650f2e82486832d7492280ac21ba9efda80da/numpy-2.4.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:2b8f157c8a6f20eb657e240f8985cc135598b2b46985c5bccbde7616dc9c6b1e", size = 17078045, upload-time = "2026-01-31T23:12:42.041Z"}, + {url = "https://files.pythonhosted.org/packages/c8/b8/54bdb43b6225badbea6389fa038c4ef868c44f5890f95dd530a218706da3/numpy-2.4.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5daf6f3914a733336dab21a05cdec343144600e964d2fcdabaac0c0269874b2a", size = 18380024, upload-time = "2026-01-31T23:12:44.331Z"}, + {url = "https://files.pythonhosted.org/packages/a5/55/6e1a61ded7af8df04016d81b5b02daa59f2ea9252ee0397cb9f631efe9e5/numpy-2.4.2-cp314-cp314t-win32.whl", hash = "sha256:8c50dd1fc8826f5b26a5ee4d77ca55d88a895f4e4819c7ecc2a9f5905047a443", size = 6153937, upload-time = "2026-01-31T23:12:47.229Z"}, + {url = "https://files.pythonhosted.org/packages/45/aa/fa6118d1ed6d776b0983f3ceac9b1a5558e80df9365b1c3aa6d42bf9eee4/numpy-2.4.2-cp314-cp314t-win_amd64.whl", hash = "sha256:fcf92bee92742edd401ba41135185866f7026c502617f422eb432cfeca4fe236", size = 12631844, upload-time = "2026-01-31T23:12:48.997Z"}, + {url = "https://files.pythonhosted.org/packages/32/0a/2ec5deea6dcd158f254a7b372fb09cfba5719419c8d66343bab35237b3fb/numpy-2.4.2-cp314-cp314t-win_arm64.whl", hash = "sha256:1f92f53998a17265194018d1cc321b2e96e900ca52d54c7c77837b71b9465181", size = 10565379, upload-time = "2026-01-31T23:12:51.345Z"} ] [[package]] -name = "openptv-python" -source = { editable = "." } dependencies = [ - { name = "mypy" }, - { name = "myst-parser" }, - { name = "numba" }, - { name = "numpy" }, - { name = "pre-commit" }, - { name = "pydata-sphinx-theme" }, - { name = "pytest" }, - { name = "pytest-cov" }, - { name = "pyyaml" }, - { name = "scipy" }, - { name = "sphinx" }, - { name = "sphinx-autoapi" }, - { name = "types-pyyaml" }, -] - -[package.optional-dependencies] -marimo = [ - { name = "marimo" }, + {name = "mypy"}, + {name = "myst-parser"}, + {name = "numba"}, + {name = "numpy"}, + {name = "pre-commit"}, + {name = "pydata-sphinx-theme"}, + {name = "pytest"}, + {name = "pytest-cov"}, + {name = "pyyaml"}, + {name = "scipy"}, + {name = "sphinx"}, + {name = "sphinx-autoapi"}, + {name = "types-pyyaml"} ] +name = "openptv-python" +source = {editable = "."} [package.metadata] +provides-extras = ["marimo"] requires-dist = [ - { name = "marimo", marker = "extra == 'marimo'" }, - { name = "mypy", specifier = ">=1.19.1" }, - { name = "myst-parser", specifier = ">=5.0.0" }, - { name = "numba", specifier = ">=0.64.0" }, - { name = "numpy", specifier = ">=2.4.2" }, - { name = "pre-commit", specifier = ">=4.5.1" }, - { name = "pydata-sphinx-theme", specifier = ">=0.16.1" }, - { name = "pytest", specifier = ">=9.0.2" }, - { name = "pytest-cov", specifier = ">=7.0.0" }, - { name = "pyyaml", specifier = ">=6.0.3" }, - { name = "scipy", specifier = ">=1.17.1" }, - { name = "sphinx", specifier = ">=9.1.0" }, - { name = "sphinx-autoapi", specifier = ">=3.7.0" }, - { name = "types-pyyaml", specifier = ">=6.0.12.20250915" }, + {name = "marimo", marker = "extra == 'marimo'"}, + {name = "mypy", specifier = ">=1.19.1"}, + {name = "myst-parser", specifier = ">=5.0.0"}, + {name = "numba", specifier = ">=0.64.0"}, + {name = "numpy", specifier = ">=2.4.2"}, + {name = "pre-commit", specifier = ">=4.5.1"}, + {name = "pydata-sphinx-theme", specifier = ">=0.16.1"}, + {name = "pytest", specifier = ">=9.0.2"}, + {name = "pytest-cov", specifier = ">=7.0.0"}, + {name = "pyyaml", specifier = ">=6.0.3"}, + {name = "scipy", specifier = ">=1.17.1"}, + {name = "sphinx", specifier = ">=9.1.0"}, + {name = "sphinx-autoapi", specifier = ">=3.7.0"}, + {name = "types-pyyaml", specifier = ">=6.0.12.20250915"} +] + +[package.optional-dependencies] +marimo = [ + {name = "marimo"} ] -provides-extras = ["marimo"] [[package]] name = "packaging" +sdist = {url = "https://files.pythonhosted.org/packages/65/ee/299d360cdc32edc7d2cf530f3accf79c4fca01e96ffc950d8a52213bd8e4/packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4", size = 143416, upload-time = "2026-01-21T20:50:39.064Z"} +source = {registry = "https://pypi.org/simple"} version = "26.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/65/ee/299d360cdc32edc7d2cf530f3accf79c4fca01e96ffc950d8a52213bd8e4/packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4", size = 143416, upload-time = "2026-01-21T20:50:39.064Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529", size = 74366, upload-time = "2026-01-21T20:50:37.788Z" }, + {url = "https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529", size = 74366, upload-time = "2026-01-21T20:50:37.788Z"} ] [[package]] name = "parso" +sdist = {url = "https://files.pythonhosted.org/packages/81/76/a1e769043c0c0c9fe391b702539d594731a4362334cdf4dc25d0c09761e7/parso-0.8.6.tar.gz", hash = "sha256:2b9a0332696df97d454fa67b81618fd69c35a7b90327cbe6ba5c92d2c68a7bfd", size = 401621, upload-time = "2026-02-09T15:45:24.425Z"} +source = {registry = "https://pypi.org/simple"} version = "0.8.6" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/81/76/a1e769043c0c0c9fe391b702539d594731a4362334cdf4dc25d0c09761e7/parso-0.8.6.tar.gz", hash = "sha256:2b9a0332696df97d454fa67b81618fd69c35a7b90327cbe6ba5c92d2c68a7bfd", size = 401621, upload-time = "2026-02-09T15:45:24.425Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b6/61/fae042894f4296ec49e3f193aff5d7c18440da9e48102c3315e1bc4519a7/parso-0.8.6-py2.py3-none-any.whl", hash = "sha256:2c549f800b70a5c4952197248825584cb00f033b29c692671d3bf08bf380baff", size = 106894, upload-time = "2026-02-09T15:45:21.391Z" }, + {url = "https://files.pythonhosted.org/packages/b6/61/fae042894f4296ec49e3f193aff5d7c18440da9e48102c3315e1bc4519a7/parso-0.8.6-py2.py3-none-any.whl", hash = "sha256:2c549f800b70a5c4952197248825584cb00f033b29c692671d3bf08bf380baff", size = 106894, upload-time = "2026-02-09T15:45:21.391Z"} ] [[package]] name = "pathspec" +sdist = {url = "https://files.pythonhosted.org/packages/fa/36/e27608899f9b8d4dff0617b2d9ab17ca5608956ca44461ac14ac48b44015/pathspec-1.0.4.tar.gz", hash = "sha256:0210e2ae8a21a9137c0d470578cb0e595af87edaa6ebf12ff176f14a02e0e645", size = 131200, upload-time = "2026-01-27T03:59:46.938Z"} +source = {registry = "https://pypi.org/simple"} version = "1.0.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/fa/36/e27608899f9b8d4dff0617b2d9ab17ca5608956ca44461ac14ac48b44015/pathspec-1.0.4.tar.gz", hash = "sha256:0210e2ae8a21a9137c0d470578cb0e595af87edaa6ebf12ff176f14a02e0e645", size = 131200, upload-time = "2026-01-27T03:59:46.938Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ef/3c/2c197d226f9ea224a9ab8d197933f9da0ae0aac5b6e0f884e2b8d9c8e9f7/pathspec-1.0.4-py3-none-any.whl", hash = "sha256:fb6ae2fd4e7c921a165808a552060e722767cfa526f99ca5156ed2ce45a5c723", size = 55206, upload-time = "2026-01-27T03:59:45.137Z" }, + {url = "https://files.pythonhosted.org/packages/ef/3c/2c197d226f9ea224a9ab8d197933f9da0ae0aac5b6e0f884e2b8d9c8e9f7/pathspec-1.0.4-py3-none-any.whl", hash = "sha256:fb6ae2fd4e7c921a165808a552060e722767cfa526f99ca5156ed2ce45a5c723", size = 55206, upload-time = "2026-01-27T03:59:45.137Z"} ] [[package]] name = "platformdirs" +sdist = {url = "https://files.pythonhosted.org/packages/19/56/8d4c30c8a1d07013911a8fdbd8f89440ef9f08d07a1b50ab8ca8be5a20f9/platformdirs-4.9.4.tar.gz", hash = "sha256:1ec356301b7dc906d83f371c8f487070e99d3ccf9e501686456394622a01a934", size = 28737, upload-time = "2026-03-05T18:34:13.271Z"} +source = {registry = "https://pypi.org/simple"} version = "4.9.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/19/56/8d4c30c8a1d07013911a8fdbd8f89440ef9f08d07a1b50ab8ca8be5a20f9/platformdirs-4.9.4.tar.gz", hash = "sha256:1ec356301b7dc906d83f371c8f487070e99d3ccf9e501686456394622a01a934", size = 28737, upload-time = "2026-03-05T18:34:13.271Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/63/d7/97f7e3a6abb67d8080dd406fd4df842c2be0efaf712d1c899c32a075027c/platformdirs-4.9.4-py3-none-any.whl", hash = "sha256:68a9a4619a666ea6439f2ff250c12a853cd1cbd5158d258bd824a7df6be2f868", size = 21216, upload-time = "2026-03-05T18:34:12.172Z" }, + {url = "https://files.pythonhosted.org/packages/63/d7/97f7e3a6abb67d8080dd406fd4df842c2be0efaf712d1c899c32a075027c/platformdirs-4.9.4-py3-none-any.whl", hash = "sha256:68a9a4619a666ea6439f2ff250c12a853cd1cbd5158d258bd824a7df6be2f868", size = 21216, upload-time = "2026-03-05T18:34:12.172Z"} ] [[package]] name = "pluggy" +sdist = {url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z"} +source = {registry = "https://pypi.org/simple"} version = "1.6.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, + {url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z"} ] [[package]] -name = "pre-commit" -version = "4.5.1" -source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "cfgv" }, - { name = "identify" }, - { name = "nodeenv" }, - { name = "pyyaml" }, - { name = "virtualenv" }, + {name = "cfgv"}, + {name = "identify"}, + {name = "nodeenv"}, + {name = "pyyaml"}, + {name = "virtualenv"} ] -sdist = { url = "https://files.pythonhosted.org/packages/40/f1/6d86a29246dfd2e9b6237f0b5823717f60cad94d47ddc26afa916d21f525/pre_commit-4.5.1.tar.gz", hash = "sha256:eb545fcff725875197837263e977ea257a402056661f09dae08e4b149b030a61", size = 198232, upload-time = "2025-12-16T21:14:33.552Z" } +name = "pre-commit" +sdist = {url = "https://files.pythonhosted.org/packages/40/f1/6d86a29246dfd2e9b6237f0b5823717f60cad94d47ddc26afa916d21f525/pre_commit-4.5.1.tar.gz", hash = "sha256:eb545fcff725875197837263e977ea257a402056661f09dae08e4b149b030a61", size = 198232, upload-time = "2025-12-16T21:14:33.552Z"} +source = {registry = "https://pypi.org/simple"} +version = "4.5.1" wheels = [ - { url = "https://files.pythonhosted.org/packages/5d/19/fd3ef348460c80af7bb4669ea7926651d1f95c23ff2df18b9d24bab4f3fa/pre_commit-4.5.1-py2.py3-none-any.whl", hash = "sha256:3b3afd891e97337708c1674210f8eba659b52a38ea5f822ff142d10786221f77", size = 226437, upload-time = "2025-12-16T21:14:32.409Z" }, + {url = "https://files.pythonhosted.org/packages/5d/19/fd3ef348460c80af7bb4669ea7926651d1f95c23ff2df18b9d24bab4f3fa/pre_commit-4.5.1-py2.py3-none-any.whl", hash = "sha256:3b3afd891e97337708c1674210f8eba659b52a38ea5f822ff142d10786221f77", size = 226437, upload-time = "2025-12-16T21:14:32.409Z"} ] [[package]] name = "psutil" +sdist = {url = "https://files.pythonhosted.org/packages/aa/c6/d1ddf4abb55e93cebc4f2ed8b5d6dbad109ecb8d63748dd2b20ab5e57ebe/psutil-7.2.2.tar.gz", hash = "sha256:0746f5f8d406af344fd547f1c8daa5f5c33dbc293bb8d6a16d80b4bb88f59372", size = 493740, upload-time = "2026-01-28T18:14:54.428Z"} +source = {registry = "https://pypi.org/simple"} version = "7.2.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/aa/c6/d1ddf4abb55e93cebc4f2ed8b5d6dbad109ecb8d63748dd2b20ab5e57ebe/psutil-7.2.2.tar.gz", hash = "sha256:0746f5f8d406af344fd547f1c8daa5f5c33dbc293bb8d6a16d80b4bb88f59372", size = 493740, upload-time = "2026-01-28T18:14:54.428Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/81/69/ef179ab5ca24f32acc1dac0c247fd6a13b501fd5534dbae0e05a1c48b66d/psutil-7.2.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:eed63d3b4d62449571547b60578c5b2c4bcccc5387148db46e0c2313dad0ee00", size = 130664, upload-time = "2026-01-28T18:15:09.469Z" }, - { url = "https://files.pythonhosted.org/packages/7b/64/665248b557a236d3fa9efc378d60d95ef56dd0a490c2cd37dafc7660d4a9/psutil-7.2.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7b6d09433a10592ce39b13d7be5a54fbac1d1228ed29abc880fb23df7cb694c9", size = 131087, upload-time = "2026-01-28T18:15:11.724Z" }, - { url = "https://files.pythonhosted.org/packages/d5/2e/e6782744700d6759ebce3043dcfa661fb61e2fb752b91cdeae9af12c2178/psutil-7.2.2-cp314-cp314t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1fa4ecf83bcdf6e6c8f4449aff98eefb5d0604bf88cb883d7da3d8d2d909546a", size = 182383, upload-time = "2026-01-28T18:15:13.445Z" }, - { url = "https://files.pythonhosted.org/packages/57/49/0a41cefd10cb7505cdc04dab3eacf24c0c2cb158a998b8c7b1d27ee2c1f5/psutil-7.2.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e452c464a02e7dc7822a05d25db4cde564444a67e58539a00f929c51eddda0cf", size = 185210, upload-time = "2026-01-28T18:15:16.002Z" }, - { url = "https://files.pythonhosted.org/packages/dd/2c/ff9bfb544f283ba5f83ba725a3c5fec6d6b10b8f27ac1dc641c473dc390d/psutil-7.2.2-cp314-cp314t-win_amd64.whl", hash = "sha256:c7663d4e37f13e884d13994247449e9f8f574bc4655d509c3b95e9ec9e2b9dc1", size = 141228, upload-time = "2026-01-28T18:15:18.385Z" }, - { url = "https://files.pythonhosted.org/packages/f2/fc/f8d9c31db14fcec13748d373e668bc3bed94d9077dbc17fb0eebc073233c/psutil-7.2.2-cp314-cp314t-win_arm64.whl", hash = "sha256:11fe5a4f613759764e79c65cf11ebdf26e33d6dd34336f8a337aa2996d71c841", size = 136284, upload-time = "2026-01-28T18:15:19.912Z" }, - { url = "https://files.pythonhosted.org/packages/e7/36/5ee6e05c9bd427237b11b3937ad82bb8ad2752d72c6969314590dd0c2f6e/psutil-7.2.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ed0cace939114f62738d808fdcecd4c869222507e266e574799e9c0faa17d486", size = 129090, upload-time = "2026-01-28T18:15:22.168Z" }, - { url = "https://files.pythonhosted.org/packages/80/c4/f5af4c1ca8c1eeb2e92ccca14ce8effdeec651d5ab6053c589b074eda6e1/psutil-7.2.2-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:1a7b04c10f32cc88ab39cbf606e117fd74721c831c98a27dc04578deb0c16979", size = 129859, upload-time = "2026-01-28T18:15:23.795Z" }, - { url = "https://files.pythonhosted.org/packages/b5/70/5d8df3b09e25bce090399cf48e452d25c935ab72dad19406c77f4e828045/psutil-7.2.2-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:076a2d2f923fd4821644f5ba89f059523da90dc9014e85f8e45a5774ca5bc6f9", size = 155560, upload-time = "2026-01-28T18:15:25.976Z" }, - { url = "https://files.pythonhosted.org/packages/63/65/37648c0c158dc222aba51c089eb3bdfa238e621674dc42d48706e639204f/psutil-7.2.2-cp36-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b0726cecd84f9474419d67252add4ac0cd9811b04d61123054b9fb6f57df6e9e", size = 156997, upload-time = "2026-01-28T18:15:27.794Z" }, - { url = "https://files.pythonhosted.org/packages/8e/13/125093eadae863ce03c6ffdbae9929430d116a246ef69866dad94da3bfbc/psutil-7.2.2-cp36-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:fd04ef36b4a6d599bbdb225dd1d3f51e00105f6d48a28f006da7f9822f2606d8", size = 148972, upload-time = "2026-01-28T18:15:29.342Z" }, - { url = "https://files.pythonhosted.org/packages/04/78/0acd37ca84ce3ddffaa92ef0f571e073faa6d8ff1f0559ab1272188ea2be/psutil-7.2.2-cp36-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b58fabe35e80b264a4e3bb23e6b96f9e45a3df7fb7eed419ac0e5947c61e47cc", size = 148266, upload-time = "2026-01-28T18:15:31.597Z" }, - { url = "https://files.pythonhosted.org/packages/b4/90/e2159492b5426be0c1fef7acba807a03511f97c5f86b3caeda6ad92351a7/psutil-7.2.2-cp37-abi3-win_amd64.whl", hash = "sha256:eb7e81434c8d223ec4a219b5fc1c47d0417b12be7ea866e24fb5ad6e84b3d988", size = 137737, upload-time = "2026-01-28T18:15:33.849Z" }, - { url = "https://files.pythonhosted.org/packages/8c/c7/7bb2e321574b10df20cbde462a94e2b71d05f9bbda251ef27d104668306a/psutil-7.2.2-cp37-abi3-win_arm64.whl", hash = "sha256:8c233660f575a5a89e6d4cb65d9f938126312bca76d8fe087b947b3a1aaac9ee", size = 134617, upload-time = "2026-01-28T18:15:36.514Z" }, +wheels = [ + {url = "https://files.pythonhosted.org/packages/81/69/ef179ab5ca24f32acc1dac0c247fd6a13b501fd5534dbae0e05a1c48b66d/psutil-7.2.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:eed63d3b4d62449571547b60578c5b2c4bcccc5387148db46e0c2313dad0ee00", size = 130664, upload-time = "2026-01-28T18:15:09.469Z"}, + {url = "https://files.pythonhosted.org/packages/7b/64/665248b557a236d3fa9efc378d60d95ef56dd0a490c2cd37dafc7660d4a9/psutil-7.2.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7b6d09433a10592ce39b13d7be5a54fbac1d1228ed29abc880fb23df7cb694c9", size = 131087, upload-time = "2026-01-28T18:15:11.724Z"}, + {url = "https://files.pythonhosted.org/packages/d5/2e/e6782744700d6759ebce3043dcfa661fb61e2fb752b91cdeae9af12c2178/psutil-7.2.2-cp314-cp314t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1fa4ecf83bcdf6e6c8f4449aff98eefb5d0604bf88cb883d7da3d8d2d909546a", size = 182383, upload-time = "2026-01-28T18:15:13.445Z"}, + {url = "https://files.pythonhosted.org/packages/57/49/0a41cefd10cb7505cdc04dab3eacf24c0c2cb158a998b8c7b1d27ee2c1f5/psutil-7.2.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e452c464a02e7dc7822a05d25db4cde564444a67e58539a00f929c51eddda0cf", size = 185210, upload-time = "2026-01-28T18:15:16.002Z"}, + {url = "https://files.pythonhosted.org/packages/dd/2c/ff9bfb544f283ba5f83ba725a3c5fec6d6b10b8f27ac1dc641c473dc390d/psutil-7.2.2-cp314-cp314t-win_amd64.whl", hash = "sha256:c7663d4e37f13e884d13994247449e9f8f574bc4655d509c3b95e9ec9e2b9dc1", size = 141228, upload-time = "2026-01-28T18:15:18.385Z"}, + {url = "https://files.pythonhosted.org/packages/f2/fc/f8d9c31db14fcec13748d373e668bc3bed94d9077dbc17fb0eebc073233c/psutil-7.2.2-cp314-cp314t-win_arm64.whl", hash = "sha256:11fe5a4f613759764e79c65cf11ebdf26e33d6dd34336f8a337aa2996d71c841", size = 136284, upload-time = "2026-01-28T18:15:19.912Z"}, + {url = "https://files.pythonhosted.org/packages/e7/36/5ee6e05c9bd427237b11b3937ad82bb8ad2752d72c6969314590dd0c2f6e/psutil-7.2.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ed0cace939114f62738d808fdcecd4c869222507e266e574799e9c0faa17d486", size = 129090, upload-time = "2026-01-28T18:15:22.168Z"}, + {url = "https://files.pythonhosted.org/packages/80/c4/f5af4c1ca8c1eeb2e92ccca14ce8effdeec651d5ab6053c589b074eda6e1/psutil-7.2.2-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:1a7b04c10f32cc88ab39cbf606e117fd74721c831c98a27dc04578deb0c16979", size = 129859, upload-time = "2026-01-28T18:15:23.795Z"}, + {url = "https://files.pythonhosted.org/packages/b5/70/5d8df3b09e25bce090399cf48e452d25c935ab72dad19406c77f4e828045/psutil-7.2.2-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:076a2d2f923fd4821644f5ba89f059523da90dc9014e85f8e45a5774ca5bc6f9", size = 155560, upload-time = "2026-01-28T18:15:25.976Z"}, + {url = "https://files.pythonhosted.org/packages/63/65/37648c0c158dc222aba51c089eb3bdfa238e621674dc42d48706e639204f/psutil-7.2.2-cp36-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b0726cecd84f9474419d67252add4ac0cd9811b04d61123054b9fb6f57df6e9e", size = 156997, upload-time = "2026-01-28T18:15:27.794Z"}, + {url = "https://files.pythonhosted.org/packages/8e/13/125093eadae863ce03c6ffdbae9929430d116a246ef69866dad94da3bfbc/psutil-7.2.2-cp36-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:fd04ef36b4a6d599bbdb225dd1d3f51e00105f6d48a28f006da7f9822f2606d8", size = 148972, upload-time = "2026-01-28T18:15:29.342Z"}, + {url = "https://files.pythonhosted.org/packages/04/78/0acd37ca84ce3ddffaa92ef0f571e073faa6d8ff1f0559ab1272188ea2be/psutil-7.2.2-cp36-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b58fabe35e80b264a4e3bb23e6b96f9e45a3df7fb7eed419ac0e5947c61e47cc", size = 148266, upload-time = "2026-01-28T18:15:31.597Z"}, + {url = "https://files.pythonhosted.org/packages/b4/90/e2159492b5426be0c1fef7acba807a03511f97c5f86b3caeda6ad92351a7/psutil-7.2.2-cp37-abi3-win_amd64.whl", hash = "sha256:eb7e81434c8d223ec4a219b5fc1c47d0417b12be7ea866e24fb5ad6e84b3d988", size = 137737, upload-time = "2026-01-28T18:15:33.849Z"}, + {url = "https://files.pythonhosted.org/packages/8c/c7/7bb2e321574b10df20cbde462a94e2b71d05f9bbda251ef27d104668306a/psutil-7.2.2-cp37-abi3-win_arm64.whl", hash = "sha256:8c233660f575a5a89e6d4cb65d9f938126312bca76d8fe087b947b3a1aaac9ee", size = 134617, upload-time = "2026-01-28T18:15:36.514Z"} ] [[package]] -name = "pydata-sphinx-theme" -version = "0.16.1" -source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "accessible-pygments" }, - { name = "babel" }, - { name = "beautifulsoup4" }, - { name = "docutils" }, - { name = "pygments" }, - { name = "sphinx" }, - { name = "typing-extensions" }, + {name = "accessible-pygments"}, + {name = "babel"}, + {name = "beautifulsoup4"}, + {name = "docutils"}, + {name = "pygments"}, + {name = "sphinx"}, + {name = "typing-extensions"} ] -sdist = { url = "https://files.pythonhosted.org/packages/00/20/bb50f9de3a6de69e6abd6b087b52fa2418a0418b19597601605f855ad044/pydata_sphinx_theme-0.16.1.tar.gz", hash = "sha256:a08b7f0b7f70387219dc659bff0893a7554d5eb39b59d3b8ef37b8401b7642d7", size = 2412693, upload-time = "2024-12-17T10:53:39.537Z" } +name = "pydata-sphinx-theme" +sdist = {url = "https://files.pythonhosted.org/packages/00/20/bb50f9de3a6de69e6abd6b087b52fa2418a0418b19597601605f855ad044/pydata_sphinx_theme-0.16.1.tar.gz", hash = "sha256:a08b7f0b7f70387219dc659bff0893a7554d5eb39b59d3b8ef37b8401b7642d7", size = 2412693, upload-time = "2024-12-17T10:53:39.537Z"} +source = {registry = "https://pypi.org/simple"} +version = "0.16.1" wheels = [ - { url = "https://files.pythonhosted.org/packages/e2/0d/8ba33fa83a7dcde13eb3c1c2a0c1cc29950a048bfed6d9b0d8b6bd710b4c/pydata_sphinx_theme-0.16.1-py3-none-any.whl", hash = "sha256:225331e8ac4b32682c18fcac5a57a6f717c4e632cea5dd0e247b55155faeccde", size = 6723264, upload-time = "2024-12-17T10:53:35.645Z" }, + {url = "https://files.pythonhosted.org/packages/e2/0d/8ba33fa83a7dcde13eb3c1c2a0c1cc29950a048bfed6d9b0d8b6bd710b4c/pydata_sphinx_theme-0.16.1-py3-none-any.whl", hash = "sha256:225331e8ac4b32682c18fcac5a57a6f717c4e632cea5dd0e247b55155faeccde", size = 6723264, upload-time = "2024-12-17T10:53:35.645Z"} ] [[package]] name = "pygments" +sdist = {url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z"} +source = {registry = "https://pypi.org/simple"} version = "2.19.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, + {url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z"} ] [[package]] -name = "pymdown-extensions" -version = "10.21" -source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "markdown" }, - { name = "pyyaml" }, + {name = "markdown"}, + {name = "pyyaml"} ] -sdist = { url = "https://files.pythonhosted.org/packages/ba/63/06673d1eb6d8f83c0ea1f677d770e12565fb516928b4109c9e2055656a9e/pymdown_extensions-10.21.tar.gz", hash = "sha256:39f4a020f40773f6b2ff31d2cd2546c2c04d0a6498c31d9c688d2be07e1767d5", size = 853363, upload-time = "2026-02-15T20:44:06.748Z" } +name = "pymdown-extensions" +sdist = {url = "https://files.pythonhosted.org/packages/ba/63/06673d1eb6d8f83c0ea1f677d770e12565fb516928b4109c9e2055656a9e/pymdown_extensions-10.21.tar.gz", hash = "sha256:39f4a020f40773f6b2ff31d2cd2546c2c04d0a6498c31d9c688d2be07e1767d5", size = 853363, upload-time = "2026-02-15T20:44:06.748Z"} +source = {registry = "https://pypi.org/simple"} +version = "10.21" wheels = [ - { url = "https://files.pythonhosted.org/packages/6f/2c/5b079febdc65e1c3fb2729bf958d18b45be7113828528e8a0b5850dd819a/pymdown_extensions-10.21-py3-none-any.whl", hash = "sha256:91b879f9f864d49794c2d9534372b10150e6141096c3908a455e45ca72ad9d3f", size = 268877, upload-time = "2026-02-15T20:44:05.464Z" }, + {url = "https://files.pythonhosted.org/packages/6f/2c/5b079febdc65e1c3fb2729bf958d18b45be7113828528e8a0b5850dd819a/pymdown_extensions-10.21-py3-none-any.whl", hash = "sha256:91b879f9f864d49794c2d9534372b10150e6141096c3908a455e45ca72ad9d3f", size = 268877, upload-time = "2026-02-15T20:44:05.464Z"} ] [[package]] -name = "pytest" -version = "9.0.2" -source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, - { name = "iniconfig" }, - { name = "packaging" }, - { name = "pluggy" }, - { name = "pygments" }, + {name = "colorama", marker = "sys_platform == 'win32'"}, + {name = "iniconfig"}, + {name = "packaging"}, + {name = "pluggy"}, + {name = "pygments"} ] -sdist = { url = "https://files.pythonhosted.org/packages/d1/db/7ef3487e0fb0049ddb5ce41d3a49c235bf9ad299b6a25d5780a89f19230f/pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11", size = 1568901, upload-time = "2025-12-06T21:30:51.014Z" } +name = "pytest" +sdist = {url = "https://files.pythonhosted.org/packages/d1/db/7ef3487e0fb0049ddb5ce41d3a49c235bf9ad299b6a25d5780a89f19230f/pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11", size = 1568901, upload-time = "2025-12-06T21:30:51.014Z"} +source = {registry = "https://pypi.org/simple"} +version = "9.0.2" wheels = [ - { url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801, upload-time = "2025-12-06T21:30:49.154Z" }, + {url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801, upload-time = "2025-12-06T21:30:49.154Z"} ] [[package]] -name = "pytest-cov" -version = "7.0.0" -source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "coverage" }, - { name = "pluggy" }, - { name = "pytest" }, + {name = "coverage"}, + {name = "pluggy"}, + {name = "pytest"} ] -sdist = { url = "https://files.pythonhosted.org/packages/5e/f7/c933acc76f5208b3b00089573cf6a2bc26dc80a8aece8f52bb7d6b1855ca/pytest_cov-7.0.0.tar.gz", hash = "sha256:33c97eda2e049a0c5298e91f519302a1334c26ac65c1a483d6206fd458361af1", size = 54328, upload-time = "2025-09-09T10:57:02.113Z" } +name = "pytest-cov" +sdist = {url = "https://files.pythonhosted.org/packages/5e/f7/c933acc76f5208b3b00089573cf6a2bc26dc80a8aece8f52bb7d6b1855ca/pytest_cov-7.0.0.tar.gz", hash = "sha256:33c97eda2e049a0c5298e91f519302a1334c26ac65c1a483d6206fd458361af1", size = 54328, upload-time = "2025-09-09T10:57:02.113Z"} +source = {registry = "https://pypi.org/simple"} +version = "7.0.0" wheels = [ - { url = "https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl", hash = "sha256:3b8e9558b16cc1479da72058bdecf8073661c7f57f7d3c5f22a1c23507f2d861", size = 22424, upload-time = "2025-09-09T10:57:00.695Z" }, + {url = "https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl", hash = "sha256:3b8e9558b16cc1479da72058bdecf8073661c7f57f7d3c5f22a1c23507f2d861", size = 22424, upload-time = "2025-09-09T10:57:00.695Z"} ] [[package]] -name = "python-discovery" -version = "1.1.0" -source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "filelock" }, - { name = "platformdirs" }, + {name = "filelock"}, + {name = "platformdirs"} ] -sdist = { url = "https://files.pythonhosted.org/packages/82/bb/93a3e83bdf9322c7e21cafd092e56a4a17c4d8ef4277b6eb01af1a540a6f/python_discovery-1.1.0.tar.gz", hash = "sha256:447941ba1aed8cc2ab7ee3cb91be5fc137c5bdbb05b7e6ea62fbdcb66e50b268", size = 55674, upload-time = "2026-02-26T09:42:49.668Z" } +name = "python-discovery" +sdist = {url = "https://files.pythonhosted.org/packages/82/bb/93a3e83bdf9322c7e21cafd092e56a4a17c4d8ef4277b6eb01af1a540a6f/python_discovery-1.1.0.tar.gz", hash = "sha256:447941ba1aed8cc2ab7ee3cb91be5fc137c5bdbb05b7e6ea62fbdcb66e50b268", size = 55674, upload-time = "2026-02-26T09:42:49.668Z"} +source = {registry = "https://pypi.org/simple"} +version = "1.1.0" wheels = [ - { url = "https://files.pythonhosted.org/packages/06/54/82a6e2ef37f0f23dccac604b9585bdcbd0698604feb64807dcb72853693e/python_discovery-1.1.0-py3-none-any.whl", hash = "sha256:a162893b8809727f54594a99ad2179d2ede4bf953e12d4c7abc3cc9cdbd1437b", size = 30687, upload-time = "2026-02-26T09:42:48.548Z" }, + {url = "https://files.pythonhosted.org/packages/06/54/82a6e2ef37f0f23dccac604b9585bdcbd0698604feb64807dcb72853693e/python_discovery-1.1.0-py3-none-any.whl", hash = "sha256:a162893b8809727f54594a99ad2179d2ede4bf953e12d4c7abc3cc9cdbd1437b", size = 30687, upload-time = "2026-02-26T09:42:48.548Z"} ] [[package]] name = "pyyaml" +sdist = {url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z"} +source = {registry = "https://pypi.org/simple"} version = "6.0.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" }, - { url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" }, - { url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" }, - { url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" }, - { url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" }, - { url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" }, - { url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" }, - { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" }, - { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" }, - { url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" }, - { url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" }, - { url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" }, - { url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" }, - { url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" }, - { url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" }, - { url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" }, - { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" }, - { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" }, +wheels = [ + {url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z"}, + {url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z"}, + {url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z"}, + {url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z"}, + {url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z"}, + {url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z"}, + {url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z"}, + {url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z"}, + {url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z"}, + {url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z"}, + {url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z"}, + {url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z"}, + {url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z"}, + {url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z"}, + {url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z"}, + {url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z"}, + {url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z"}, + {url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z"} ] [[package]] -name = "requests" -version = "2.32.5" -source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "certifi" }, - { name = "charset-normalizer" }, - { name = "idna" }, - { name = "urllib3" }, + {name = "certifi"}, + {name = "charset-normalizer"}, + {name = "idna"}, + {name = "urllib3"} ] -sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" } +name = "requests" +sdist = {url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z"} +source = {registry = "https://pypi.org/simple"} +version = "2.32.5" wheels = [ - { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" }, + {url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z"} ] [[package]] name = "roman-numerals" +sdist = {url = "https://files.pythonhosted.org/packages/ae/f9/41dc953bbeb056c17d5f7a519f50fdf010bd0553be2d630bc69d1e022703/roman_numerals-4.1.0.tar.gz", hash = "sha256:1af8b147eb1405d5839e78aeb93131690495fe9da5c91856cb33ad55a7f1e5b2", size = 9077, upload-time = "2025-12-17T18:25:34.381Z"} +source = {registry = "https://pypi.org/simple"} version = "4.1.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ae/f9/41dc953bbeb056c17d5f7a519f50fdf010bd0553be2d630bc69d1e022703/roman_numerals-4.1.0.tar.gz", hash = "sha256:1af8b147eb1405d5839e78aeb93131690495fe9da5c91856cb33ad55a7f1e5b2", size = 9077, upload-time = "2025-12-17T18:25:34.381Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/04/54/6f679c435d28e0a568d8e8a7c0a93a09010818634c3c3907fc98d8983770/roman_numerals-4.1.0-py3-none-any.whl", hash = "sha256:647ba99caddc2cc1e55a51e4360689115551bf4476d90e8162cf8c345fe233c7", size = 7676, upload-time = "2025-12-17T18:25:33.098Z" }, + {url = "https://files.pythonhosted.org/packages/04/54/6f679c435d28e0a568d8e8a7c0a93a09010818634c3c3907fc98d8983770/roman_numerals-4.1.0-py3-none-any.whl", hash = "sha256:647ba99caddc2cc1e55a51e4360689115551bf4476d90e8162cf8c345fe233c7", size = 7676, upload-time = "2025-12-17T18:25:33.098Z"} ] [[package]] +dependencies = [ + {name = "numpy"} +] name = "scipy" +sdist = {url = "https://files.pythonhosted.org/packages/7a/97/5a3609c4f8d58b039179648e62dd220f89864f56f7357f5d4f45c29eb2cc/scipy-1.17.1.tar.gz", hash = "sha256:95d8e012d8cb8816c226aef832200b1d45109ed4464303e997c5b13122b297c0", size = 30573822, upload-time = "2026-02-23T00:26:24.851Z"} +source = {registry = "https://pypi.org/simple"} version = "1.17.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "numpy" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/7a/97/5a3609c4f8d58b039179648e62dd220f89864f56f7357f5d4f45c29eb2cc/scipy-1.17.1.tar.gz", hash = "sha256:95d8e012d8cb8816c226aef832200b1d45109ed4464303e997c5b13122b297c0", size = 30573822, upload-time = "2026-02-23T00:26:24.851Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/cf/83/333afb452af6f0fd70414dc04f898647ee1423979ce02efa75c3b0f2c28e/scipy-1.17.1-cp314-cp314-macosx_10_14_x86_64.whl", hash = "sha256:a48a72c77a310327f6a3a920092fa2b8fd03d7deaa60f093038f22d98e096717", size = 31584510, upload-time = "2026-02-23T00:21:01.015Z" }, - { url = "https://files.pythonhosted.org/packages/ed/a6/d05a85fd51daeb2e4ea71d102f15b34fedca8e931af02594193ae4fd25f7/scipy-1.17.1-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:45abad819184f07240d8a696117a7aacd39787af9e0b719d00285549ed19a1e9", size = 28170131, upload-time = "2026-02-23T00:21:05.888Z" }, - { url = "https://files.pythonhosted.org/packages/db/7b/8624a203326675d7746a254083a187398090a179335b2e4a20e2ddc46e83/scipy-1.17.1-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:3fd1fcdab3ea951b610dc4cef356d416d5802991e7e32b5254828d342f7b7e0b", size = 20342032, upload-time = "2026-02-23T00:21:09.904Z" }, - { url = "https://files.pythonhosted.org/packages/c9/35/2c342897c00775d688d8ff3987aced3426858fd89d5a0e26e020b660b301/scipy-1.17.1-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:7bdf2da170b67fdf10bca777614b1c7d96ae3ca5794fd9587dce41eb2966e866", size = 22678766, upload-time = "2026-02-23T00:21:14.313Z" }, - { url = "https://files.pythonhosted.org/packages/ef/f2/7cdb8eb308a1a6ae1e19f945913c82c23c0c442a462a46480ce487fdc0ac/scipy-1.17.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:adb2642e060a6549c343603a3851ba76ef0b74cc8c079a9a58121c7ec9fe2350", size = 32957007, upload-time = "2026-02-23T00:21:19.663Z" }, - { url = "https://files.pythonhosted.org/packages/0b/2e/7eea398450457ecb54e18e9d10110993fa65561c4f3add5e8eccd2b9cd41/scipy-1.17.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:eee2cfda04c00a857206a4330f0c5e3e56535494e30ca445eb19ec624ae75118", size = 35221333, upload-time = "2026-02-23T00:21:25.278Z" }, - { url = "https://files.pythonhosted.org/packages/d9/77/5b8509d03b77f093a0d52e606d3c4f79e8b06d1d38c441dacb1e26cacf46/scipy-1.17.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d2650c1fb97e184d12d8ba010493ee7b322864f7d3d00d3f9bb97d9c21de4068", size = 35042066, upload-time = "2026-02-23T00:21:31.358Z" }, - { url = "https://files.pythonhosted.org/packages/f9/df/18f80fb99df40b4070328d5ae5c596f2f00fffb50167e31439e932f29e7d/scipy-1.17.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:08b900519463543aa604a06bec02461558a6e1cef8fdbb8098f77a48a83c8118", size = 37612763, upload-time = "2026-02-23T00:21:37.247Z" }, - { url = "https://files.pythonhosted.org/packages/4b/39/f0e8ea762a764a9dc52aa7dabcfad51a354819de1f0d4652b6a1122424d6/scipy-1.17.1-cp314-cp314-win_amd64.whl", hash = "sha256:3877ac408e14da24a6196de0ddcace62092bfc12a83823e92e49e40747e52c19", size = 37290984, upload-time = "2026-02-23T00:22:35.023Z" }, - { url = "https://files.pythonhosted.org/packages/7c/56/fe201e3b0f93d1a8bcf75d3379affd228a63d7e2d80ab45467a74b494947/scipy-1.17.1-cp314-cp314-win_arm64.whl", hash = "sha256:f8885db0bc2bffa59d5c1b72fad7a6a92d3e80e7257f967dd81abb553a90d293", size = 25192877, upload-time = "2026-02-23T00:22:39.798Z" }, - { url = "https://files.pythonhosted.org/packages/96/ad/f8c414e121f82e02d76f310f16db9899c4fcde36710329502a6b2a3c0392/scipy-1.17.1-cp314-cp314t-macosx_10_14_x86_64.whl", hash = "sha256:1cc682cea2ae55524432f3cdff9e9a3be743d52a7443d0cba9017c23c87ae2f6", size = 31949750, upload-time = "2026-02-23T00:21:42.289Z" }, - { url = "https://files.pythonhosted.org/packages/7c/b0/c741e8865d61b67c81e255f4f0a832846c064e426636cd7de84e74d209be/scipy-1.17.1-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:2040ad4d1795a0ae89bfc7e8429677f365d45aa9fd5e4587cf1ea737f927b4a1", size = 28585858, upload-time = "2026-02-23T00:21:47.706Z" }, - { url = "https://files.pythonhosted.org/packages/ed/1b/3985219c6177866628fa7c2595bfd23f193ceebbe472c98a08824b9466ff/scipy-1.17.1-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:131f5aaea57602008f9822e2115029b55d4b5f7c070287699fe45c661d051e39", size = 20757723, upload-time = "2026-02-23T00:21:52.039Z" }, - { url = "https://files.pythonhosted.org/packages/c0/19/2a04aa25050d656d6f7b9e7b685cc83d6957fb101665bfd9369ca6534563/scipy-1.17.1-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:9cdc1a2fcfd5c52cfb3045feb399f7b3ce822abdde3a193a6b9a60b3cb5854ca", size = 23043098, upload-time = "2026-02-23T00:21:56.185Z" }, - { url = "https://files.pythonhosted.org/packages/86/f1/3383beb9b5d0dbddd030335bf8a8b32d4317185efe495374f134d8be6cce/scipy-1.17.1-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e3dcd57ab780c741fde8dc68619de988b966db759a3c3152e8e9142c26295ad", size = 33030397, upload-time = "2026-02-23T00:22:01.404Z" }, - { url = "https://files.pythonhosted.org/packages/41/68/8f21e8a65a5a03f25a79165ec9d2b28c00e66dc80546cf5eb803aeeff35b/scipy-1.17.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a9956e4d4f4a301ebf6cde39850333a6b6110799d470dbbb1e25326ac447f52a", size = 35281163, upload-time = "2026-02-23T00:22:07.024Z" }, - { url = "https://files.pythonhosted.org/packages/84/8d/c8a5e19479554007a5632ed7529e665c315ae7492b4f946b0deb39870e39/scipy-1.17.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:a4328d245944d09fd639771de275701ccadf5f781ba0ff092ad141e017eccda4", size = 35116291, upload-time = "2026-02-23T00:22:12.585Z" }, - { url = "https://files.pythonhosted.org/packages/52/52/e57eceff0e342a1f50e274264ed47497b59e6a4e3118808ee58ddda7b74a/scipy-1.17.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a77cbd07b940d326d39a1d1b37817e2ee4d79cb30e7338f3d0cddffae70fcaa2", size = 37682317, upload-time = "2026-02-23T00:22:18.513Z" }, - { url = "https://files.pythonhosted.org/packages/11/2f/b29eafe4a3fbc3d6de9662b36e028d5f039e72d345e05c250e121a230dd4/scipy-1.17.1-cp314-cp314t-win_amd64.whl", hash = "sha256:eb092099205ef62cd1782b006658db09e2fed75bffcae7cc0d44052d8aa0f484", size = 37345327, upload-time = "2026-02-23T00:22:24.442Z" }, - { url = "https://files.pythonhosted.org/packages/07/39/338d9219c4e87f3e708f18857ecd24d22a0c3094752393319553096b98af/scipy-1.17.1-cp314-cp314t-win_arm64.whl", hash = "sha256:200e1050faffacc162be6a486a984a0497866ec54149a01270adc8a59b7c7d21", size = 25489165, upload-time = "2026-02-23T00:22:29.563Z" }, +wheels = [ + {url = "https://files.pythonhosted.org/packages/cf/83/333afb452af6f0fd70414dc04f898647ee1423979ce02efa75c3b0f2c28e/scipy-1.17.1-cp314-cp314-macosx_10_14_x86_64.whl", hash = "sha256:a48a72c77a310327f6a3a920092fa2b8fd03d7deaa60f093038f22d98e096717", size = 31584510, upload-time = "2026-02-23T00:21:01.015Z"}, + {url = "https://files.pythonhosted.org/packages/ed/a6/d05a85fd51daeb2e4ea71d102f15b34fedca8e931af02594193ae4fd25f7/scipy-1.17.1-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:45abad819184f07240d8a696117a7aacd39787af9e0b719d00285549ed19a1e9", size = 28170131, upload-time = "2026-02-23T00:21:05.888Z"}, + {url = "https://files.pythonhosted.org/packages/db/7b/8624a203326675d7746a254083a187398090a179335b2e4a20e2ddc46e83/scipy-1.17.1-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:3fd1fcdab3ea951b610dc4cef356d416d5802991e7e32b5254828d342f7b7e0b", size = 20342032, upload-time = "2026-02-23T00:21:09.904Z"}, + {url = "https://files.pythonhosted.org/packages/c9/35/2c342897c00775d688d8ff3987aced3426858fd89d5a0e26e020b660b301/scipy-1.17.1-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:7bdf2da170b67fdf10bca777614b1c7d96ae3ca5794fd9587dce41eb2966e866", size = 22678766, upload-time = "2026-02-23T00:21:14.313Z"}, + {url = "https://files.pythonhosted.org/packages/ef/f2/7cdb8eb308a1a6ae1e19f945913c82c23c0c442a462a46480ce487fdc0ac/scipy-1.17.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:adb2642e060a6549c343603a3851ba76ef0b74cc8c079a9a58121c7ec9fe2350", size = 32957007, upload-time = "2026-02-23T00:21:19.663Z"}, + {url = "https://files.pythonhosted.org/packages/0b/2e/7eea398450457ecb54e18e9d10110993fa65561c4f3add5e8eccd2b9cd41/scipy-1.17.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:eee2cfda04c00a857206a4330f0c5e3e56535494e30ca445eb19ec624ae75118", size = 35221333, upload-time = "2026-02-23T00:21:25.278Z"}, + {url = "https://files.pythonhosted.org/packages/d9/77/5b8509d03b77f093a0d52e606d3c4f79e8b06d1d38c441dacb1e26cacf46/scipy-1.17.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d2650c1fb97e184d12d8ba010493ee7b322864f7d3d00d3f9bb97d9c21de4068", size = 35042066, upload-time = "2026-02-23T00:21:31.358Z"}, + {url = "https://files.pythonhosted.org/packages/f9/df/18f80fb99df40b4070328d5ae5c596f2f00fffb50167e31439e932f29e7d/scipy-1.17.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:08b900519463543aa604a06bec02461558a6e1cef8fdbb8098f77a48a83c8118", size = 37612763, upload-time = "2026-02-23T00:21:37.247Z"}, + {url = "https://files.pythonhosted.org/packages/4b/39/f0e8ea762a764a9dc52aa7dabcfad51a354819de1f0d4652b6a1122424d6/scipy-1.17.1-cp314-cp314-win_amd64.whl", hash = "sha256:3877ac408e14da24a6196de0ddcace62092bfc12a83823e92e49e40747e52c19", size = 37290984, upload-time = "2026-02-23T00:22:35.023Z"}, + {url = "https://files.pythonhosted.org/packages/7c/56/fe201e3b0f93d1a8bcf75d3379affd228a63d7e2d80ab45467a74b494947/scipy-1.17.1-cp314-cp314-win_arm64.whl", hash = "sha256:f8885db0bc2bffa59d5c1b72fad7a6a92d3e80e7257f967dd81abb553a90d293", size = 25192877, upload-time = "2026-02-23T00:22:39.798Z"}, + {url = "https://files.pythonhosted.org/packages/96/ad/f8c414e121f82e02d76f310f16db9899c4fcde36710329502a6b2a3c0392/scipy-1.17.1-cp314-cp314t-macosx_10_14_x86_64.whl", hash = "sha256:1cc682cea2ae55524432f3cdff9e9a3be743d52a7443d0cba9017c23c87ae2f6", size = 31949750, upload-time = "2026-02-23T00:21:42.289Z"}, + {url = "https://files.pythonhosted.org/packages/7c/b0/c741e8865d61b67c81e255f4f0a832846c064e426636cd7de84e74d209be/scipy-1.17.1-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:2040ad4d1795a0ae89bfc7e8429677f365d45aa9fd5e4587cf1ea737f927b4a1", size = 28585858, upload-time = "2026-02-23T00:21:47.706Z"}, + {url = "https://files.pythonhosted.org/packages/ed/1b/3985219c6177866628fa7c2595bfd23f193ceebbe472c98a08824b9466ff/scipy-1.17.1-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:131f5aaea57602008f9822e2115029b55d4b5f7c070287699fe45c661d051e39", size = 20757723, upload-time = "2026-02-23T00:21:52.039Z"}, + {url = "https://files.pythonhosted.org/packages/c0/19/2a04aa25050d656d6f7b9e7b685cc83d6957fb101665bfd9369ca6534563/scipy-1.17.1-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:9cdc1a2fcfd5c52cfb3045feb399f7b3ce822abdde3a193a6b9a60b3cb5854ca", size = 23043098, upload-time = "2026-02-23T00:21:56.185Z"}, + {url = "https://files.pythonhosted.org/packages/86/f1/3383beb9b5d0dbddd030335bf8a8b32d4317185efe495374f134d8be6cce/scipy-1.17.1-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e3dcd57ab780c741fde8dc68619de988b966db759a3c3152e8e9142c26295ad", size = 33030397, upload-time = "2026-02-23T00:22:01.404Z"}, + {url = "https://files.pythonhosted.org/packages/41/68/8f21e8a65a5a03f25a79165ec9d2b28c00e66dc80546cf5eb803aeeff35b/scipy-1.17.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a9956e4d4f4a301ebf6cde39850333a6b6110799d470dbbb1e25326ac447f52a", size = 35281163, upload-time = "2026-02-23T00:22:07.024Z"}, + {url = "https://files.pythonhosted.org/packages/84/8d/c8a5e19479554007a5632ed7529e665c315ae7492b4f946b0deb39870e39/scipy-1.17.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:a4328d245944d09fd639771de275701ccadf5f781ba0ff092ad141e017eccda4", size = 35116291, upload-time = "2026-02-23T00:22:12.585Z"}, + {url = "https://files.pythonhosted.org/packages/52/52/e57eceff0e342a1f50e274264ed47497b59e6a4e3118808ee58ddda7b74a/scipy-1.17.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a77cbd07b940d326d39a1d1b37817e2ee4d79cb30e7338f3d0cddffae70fcaa2", size = 37682317, upload-time = "2026-02-23T00:22:18.513Z"}, + {url = "https://files.pythonhosted.org/packages/11/2f/b29eafe4a3fbc3d6de9662b36e028d5f039e72d345e05c250e121a230dd4/scipy-1.17.1-cp314-cp314t-win_amd64.whl", hash = "sha256:eb092099205ef62cd1782b006658db09e2fed75bffcae7cc0d44052d8aa0f484", size = 37345327, upload-time = "2026-02-23T00:22:24.442Z"}, + {url = "https://files.pythonhosted.org/packages/07/39/338d9219c4e87f3e708f18857ecd24d22a0c3094752393319553096b98af/scipy-1.17.1-cp314-cp314t-win_arm64.whl", hash = "sha256:200e1050faffacc162be6a486a984a0497866ec54149a01270adc8a59b7c7d21", size = 25489165, upload-time = "2026-02-23T00:22:29.563Z"} ] [[package]] name = "snowballstemmer" +sdist = {url = "https://files.pythonhosted.org/packages/75/a7/9810d872919697c9d01295633f5d574fb416d47e535f258272ca1f01f447/snowballstemmer-3.0.1.tar.gz", hash = "sha256:6d5eeeec8e9f84d4d56b847692bacf79bc2c8e90c7f80ca4444ff8b6f2e52895", size = 105575, upload-time = "2025-05-09T16:34:51.843Z"} +source = {registry = "https://pypi.org/simple"} version = "3.0.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/75/a7/9810d872919697c9d01295633f5d574fb416d47e535f258272ca1f01f447/snowballstemmer-3.0.1.tar.gz", hash = "sha256:6d5eeeec8e9f84d4d56b847692bacf79bc2c8e90c7f80ca4444ff8b6f2e52895", size = 105575, upload-time = "2025-05-09T16:34:51.843Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c8/78/3565d011c61f5a43488987ee32b6f3f656e7f107ac2782dd57bdd7d91d9a/snowballstemmer-3.0.1-py3-none-any.whl", hash = "sha256:6cd7b3897da8d6c9ffb968a6781fa6532dce9c3618a4b127d920dab764a19064", size = 103274, upload-time = "2025-05-09T16:34:50.371Z" }, + {url = "https://files.pythonhosted.org/packages/c8/78/3565d011c61f5a43488987ee32b6f3f656e7f107ac2782dd57bdd7d91d9a/snowballstemmer-3.0.1-py3-none-any.whl", hash = "sha256:6cd7b3897da8d6c9ffb968a6781fa6532dce9c3618a4b127d920dab764a19064", size = 103274, upload-time = "2025-05-09T16:34:50.371Z"} ] [[package]] name = "soupsieve" +sdist = {url = "https://files.pythonhosted.org/packages/7b/ae/2d9c981590ed9999a0d91755b47fc74f74de286b0f5cee14c9269041e6c4/soupsieve-2.8.3.tar.gz", hash = "sha256:3267f1eeea4251fb42728b6dfb746edc9acaffc4a45b27e19450b676586e8349", size = 118627, upload-time = "2026-01-20T04:27:02.457Z"} +source = {registry = "https://pypi.org/simple"} version = "2.8.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7b/ae/2d9c981590ed9999a0d91755b47fc74f74de286b0f5cee14c9269041e6c4/soupsieve-2.8.3.tar.gz", hash = "sha256:3267f1eeea4251fb42728b6dfb746edc9acaffc4a45b27e19450b676586e8349", size = 118627, upload-time = "2026-01-20T04:27:02.457Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/46/2c/1462b1d0a634697ae9e55b3cecdcb64788e8b7d63f54d923fcd0bb140aed/soupsieve-2.8.3-py3-none-any.whl", hash = "sha256:ed64f2ba4eebeab06cc4962affce381647455978ffc1e36bb79a545b91f45a95", size = 37016, upload-time = "2026-01-20T04:27:01.012Z" }, + {url = "https://files.pythonhosted.org/packages/46/2c/1462b1d0a634697ae9e55b3cecdcb64788e8b7d63f54d923fcd0bb140aed/soupsieve-2.8.3-py3-none-any.whl", hash = "sha256:ed64f2ba4eebeab06cc4962affce381647455978ffc1e36bb79a545b91f45a95", size = 37016, upload-time = "2026-01-20T04:27:01.012Z"} ] [[package]] +dependencies = [ + {name = "alabaster"}, + {name = "babel"}, + {name = "colorama", marker = "sys_platform == 'win32'"}, + {name = "docutils"}, + {name = "imagesize"}, + {name = "jinja2"}, + {name = "packaging"}, + {name = "pygments"}, + {name = "requests"}, + {name = "roman-numerals"}, + {name = "snowballstemmer"}, + {name = "sphinxcontrib-applehelp"}, + {name = "sphinxcontrib-devhelp"}, + {name = "sphinxcontrib-htmlhelp"}, + {name = "sphinxcontrib-jsmath"}, + {name = "sphinxcontrib-qthelp"}, + {name = "sphinxcontrib-serializinghtml"} +] name = "sphinx" +sdist = {url = "https://files.pythonhosted.org/packages/cd/bd/f08eb0f4eed5c83f1ba2a3bd18f7745a2b1525fad70660a1c00224ec468a/sphinx-9.1.0.tar.gz", hash = "sha256:7741722357dd75f8190766926071fed3bdc211c74dd2d7d4df5404da95930ddb", size = 8718324, upload-time = "2025-12-31T15:09:27.646Z"} +source = {registry = "https://pypi.org/simple"} version = "9.1.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "alabaster" }, - { name = "babel" }, - { name = "colorama", marker = "sys_platform == 'win32'" }, - { name = "docutils" }, - { name = "imagesize" }, - { name = "jinja2" }, - { name = "packaging" }, - { name = "pygments" }, - { name = "requests" }, - { name = "roman-numerals" }, - { name = "snowballstemmer" }, - { name = "sphinxcontrib-applehelp" }, - { name = "sphinxcontrib-devhelp" }, - { name = "sphinxcontrib-htmlhelp" }, - { name = "sphinxcontrib-jsmath" }, - { name = "sphinxcontrib-qthelp" }, - { name = "sphinxcontrib-serializinghtml" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/cd/bd/f08eb0f4eed5c83f1ba2a3bd18f7745a2b1525fad70660a1c00224ec468a/sphinx-9.1.0.tar.gz", hash = "sha256:7741722357dd75f8190766926071fed3bdc211c74dd2d7d4df5404da95930ddb", size = 8718324, upload-time = "2025-12-31T15:09:27.646Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/73/f7/b1884cb3188ab181fc81fa00c266699dab600f927a964df02ec3d5d1916a/sphinx-9.1.0-py3-none-any.whl", hash = "sha256:c84fdd4e782504495fe4f2c0b3413d6c2bf388589bb352d439b2a3bb99991978", size = 3921742, upload-time = "2025-12-31T15:09:25.561Z" }, +wheels = [ + {url = "https://files.pythonhosted.org/packages/73/f7/b1884cb3188ab181fc81fa00c266699dab600f927a964df02ec3d5d1916a/sphinx-9.1.0-py3-none-any.whl", hash = "sha256:c84fdd4e782504495fe4f2c0b3413d6c2bf388589bb352d439b2a3bb99991978", size = 3921742, upload-time = "2025-12-31T15:09:25.561Z"} ] [[package]] -name = "sphinx-autoapi" -version = "3.7.0" -source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "astroid" }, - { name = "jinja2" }, - { name = "pyyaml" }, - { name = "sphinx" }, + {name = "astroid"}, + {name = "jinja2"}, + {name = "pyyaml"}, + {name = "sphinx"} ] -sdist = { url = "https://files.pythonhosted.org/packages/47/22/7ea4b660e98ffa271dbec5847b64738127955746d887f9596940279d30bf/sphinx_autoapi-3.7.0.tar.gz", hash = "sha256:5c8a934d788f00d11b8c8092536c7373592cce986820de8dd7daf6dfd79afd91", size = 58136, upload-time = "2026-02-11T05:24:29.86Z" } +name = "sphinx-autoapi" +sdist = {url = "https://files.pythonhosted.org/packages/47/22/7ea4b660e98ffa271dbec5847b64738127955746d887f9596940279d30bf/sphinx_autoapi-3.7.0.tar.gz", hash = "sha256:5c8a934d788f00d11b8c8092536c7373592cce986820de8dd7daf6dfd79afd91", size = 58136, upload-time = "2026-02-11T05:24:29.86Z"} +source = {registry = "https://pypi.org/simple"} +version = "3.7.0" wheels = [ - { url = "https://files.pythonhosted.org/packages/d8/11/9825443890d3ef6b6a0db938054e7c806e9911509e0315fe984d00a090c1/sphinx_autoapi-3.7.0-py3-none-any.whl", hash = "sha256:5ea37271b50de08538cf6e33044ef4fb6e981ddb693060ec84f297005014fdfd", size = 36021, upload-time = "2026-02-11T05:24:28.657Z" }, + {url = "https://files.pythonhosted.org/packages/d8/11/9825443890d3ef6b6a0db938054e7c806e9911509e0315fe984d00a090c1/sphinx_autoapi-3.7.0-py3-none-any.whl", hash = "sha256:5ea37271b50de08538cf6e33044ef4fb6e981ddb693060ec84f297005014fdfd", size = 36021, upload-time = "2026-02-11T05:24:28.657Z"} ] [[package]] name = "sphinxcontrib-applehelp" +sdist = {url = "https://files.pythonhosted.org/packages/ba/6e/b837e84a1a704953c62ef8776d45c3e8d759876b4a84fe14eba2859106fe/sphinxcontrib_applehelp-2.0.0.tar.gz", hash = "sha256:2f29ef331735ce958efa4734873f084941970894c6090408b079c61b2e1c06d1", size = 20053, upload-time = "2024-07-29T01:09:00.465Z"} +source = {registry = "https://pypi.org/simple"} version = "2.0.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ba/6e/b837e84a1a704953c62ef8776d45c3e8d759876b4a84fe14eba2859106fe/sphinxcontrib_applehelp-2.0.0.tar.gz", hash = "sha256:2f29ef331735ce958efa4734873f084941970894c6090408b079c61b2e1c06d1", size = 20053, upload-time = "2024-07-29T01:09:00.465Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5d/85/9ebeae2f76e9e77b952f4b274c27238156eae7979c5421fba91a28f4970d/sphinxcontrib_applehelp-2.0.0-py3-none-any.whl", hash = "sha256:4cd3f0ec4ac5dd9c17ec65e9ab272c9b867ea77425228e68ecf08d6b28ddbdb5", size = 119300, upload-time = "2024-07-29T01:08:58.99Z" }, + {url = "https://files.pythonhosted.org/packages/5d/85/9ebeae2f76e9e77b952f4b274c27238156eae7979c5421fba91a28f4970d/sphinxcontrib_applehelp-2.0.0-py3-none-any.whl", hash = "sha256:4cd3f0ec4ac5dd9c17ec65e9ab272c9b867ea77425228e68ecf08d6b28ddbdb5", size = 119300, upload-time = "2024-07-29T01:08:58.99Z"} ] [[package]] name = "sphinxcontrib-devhelp" +sdist = {url = "https://files.pythonhosted.org/packages/f6/d2/5beee64d3e4e747f316bae86b55943f51e82bb86ecd325883ef65741e7da/sphinxcontrib_devhelp-2.0.0.tar.gz", hash = "sha256:411f5d96d445d1d73bb5d52133377b4248ec79db5c793ce7dbe59e074b4dd1ad", size = 12967, upload-time = "2024-07-29T01:09:23.417Z"} +source = {registry = "https://pypi.org/simple"} version = "2.0.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f6/d2/5beee64d3e4e747f316bae86b55943f51e82bb86ecd325883ef65741e7da/sphinxcontrib_devhelp-2.0.0.tar.gz", hash = "sha256:411f5d96d445d1d73bb5d52133377b4248ec79db5c793ce7dbe59e074b4dd1ad", size = 12967, upload-time = "2024-07-29T01:09:23.417Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/35/7a/987e583882f985fe4d7323774889ec58049171828b58c2217e7f79cdf44e/sphinxcontrib_devhelp-2.0.0-py3-none-any.whl", hash = "sha256:aefb8b83854e4b0998877524d1029fd3e6879210422ee3780459e28a1f03a8a2", size = 82530, upload-time = "2024-07-29T01:09:21.945Z" }, + {url = "https://files.pythonhosted.org/packages/35/7a/987e583882f985fe4d7323774889ec58049171828b58c2217e7f79cdf44e/sphinxcontrib_devhelp-2.0.0-py3-none-any.whl", hash = "sha256:aefb8b83854e4b0998877524d1029fd3e6879210422ee3780459e28a1f03a8a2", size = 82530, upload-time = "2024-07-29T01:09:21.945Z"} ] [[package]] name = "sphinxcontrib-htmlhelp" +sdist = {url = "https://files.pythonhosted.org/packages/43/93/983afd9aa001e5201eab16b5a444ed5b9b0a7a010541e0ddfbbfd0b2470c/sphinxcontrib_htmlhelp-2.1.0.tar.gz", hash = "sha256:c9e2916ace8aad64cc13a0d233ee22317f2b9025b9cf3295249fa985cc7082e9", size = 22617, upload-time = "2024-07-29T01:09:37.889Z"} +source = {registry = "https://pypi.org/simple"} version = "2.1.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/43/93/983afd9aa001e5201eab16b5a444ed5b9b0a7a010541e0ddfbbfd0b2470c/sphinxcontrib_htmlhelp-2.1.0.tar.gz", hash = "sha256:c9e2916ace8aad64cc13a0d233ee22317f2b9025b9cf3295249fa985cc7082e9", size = 22617, upload-time = "2024-07-29T01:09:37.889Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0a/7b/18a8c0bcec9182c05a0b3ec2a776bba4ead82750a55ff798e8d406dae604/sphinxcontrib_htmlhelp-2.1.0-py3-none-any.whl", hash = "sha256:166759820b47002d22914d64a075ce08f4c46818e17cfc9470a9786b759b19f8", size = 98705, upload-time = "2024-07-29T01:09:36.407Z" }, + {url = "https://files.pythonhosted.org/packages/0a/7b/18a8c0bcec9182c05a0b3ec2a776bba4ead82750a55ff798e8d406dae604/sphinxcontrib_htmlhelp-2.1.0-py3-none-any.whl", hash = "sha256:166759820b47002d22914d64a075ce08f4c46818e17cfc9470a9786b759b19f8", size = 98705, upload-time = "2024-07-29T01:09:36.407Z"} ] [[package]] name = "sphinxcontrib-jsmath" +sdist = {url = "https://files.pythonhosted.org/packages/b2/e8/9ed3830aeed71f17c026a07a5097edcf44b692850ef215b161b8ad875729/sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8", size = 5787, upload-time = "2019-01-21T16:10:16.347Z"} +source = {registry = "https://pypi.org/simple"} version = "1.0.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b2/e8/9ed3830aeed71f17c026a07a5097edcf44b692850ef215b161b8ad875729/sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8", size = 5787, upload-time = "2019-01-21T16:10:16.347Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c2/42/4c8646762ee83602e3fb3fbe774c2fac12f317deb0b5dbeeedd2d3ba4b77/sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178", size = 5071, upload-time = "2019-01-21T16:10:14.333Z" }, + {url = "https://files.pythonhosted.org/packages/c2/42/4c8646762ee83602e3fb3fbe774c2fac12f317deb0b5dbeeedd2d3ba4b77/sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178", size = 5071, upload-time = "2019-01-21T16:10:14.333Z"} ] [[package]] name = "sphinxcontrib-qthelp" +sdist = {url = "https://files.pythonhosted.org/packages/68/bc/9104308fc285eb3e0b31b67688235db556cd5b0ef31d96f30e45f2e51cae/sphinxcontrib_qthelp-2.0.0.tar.gz", hash = "sha256:4fe7d0ac8fc171045be623aba3e2a8f613f8682731f9153bb2e40ece16b9bbab", size = 17165, upload-time = "2024-07-29T01:09:56.435Z"} +source = {registry = "https://pypi.org/simple"} version = "2.0.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/68/bc/9104308fc285eb3e0b31b67688235db556cd5b0ef31d96f30e45f2e51cae/sphinxcontrib_qthelp-2.0.0.tar.gz", hash = "sha256:4fe7d0ac8fc171045be623aba3e2a8f613f8682731f9153bb2e40ece16b9bbab", size = 17165, upload-time = "2024-07-29T01:09:56.435Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/27/83/859ecdd180cacc13b1f7e857abf8582a64552ea7a061057a6c716e790fce/sphinxcontrib_qthelp-2.0.0-py3-none-any.whl", hash = "sha256:b18a828cdba941ccd6ee8445dbe72ffa3ef8cbe7505d8cd1fa0d42d3f2d5f3eb", size = 88743, upload-time = "2024-07-29T01:09:54.885Z" }, + {url = "https://files.pythonhosted.org/packages/27/83/859ecdd180cacc13b1f7e857abf8582a64552ea7a061057a6c716e790fce/sphinxcontrib_qthelp-2.0.0-py3-none-any.whl", hash = "sha256:b18a828cdba941ccd6ee8445dbe72ffa3ef8cbe7505d8cd1fa0d42d3f2d5f3eb", size = 88743, upload-time = "2024-07-29T01:09:54.885Z"} ] [[package]] name = "sphinxcontrib-serializinghtml" +sdist = {url = "https://files.pythonhosted.org/packages/3b/44/6716b257b0aa6bfd51a1b31665d1c205fb12cb5ad56de752dfa15657de2f/sphinxcontrib_serializinghtml-2.0.0.tar.gz", hash = "sha256:e9d912827f872c029017a53f0ef2180b327c3f7fd23c87229f7a8e8b70031d4d", size = 16080, upload-time = "2024-07-29T01:10:09.332Z"} +source = {registry = "https://pypi.org/simple"} version = "2.0.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/3b/44/6716b257b0aa6bfd51a1b31665d1c205fb12cb5ad56de752dfa15657de2f/sphinxcontrib_serializinghtml-2.0.0.tar.gz", hash = "sha256:e9d912827f872c029017a53f0ef2180b327c3f7fd23c87229f7a8e8b70031d4d", size = 16080, upload-time = "2024-07-29T01:10:09.332Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/52/a7/d2782e4e3f77c8450f727ba74a8f12756d5ba823d81b941f1b04da9d033a/sphinxcontrib_serializinghtml-2.0.0-py3-none-any.whl", hash = "sha256:6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331", size = 92072, upload-time = "2024-07-29T01:10:08.203Z" }, + {url = "https://files.pythonhosted.org/packages/52/a7/d2782e4e3f77c8450f727ba74a8f12756d5ba823d81b941f1b04da9d033a/sphinxcontrib_serializinghtml-2.0.0-py3-none-any.whl", hash = "sha256:6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331", size = 92072, upload-time = "2024-07-29T01:10:08.203Z"} ] [[package]] -name = "starlette" -version = "0.52.1" -source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "anyio" }, + {name = "anyio"} ] -sdist = { url = "https://files.pythonhosted.org/packages/c4/68/79977123bb7be889ad680d79a40f339082c1978b5cfcf62c2d8d196873ac/starlette-0.52.1.tar.gz", hash = "sha256:834edd1b0a23167694292e94f597773bc3f89f362be6effee198165a35d62933", size = 2653702, upload-time = "2026-01-18T13:34:11.062Z" } +name = "starlette" +sdist = {url = "https://files.pythonhosted.org/packages/c4/68/79977123bb7be889ad680d79a40f339082c1978b5cfcf62c2d8d196873ac/starlette-0.52.1.tar.gz", hash = "sha256:834edd1b0a23167694292e94f597773bc3f89f362be6effee198165a35d62933", size = 2653702, upload-time = "2026-01-18T13:34:11.062Z"} +source = {registry = "https://pypi.org/simple"} +version = "0.52.1" wheels = [ - { url = "https://files.pythonhosted.org/packages/81/0d/13d1d239a25cbfb19e740db83143e95c772a1fe10202dda4b76792b114dd/starlette-0.52.1-py3-none-any.whl", hash = "sha256:0029d43eb3d273bc4f83a08720b4912ea4b071087a3b48db01b7c839f7954d74", size = 74272, upload-time = "2026-01-18T13:34:09.188Z" }, + {url = "https://files.pythonhosted.org/packages/81/0d/13d1d239a25cbfb19e740db83143e95c772a1fe10202dda4b76792b114dd/starlette-0.52.1-py3-none-any.whl", hash = "sha256:0029d43eb3d273bc4f83a08720b4912ea4b071087a3b48db01b7c839f7954d74", size = 74272, upload-time = "2026-01-18T13:34:09.188Z"} ] [[package]] name = "tomlkit" +sdist = {url = "https://files.pythonhosted.org/packages/c3/af/14b24e41977adb296d6bd1fb59402cf7d60ce364f90c890bd2ec65c43b5a/tomlkit-0.14.0.tar.gz", hash = "sha256:cf00efca415dbd57575befb1f6634c4f42d2d87dbba376128adb42c121b87064", size = 187167, upload-time = "2026-01-13T01:14:53.304Z"} +source = {registry = "https://pypi.org/simple"} version = "0.14.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c3/af/14b24e41977adb296d6bd1fb59402cf7d60ce364f90c890bd2ec65c43b5a/tomlkit-0.14.0.tar.gz", hash = "sha256:cf00efca415dbd57575befb1f6634c4f42d2d87dbba376128adb42c121b87064", size = 187167, upload-time = "2026-01-13T01:14:53.304Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b5/11/87d6d29fb5d237229d67973a6c9e06e048f01cf4994dee194ab0ea841814/tomlkit-0.14.0-py3-none-any.whl", hash = "sha256:592064ed85b40fa213469f81ac584f67a4f2992509a7c3ea2d632208623a3680", size = 39310, upload-time = "2026-01-13T01:14:51.965Z" }, + {url = "https://files.pythonhosted.org/packages/b5/11/87d6d29fb5d237229d67973a6c9e06e048f01cf4994dee194ab0ea841814/tomlkit-0.14.0-py3-none-any.whl", hash = "sha256:592064ed85b40fa213469f81ac584f67a4f2992509a7c3ea2d632208623a3680", size = 39310, upload-time = "2026-01-13T01:14:51.965Z"} ] [[package]] name = "types-pyyaml" +sdist = {url = "https://files.pythonhosted.org/packages/7e/69/3c51b36d04da19b92f9e815be12753125bd8bc247ba0470a982e6979e71c/types_pyyaml-6.0.12.20250915.tar.gz", hash = "sha256:0f8b54a528c303f0e6f7165687dd33fafa81c807fcac23f632b63aa624ced1d3", size = 17522, upload-time = "2025-09-15T03:01:00.728Z"} +source = {registry = "https://pypi.org/simple"} version = "6.0.12.20250915" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7e/69/3c51b36d04da19b92f9e815be12753125bd8bc247ba0470a982e6979e71c/types_pyyaml-6.0.12.20250915.tar.gz", hash = "sha256:0f8b54a528c303f0e6f7165687dd33fafa81c807fcac23f632b63aa624ced1d3", size = 17522, upload-time = "2025-09-15T03:01:00.728Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/bd/e0/1eed384f02555dde685fff1a1ac805c1c7dcb6dd019c916fe659b1c1f9ec/types_pyyaml-6.0.12.20250915-py3-none-any.whl", hash = "sha256:e7d4d9e064e89a3b3cae120b4990cd370874d2bf12fa5f46c97018dd5d3c9ab6", size = 20338, upload-time = "2025-09-15T03:00:59.218Z" }, + {url = "https://files.pythonhosted.org/packages/bd/e0/1eed384f02555dde685fff1a1ac805c1c7dcb6dd019c916fe659b1c1f9ec/types_pyyaml-6.0.12.20250915-py3-none-any.whl", hash = "sha256:e7d4d9e064e89a3b3cae120b4990cd370874d2bf12fa5f46c97018dd5d3c9ab6", size = 20338, upload-time = "2025-09-15T03:00:59.218Z"} ] [[package]] name = "typing-extensions" +sdist = {url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z"} +source = {registry = "https://pypi.org/simple"} version = "4.15.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, + {url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z"} ] [[package]] name = "urllib3" +sdist = {url = "https://files.pythonhosted.org/packages/c7/24/5f1b3bdffd70275f6661c76461e25f024d5a38a46f04aaca912426a2b1d3/urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed", size = 435556, upload-time = "2026-01-07T16:24:43.925Z"} +source = {registry = "https://pypi.org/simple"} version = "2.6.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c7/24/5f1b3bdffd70275f6661c76461e25f024d5a38a46f04aaca912426a2b1d3/urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed", size = 435556, upload-time = "2026-01-07T16:24:43.925Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4", size = 131584, upload-time = "2026-01-07T16:24:42.685Z" }, + {url = "https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4", size = 131584, upload-time = "2026-01-07T16:24:42.685Z"} ] [[package]] -name = "uvicorn" -version = "0.41.0" -source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "click" }, - { name = "h11" }, + {name = "click"}, + {name = "h11"} ] -sdist = { url = "https://files.pythonhosted.org/packages/32/ce/eeb58ae4ac36fe09e3842eb02e0eb676bf2c53ae062b98f1b2531673efdd/uvicorn-0.41.0.tar.gz", hash = "sha256:09d11cf7008da33113824ee5a1c6422d89fbc2ff476540d69a34c87fab8b571a", size = 82633, upload-time = "2026-02-16T23:07:24.1Z" } +name = "uvicorn" +sdist = {url = "https://files.pythonhosted.org/packages/32/ce/eeb58ae4ac36fe09e3842eb02e0eb676bf2c53ae062b98f1b2531673efdd/uvicorn-0.41.0.tar.gz", hash = "sha256:09d11cf7008da33113824ee5a1c6422d89fbc2ff476540d69a34c87fab8b571a", size = 82633, upload-time = "2026-02-16T23:07:24.1Z"} +source = {registry = "https://pypi.org/simple"} +version = "0.41.0" wheels = [ - { url = "https://files.pythonhosted.org/packages/83/e4/d04a086285c20886c0daad0e026f250869201013d18f81d9ff5eada73a88/uvicorn-0.41.0-py3-none-any.whl", hash = "sha256:29e35b1d2c36a04b9e180d4007ede3bcb32a85fbdfd6c6aeb3f26839de088187", size = 68783, upload-time = "2026-02-16T23:07:22.357Z" }, + {url = "https://files.pythonhosted.org/packages/83/e4/d04a086285c20886c0daad0e026f250869201013d18f81d9ff5eada73a88/uvicorn-0.41.0-py3-none-any.whl", hash = "sha256:29e35b1d2c36a04b9e180d4007ede3bcb32a85fbdfd6c6aeb3f26839de088187", size = 68783, upload-time = "2026-02-16T23:07:22.357Z"} ] [[package]] -name = "virtualenv" -version = "21.1.0" -source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "distlib" }, - { name = "filelock" }, - { name = "platformdirs" }, - { name = "python-discovery" }, + {name = "distlib"}, + {name = "filelock"}, + {name = "platformdirs"}, + {name = "python-discovery"} ] -sdist = { url = "https://files.pythonhosted.org/packages/2f/c9/18d4b36606d6091844daa3bd93cf7dc78e6f5da21d9f21d06c221104b684/virtualenv-21.1.0.tar.gz", hash = "sha256:1990a0188c8f16b6b9cf65c9183049007375b26aad415514d377ccacf1e4fb44", size = 5840471, upload-time = "2026-02-27T08:49:29.702Z" } +name = "virtualenv" +sdist = {url = "https://files.pythonhosted.org/packages/2f/c9/18d4b36606d6091844daa3bd93cf7dc78e6f5da21d9f21d06c221104b684/virtualenv-21.1.0.tar.gz", hash = "sha256:1990a0188c8f16b6b9cf65c9183049007375b26aad415514d377ccacf1e4fb44", size = 5840471, upload-time = "2026-02-27T08:49:29.702Z"} +source = {registry = "https://pypi.org/simple"} +version = "21.1.0" wheels = [ - { url = "https://files.pythonhosted.org/packages/78/55/896b06bf93a49bec0f4ae2a6f1ed12bd05c8860744ac3a70eda041064e4d/virtualenv-21.1.0-py3-none-any.whl", hash = "sha256:164f5e14c5587d170cf98e60378eb91ea35bf037be313811905d3a24ea33cc07", size = 5825072, upload-time = "2026-02-27T08:49:27.516Z" }, + {url = "https://files.pythonhosted.org/packages/78/55/896b06bf93a49bec0f4ae2a6f1ed12bd05c8860744ac3a70eda041064e4d/virtualenv-21.1.0-py3-none-any.whl", hash = "sha256:164f5e14c5587d170cf98e60378eb91ea35bf037be313811905d3a24ea33cc07", size = 5825072, upload-time = "2026-02-27T08:49:27.516Z"} ] [[package]] name = "websockets" +sdist = {url = "https://files.pythonhosted.org/packages/04/24/4b2031d72e840ce4c1ccb255f693b15c334757fc50023e4db9537080b8c4/websockets-16.0.tar.gz", hash = "sha256:5f6261a5e56e8d5c42a4497b364ea24d94d9563e8fbd44e78ac40879c60179b5", size = 179346, upload-time = "2026-01-10T09:23:47.181Z"} +source = {registry = "https://pypi.org/simple"} version = "16.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/04/24/4b2031d72e840ce4c1ccb255f693b15c334757fc50023e4db9537080b8c4/websockets-16.0.tar.gz", hash = "sha256:5f6261a5e56e8d5c42a4497b364ea24d94d9563e8fbd44e78ac40879c60179b5", size = 179346, upload-time = "2026-01-10T09:23:47.181Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f3/1d/e88022630271f5bd349ed82417136281931e558d628dd52c4d8621b4a0b2/websockets-16.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8cc451a50f2aee53042ac52d2d053d08bf89bcb31ae799cb4487587661c038a0", size = 177406, upload-time = "2026-01-10T09:23:12.178Z" }, - { url = "https://files.pythonhosted.org/packages/f2/78/e63be1bf0724eeb4616efb1ae1c9044f7c3953b7957799abb5915bffd38e/websockets-16.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:daa3b6ff70a9241cf6c7fc9e949d41232d9d7d26fd3522b1ad2b4d62487e9904", size = 175085, upload-time = "2026-01-10T09:23:13.511Z" }, - { url = "https://files.pythonhosted.org/packages/bb/f4/d3c9220d818ee955ae390cf319a7c7a467beceb24f05ee7aaaa2414345ba/websockets-16.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:fd3cb4adb94a2a6e2b7c0d8d05cb94e6f1c81a0cf9dc2694fb65c7e8d94c42e4", size = 175328, upload-time = "2026-01-10T09:23:14.727Z" }, - { url = "https://files.pythonhosted.org/packages/63/bc/d3e208028de777087e6fb2b122051a6ff7bbcca0d6df9d9c2bf1dd869ae9/websockets-16.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:781caf5e8eee67f663126490c2f96f40906594cb86b408a703630f95550a8c3e", size = 185044, upload-time = "2026-01-10T09:23:15.939Z" }, - { url = "https://files.pythonhosted.org/packages/ad/6e/9a0927ac24bd33a0a9af834d89e0abc7cfd8e13bed17a86407a66773cc0e/websockets-16.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:caab51a72c51973ca21fa8a18bd8165e1a0183f1ac7066a182ff27107b71e1a4", size = 186279, upload-time = "2026-01-10T09:23:17.148Z" }, - { url = "https://files.pythonhosted.org/packages/b9/ca/bf1c68440d7a868180e11be653c85959502efd3a709323230314fda6e0b3/websockets-16.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:19c4dc84098e523fd63711e563077d39e90ec6702aff4b5d9e344a60cb3c0cb1", size = 185711, upload-time = "2026-01-10T09:23:18.372Z" }, - { url = "https://files.pythonhosted.org/packages/c4/f8/fdc34643a989561f217bb477cbc47a3a07212cbda91c0e4389c43c296ebf/websockets-16.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:a5e18a238a2b2249c9a9235466b90e96ae4795672598a58772dd806edc7ac6d3", size = 184982, upload-time = "2026-01-10T09:23:19.652Z" }, - { url = "https://files.pythonhosted.org/packages/dd/d1/574fa27e233764dbac9c52730d63fcf2823b16f0856b3329fc6268d6ae4f/websockets-16.0-cp314-cp314-win32.whl", hash = "sha256:a069d734c4a043182729edd3e9f247c3b2a4035415a9172fd0f1b71658a320a8", size = 177915, upload-time = "2026-01-10T09:23:21.458Z" }, - { url = "https://files.pythonhosted.org/packages/8a/f1/ae6b937bf3126b5134ce1f482365fde31a357c784ac51852978768b5eff4/websockets-16.0-cp314-cp314-win_amd64.whl", hash = "sha256:c0ee0e63f23914732c6d7e0cce24915c48f3f1512ec1d079ed01fc629dab269d", size = 178381, upload-time = "2026-01-10T09:23:22.715Z" }, - { url = "https://files.pythonhosted.org/packages/06/9b/f791d1db48403e1f0a27577a6beb37afae94254a8c6f08be4a23e4930bc0/websockets-16.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:a35539cacc3febb22b8f4d4a99cc79b104226a756aa7400adc722e83b0d03244", size = 177737, upload-time = "2026-01-10T09:23:24.523Z" }, - { url = "https://files.pythonhosted.org/packages/bd/40/53ad02341fa33b3ce489023f635367a4ac98b73570102ad2cdd770dacc9a/websockets-16.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:b784ca5de850f4ce93ec85d3269d24d4c82f22b7212023c974c401d4980ebc5e", size = 175268, upload-time = "2026-01-10T09:23:25.781Z" }, - { url = "https://files.pythonhosted.org/packages/74/9b/6158d4e459b984f949dcbbb0c5d270154c7618e11c01029b9bbd1bb4c4f9/websockets-16.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:569d01a4e7fba956c5ae4fc988f0d4e187900f5497ce46339c996dbf24f17641", size = 175486, upload-time = "2026-01-10T09:23:27.033Z" }, - { url = "https://files.pythonhosted.org/packages/e5/2d/7583b30208b639c8090206f95073646c2c9ffd66f44df967981a64f849ad/websockets-16.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:50f23cdd8343b984957e4077839841146f67a3d31ab0d00e6b824e74c5b2f6e8", size = 185331, upload-time = "2026-01-10T09:23:28.259Z" }, - { url = "https://files.pythonhosted.org/packages/45/b0/cce3784eb519b7b5ad680d14b9673a31ab8dcb7aad8b64d81709d2430aa8/websockets-16.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:152284a83a00c59b759697b7f9e9cddf4e3c7861dd0d964b472b70f78f89e80e", size = 186501, upload-time = "2026-01-10T09:23:29.449Z" }, - { url = "https://files.pythonhosted.org/packages/19/60/b8ebe4c7e89fb5f6cdf080623c9d92789a53636950f7abacfc33fe2b3135/websockets-16.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:bc59589ab64b0022385f429b94697348a6a234e8ce22544e3681b2e9331b5944", size = 186062, upload-time = "2026-01-10T09:23:31.368Z" }, - { url = "https://files.pythonhosted.org/packages/88/a8/a080593f89b0138b6cba1b28f8df5673b5506f72879322288b031337c0b8/websockets-16.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:32da954ffa2814258030e5a57bc73a3635463238e797c7375dc8091327434206", size = 185356, upload-time = "2026-01-10T09:23:32.627Z" }, - { url = "https://files.pythonhosted.org/packages/c2/b6/b9afed2afadddaf5ebb2afa801abf4b0868f42f8539bfe4b071b5266c9fe/websockets-16.0-cp314-cp314t-win32.whl", hash = "sha256:5a4b4cc550cb665dd8a47f868c8d04c8230f857363ad3c9caf7a0c3bf8c61ca6", size = 178085, upload-time = "2026-01-10T09:23:33.816Z" }, - { url = "https://files.pythonhosted.org/packages/9f/3e/28135a24e384493fa804216b79a6a6759a38cc4ff59118787b9fb693df93/websockets-16.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b14dc141ed6d2dde437cddb216004bcac6a1df0935d79656387bd41632ba0bbd", size = 178531, upload-time = "2026-01-10T09:23:35.016Z" }, - { url = "https://files.pythonhosted.org/packages/6f/28/258ebab549c2bf3e64d2b0217b973467394a9cea8c42f70418ca2c5d0d2e/websockets-16.0-py3-none-any.whl", hash = "sha256:1637db62fad1dc833276dded54215f2c7fa46912301a24bd94d45d46a011ceec", size = 171598, upload-time = "2026-01-10T09:23:45.395Z" }, +wheels = [ + {url = "https://files.pythonhosted.org/packages/f3/1d/e88022630271f5bd349ed82417136281931e558d628dd52c4d8621b4a0b2/websockets-16.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8cc451a50f2aee53042ac52d2d053d08bf89bcb31ae799cb4487587661c038a0", size = 177406, upload-time = "2026-01-10T09:23:12.178Z"}, + {url = "https://files.pythonhosted.org/packages/f2/78/e63be1bf0724eeb4616efb1ae1c9044f7c3953b7957799abb5915bffd38e/websockets-16.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:daa3b6ff70a9241cf6c7fc9e949d41232d9d7d26fd3522b1ad2b4d62487e9904", size = 175085, upload-time = "2026-01-10T09:23:13.511Z"}, + {url = "https://files.pythonhosted.org/packages/bb/f4/d3c9220d818ee955ae390cf319a7c7a467beceb24f05ee7aaaa2414345ba/websockets-16.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:fd3cb4adb94a2a6e2b7c0d8d05cb94e6f1c81a0cf9dc2694fb65c7e8d94c42e4", size = 175328, upload-time = "2026-01-10T09:23:14.727Z"}, + {url = "https://files.pythonhosted.org/packages/63/bc/d3e208028de777087e6fb2b122051a6ff7bbcca0d6df9d9c2bf1dd869ae9/websockets-16.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:781caf5e8eee67f663126490c2f96f40906594cb86b408a703630f95550a8c3e", size = 185044, upload-time = "2026-01-10T09:23:15.939Z"}, + {url = "https://files.pythonhosted.org/packages/ad/6e/9a0927ac24bd33a0a9af834d89e0abc7cfd8e13bed17a86407a66773cc0e/websockets-16.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:caab51a72c51973ca21fa8a18bd8165e1a0183f1ac7066a182ff27107b71e1a4", size = 186279, upload-time = "2026-01-10T09:23:17.148Z"}, + {url = "https://files.pythonhosted.org/packages/b9/ca/bf1c68440d7a868180e11be653c85959502efd3a709323230314fda6e0b3/websockets-16.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:19c4dc84098e523fd63711e563077d39e90ec6702aff4b5d9e344a60cb3c0cb1", size = 185711, upload-time = "2026-01-10T09:23:18.372Z"}, + {url = "https://files.pythonhosted.org/packages/c4/f8/fdc34643a989561f217bb477cbc47a3a07212cbda91c0e4389c43c296ebf/websockets-16.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:a5e18a238a2b2249c9a9235466b90e96ae4795672598a58772dd806edc7ac6d3", size = 184982, upload-time = "2026-01-10T09:23:19.652Z"}, + {url = "https://files.pythonhosted.org/packages/dd/d1/574fa27e233764dbac9c52730d63fcf2823b16f0856b3329fc6268d6ae4f/websockets-16.0-cp314-cp314-win32.whl", hash = "sha256:a069d734c4a043182729edd3e9f247c3b2a4035415a9172fd0f1b71658a320a8", size = 177915, upload-time = "2026-01-10T09:23:21.458Z"}, + {url = "https://files.pythonhosted.org/packages/8a/f1/ae6b937bf3126b5134ce1f482365fde31a357c784ac51852978768b5eff4/websockets-16.0-cp314-cp314-win_amd64.whl", hash = "sha256:c0ee0e63f23914732c6d7e0cce24915c48f3f1512ec1d079ed01fc629dab269d", size = 178381, upload-time = "2026-01-10T09:23:22.715Z"}, + {url = "https://files.pythonhosted.org/packages/06/9b/f791d1db48403e1f0a27577a6beb37afae94254a8c6f08be4a23e4930bc0/websockets-16.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:a35539cacc3febb22b8f4d4a99cc79b104226a756aa7400adc722e83b0d03244", size = 177737, upload-time = "2026-01-10T09:23:24.523Z"}, + {url = "https://files.pythonhosted.org/packages/bd/40/53ad02341fa33b3ce489023f635367a4ac98b73570102ad2cdd770dacc9a/websockets-16.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:b784ca5de850f4ce93ec85d3269d24d4c82f22b7212023c974c401d4980ebc5e", size = 175268, upload-time = "2026-01-10T09:23:25.781Z"}, + {url = "https://files.pythonhosted.org/packages/74/9b/6158d4e459b984f949dcbbb0c5d270154c7618e11c01029b9bbd1bb4c4f9/websockets-16.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:569d01a4e7fba956c5ae4fc988f0d4e187900f5497ce46339c996dbf24f17641", size = 175486, upload-time = "2026-01-10T09:23:27.033Z"}, + {url = "https://files.pythonhosted.org/packages/e5/2d/7583b30208b639c8090206f95073646c2c9ffd66f44df967981a64f849ad/websockets-16.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:50f23cdd8343b984957e4077839841146f67a3d31ab0d00e6b824e74c5b2f6e8", size = 185331, upload-time = "2026-01-10T09:23:28.259Z"}, + {url = "https://files.pythonhosted.org/packages/45/b0/cce3784eb519b7b5ad680d14b9673a31ab8dcb7aad8b64d81709d2430aa8/websockets-16.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:152284a83a00c59b759697b7f9e9cddf4e3c7861dd0d964b472b70f78f89e80e", size = 186501, upload-time = "2026-01-10T09:23:29.449Z"}, + {url = "https://files.pythonhosted.org/packages/19/60/b8ebe4c7e89fb5f6cdf080623c9d92789a53636950f7abacfc33fe2b3135/websockets-16.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:bc59589ab64b0022385f429b94697348a6a234e8ce22544e3681b2e9331b5944", size = 186062, upload-time = "2026-01-10T09:23:31.368Z"}, + {url = "https://files.pythonhosted.org/packages/88/a8/a080593f89b0138b6cba1b28f8df5673b5506f72879322288b031337c0b8/websockets-16.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:32da954ffa2814258030e5a57bc73a3635463238e797c7375dc8091327434206", size = 185356, upload-time = "2026-01-10T09:23:32.627Z"}, + {url = "https://files.pythonhosted.org/packages/c2/b6/b9afed2afadddaf5ebb2afa801abf4b0868f42f8539bfe4b071b5266c9fe/websockets-16.0-cp314-cp314t-win32.whl", hash = "sha256:5a4b4cc550cb665dd8a47f868c8d04c8230f857363ad3c9caf7a0c3bf8c61ca6", size = 178085, upload-time = "2026-01-10T09:23:33.816Z"}, + {url = "https://files.pythonhosted.org/packages/9f/3e/28135a24e384493fa804216b79a6a6759a38cc4ff59118787b9fb693df93/websockets-16.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b14dc141ed6d2dde437cddb216004bcac6a1df0935d79656387bd41632ba0bbd", size = 178531, upload-time = "2026-01-10T09:23:35.016Z"}, + {url = "https://files.pythonhosted.org/packages/6f/28/258ebab549c2bf3e64d2b0217b973467394a9cea8c42f70418ca2c5d0d2e/websockets-16.0-py3-none-any.whl", hash = "sha256:1637db62fad1dc833276dded54215f2c7fa46912301a24bd94d45d46a011ceec", size = 171598, upload-time = "2026-01-10T09:23:45.395Z"} ] From 9b3bcc18bb8cb9c8d420a02c98b2f44382dd9622 Mon Sep 17 00:00:00 2001 From: Alex Liberzon Date: Fri, 6 Mar 2026 22:21:58 +0200 Subject: [PATCH 07/27] added bundle_adjustment --- docs/bundle_adjustment.md | 241 ++++++++++ docs/conf.py | 4 +- docs/index.md | 2 +- mypy.ini | 1 + openptv_python/demo_bundle_adjustment.py | 573 +++++++++++++++++++++++ openptv_python/orientation.py | 130 ++++- tests/test_bundle_adjustment.py | 160 ++++--- 7 files changed, 1018 insertions(+), 93 deletions(-) create mode 100644 docs/bundle_adjustment.md create mode 100644 openptv_python/demo_bundle_adjustment.py diff --git a/docs/bundle_adjustment.md b/docs/bundle_adjustment.md new file mode 100644 index 0000000..7c2d2fe --- /dev/null +++ b/docs/bundle_adjustment.md @@ -0,0 +1,241 @@ +# Bundle Adjustment + +This page documents the bundle-adjustment routines implemented in `openptv_python.orientation` and the demo driver in `openptv_python.demo_bundle_adjustment`. + +## Overview + +The repository currently exposes two related calibration-refinement workflows: + +1. `multi_camera_bundle_adjustment`: jointly refine camera parameters and optionally 3D points by minimizing reprojection error. +2. `guarded_two_step_bundle_adjustment`: run a pose-focused bundle-adjustment stage first, then a tightly constrained intrinsic stage, and reject the second stage if it makes the solution worse. + +The demo module compares several configurations on a case folder such as `tests/testing_fodder/test_cavity` and can write one updated calibration folder per experiment. + +## Case Layout + +The demo expects a case folder with this structure: + +```text +case_dir/ + cal/ + cam1.tif.ori + cam1.tif.addpar + ... + parameters/ + ptv.par + sequence.par + res_orig/ + rt_is.* + img_orig/ + cam1.%05d_targets + ... +``` + +This is the same layout used by `tests/testing_fodder/test_cavity`. + +## Objective Function + +For each observed 2D image point $u_{ij} = (x_{ij}, y_{ij})$ from camera $j$ and 3D point $X_i$, bundle adjustment minimizes reprojection residuals of the form + +$$ +r_{ij} = +\begin{bmatrix} +\hat{x}_{ij}(X_i, \theta_j) - x_{ij} \\ +\hat{y}_{ij}(X_i, \theta_j) - y_{ij} +\end{bmatrix}, +$$ + +where $\theta_j$ denotes the active calibration parameters for camera $j$. + +The solver minimizes a robust or linear least-squares objective + +$$ +\min_{\Theta, X} +\sum_{i,j} \rho\left(\left\|W r_{ij}\right\|^2\right) ++ \sum_k \left(\frac{p_k - p_{k,0}}{\sigma_k}\right)^2, +$$ + +with: + +1. $W$ converting pixel residuals into normalized units using `pix_x` and `pix_y`. +2. $\rho(\cdot)$ chosen by the `loss` argument, for example `linear` or `soft_l1`. +3. Optional Gaussian-style priors on selected parameters via `prior_sigmas`. + +The implementation evaluates the forward projection through OpenPTV's existing camera and multimedia model, so refraction and the Brown-affine distortion model remain part of the optimization. + +## Implemented Algorithms + +## `multi_camera_bundle_adjustment` + +This is the general solver. It can optimize: + +1. Exterior parameters: `x0`, `y0`, `z0`, `omega`, `phi`, `kappa`. +2. Optional intrinsic and distortion parameters controlled by `OrientPar` flags. +3. The 3D points themselves, if `optimize_points=True`. + +Important implementation details: + +1. It uses `scipy.optimize.least_squares`. +2. For `trf` and `dogbox`, the code supplies a Jacobian sparsity pattern so finite differencing scales with the true bundle-adjustment dependency graph instead of a dense matrix. +3. Residuals are assembled camera-by-camera to reduce Python overhead. +4. The function guards against scale ambiguity when points and camera poses are both free but too few cameras are fixed. + +In practice, the free variables are partitioned as: + +$$ +z = [\theta_{j_1}, \theta_{j_2}, \dots, X_1, X_2, \dots]. +$$ + +Each observation residual depends only on: + +1. One camera parameter block. +2. One 3D point block. + +That sparse structure is the reason `jac_sparsity` matters so much for runtime. + +## `guarded_two_step_bundle_adjustment` + +This routine is designed for a more conservative refinement flow: + +1. Start from baseline calibrations and points. +2. Run a pose-oriented bundle-adjustment stage. +3. Run a second intrinsic-focused stage with all cameras fixed in pose. +4. Accept the intrinsic stage only if it does not degrade reprojection RMS and, by default, does not worsen mean ray convergence. + +This gives three possible final outcomes: + +1. `baseline`: both optimized stages are rejected. +2. `pose`: the pose stage is accepted but the intrinsic stage is rejected. +3. `intrinsics`: both stages are accepted. + +This is useful when the intrinsic update is intentionally tiny and should only be kept if it is clearly beneficial. + +## Metrics Reported + +The current routines report and the demo prints: + +1. `initial_reprojection_rms` and `final_reprojection_rms`. +2. Per-camera reprojection RMS for `multi_camera_bundle_adjustment`. +3. `baseline_mean_ray_convergence`, `pose_mean_ray_convergence`, and `final_mean_ray_convergence` for guarded runs. +4. Runtime in seconds for each experiment. + +Reprojection RMS answers "how well do the updated cameras explain the observed image measurements?" + +Mean ray convergence answers "how tightly do back-projected camera rays meet in 3D?" + +Lower is better for both metrics. + +## Demo Script + +The demo entry point is: + +```bash +python -m openptv_python.demo_bundle_adjustment +``` + +By default it uses `tests/testing_fodder/test_cavity`, writes results into `tmp/bundle_adjustment_demo`, and evaluates several presets. + +### Demo Options + +The command-line options are: + +1. `case_dir`: optional positional argument pointing at a compatible case folder. +2. `--max-frames N`: only load the first `N` frames from `sequence.par`. +3. `--max-points-per-frame N`: only keep the first `N` fully observed points per frame. +4. `--perturbation-scale S`: scale the deterministic starting pose perturbation. +5. `--output-dir PATH`: write one output case folder per experiment under this directory. +6. `--skip-write`: run the experiments without writing updated calibration folders. + +### Included Demo Presets + +The current demo compares four presets: + +1. `pose_trf_linear`: pose-only bundle adjustment with `method="trf"` and `loss="linear"`. +2. `pose_soft_l1`: pose-only bundle adjustment with robust `soft_l1` loss. +3. `pose_fixed_points`: pose-only bundle adjustment with `optimize_points=False`. +4. `guarded_two_step`: pose stage plus tightly constrained intrinsic stage. + +### Example Commands + +Small, fast smoke test: + +```bash +python -m openptv_python.demo_bundle_adjustment \ + --max-frames 1 \ + --max-points-per-frame 16 \ + --output-dir .tmp/demo_bundle_adjustment_check +``` + +Larger comparison on `test_cavity`: + +```bash +python -m openptv_python.demo_bundle_adjustment \ + tests/testing_fodder/test_cavity \ + --max-frames 2 \ + --max-points-per-frame 80 \ + --output-dir .tmp/demo_bundle_adjustment_runs +``` + +Run without writing calibration folders: + +```bash +python -m openptv_python.demo_bundle_adjustment --skip-write +``` + +## Output Folders + +When writing is enabled, each experiment produces a case copy like: + +```text +tmp/bundle_adjustment_demo/ + pose_trf_linear/ + cal/ + cam1.tif.ori + cam1.tif.addpar + ... + calibration_delta.txt +``` + +`calibration_delta.txt` is generated with `openptv_python.calibration_compare` and shows camera-by-camera parameter differences relative to the source case. + +## Choosing Options + +These are the main tradeoffs when selecting settings. + +### Loss Function + +1. `linear`: use when correspondences are trusted and you want a pure least-squares solution. +2. `soft_l1`: use when some observations may behave like outliers. + +### Optimizing Points + +1. `optimize_points=True`: stronger joint refinement, but more variables and slower solves. +2. `optimize_points=False`: faster, more stable if initial 3D points are already good enough. + +### Fixed Cameras + +Fixing multiple cameras helps remove similarity-gauge ambiguity. If both points and poses are free, the implementation requires either: + +1. At least two fixed cameras. +2. Or translation priors for `x0`, `y0`, and `z0`. + +### Priors and Bounds + +1. `prior_sigmas` softly regularize motion away from the starting calibration. +2. `parameter_bounds` clip the allowed movement relative to the starting calibration. + +This is especially useful for guarded intrinsic updates where you only want very small corrections. + +## Practical Notes + +1. `lm` can be effective on small synthetic problems, but `trf` is generally the better default for larger real cases because it supports bounds and Jacobian sparsity. +2. Real-case runtime depends strongly on the number of frames and points included. +3. `read_targets` currently prints filenames as it loads target files, so demo output includes those lines. + +## Related Code + +The main implementation lives in: + +1. `openptv_python.orientation` +2. `openptv_python.demo_bundle_adjustment` +3. `openptv_python.calibration_compare` diff --git a/docs/conf.py b/docs/conf.py index 6673d7d..5b1ba6f 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -27,12 +27,14 @@ # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ - "autoapi.extension", "myst_parser", "sphinx.ext.autodoc", "sphinx.ext.napoleon", ] +if os.environ.get("OPENPTV_ENABLE_AUTOAPI") == "1": + extensions.append("autoapi.extension") + # autodoc configuration autodoc_typehints = "none" diff --git a/docs/index.md b/docs/index.md index 6feb5cd..37ea85c 100644 --- a/docs/index.md +++ b/docs/index.md @@ -7,7 +7,7 @@ Python version of the OpenPTV library. :maxdepth: 2 README.md -API Reference <_api/openptv_python/index> +bundle_adjustment.md ``` # Indices and tables diff --git a/mypy.ini b/mypy.ini index e5d1ae2..1f68091 100644 --- a/mypy.ini +++ b/mypy.ini @@ -1,5 +1,6 @@ [mypy] ignore_missing_imports = True +exclude = (^build/|^docs/_build/) ; disallow_untyped_defs = True ; disallow_incomplete_defs = True ; check_untyped_defs = True diff --git a/openptv_python/demo_bundle_adjustment.py b/openptv_python/demo_bundle_adjustment.py new file mode 100644 index 0000000..4fed2db --- /dev/null +++ b/openptv_python/demo_bundle_adjustment.py @@ -0,0 +1,573 @@ +"""Command-line demo for bundle-adjustment experiments on OpenPTV cases.""" + +from __future__ import annotations + +import argparse +import shutil +from dataclasses import dataclass +from pathlib import Path +from time import perf_counter +from typing import Dict, Iterable, List, cast + +import numpy as np + +from .calibration import Calibration, read_calibration, write_calibration +from .calibration_compare import compare_calibration_folders, format_calibration_comparison +from .orientation import ( + guarded_two_step_bundle_adjustment, + mean_ray_convergence, + multi_camera_bundle_adjustment, + reprojection_rms, +) +from .parameters import ControlPar, OrientPar, SequencePar +from .tracking_frame_buf import read_path_frame, read_targets + + +@dataclass(frozen=True) +class ExperimentSpec: + """Configuration for one bundle-adjustment demo run.""" + + name: str + description: str + mode: str + ba_kwargs: Dict[str, object] + + +@dataclass +class ExperimentResult: + """Collected metrics for one bundle-adjustment demo run.""" + + name: str + description: str + duration_sec: float + success: bool + initial_rms: float + final_rms: float + baseline_ray_convergence: float + final_ray_convergence: float + notes: str + cal_dir: Path | None + + +def clone_calibration(cal: Calibration) -> Calibration: + """Return a detached copy of a calibration object.""" + return Calibration( + ext_par=cal.ext_par.copy(), + int_par=cal.int_par.copy(), + glass_par=cal.glass_par.copy(), + added_par=cal.added_par.copy(), + mmlut=cal.mmlut, + mmlut_data=cal.mmlut_data, + ) + + +def perturb_calibrations(cals: List[Calibration], scale: float) -> List[Calibration]: + """Apply deterministic pose perturbations so BA has something to recover.""" + perturbed = [clone_calibration(cal) for cal in cals] + deltas = [ + (np.array([0.0, 0.0, 0.0]), np.array([0.0, 0.0, 0.0])), + (np.array([0.5, -0.3, 0.2]), np.array([0.004, -0.003, 0.002])), + (np.array([-0.4, 0.3, -0.2]), np.array([-0.003, 0.003, -0.002])), + (np.array([0.3, 0.4, -0.2]), np.array([0.003, 0.002, -0.002])), + ] + for cal, (pos_delta, angle_delta) in zip(perturbed, deltas): + cal.set_pos(cal.get_pos() + pos_delta * scale) + cal.set_angles(cal.get_angles() + angle_delta * scale) + return perturbed + + +def discover_num_cams(cal_dir: Path) -> int: + """Infer the number of cameras from calibration .ori files.""" + return len(sorted(cal_dir.glob("*.ori"))) + + +def load_calibrations(case_dir: Path, num_cams: int) -> List[Calibration]: + """Load all camera calibrations from a case folder.""" + cal_dir = case_dir / "cal" + return [ + read_calibration( + cal_dir / f"cam{cam_num}.tif.ori", + cal_dir / f"cam{cam_num}.tif.addpar", + ) + for cam_num in range(1, num_cams + 1) + ] + + +def load_case_observations( + case_dir: Path, + num_cams: int, + *, + max_frames: int | None, + max_points_per_frame: int | None, +) -> tuple[ControlPar, np.ndarray, np.ndarray]: + """Load quadruplet observations and initial 3D points from a case folder.""" + control = ControlPar(num_cams).from_file(case_dir / "parameters/ptv.par") + seq = SequencePar.from_file(case_dir / "parameters/sequence.par", num_cams) + + observed_batches = [] + point_batches = [] + frames = list(range(seq.first, seq.last + 1)) + if max_frames is not None: + frames = frames[:max_frames] + + for frame in frames: + cor_buf, path_buf = read_path_frame( + str(case_dir / "res_orig/rt_is"), + "", + "", + frame, + ) + targets = [ + read_targets(str(case_dir / f"img_orig/cam{cam_num}.%05d"), frame) + for cam_num in range(1, num_cams + 1) + ] + subset = [ + pt_num + for pt_num, corres in enumerate(cor_buf) + if np.all(corres.p[:num_cams] >= 0) + ] + if max_points_per_frame is not None: + subset = subset[:max_points_per_frame] + + observed_pixels = np.full((len(subset), num_cams, 2), np.nan, dtype=float) + point_init = np.empty((len(subset), 3), dtype=float) + for out_num, pt_num in enumerate(subset): + point_init[out_num] = path_buf[pt_num].x + for cam in range(num_cams): + target_index = cor_buf[pt_num].p[cam] + observed_pixels[out_num, cam, 0] = targets[cam][target_index].x + observed_pixels[out_num, cam, 1] = targets[cam][target_index].y + + observed_batches.append(observed_pixels) + point_batches.append(point_init) + + if not observed_batches: + raise ValueError(f"No observations loaded from {case_dir}") + + return ( + control, + np.concatenate(observed_batches, axis=0), + np.concatenate(point_batches, axis=0), + ) + + +def default_experiments() -> List[ExperimentSpec]: + """Return a set of representative BA configurations.""" + pose_priors = { + "x0": 0.5, + "y0": 0.5, + "z0": 0.5, + "omega": 0.005, + "phi": 0.005, + "kappa": 0.005, + } + pose_bounds = { + "x0": (-2.0, 2.0), + "y0": (-2.0, 2.0), + "z0": (-2.0, 2.0), + "omega": (-0.02, 0.02), + "phi": (-0.02, 0.02), + "kappa": (-0.02, 0.02), + } + tight_intrinsic_priors = { + "k1": 1e-12, + "k2": 1e-12, + "k3": 1e-12, + "p1": 1e-12, + "p2": 1e-12, + "scx": 1e-12, + "she": 1e-12, + "cc": 1e-12, + "xh": 1e-12, + "yh": 1e-12, + } + tight_intrinsic_bounds = { + "k1": (-1e-10, 1e-10), + "k2": (-1e-10, 1e-10), + "k3": (-1e-10, 1e-10), + "p1": (-1e-10, 1e-10), + "p2": (-1e-10, 1e-10), + "scx": (-1e-12, 1e-12), + "she": (-1e-12, 1e-12), + "cc": (-1e-12, 1e-12), + "xh": (-1e-12, 1e-12), + "yh": (-1e-12, 1e-12), + } + + intrinsics = OrientPar() + intrinsics.k1flag = 1 + intrinsics.p1flag = 1 + intrinsics.p2flag = 1 + + return [ + ExperimentSpec( + name="pose_trf_linear", + description="Pose-only BA with linear loss and TRF", + mode="multi", + ba_kwargs={ + "orient_par": OrientPar(), + "fixed_camera_indices": [0, 1], + "loss": "linear", + "method": "trf", + "prior_sigmas": pose_priors, + "parameter_bounds": pose_bounds, + "max_nfev": 12, + }, + ), + ExperimentSpec( + name="pose_soft_l1", + description="Pose-only BA with robust soft_l1 loss", + mode="multi", + ba_kwargs={ + "orient_par": OrientPar(), + "fixed_camera_indices": [0, 1], + "loss": "soft_l1", + "f_scale": 1.0, + "method": "trf", + "prior_sigmas": pose_priors, + "parameter_bounds": pose_bounds, + "max_nfev": 12, + }, + ), + ExperimentSpec( + name="pose_fixed_points", + description="Pose-only BA with fixed 3D points", + mode="multi", + ba_kwargs={ + "orient_par": OrientPar(), + "fixed_camera_indices": [0, 1], + "loss": "linear", + "method": "trf", + "prior_sigmas": pose_priors, + "parameter_bounds": pose_bounds, + "max_nfev": 12, + "optimize_points": False, + }, + ), + ExperimentSpec( + name="guarded_two_step", + description="Pose stage followed by tightly constrained intrinsic stage", + mode="guarded", + ba_kwargs={ + "pose_orient_par": OrientPar(), + "intrinsic_orient_par": intrinsics, + "fixed_camera_indices": [0, 1], + "pose_prior_sigmas": pose_priors, + "pose_parameter_bounds": pose_bounds, + "pose_max_nfev": 8, + "intrinsic_prior_sigmas": tight_intrinsic_priors, + "intrinsic_parameter_bounds": tight_intrinsic_bounds, + "intrinsic_max_nfev": 4, + }, + ), + ] + + +def ensure_output_case_layout(source_case_dir: Path, output_case_dir: Path) -> Path: + """Copy the source case and return the writable calibration directory.""" + shutil.copytree(source_case_dir, output_case_dir, dirs_exist_ok=True) + cal_dir = output_case_dir / "cal" + cal_dir.mkdir(parents=True, exist_ok=True) + return cal_dir + + +def write_calibration_folder(cals: List[Calibration], cal_dir: Path) -> None: + """Write one calibration folder using OpenPTV naming conventions.""" + cal_dir.mkdir(parents=True, exist_ok=True) + for cam_num, cal in enumerate(cals, start=1): + stem = f"cam{cam_num}.tif" + write_calibration(cal, cal_dir / f"{stem}.ori", cal_dir / f"{stem}.addpar") + + +def run_experiment( + spec: ExperimentSpec, + *, + observed_pixels: np.ndarray, + point_init: np.ndarray, + control: ControlPar, + start_cals: List[Calibration], + source_case_dir: Path, + output_dir: Path | None, +) -> ExperimentResult: + """Execute one experiment and collect metrics and optional outputs.""" + working_cals = [clone_calibration(cal) for cal in start_cals] + baseline_rms = reprojection_rms(observed_pixels, point_init, working_cals, control) + baseline_ray = mean_ray_convergence(observed_pixels, working_cals, control) + + start = perf_counter() + notes = "" + if spec.mode == "multi": + orient_par = cast(OrientPar, spec.ba_kwargs["orient_par"]) + refined_cals, refined_points, result = multi_camera_bundle_adjustment( + observed_pixels, + working_cals, + control, + orient_par, + point_init=point_init, + fix_first_camera=cast(bool, spec.ba_kwargs.get("fix_first_camera", True)), + fixed_camera_indices=cast( + List[int] | None, + spec.ba_kwargs.get("fixed_camera_indices"), + ), + loss=cast(str, spec.ba_kwargs.get("loss", "soft_l1")), + f_scale=cast(float, spec.ba_kwargs.get("f_scale", 1.0)), + method=cast(str, spec.ba_kwargs.get("method", "trf")), + prior_sigmas=cast( + Dict[str, float] | None, + spec.ba_kwargs.get("prior_sigmas"), + ), + parameter_bounds=cast( + Dict[str, tuple[float, float]] | None, + spec.ba_kwargs.get("parameter_bounds"), + ), + max_nfev=cast(int | None, spec.ba_kwargs.get("max_nfev")), + optimize_extrinsics=cast( + bool, + spec.ba_kwargs.get("optimize_extrinsics", True), + ), + optimize_points=cast(bool, spec.ba_kwargs.get("optimize_points", True)), + ftol=cast(float | None, spec.ba_kwargs.get("ftol")), + xtol=cast(float | None, spec.ba_kwargs.get("xtol")), + gtol=cast(float | None, spec.ba_kwargs.get("gtol")), + ) + success = bool(result.success) + final_rms = float(result["final_reprojection_rms"]) + final_ray = mean_ray_convergence(observed_pixels, refined_cals, control) + notes = str(result.message) + elif spec.mode == "guarded": + pose_orient_par = cast(OrientPar, spec.ba_kwargs["pose_orient_par"]) + intrinsic_orient_par = cast(OrientPar, spec.ba_kwargs["intrinsic_orient_par"]) + refined_cals, refined_points, summary = guarded_two_step_bundle_adjustment( + observed_pixels, + working_cals, + control, + pose_orient_par, + intrinsic_orient_par, + point_init=point_init, + fixed_camera_indices=cast( + List[int] | None, + spec.ba_kwargs.get("fixed_camera_indices"), + ), + pose_prior_sigmas=cast( + Dict[str, float] | None, + spec.ba_kwargs.get("pose_prior_sigmas"), + ), + pose_parameter_bounds=cast( + Dict[str, tuple[float, float]] | None, + spec.ba_kwargs.get("pose_parameter_bounds"), + ), + pose_loss=cast(str, spec.ba_kwargs.get("pose_loss", "linear")), + pose_method=cast(str, spec.ba_kwargs.get("pose_method", "trf")), + pose_max_nfev=cast(int | None, spec.ba_kwargs.get("pose_max_nfev")), + intrinsic_prior_sigmas=cast( + Dict[str, float] | None, + spec.ba_kwargs.get("intrinsic_prior_sigmas"), + ), + intrinsic_parameter_bounds=cast( + Dict[str, tuple[float, float]] | None, + spec.ba_kwargs.get("intrinsic_parameter_bounds"), + ), + intrinsic_loss=cast( + str, + spec.ba_kwargs.get("intrinsic_loss", "linear"), + ), + intrinsic_method=cast( + str, + spec.ba_kwargs.get("intrinsic_method", "trf"), + ), + intrinsic_max_nfev=cast( + int | None, + spec.ba_kwargs.get("intrinsic_max_nfev"), + ), + intrinsic_ftol=cast(float | None, spec.ba_kwargs.get("intrinsic_ftol", 1e-12)), + intrinsic_xtol=cast(float | None, spec.ba_kwargs.get("intrinsic_xtol", 1e-12)), + intrinsic_gtol=cast(float | None, spec.ba_kwargs.get("intrinsic_gtol", 1e-12)), + pose_optimize_points=cast( + bool, + spec.ba_kwargs.get("pose_optimize_points", True), + ), + intrinsic_optimize_points=cast( + bool, + spec.ba_kwargs.get("intrinsic_optimize_points", True), + ), + reject_worse_solution=cast( + bool, + spec.ba_kwargs.get("reject_worse_solution", True), + ), + reject_on_ray_convergence=cast( + bool, + spec.ba_kwargs.get("reject_on_ray_convergence", True), + ), + ) + success = True + final_rms = cast(float, summary["final_reprojection_rms"]) + final_ray = cast(float, summary["final_mean_ray_convergence"]) + notes = f"accepted_stage={summary['accepted_stage']}" + else: + raise ValueError(f"Unknown experiment mode: {spec.mode}") + + duration_sec = perf_counter() - start + + cal_dir = None + if output_dir is not None: + case_out_dir = output_dir / spec.name + cal_dir = ensure_output_case_layout(source_case_dir, case_out_dir) + write_calibration_folder(refined_cals, cal_dir) + comparison = compare_calibration_folders(source_case_dir / "cal", cal_dir) + (case_out_dir / "calibration_delta.txt").write_text( + format_calibration_comparison( + comparison, + reference_dir=source_case_dir / "cal", + candidate_dir=cal_dir, + ) + + "\n", + encoding="utf-8", + ) + + return ExperimentResult( + name=spec.name, + description=spec.description, + duration_sec=duration_sec, + success=success, + initial_rms=baseline_rms, + final_rms=final_rms, + baseline_ray_convergence=baseline_ray, + final_ray_convergence=final_ray, + notes=notes, + cal_dir=cal_dir, + ) + + +def format_results(results: Iterable[ExperimentResult]) -> str: + """Render a compact plain-text summary table.""" + rows = list(results) + headers = ( + "name", + "success", + "seconds", + "rms_before", + "rms_after", + "ray_before", + "ray_after", + "notes", + ) + data = [ + [ + row.name, + "yes" if row.success else "no", + f"{row.duration_sec:.2f}", + f"{row.initial_rms:.6f}", + f"{row.final_rms:.6f}", + f"{row.baseline_ray_convergence:.6f}", + f"{row.final_ray_convergence:.6f}", + row.notes, + ] + for row in rows + ] + widths = [len(header) for header in headers] + for row in data: + for index, value in enumerate(row): + widths[index] = max(widths[index], len(value)) + + def render_row(values: List[str]) -> str: + return " ".join(value.ljust(widths[index]) for index, value in enumerate(values)) + + separator = " ".join("-" * width for width in widths) + lines = [render_row(list(headers)), separator] + lines.extend(render_row(row) for row in data) + return "\n".join(lines) + + +def parse_args(argv: Iterable[str] | None = None) -> argparse.Namespace: + """Parse command-line arguments for the BA demo.""" + parser = argparse.ArgumentParser( + description="Demonstrate bundle-adjustment options on test_cavity or another compatible case.", + ) + parser.add_argument( + "case_dir", + type=Path, + nargs="?", + default=Path("tests/testing_fodder/test_cavity"), + help="Case folder containing cal/, parameters/, res_orig/, and img_orig/.", + ) + parser.add_argument( + "--max-frames", + type=int, + default=None, + help="Only use the first N frames from sequence.par.", + ) + parser.add_argument( + "--max-points-per-frame", + type=int, + default=80, + help="Only use the first N fully observed points from each frame.", + ) + parser.add_argument( + "--perturbation-scale", + type=float, + default=1.0, + help="Scale factor for the deterministic starting-calibration perturbation.", + ) + parser.add_argument( + "--output-dir", + type=Path, + default=Path("tmp/bundle_adjustment_demo"), + help="Where to write one output case folder per experiment.", + ) + parser.add_argument( + "--skip-write", + action="store_true", + help="Run comparisons but do not write updated calibration folders.", + ) + return parser.parse_args(list(argv) if argv is not None else None) + + +def main(argv: Iterable[str] | None = None) -> int: + """Run the bundle-adjustment demo.""" + args = parse_args(argv) + case_dir = args.case_dir.resolve() + num_cams = discover_num_cams(case_dir / "cal") + + control, observed_pixels, point_init = load_case_observations( + case_dir, + num_cams, + max_frames=args.max_frames, + max_points_per_frame=args.max_points_per_frame, + ) + true_cals = load_calibrations(case_dir, num_cams) + start_cals = perturb_calibrations(true_cals, args.perturbation_scale) + + output_dir = None if args.skip_write else args.output_dir.resolve() + if output_dir is not None: + output_dir.mkdir(parents=True, exist_ok=True) + + print(f"Case: {case_dir}") + print(f"Cameras: {num_cams}") + print(f"Observations: {observed_pixels.shape[0]} points across {observed_pixels.shape[1]} cameras") + print(f"Output folders: {output_dir if output_dir is not None else 'disabled'}") + print() + + results = [] + for spec in default_experiments(): + print(f"Running {spec.name}: {spec.description}") + result = run_experiment( + spec, + observed_pixels=observed_pixels, + point_init=point_init, + control=control, + start_cals=start_cals, + source_case_dir=case_dir, + output_dir=output_dir, + ) + results.append(result) + if result.cal_dir is not None: + print(f" wrote calibration folder: {result.cal_dir}") + print(f" final RMS: {result.final_rms:.6f} px in {result.duration_sec:.2f} s") + print() + + print(format_results(results)) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) \ No newline at end of file diff --git a/openptv_python/orientation.py b/openptv_python/orientation.py index 1f914df..b86c0ad 100644 --- a/openptv_python/orientation.py +++ b/openptv_python/orientation.py @@ -11,14 +11,20 @@ from .calibration import Calibration from .constants import CONVERGENCE, IDT, NPAR, NUM_ITER, POS_INF from .epi import epi_mm_2D -from .imgcoord import img_coord +from .imgcoord import image_coordinates, img_coord # from .lsqadj import ata, atl, matinv, matmul from .parameters import ControlPar, MultimediaPar, OrientPar, VolumePar from .ray_tracing import ray_tracing from .sortgrid import sortgrid from .tracking_frame_buf import Target -from .trafo import correct_brown_affine, dist_to_flat, metric_to_pixel, pixel_to_metric +from .trafo import ( + arr_metric_to_pixel, + correct_brown_affine, + dist_to_flat, + metric_to_pixel, + pixel_to_metric, +) from .vec_utils import unit_vector, vec_norm, vec_set @@ -1355,6 +1361,54 @@ def _expand_parameter_limits( return np.asarray(lower, dtype=np.float64), np.asarray(upper, dtype=np.float64) +def _bundle_adjustment_jacobian_sparsity( + obs_mask: np.ndarray, + num_cams: int, + optimized_cam_indices: List[int], + camera_block_size: int, + point_offset: int, + optimize_points: bool, + active_priors: List[Tuple[int, int, float, float]], +) -> scipy.sparse.csr_matrix: + """Build the Jacobian sparsity pattern for finite-difference bundle adjustment.""" + num_observation_residuals = int(np.count_nonzero(obs_mask) * 2) + num_prior_residuals = len(active_priors) + total_residuals = num_observation_residuals + num_prior_residuals + total_parameters = point_offset + (obs_mask.shape[0] * 3 if optimize_points else 0) + + sparsity = scipy.sparse.lil_matrix( + (total_residuals, total_parameters), + dtype=np.int8, + ) + optimized_cam_lookup = { + cam: cam_index for cam_index, cam in enumerate(optimized_cam_indices) + } + + row = 0 + for point_index in range(obs_mask.shape[0]): + point_start = point_offset + point_index * 3 + for cam in range(num_cams): + if not obs_mask[point_index, cam]: + continue + + cam_index = optimized_cam_lookup.get(cam) + if cam_index is not None and camera_block_size > 0: + cam_start = cam_index * camera_block_size + sparsity[row : row + 2, cam_start : cam_start + camera_block_size] = 1 + + if optimize_points: + sparsity[row : row + 2, point_start : point_start + 3] = 1 + + row += 2 + + for cam_index, param_index, _sigma, _base_value in active_priors: + cam_start = cam_index * camera_block_size + sparsity[row, cam_start + param_index] = 1 + row += 1 + + return sparsity.tocsr() + + def multi_camera_bundle_adjustment( observed_pixels: np.ndarray, cals: List[Calibration], @@ -1482,6 +1536,20 @@ def multi_camera_bundle_adjustment( x0 = np.empty(0, dtype=np.float64) point_offset = camera_block_size * len(optimized_cam_indices) + obs_mask = np.all(np.isfinite(observed_pixels), axis=2) + observation_point_indices = [ + np.flatnonzero(obs_mask[:, cam]) for cam in range(num_cams) + ] + + active_priors = [] + for cam_index, _cam in enumerate(optimized_cam_indices): + for param_index, (name, base_value) in enumerate( + zip(parameter_names, base_camera_blocks[cam_index]) + ): + sigma = prior_sigmas.get(name) + if sigma is None or sigma <= 0: + continue + active_priors.append((cam_index, param_index, sigma, base_value)) camera_lower, camera_upper = _expand_parameter_limits( parameter_names, @@ -1524,30 +1592,30 @@ def unpack_parameters(params: np.ndarray) -> Tuple[List[Calibration], np.ndarray def residual_vector(params: np.ndarray) -> np.ndarray: trial_cals, points = unpack_parameters(params) - residuals = [] - for pt in range(num_points): - for cam in range(num_cams): - obs = observed_pixels[pt, cam] - if not np.all(np.isfinite(obs)): - continue - x_metric, y_metric = img_coord(points[pt], trial_cals[cam], cpar.mm) - proj_x, proj_y = metric_to_pixel(x_metric, y_metric, cpar) - residuals.append((proj_x - obs[0]) / cpar.pix_x) - residuals.append((proj_y - obs[1]) / cpar.pix_y) - - if prior_sigmas: - offset = 0 - for cam_index, cam in enumerate(optimized_cam_indices): - block = params[offset : offset + camera_block_size] - base_block = base_camera_blocks[cam_index] - for name, value, base_value in zip(parameter_names, block, base_block): - sigma = prior_sigmas.get(name) - if sigma is None or sigma <= 0: - continue - residuals.append((value - base_value) / sigma) - offset += camera_block_size - - return np.asarray(residuals, dtype=np.float64) + residuals = np.empty(int(np.count_nonzero(obs_mask) * 2) + len(active_priors)) + row = 0 + + for cam, point_indices in enumerate(observation_point_indices): + if point_indices.size == 0: + continue + + projected_pixels = arr_metric_to_pixel( + image_coordinates(points[point_indices], trial_cals[cam], cpar.mm), + cpar, + ) + observed = observed_pixels[point_indices, cam, :] + diffs = projected_pixels - observed + residual_count = point_indices.size * 2 + residuals[row : row + residual_count : 2] = diffs[:, 0] / cpar.pix_x + residuals[row + 1 : row + residual_count : 2] = diffs[:, 1] / cpar.pix_y + row += residual_count + + for cam_index, param_index, sigma, base_value in active_priors: + value = params[cam_index * camera_block_size + param_index] + residuals[row] = (value - base_value) / sigma + row += 1 + + return residuals initial_cals, initial_points = unpack_parameters(x0) initial_rms = reprojection_rms(observed_pixels, initial_points, initial_cals, cpar) @@ -1562,6 +1630,16 @@ def residual_vector(params: np.ndarray) -> np.ndarray: "max_nfev": max_nfev, "bounds": bounds, } + if method != "lm" and x0.size > 0: + least_squares_kwargs["jac_sparsity"] = _bundle_adjustment_jacobian_sparsity( + obs_mask, + num_cams, + optimized_cam_indices, + camera_block_size, + point_offset, + optimize_points, + active_priors, + ) if ftol is not None: least_squares_kwargs["ftol"] = ftol if xtol is not None: diff --git a/tests/test_bundle_adjustment.py b/tests/test_bundle_adjustment.py index cb84caf..2ca4d70 100644 --- a/tests/test_bundle_adjustment.py +++ b/tests/test_bundle_adjustment.py @@ -1,5 +1,6 @@ import unittest from pathlib import Path +from unittest.mock import patch import numpy as np @@ -237,78 +238,107 @@ def test_bundle_adjustment_rejects_scale_ambiguous_configuration(self): ) def test_guarded_two_step_bundle_adjustment_rejects_bad_intrinsics_stage(self): - cavity_dir = Path("tests/testing_fodder/test_cavity") - control = ControlPar(4).from_file(cavity_dir / "parameters/ptv.par") - true_cals = [ - read_calibration( - cavity_dir / f"cal/cam{cam_num}.tif.ori", - cavity_dir / f"cal/cam{cam_num}.tif.addpar", - ) - for cam_num in range(1, 5) - ] - observed_pixels, point_init = self.cavity_quadruplet_observations( - cavity_dir, control + points = np.array( + [ + [-10.0, -10.0, 0.0], + [10.0, -10.0, 1.0], + [-10.0, 10.0, -1.0], + [10.0, 10.0, 0.5], + [0.0, 0.0, 3.0], + [6.0, -4.0, -2.0], + ], + dtype=float, ) - start_cals = self.lightly_perturb_calibrations(true_cals) + observed_pixels = np.empty((len(points), 4, 2), dtype=float) + for cam_num, cal in enumerate(self.true_cals): + observed_pixels[:, cam_num, :] = arr_metric_to_pixel( + image_coordinates(points, cal, self.control.mm), self.control + ) + + start_cals = self.lightly_perturb_calibrations(self.true_cals) + pose_cals = [self.clone_calibration(cal) for cal in self.true_cals] + bad_intrinsic_cals = [self.clone_calibration(cal) for cal in pose_cals] + for cal in bad_intrinsic_cals[1:]: + cal.added_par[0] += 1e-3 + cal.added_par[3] += 5e-4 + cal.added_par[4] -= 5e-4 intrinsics = OrientPar() intrinsics.k1flag = 1 intrinsics.p1flag = 1 intrinsics.p2flag = 1 - final_cals, final_points, summary = guarded_two_step_bundle_adjustment( - observed_pixels, - start_cals, - control, - OrientPar(), - intrinsics, - point_init=point_init, - fixed_camera_indices=[0, 1], - pose_prior_sigmas={ - "x0": 0.5, - "y0": 0.5, - "z0": 0.5, - "omega": 0.005, - "phi": 0.005, - "kappa": 0.005, - }, - pose_parameter_bounds={ - "x0": (-2.0, 2.0), - "y0": (-2.0, 2.0), - "z0": (-2.0, 2.0), - "omega": (-0.02, 0.02), - "phi": (-0.02, 0.02), - "kappa": (-0.02, 0.02), - }, - pose_max_nfev=60, - intrinsic_prior_sigmas={ - "k1": 1e-12, - "k2": 1e-12, - "k3": 1e-12, - "p1": 1e-12, - "p2": 1e-12, - "scx": 1e-12, - "she": 1e-12, - "cc": 1e-12, - "xh": 1e-12, - "yh": 1e-12, - }, - intrinsic_parameter_bounds={ - "k1": (-1e-10, 1e-10), - "k2": (-1e-10, 1e-10), - "k3": (-1e-10, 1e-10), - "p1": (-1e-10, 1e-10), - "p2": (-1e-10, 1e-10), - "scx": (-1e-12, 1e-12), - "she": (-1e-12, 1e-12), - "cc": (-1e-12, 1e-12), - "xh": (-1e-12, 1e-12), - "yh": (-1e-12, 1e-12), - }, - intrinsic_max_nfev=20, - ) + with patch( + "openptv_python.orientation.multi_camera_bundle_adjustment", + side_effect=[ + (pose_cals, points.copy(), {"success": True, "stage": "pose"}), + ( + bad_intrinsic_cals, + points.copy(), + {"success": True, "stage": "intrinsics"}, + ), + ], + ) as mocked_adjustment: + final_cals, final_points, summary = guarded_two_step_bundle_adjustment( + observed_pixels, + start_cals, + self.control, + OrientPar(), + intrinsics, + point_init=points, + fixed_camera_indices=[0, 1], + pose_prior_sigmas={ + "x0": 0.5, + "y0": 0.5, + "z0": 0.5, + "omega": 0.005, + "phi": 0.005, + "kappa": 0.005, + }, + pose_parameter_bounds={ + "x0": (-2.0, 2.0), + "y0": (-2.0, 2.0), + "z0": (-2.0, 2.0), + "omega": (-0.02, 0.02), + "phi": (-0.02, 0.02), + "kappa": (-0.02, 0.02), + }, + pose_max_nfev=60, + intrinsic_prior_sigmas={ + "k1": 1e-12, + "k2": 1e-12, + "k3": 1e-12, + "p1": 1e-12, + "p2": 1e-12, + "scx": 1e-12, + "she": 1e-12, + "cc": 1e-12, + "xh": 1e-12, + "yh": 1e-12, + }, + intrinsic_parameter_bounds={ + "k1": (-1e-10, 1e-10), + "k2": (-1e-10, 1e-10), + "k3": (-1e-10, 1e-10), + "p1": (-1e-10, 1e-10), + "p2": (-1e-10, 1e-10), + "scx": (-1e-12, 1e-12), + "she": (-1e-12, 1e-12), + "cc": (-1e-12, 1e-12), + "xh": (-1e-12, 1e-12), + "yh": (-1e-12, 1e-12), + }, + intrinsic_max_nfev=20, + ) - self.assertIn(summary["accepted_stage"], {"baseline", "pose"}) + self.assertEqual(mocked_adjustment.call_count, 2) + self.assertEqual(summary["accepted_stage"], "pose") + self.assertLess( + summary["pose_reprojection_rms"], summary["baseline_reprojection_rms"] + ) + self.assertGreater( + summary["intrinsic_reprojection_rms"], summary["pose_reprojection_rms"] + ) self.assertLessEqual( summary["final_reprojection_rms"], summary["baseline_reprojection_rms"] ) @@ -317,7 +347,7 @@ def test_guarded_two_step_bundle_adjustment_rejects_bad_intrinsics_stage(self): summary["baseline_mean_ray_convergence"], ) np.testing.assert_allclose( - reprojection_rms(observed_pixels, final_points, final_cals, control), + reprojection_rms(observed_pixels, final_points, final_cals, self.control), summary["final_reprojection_rms"], ) From 524b533b48a01a5b6d1d2501592b9b56cc2520e6 Mon Sep 17 00:00:00 2001 From: Alex Liberzon Date: Sat, 7 Mar 2026 00:13:07 +0200 Subject: [PATCH 08/27] added synthetic case --- .gitignore | 1 + docs/bundle_adjustment.md | 220 +++- openptv_python/demo_bundle_adjustment.py | 1077 ++++++++++++++++- .../generate_synthetic_cavity_case.py | 518 ++++++++ openptv_python/orientation.py | 193 ++- tests/test_bundle_adjustment.py | 431 +++++++ tests/test_demo_bundle_adjustment.py | 240 ++++ tests/test_synthetic_cavity_case.py | 270 +++++ .../test_cavity_synthetic/README.md | 14 + .../test_cavity_synthetic/cal/calblock.txt | 48 + .../test_cavity_synthetic/cal/cam1.tif.addpar | 1 + .../test_cavity_synthetic/cal/cam1.tif.ori | 11 + .../test_cavity_synthetic/cal/cam2.tif.addpar | 1 + .../test_cavity_synthetic/cal/cam2.tif.ori | 11 + .../test_cavity_synthetic/cal/cam3.tif.addpar | 1 + .../test_cavity_synthetic/cal/cam3.tif.ori | 11 + .../test_cavity_synthetic/cal/cam4.tif.addpar | 1 + .../test_cavity_synthetic/cal/cam4.tif.ori | 11 + .../calibration_targets/cam1.00001_targets | 49 + .../calibration_targets/cam2.00001_targets | 49 + .../calibration_targets/cam3.00001_targets | 49 + .../calibration_targets/cam4.00001_targets | 49 + .../ground_truth/cal/cam1.tif.addpar | 1 + .../ground_truth/cal/cam1.tif.ori | 11 + .../ground_truth/cal/cam2.tif.addpar | 1 + .../ground_truth/cal/cam2.tif.ori | 11 + .../ground_truth/cal/cam3.tif.addpar | 1 + .../ground_truth/cal/cam3.tif.ori | 11 + .../ground_truth/cal/cam4.tif.addpar | 1 + .../ground_truth/cal/cam4.tif.ori | 11 + .../ground_truth/calibration_body_points.txt | 48 + .../ground_truth/manifest.json | 18 + .../ground_truth/particles/frame_10001.txt | 97 ++ .../ground_truth/particles/frame_10002.txt | 97 ++ .../img_orig/cam1.10001_targets | 97 ++ .../img_orig/cam1.10002_targets | 97 ++ .../img_orig/cam2.10001_targets | 97 ++ .../img_orig/cam2.10002_targets | 97 ++ .../img_orig/cam3.10001_targets | 97 ++ .../img_orig/cam3.10002_targets | 97 ++ .../img_orig/cam4.10001_targets | 97 ++ .../img_orig/cam4.10002_targets | 97 ++ .../parameters/cal_ori.par | 12 + .../parameters/criteria.par | 12 + .../parameters/detect_plate.par | 13 + .../parameters/dumbbell.par | 6 + .../parameters/examine.par | 2 + .../parameters/man_ori.par | 16 + .../parameters/multi_planes.par | 4 + .../parameters/orient.par | 12 + .../parameters/pft_version | 1 + .../parameters/pft_version.par | 1 + .../test_cavity_synthetic/parameters/ptv.par | 21 + .../parameters/sequence.par | 6 + .../parameters/shaking.par | 4 + .../parameters/sortgrid.par | 1 + .../parameters/targ_rec.par | 13 + .../parameters/track.par | 9 + .../parameters/unsharp_mask.par | 1 + .../parameters_Run1.yaml | 174 +++ .../res_orig/added.10001 | 97 ++ .../res_orig/added.10002 | 97 ++ .../res_orig/ptv_is.10001 | 97 ++ .../res_orig/ptv_is.10002 | 97 ++ .../res_orig/rt_is.10001 | 97 ++ .../res_orig/rt_is.10002 | 97 ++ 66 files changed, 5171 insertions(+), 59 deletions(-) create mode 100644 openptv_python/generate_synthetic_cavity_case.py create mode 100644 tests/test_demo_bundle_adjustment.py create mode 100644 tests/test_synthetic_cavity_case.py create mode 100644 tests/testing_fodder/test_cavity_synthetic/README.md create mode 100644 tests/testing_fodder/test_cavity_synthetic/cal/calblock.txt create mode 100644 tests/testing_fodder/test_cavity_synthetic/cal/cam1.tif.addpar create mode 100644 tests/testing_fodder/test_cavity_synthetic/cal/cam1.tif.ori create mode 100644 tests/testing_fodder/test_cavity_synthetic/cal/cam2.tif.addpar create mode 100644 tests/testing_fodder/test_cavity_synthetic/cal/cam2.tif.ori create mode 100644 tests/testing_fodder/test_cavity_synthetic/cal/cam3.tif.addpar create mode 100644 tests/testing_fodder/test_cavity_synthetic/cal/cam3.tif.ori create mode 100644 tests/testing_fodder/test_cavity_synthetic/cal/cam4.tif.addpar create mode 100644 tests/testing_fodder/test_cavity_synthetic/cal/cam4.tif.ori create mode 100644 tests/testing_fodder/test_cavity_synthetic/calibration_targets/cam1.00001_targets create mode 100644 tests/testing_fodder/test_cavity_synthetic/calibration_targets/cam2.00001_targets create mode 100644 tests/testing_fodder/test_cavity_synthetic/calibration_targets/cam3.00001_targets create mode 100644 tests/testing_fodder/test_cavity_synthetic/calibration_targets/cam4.00001_targets create mode 100644 tests/testing_fodder/test_cavity_synthetic/ground_truth/cal/cam1.tif.addpar create mode 100644 tests/testing_fodder/test_cavity_synthetic/ground_truth/cal/cam1.tif.ori create mode 100644 tests/testing_fodder/test_cavity_synthetic/ground_truth/cal/cam2.tif.addpar create mode 100644 tests/testing_fodder/test_cavity_synthetic/ground_truth/cal/cam2.tif.ori create mode 100644 tests/testing_fodder/test_cavity_synthetic/ground_truth/cal/cam3.tif.addpar create mode 100644 tests/testing_fodder/test_cavity_synthetic/ground_truth/cal/cam3.tif.ori create mode 100644 tests/testing_fodder/test_cavity_synthetic/ground_truth/cal/cam4.tif.addpar create mode 100644 tests/testing_fodder/test_cavity_synthetic/ground_truth/cal/cam4.tif.ori create mode 100644 tests/testing_fodder/test_cavity_synthetic/ground_truth/calibration_body_points.txt create mode 100644 tests/testing_fodder/test_cavity_synthetic/ground_truth/manifest.json create mode 100644 tests/testing_fodder/test_cavity_synthetic/ground_truth/particles/frame_10001.txt create mode 100644 tests/testing_fodder/test_cavity_synthetic/ground_truth/particles/frame_10002.txt create mode 100644 tests/testing_fodder/test_cavity_synthetic/img_orig/cam1.10001_targets create mode 100644 tests/testing_fodder/test_cavity_synthetic/img_orig/cam1.10002_targets create mode 100644 tests/testing_fodder/test_cavity_synthetic/img_orig/cam2.10001_targets create mode 100644 tests/testing_fodder/test_cavity_synthetic/img_orig/cam2.10002_targets create mode 100644 tests/testing_fodder/test_cavity_synthetic/img_orig/cam3.10001_targets create mode 100644 tests/testing_fodder/test_cavity_synthetic/img_orig/cam3.10002_targets create mode 100644 tests/testing_fodder/test_cavity_synthetic/img_orig/cam4.10001_targets create mode 100644 tests/testing_fodder/test_cavity_synthetic/img_orig/cam4.10002_targets create mode 100644 tests/testing_fodder/test_cavity_synthetic/parameters/cal_ori.par create mode 100644 tests/testing_fodder/test_cavity_synthetic/parameters/criteria.par create mode 100644 tests/testing_fodder/test_cavity_synthetic/parameters/detect_plate.par create mode 100644 tests/testing_fodder/test_cavity_synthetic/parameters/dumbbell.par create mode 100644 tests/testing_fodder/test_cavity_synthetic/parameters/examine.par create mode 100644 tests/testing_fodder/test_cavity_synthetic/parameters/man_ori.par create mode 100644 tests/testing_fodder/test_cavity_synthetic/parameters/multi_planes.par create mode 100644 tests/testing_fodder/test_cavity_synthetic/parameters/orient.par create mode 100644 tests/testing_fodder/test_cavity_synthetic/parameters/pft_version create mode 100644 tests/testing_fodder/test_cavity_synthetic/parameters/pft_version.par create mode 100644 tests/testing_fodder/test_cavity_synthetic/parameters/ptv.par create mode 100644 tests/testing_fodder/test_cavity_synthetic/parameters/sequence.par create mode 100644 tests/testing_fodder/test_cavity_synthetic/parameters/shaking.par create mode 100644 tests/testing_fodder/test_cavity_synthetic/parameters/sortgrid.par create mode 100644 tests/testing_fodder/test_cavity_synthetic/parameters/targ_rec.par create mode 100644 tests/testing_fodder/test_cavity_synthetic/parameters/track.par create mode 100644 tests/testing_fodder/test_cavity_synthetic/parameters/unsharp_mask.par create mode 100644 tests/testing_fodder/test_cavity_synthetic/parameters_Run1.yaml create mode 100644 tests/testing_fodder/test_cavity_synthetic/res_orig/added.10001 create mode 100644 tests/testing_fodder/test_cavity_synthetic/res_orig/added.10002 create mode 100644 tests/testing_fodder/test_cavity_synthetic/res_orig/ptv_is.10001 create mode 100644 tests/testing_fodder/test_cavity_synthetic/res_orig/ptv_is.10002 create mode 100644 tests/testing_fodder/test_cavity_synthetic/res_orig/rt_is.10001 create mode 100644 tests/testing_fodder/test_cavity_synthetic/res_orig/rt_is.10002 diff --git a/.gitignore b/.gitignore index 39c22cd..9727a52 100644 --- a/.gitignore +++ b/.gitignore @@ -471,3 +471,4 @@ $RECYCLE.BIN/ # End of https://www.toptal.com/developers/gitignore/api/python,jupyternotebooks,vim,visualstudiocode,pycharm,emacs,linux,macos,windows .cruft.json +/.tmp diff --git a/docs/bundle_adjustment.md b/docs/bundle_adjustment.md index 7c2d2fe..ee39fc0 100644 --- a/docs/bundle_adjustment.md +++ b/docs/bundle_adjustment.md @@ -7,7 +7,7 @@ This page documents the bundle-adjustment routines implemented in `openptv_pytho The repository currently exposes two related calibration-refinement workflows: 1. `multi_camera_bundle_adjustment`: jointly refine camera parameters and optionally 3D points by minimizing reprojection error. -2. `guarded_two_step_bundle_adjustment`: run a pose-focused bundle-adjustment stage first, then a tightly constrained intrinsic stage, and reject the second stage if it makes the solution worse. +1. `guarded_two_step_bundle_adjustment`: run a pose-focused bundle-adjustment stage first, then a tightly constrained intrinsic stage, and reject the second stage if it makes the solution worse. The demo module compares several configurations on a case folder such as `tests/testing_fodder/test_cavity` and can write one updated calibration folder per experiment. @@ -35,31 +35,33 @@ This is the same layout used by `tests/testing_fodder/test_cavity`. ## Objective Function -For each observed 2D image point $u_{ij} = (x_{ij}, y_{ij})$ from camera $j$ and 3D point $X_i$, bundle adjustment minimizes reprojection residuals of the form +For each observed 2D image point $u\_{ij} = (x\_{ij}, y\_{ij})$ from camera $j$ and 3D point $X_i$, bundle adjustment minimizes reprojection residuals of the form $$ -r_{ij} = -\begin{bmatrix} -\hat{x}_{ij}(X_i, \theta_j) - x_{ij} \\ -\hat{y}_{ij}(X_i, \theta_j) - y_{ij} -\end{bmatrix}, +r\_{ij} = +\\begin{bmatrix} +\\hat{x}_{ij}(X_i, \\theta_j) - x_{ij} \\ +\\hat{y}_{ij}(X_i, \\theta_j) - y_{ij} +\\end{bmatrix}, $$ -where $\theta_j$ denotes the active calibration parameters for camera $j$. +where $\\theta_j$ denotes the active calibration parameters for camera $j$. The solver minimizes a robust or linear least-squares objective $$ \min_{\Theta, X} \sum_{i,j} \rho\left(\left\|W r_{ij}\right\|^2\right) -+ \sum_k \left(\frac{p_k - p_{k,0}}{\sigma_k}\right)^2, ++ \sum_k \left(\frac{p_k - p_{k,0}}{\sigma_k}\right)^2 ++ \sum_{m \in \mathcal{M}} \left\|\frac{X_m - X_m^*}{\sigma^{(X)}_m}\right\|^2. $$ with: 1. $W$ converting pixel residuals into normalized units using `pix_x` and `pix_y`. -2. $\rho(\cdot)$ chosen by the `loss` argument, for example `linear` or `soft_l1`. -3. Optional Gaussian-style priors on selected parameters via `prior_sigmas`. +1. $\\rho(\\cdot)$ chosen by the `loss` argument, for example `linear` or `soft_l1`. +1. Optional Gaussian-style priors on selected parameters via `prior_sigmas`. +1. Optional soft 3D geometry anchors on selected points via `known_points` and `known_point_sigmas`. The implementation evaluates the forward projection through OpenPTV's existing camera and multimedia model, so refraction and the Brown-affine distortion model remain part of the optimization. @@ -70,26 +72,31 @@ The implementation evaluates the forward projection through OpenPTV's existing c This is the general solver. It can optimize: 1. Exterior parameters: `x0`, `y0`, `z0`, `omega`, `phi`, `kappa`. -2. Optional intrinsic and distortion parameters controlled by `OrientPar` flags. -3. The 3D points themselves, if `optimize_points=True`. +1. Optional intrinsic and distortion parameters controlled by `OrientPar` flags. +1. The 3D points themselves, if `optimize_points=True`. Important implementation details: 1. It uses `scipy.optimize.least_squares`. -2. For `trf` and `dogbox`, the code supplies a Jacobian sparsity pattern so finite differencing scales with the true bundle-adjustment dependency graph instead of a dense matrix. -3. Residuals are assembled camera-by-camera to reduce Python overhead. -4. The function guards against scale ambiguity when points and camera poses are both free but too few cameras are fixed. +1. For `trf` and `dogbox`, the code supplies a Jacobian sparsity pattern so finite differencing scales with the true bundle-adjustment dependency graph instead of a dense matrix. +1. Residuals are assembled camera-by-camera to reduce Python overhead. +1. The function guards against scale ambiguity when points and camera poses are both free but too few cameras are fixed. +1. If `known_points` is provided, the solver appends three residuals per constrained point to keep selected 3D coordinates near supplied target positions. In practice, the free variables are partitioned as: $$ -z = [\theta_{j_1}, \theta_{j_2}, \dots, X_1, X_2, \dots]. +z = [\\theta\_{j_1}, \\theta\_{j_2}, \\dots, X_1, X_2, \\dots]. $$ Each observation residual depends only on: 1. One camera parameter block. -2. One 3D point block. +1. One 3D point block. + +Each known-point anchor residual depends only on: + +1. One 3D point block. That sparse structure is the reason `jac_sparsity` matters so much for runtime. @@ -98,15 +105,17 @@ That sparse structure is the reason `jac_sparsity` matters so much for runtime. This routine is designed for a more conservative refinement flow: 1. Start from baseline calibrations and points. -2. Run a pose-oriented bundle-adjustment stage. -3. Run a second intrinsic-focused stage with all cameras fixed in pose. -4. Accept the intrinsic stage only if it does not degrade reprojection RMS and, by default, does not worsen mean ray convergence. +1. Run a pose-oriented bundle-adjustment stage. +1. Run a second intrinsic-focused stage with all cameras fixed in pose. +1. Accept the intrinsic stage only if it does not degrade reprojection RMS and, by default, does not worsen mean ray convergence. + +When `known_points` is supplied, the same soft geometry anchors are applied consistently in both stages. This gives three possible final outcomes: 1. `baseline`: both optimized stages are rejected. -2. `pose`: the pose stage is accepted but the intrinsic stage is rejected. -3. `intrinsics`: both stages are accepted. +1. `pose`: the pose stage is accepted but the intrinsic stage is rejected. +1. `intrinsics`: both stages are accepted. This is useful when the intrinsic update is intentionally tiny and should only be kept if it is clearly beneficial. @@ -115,9 +124,10 @@ This is useful when the intrinsic update is intentionally tiny and should only b The current routines report and the demo prints: 1. `initial_reprojection_rms` and `final_reprojection_rms`. -2. Per-camera reprojection RMS for `multi_camera_bundle_adjustment`. -3. `baseline_mean_ray_convergence`, `pose_mean_ray_convergence`, and `final_mean_ray_convergence` for guarded runs. -4. Runtime in seconds for each experiment. +1. Per-camera reprojection RMS for `multi_camera_bundle_adjustment`. +1. `baseline_mean_ray_convergence`, `pose_mean_ray_convergence`, and `final_mean_ray_convergence` for guarded runs. +1. Runtime in seconds for each experiment. +1. `known_point_indices` for constrained runs in `multi_camera_bundle_adjustment`. Reprojection RMS answers "how well do the updated cameras explain the observed image measurements?" @@ -140,20 +150,42 @@ By default it uses `tests/testing_fodder/test_cavity`, writes results into `tmp/ The command-line options are: 1. `case_dir`: optional positional argument pointing at a compatible case folder. -2. `--max-frames N`: only load the first `N` frames from `sequence.par`. -3. `--max-points-per-frame N`: only keep the first `N` fully observed points per frame. -4. `--perturbation-scale S`: scale the deterministic starting pose perturbation. -5. `--output-dir PATH`: write one output case folder per experiment under this directory. -6. `--skip-write`: run the experiments without writing updated calibration folders. +1. `--max-frames N`: only load the first `N` frames from `sequence.par`. +1. `--max-points-per-frame N`: only keep the first `N` fully observed points per frame. +1. `--perturbation-scale S`: scale the deterministic starting pose perturbation. +1. `--output-dir PATH`: write one output case folder per experiment under this directory. +1. `--skip-write`: run the experiments without writing updated calibration folders. +1. `--known-points N`: select `N` evenly spaced input 3D points as soft geometry anchors. `0` disables constrained presets. +1. `--known-point-sigma S`: apply the same object-space sigma to each anchored 3D coordinate. +1. `--diagnose-fixed-pairs`: run one selected experiment across every two-camera fixed pair instead of running the normal preset table. +1. `--diagnostic-experiment NAME`: choose which preset to sweep when `--diagnose-fixed-pairs` is enabled. +1. `--diagnose-epipolar`: compare pairwise epipolar consistency before and after the selected diagnostic experiment. +1. `--diagnose-quadruplets`: compare leave-one-camera-out quadruplet stability before and after the selected diagnostic experiment. +1. `--epipolar-curve-points N`: sample `N` points along each epipolar curve when approximating point-to-curve distance in pixels. +1. `--geometry-guard-mode {auto,off,soft,hard}`: control whether guarded two-step BA rejects stages that move a known 3D calibration target too far from a trusted reference calibration. `auto` enables `hard` when `parameters/cal_ori.par` points to a known 3D target file such as `cal/target_on_a_side.txt`. +1. `--geometry-guard-threshold S`: pixel threshold used by `hard` geometry guards. +1. `--geometry-export-threshold S`: refuse to write output case folders whose final calibration-body drift exceeds `S` pixels. Use `0` to disable export blocking. ### Included Demo Presets -The current demo compares four presets: +The current demo always compares five unconstrained presets: 1. `pose_trf_linear`: pose-only bundle adjustment with `method="trf"` and `loss="linear"`. -2. `pose_soft_l1`: pose-only bundle adjustment with robust `soft_l1` loss. -3. `pose_fixed_points`: pose-only bundle adjustment with `optimize_points=False`. -4. `guarded_two_step`: pose stage plus tightly constrained intrinsic stage. +1. `pose_soft_l1`: pose-only bundle adjustment with robust `soft_l1` loss. +1. `pose_fixed_points`: pose-only bundle adjustment with `optimize_points=False`. +1. `intrinsics_only`: the demo resets to the reference camera poses, perturbs only `k1`, `p1`, and `p2`, and then refines just those intrinsic terms while all camera poses and 3D points remain fixed. +1. `guarded_two_step`: pose stage plus tightly constrained intrinsic stage. + +When a case exposes known 3D calibration-target points through `cal_ori.par`, the demo now defaults to a geometry-preserving workflow: + +1. Guarded presets use a `hard` acceptance check against the known target projections. +1. Output case folders are written only if the final calibration stays within the configured export drift threshold. +1. Cases without a known 3D target file still run normally; the geometry guard simply stays off unless you explicitly enable it another way. + +If `--known-points` is greater than zero, the demo also compares two constrained presets: + +1. `pose_trf_known_points`: pose-only bundle adjustment with soft 3D anchors. +1. `guarded_two_step_known_points`: guarded two-step refinement with the same 3D anchors active in both stages. ### Example Commands @@ -173,15 +205,78 @@ python -m openptv_python.demo_bundle_adjustment \ tests/testing_fodder/test_cavity \ --max-frames 2 \ --max-points-per-frame 80 \ + --known-points 12 \ + --known-point-sigma 0.25 \ --output-dir .tmp/demo_bundle_adjustment_runs ``` +Run the same case but disable automatic geometry blocking if you intentionally want to inspect all candidate exports: + +```bash +python -m openptv_python.demo_bundle_adjustment \ + tests/testing_fodder/test_cavity \ + --max-frames 2 \ + --max-points-per-frame 80 \ + --known-points 12 \ + --known-point-sigma 0.25 \ + --geometry-guard-mode off \ + --geometry-export-threshold 0 \ + --output-dir .tmp/demo_bundle_adjustment_runs_all +``` + +Keep export blocking but switch guarded acceptance to a softer monotonic geometry check: + +```bash +python -m openptv_python.demo_bundle_adjustment \ + tests/testing_fodder/test_cavity \ + --max-frames 2 \ + --max-points-per-frame 80 \ + --geometry-guard-mode soft \ + --geometry-export-threshold 2.5 \ + --output-dir .tmp/demo_bundle_adjustment_runs_soft +``` + Run without writing calibration folders: ```bash python -m openptv_python.demo_bundle_adjustment --skip-write ``` +Disable constrained presets entirely: + +```bash +python -m openptv_python.demo_bundle_adjustment --known-points 0 --skip-write +``` + +Sweep all two-camera anchor pairs for the guarded solver: + +```bash +python -m openptv_python.demo_bundle_adjustment \ + tests/testing_fodder/test_cavity \ + --max-frames 2 \ + --max-points-per-frame 80 \ + --known-points 12 \ + --known-point-sigma 0.25 \ + --skip-write \ + --diagnose-fixed-pairs \ + --diagnostic-experiment guarded_two_step +``` + +Compare epipolar consistency and quadruplet sensitivity before and after one guarded run: + +```bash +python -m openptv_python.demo_bundle_adjustment \ + tests/testing_fodder/test_cavity \ + --max-frames 2 \ + --max-points-per-frame 80 \ + --known-points 12 \ + --known-point-sigma 0.25 \ + --skip-write \ + --diagnostic-experiment guarded_two_step \ + --diagnose-epipolar \ + --diagnose-quadruplets +``` + ## Output Folders When writing is enabled, each experiment produces a case copy like: @@ -194,10 +289,13 @@ tmp/bundle_adjustment_demo/ cam1.tif.addpar ... calibration_delta.txt + geometry_check.txt ``` `calibration_delta.txt` is generated with `openptv_python.calibration_compare` and shows camera-by-camera parameter differences relative to the source case. +`geometry_check.txt` reports per-camera drift of the known 3D calibration-body projections relative to the trusted reference calibration. If the final drift exceeds `--geometry-export-threshold`, the demo does not write that experiment's case folder at all. + ## Choosing Options These are the main tradeoffs when selecting settings. @@ -205,37 +303,73 @@ These are the main tradeoffs when selecting settings. ### Loss Function 1. `linear`: use when correspondences are trusted and you want a pure least-squares solution. -2. `soft_l1`: use when some observations may behave like outliers. +1. `soft_l1`: use when some observations may behave like outliers. ### Optimizing Points 1. `optimize_points=True`: stronger joint refinement, but more variables and slower solves. -2. `optimize_points=False`: faster, more stable if initial 3D points are already good enough. +1. `optimize_points=False`: faster, more stable if initial 3D points are already good enough. + +The `intrinsics_only` preset deliberately uses `optimize_points=False` so the run changes only the selected intrinsic parameters and leaves both poses and 3D points untouched. + +Known-point constraints currently require `optimize_points=True` because the anchors act directly on the point coordinates. + +### Known 3D Point Anchors + +1. Use `known_points` when some reconstructed 3D points correspond to trusted reference-object coordinates or to positions you deliberately want to preserve. +1. Smaller `known_point_sigmas` keep the selected points closer to their supplied coordinates. +1. Larger `known_point_sigmas` let reprojection error dominate while still damping large geometric drift. +1. In the demo, anchored points are selected from the loaded input 3D points, so the constrained presets show how soft geometry anchoring changes the solution relative to the unconstrained runs rather than injecting an external ground-truth object. ### Fixed Cameras Fixing multiple cameras helps remove similarity-gauge ambiguity. If both points and poses are free, the implementation requires either: 1. At least two fixed cameras. -2. Or translation priors for `x0`, `y0`, and `z0`. +1. Or translation priors for `x0`, `y0`, and `z0`. + +If you suspect that one camera is drifting more than the others, the first thing to test is not a single "best" fixed camera. A single fixed camera is still gauge-ambiguous for free-point bundle adjustment. The more defensible diagnostic is to sweep all two-camera fixed pairs and compare: + +1. Final reprojection RMS. +1. Final mean ray convergence. +1. Whether the fixed cameras stayed numerically unchanged. +1. How much the free cameras moved relative to the starting calibration. + +The demo's `--diagnose-fixed-pairs` mode prints exactly this table so you can see whether the suspicious motion is tied to one anchor pair or appears across many pairs. + +### Epipolar And Quadruplet Diagnostics + +The new diagnostic mode is intended for the situation where reprojection error improves but calibration-body structure or correspondence quality still looks wrong. + +`--diagnose-epipolar` reports pairwise point-to-epipolar-curve distances in pixels for every camera pair. This is not yet an optimization term in bundle adjustment; it is a diagnostic on the observed image correspondences under the current calibration. + +`--diagnose-quadruplets` runs a leave-one-camera-out reconstruction check on fully observed points and reports how much the reconstructed 3D point moves when one camera is omitted. Large spreads indicate unstable quadruplets or geometry that is highly sensitive to one view. + +This split is useful because the two diagnostics answer different questions: + +1. Epipolar distance asks whether the image correspondences are pairwise consistent with the current camera geometry. +1. Quadruplet sensitivity asks whether a nominal four-camera match remains stable when one view is removed. +1. Reprojection RMS asks whether the final 3D points and cameras explain the full observation set. + +If epipolar distance stays bad while reprojection improves, the next step is usually not to add an epipolar term blindly. First identify whether a subset of camera pairs or quadruplets is driving the inconsistency, then decide whether to reject or downweight those observations. ### Priors and Bounds 1. `prior_sigmas` softly regularize motion away from the starting calibration. -2. `parameter_bounds` clip the allowed movement relative to the starting calibration. +1. `parameter_bounds` clip the allowed movement relative to the starting calibration. This is especially useful for guarded intrinsic updates where you only want very small corrections. ## Practical Notes 1. `lm` can be effective on small synthetic problems, but `trf` is generally the better default for larger real cases because it supports bounds and Jacobian sparsity. -2. Real-case runtime depends strongly on the number of frames and points included. -3. `read_targets` currently prints filenames as it loads target files, so demo output includes those lines. +1. Real-case runtime depends strongly on the number of frames and points included. +1. `read_targets` currently prints filenames as it loads target files, so demo output includes those lines. ## Related Code The main implementation lives in: 1. `openptv_python.orientation` -2. `openptv_python.demo_bundle_adjustment` -3. `openptv_python.calibration_compare` +1. `openptv_python.demo_bundle_adjustment` +1. `openptv_python.calibration_compare` diff --git a/openptv_python/demo_bundle_adjustment.py b/openptv_python/demo_bundle_adjustment.py index 4fed2db..ebc95f6 100644 --- a/openptv_python/demo_bundle_adjustment.py +++ b/openptv_python/demo_bundle_adjustment.py @@ -7,20 +7,36 @@ from dataclasses import dataclass from pathlib import Path from time import perf_counter -from typing import Dict, Iterable, List, cast +from typing import Dict, Iterable, List, Sequence, cast import numpy as np from .calibration import Calibration, read_calibration, write_calibration -from .calibration_compare import compare_calibration_folders, format_calibration_comparison +from .calibration_compare import ( + compare_calibration_folders, + format_calibration_comparison, +) +from .epi import epipolar_curve +from .imgcoord import img_coord from .orientation import ( guarded_two_step_bundle_adjustment, + initialize_bundle_adjustment_points, mean_ray_convergence, + metric_observations_from_pixels, multi_camera_bundle_adjustment, reprojection_rms, ) -from .parameters import ControlPar, OrientPar, SequencePar +from .parameters import ( + ControlPar, + OrientPar, + SequencePar, + VolumePar, + read_cal_ori_parameters, + read_volume_par, +) +from .sortgrid import read_calblock as read_sortgrid_calblock from .tracking_frame_buf import read_path_frame, read_targets +from .trafo import metric_to_pixel @dataclass(frozen=True) @@ -47,6 +63,63 @@ class ExperimentResult: final_ray_convergence: float notes: str cal_dir: Path | None + fixed_camera_indices: tuple[int, ...] + camera_position_shifts: List[float] + camera_angle_shifts: List[float] + geometry_projection_drift: List[ProjectionDriftSummary] | None = None + refined_cals: List[Calibration] | None = None + refined_points: np.ndarray | None = None + + +@dataclass +class FixedCameraDiagnostic: + """Summary of one anchor-pair diagnostic run.""" + + fixed_camera_indices: tuple[int, int] + final_rms: float + final_ray_convergence: float + fixed_position_shift: float + fixed_angle_shift: float + mean_free_position_shift: float + max_free_position_shift: float + mean_free_angle_shift: float + notes: str + + +@dataclass +class CameraPairEpipolarSummary: + """Summary of pairwise epipolar consistency for one camera pair.""" + + camera_pair: tuple[int, int] + mean_distance: float + median_distance: float + p95_distance: float + max_distance: float + acceptance_rates: Dict[float, float] + + +@dataclass +class QuadrupletSensitivitySummary: + """Leave-one-camera-out stability summary for fully observed points.""" + + num_points: int + mean_spread: float + median_spread: float + p95_spread: float + max_spread: float + mean_full_ray_convergence: float + mean_leave_one_out_ray_convergence: float + worst_point_indices: List[int] + + +@dataclass(frozen=True) +class ProjectionDriftSummary: + """Reference calibration-body projection drift for one camera.""" + + camera_index: int + mean_distance: float + p95_distance: float + max_distance: float def clone_calibration(cal: Calibration) -> Calibration: @@ -76,6 +149,159 @@ def perturb_calibrations(cals: List[Calibration], scale: float) -> List[Calibrat return perturbed +def perturb_intrinsic_parameters(cals: List[Calibration], scale: float) -> List[Calibration]: + """Apply deterministic distortion perturbations while keeping poses fixed.""" + perturbed = [clone_calibration(cal) for cal in cals] + deltas = [ + (2.5e-5, -8.0e-5, 6.0e-5), + (-2.0e-5, 7.0e-5, -5.0e-5), + (1.8e-5, 5.0e-5, 4.0e-5), + (-1.6e-5, -6.0e-5, 5.0e-5), + ] + for cal, (k1_delta, p1_delta, p2_delta) in zip(perturbed, deltas): + radial = cal.get_radial_distortion().copy() + radial[0] += k1_delta * scale + cal.set_radial_distortion(radial) + + decentering = cal.get_decentering().copy() + decentering[0] += p1_delta * scale + decentering[1] += p2_delta * scale + cal.set_decentering(decentering) + + return perturbed + + +def build_experiment_start_calibrations( + spec: ExperimentSpec, + *, + start_cals: List[Calibration], + reference_cals: List[Calibration], +) -> List[Calibration]: + """Construct the initial calibration set for one experiment.""" + if bool(spec.ba_kwargs.get("use_reference_cals", False)): + base_cals = reference_cals + else: + base_cals = start_cals + + working_cals = [clone_calibration(cal) for cal in base_cals] + if bool(spec.ba_kwargs.get("perturb_intrinsics_only", False)): + scale = cast(float, spec.ba_kwargs.get("intrinsic_perturbation_scale", 1.0)) + working_cals = perturb_intrinsic_parameters(working_cals, scale) + + fixed_camera_indices = tuple( + cast(List[int] | None, spec.ba_kwargs.get("fixed_camera_indices")) or [] + ) + for camera_index in fixed_camera_indices: + if camera_index < 0 or camera_index >= len(working_cals): + continue + working_cals[camera_index].set_pos(reference_cals[camera_index].get_pos().copy()) + working_cals[camera_index].set_angles( + reference_cals[camera_index].get_angles().copy() + ) + + return working_cals + + +def load_reference_geometry_points( + case_dir: Path, + num_cams: int, +) -> np.ndarray | None: + """Load known calibration-body points referenced by cal_ori.par, if present.""" + cal_ori_path = case_dir / "parameters/cal_ori.par" + if not cal_ori_path.exists(): + return None + + calibration_par = read_cal_ori_parameters(cal_ori_path, num_cams) + if not calibration_par.fixp_name: + return None + + calblock_path = case_dir / calibration_par.fixp_name + if not calblock_path.exists(): + return None + + calblock = read_sortgrid_calblock(calblock_path) + if len(calblock) == 0: + return None + + return np.column_stack([calblock.x, calblock.y, calblock.z]).astype(float) + + +def calibration_body_projection_drift( + reference_cals: Sequence[Calibration], + candidate_cals: Sequence[Calibration], + control: ControlPar, + reference_points: np.ndarray | None, +) -> List[ProjectionDriftSummary] | None: + """Compare candidate calibration-body projections to a trusted reference set.""" + if reference_points is None: + return None + + summaries: List[ProjectionDriftSummary] = [] + for camera_index, (reference_cal, candidate_cal) in enumerate( + zip(reference_cals, candidate_cals), + start=1, + ): + reference_pixels = [] + candidate_pixels = [] + for point in reference_points: + ref_x, ref_y = img_coord(point, reference_cal, control.mm) + cand_x, cand_y = img_coord(point, candidate_cal, control.mm) + reference_pixels.append(metric_to_pixel(ref_x, ref_y, control)) + candidate_pixels.append(metric_to_pixel(cand_x, cand_y, control)) + + displacement = np.linalg.norm( + np.asarray(candidate_pixels) - np.asarray(reference_pixels), + axis=1, + ) + summaries.append( + ProjectionDriftSummary( + camera_index=camera_index, + mean_distance=float(displacement.mean()), + p95_distance=float(np.percentile(displacement, 95.0)), + max_distance=float(displacement.max()), + ) + ) + + return summaries + + +def format_projection_drift(summaries: Sequence[ProjectionDriftSummary] | None) -> str: + """Render calibration-body projection drift summaries.""" + if not summaries: + return "No reference calibration-body geometry available." + + lines = ["camera mean_px p95_px max_px", "------ ------- ------ ------"] + for item in summaries: + lines.append( + f"{item.camera_index:>6} {item.mean_distance:>7.3f} {item.p95_distance:>6.3f} {item.max_distance:>6.3f}" + ) + return "\n".join(lines) + + +def max_projection_drift( + summaries: Sequence[ProjectionDriftSummary] | None, +) -> float | None: + """Return the worst per-camera maximum projection drift in pixels.""" + if not summaries: + return None + return max(item.max_distance for item in summaries) + + +def should_block_export_on_geometry( + summaries: Sequence[ProjectionDriftSummary] | None, + threshold_px: float | None, +) -> tuple[bool, str]: + """Return whether an export should be blocked by geometry drift.""" + if summaries is None or threshold_px is None or threshold_px <= 0: + return False, "" + + drift_max = max_projection_drift(summaries) + if drift_max is None or drift_max <= threshold_px: + return False, "" + + return True, f"geometry_export_blocked=max_drift={drift_max:.3f}px>{threshold_px:.3f}px" + + def discover_num_cams(cal_dir: Path) -> int: """Infer the number of cameras from calibration .ori files.""" return len(sorted(cal_dir.glob("*.ori"))) @@ -151,7 +377,357 @@ def load_case_observations( ) -def default_experiments() -> List[ExperimentSpec]: +def build_known_point_constraints( + point_init: np.ndarray, + count: int, +) -> Dict[int, np.ndarray]: + """Select a deterministic subset of 3D points to use as soft anchors.""" + if count <= 0: + return {} + + num_points = int(point_init.shape[0]) + if num_points == 0: + raise ValueError("Cannot build known-point constraints from an empty point set") + + target_count = min(count, num_points) + selected_indices = np.linspace(0, num_points - 1, num=target_count, dtype=int) + return { + int(point_index): point_init[int(point_index)].copy() + for point_index in selected_indices.tolist() + } + + +def all_fixed_camera_pairs(num_cams: int) -> List[tuple[int, int]]: + """Return all unique two-camera anchor pairs.""" + return [ + (first, second) + for first in range(num_cams - 1) + for second in range(first + 1, num_cams) + ] + + +def calibration_pose_shifts( + reference_cals: Sequence[Calibration], + candidate_cals: Sequence[Calibration], +) -> tuple[List[float], List[float]]: + """Measure per-camera pose changes relative to a reference calibration set.""" + position_shifts = [ + float(np.linalg.norm(candidate.get_pos() - reference.get_pos())) + for reference, candidate in zip(reference_cals, candidate_cals) + ] + angle_shifts = [ + float(np.linalg.norm(candidate.get_angles() - reference.get_angles())) + for reference, candidate in zip(reference_cals, candidate_cals) + ] + return position_shifts, angle_shifts + + +def _point_to_polyline_distance(point: np.ndarray, polyline: np.ndarray) -> float: + """Return the minimum Euclidean distance from a point to a polyline in pixels.""" + if polyline.shape[0] < 2: + raise ValueError("Polyline must have at least two vertices") + + best = np.inf + for start, end in zip(polyline[:-1], polyline[1:]): + segment = end - start + segment_length_sq = float(np.dot(segment, segment)) + if segment_length_sq == 0.0: + distance = float(np.linalg.norm(point - start)) + else: + t = float(np.dot(point - start, segment) / segment_length_sq) + t = min(1.0, max(0.0, t)) + projection = start + t * segment + distance = float(np.linalg.norm(point - projection)) + best = min(best, distance) + return float(best) + + +def symmetric_epipolar_distance( + origin_obs: np.ndarray, + target_obs: np.ndarray, + origin_cal: Calibration, + target_cal: Calibration, + cpar: ControlPar, + vpar: VolumePar, + num_curve_points: int, +) -> float: + """Return a symmetric epipolar inconsistency score in pixels.""" + forward_curve = epipolar_curve( + origin_obs, + origin_cal, + target_cal, + num_curve_points, + cpar, + vpar, + ) + backward_curve = epipolar_curve( + target_obs, + target_cal, + origin_cal, + num_curve_points, + cpar, + vpar, + ) + forward_distance = _point_to_polyline_distance(target_obs, forward_curve) + backward_distance = _point_to_polyline_distance(origin_obs, backward_curve) + return 0.5 * (forward_distance + backward_distance) + + +def summarize_epipolar_consistency( + observed_pixels: np.ndarray, + cals: Sequence[Calibration], + cpar: ControlPar, + vpar: VolumePar, + *, + num_curve_points: int = 64, + threshold_scales: Sequence[float] = (0.5, 1.0, 2.0), +) -> List[CameraPairEpipolarSummary]: + """Summarize pairwise epipolar distance statistics for observed correspondences.""" + thresholds = [float(vpar.eps0 * scale) for scale in threshold_scales] + summaries = [] + for cam_a, cam_b in all_fixed_camera_pairs(len(cals)): + distances = [] + for point_index in range(observed_pixels.shape[0]): + obs_a = observed_pixels[point_index, cam_a] + obs_b = observed_pixels[point_index, cam_b] + if not (np.all(np.isfinite(obs_a)) and np.all(np.isfinite(obs_b))): + continue + distances.append( + symmetric_epipolar_distance( + obs_a, + obs_b, + cals[cam_a], + cals[cam_b], + cpar, + vpar, + num_curve_points, + ) + ) + + if not distances: + continue + distance_array = np.asarray(distances, dtype=np.float64) + summaries.append( + CameraPairEpipolarSummary( + camera_pair=(cam_a, cam_b), + mean_distance=float(np.mean(distance_array)), + median_distance=float(np.median(distance_array)), + p95_distance=float(np.percentile(distance_array, 95.0)), + max_distance=float(np.max(distance_array)), + acceptance_rates={ + threshold: float(np.mean(distance_array <= threshold)) + for threshold in thresholds + }, + ) + ) + + summaries.sort(key=lambda item: item.mean_distance) + return summaries + + +def format_epipolar_diagnostics( + summaries: Sequence[CameraPairEpipolarSummary], +) -> str: + """Render pairwise epipolar statistics as a compact table.""" + thresholds = sorted( + {threshold for item in summaries for threshold in item.acceptance_rates} + ) + headers = [ + "pair", + "mean_epi", + "median_epi", + "p95_epi", + "max_epi", + ] + headers.extend([f"<= {threshold:.3f}" for threshold in thresholds]) + data = [] + for item in summaries: + row = [ + f"{item.camera_pair[0] + 1},{item.camera_pair[1] + 1}", + f"{item.mean_distance:.6f}", + f"{item.median_distance:.6f}", + f"{item.p95_distance:.6f}", + f"{item.max_distance:.6f}", + ] + row.extend([f"{100.0 * item.acceptance_rates[threshold]:.1f}%" for threshold in thresholds]) + data.append(row) + + widths = [len(header) for header in headers] + for row in data: + for index, value in enumerate(row): + widths[index] = max(widths[index], len(value)) + + def render_row(values: List[str]) -> str: + return " ".join(value.ljust(widths[index]) for index, value in enumerate(values)) + + separator = " ".join("-" * width for width in widths) + lines = [render_row(headers), separator] + lines.extend(render_row(row) for row in data) + return "\n".join(lines) + + +def summarize_quadruplet_sensitivity( + observed_pixels: np.ndarray, + cals: Sequence[Calibration], + cpar: ControlPar, +) -> QuadrupletSensitivitySummary: + """Measure leave-one-camera-out instability for fully observed points.""" + if observed_pixels.shape[1] < 4: + raise ValueError("Quadruplet sensitivity requires at least four cameras") + + full_mask = np.all(np.isfinite(observed_pixels), axis=(1, 2)) + full_indices = np.flatnonzero(full_mask) + if full_indices.size == 0: + raise ValueError("No fully observed quadruplets available for sensitivity analysis") + + full_points, full_ray_convergence = initialize_bundle_adjustment_points( + observed_pixels[full_indices], + list(cals), + cpar, + ) + + spreads = np.empty(full_indices.size, dtype=np.float64) + leave_one_out_rays = np.empty(full_indices.size, dtype=np.float64) + for local_index, point_index in enumerate(full_indices): + subset_points = [] + subset_rays = [] + for omitted_camera in range(observed_pixels.shape[1]): + keep = [cam for cam in range(observed_pixels.shape[1]) if cam != omitted_camera] + subset_observed = observed_pixels[point_index : point_index + 1, keep, :] + subset_cals = [cals[cam] for cam in keep] + subset_point, subset_ray = initialize_bundle_adjustment_points( + subset_observed, + subset_cals, + cpar, + ) + subset_points.append(subset_point[0]) + subset_rays.append(float(subset_ray[0])) + + subset_stack = np.asarray(subset_points, dtype=np.float64) + spreads[local_index] = float( + np.max(np.linalg.norm(subset_stack - full_points[local_index], axis=1)) + ) + leave_one_out_rays[local_index] = float(np.mean(subset_rays)) + + worst_order = np.argsort(spreads)[::-1][:5] + return QuadrupletSensitivitySummary( + num_points=int(full_indices.size), + mean_spread=float(np.mean(spreads)), + median_spread=float(np.median(spreads)), + p95_spread=float(np.percentile(spreads, 95.0)), + max_spread=float(np.max(spreads)), + mean_full_ray_convergence=float(np.mean(full_ray_convergence)), + mean_leave_one_out_ray_convergence=float(np.mean(leave_one_out_rays)), + worst_point_indices=[int(full_indices[index]) for index in worst_order.tolist()], + ) + + +def format_quadruplet_sensitivity( + baseline: QuadrupletSensitivitySummary, + final: QuadrupletSensitivitySummary, +) -> str: + """Render before/after quadruplet sensitivity diagnostics.""" + headers = ( + "phase", + "points", + "mean_spread", + "median_spread", + "p95_spread", + "max_spread", + "mean_full_ray", + "mean_loo_ray", + "worst_points", + ) + data = [] + for phase, summary in (("baseline", baseline), ("final", final)): + data.append( + [ + phase, + str(summary.num_points), + f"{summary.mean_spread:.6f}", + f"{summary.median_spread:.6f}", + f"{summary.p95_spread:.6f}", + f"{summary.max_spread:.6f}", + f"{summary.mean_full_ray_convergence:.6f}", + f"{summary.mean_leave_one_out_ray_convergence:.6f}", + ",".join(str(index) for index in summary.worst_point_indices), + ] + ) + + widths = [len(header) for header in headers] + for row in data: + for index, value in enumerate(row): + widths[index] = max(widths[index], len(value)) + + def render_row(values: List[str]) -> str: + return " ".join(value.ljust(widths[index]) for index, value in enumerate(values)) + + separator = " ".join("-" * width for width in widths) + lines = [render_row(list(headers)), separator] + lines.extend(render_row(row) for row in data) + return "\n".join(lines) + + +def summarize_fixed_camera_diagnostics( + results: Sequence[ExperimentResult], +) -> List[FixedCameraDiagnostic]: + """Collapse experiment results into anchor-pair diagnostic metrics.""" + diagnostics = [] + for result in results: + if len(result.fixed_camera_indices) != 2: + raise ValueError("Fixed-camera diagnostics require exactly two fixed cameras") + + fixed_indices = set(result.fixed_camera_indices) + fixed_position_shifts = [ + result.camera_position_shifts[index] for index in result.fixed_camera_indices + ] + fixed_angle_shifts = [ + result.camera_angle_shifts[index] for index in result.fixed_camera_indices + ] + free_position_shifts = [ + shift + for index, shift in enumerate(result.camera_position_shifts) + if index not in fixed_indices + ] + free_angle_shifts = [ + shift + for index, shift in enumerate(result.camera_angle_shifts) + if index not in fixed_indices + ] + + diagnostics.append( + FixedCameraDiagnostic( + fixed_camera_indices=cast(tuple[int, int], result.fixed_camera_indices), + final_rms=result.final_rms, + final_ray_convergence=result.final_ray_convergence, + fixed_position_shift=max(fixed_position_shifts, default=0.0), + fixed_angle_shift=max(fixed_angle_shifts, default=0.0), + mean_free_position_shift=float(np.mean(free_position_shifts)), + max_free_position_shift=max(free_position_shifts, default=0.0), + mean_free_angle_shift=float(np.mean(free_angle_shifts)), + notes=result.notes, + ) + ) + + diagnostics.sort( + key=lambda item: ( + item.fixed_position_shift, + item.fixed_angle_shift, + item.final_rms, + item.final_ray_convergence, + ) + ) + return diagnostics + + +def default_experiments( + *, + known_points: Dict[int, np.ndarray] | None = None, + known_point_sigmas: float | np.ndarray | None = None, + perturbation_scale: float = 1.0, + geometry_guard_mode: str = "off", + geometry_guard_threshold: float | None = None, +) -> List[ExperimentSpec]: """Return a set of representative BA configurations.""" pose_priors = { "x0": 0.5, @@ -199,7 +775,12 @@ def default_experiments() -> List[ExperimentSpec]: intrinsics.p1flag = 1 intrinsics.p2flag = 1 - return [ + intrinsics_only = OrientPar() + intrinsics_only.k1flag = 1 + intrinsics_only.p1flag = 1 + intrinsics_only.p2flag = 1 + + experiments = [ ExperimentSpec( name="pose_trf_linear", description="Pose-only BA with linear loss and TRF", @@ -244,6 +825,33 @@ def default_experiments() -> List[ExperimentSpec]: "optimize_points": False, }, ), + ExperimentSpec( + name="intrinsics_only", + description="Intrinsics-only BA from fixed reference poses with tightly bounded distortion updates", + mode="multi", + ba_kwargs={ + "orient_par": intrinsics_only, + "fixed_camera_indices": [0, 1, 2, 3], + "loss": "linear", + "method": "trf", + "prior_sigmas": { + "k1": 5e-5, + "p1": 1e-4, + "p2": 1e-4, + }, + "parameter_bounds": { + "k1": (-5e-5, 5e-5), + "p1": (-2e-4, 2e-4), + "p2": (-2e-4, 2e-4), + }, + "max_nfev": 40, + "optimize_extrinsics": False, + "optimize_points": False, + "use_reference_cals": True, + "perturb_intrinsics_only": True, + "intrinsic_perturbation_scale": perturbation_scale, + }, + ), ExperimentSpec( name="guarded_two_step", description="Pose stage followed by tightly constrained intrinsic stage", @@ -258,10 +866,56 @@ def default_experiments() -> List[ExperimentSpec]: "intrinsic_prior_sigmas": tight_intrinsic_priors, "intrinsic_parameter_bounds": tight_intrinsic_bounds, "intrinsic_max_nfev": 4, + "geometry_guard_mode": geometry_guard_mode, + "geometry_guard_threshold": geometry_guard_threshold, }, ), ] + if known_points: + experiments.extend( + [ + ExperimentSpec( + name="pose_trf_known_points", + description="Pose-only BA with soft known-point geometry anchors", + mode="multi", + ba_kwargs={ + "orient_par": OrientPar(), + "fixed_camera_indices": [0, 1], + "loss": "linear", + "method": "trf", + "prior_sigmas": pose_priors, + "parameter_bounds": pose_bounds, + "max_nfev": 12, + "known_points": known_points, + "known_point_sigmas": known_point_sigmas, + }, + ), + ExperimentSpec( + name="guarded_two_step_known_points", + description="Guarded two-step BA with soft known-point geometry anchors", + mode="guarded", + ba_kwargs={ + "pose_orient_par": OrientPar(), + "intrinsic_orient_par": intrinsics, + "fixed_camera_indices": [0, 1], + "pose_prior_sigmas": pose_priors, + "pose_parameter_bounds": pose_bounds, + "pose_max_nfev": 8, + "intrinsic_prior_sigmas": tight_intrinsic_priors, + "intrinsic_parameter_bounds": tight_intrinsic_bounds, + "intrinsic_max_nfev": 4, + "known_points": known_points, + "known_point_sigmas": known_point_sigmas, + "geometry_guard_mode": geometry_guard_mode, + "geometry_guard_threshold": geometry_guard_threshold, + }, + ), + ] + ) + + return experiments + def ensure_output_case_layout(source_case_dir: Path, output_case_dir: Path) -> Path: """Copy the source case and return the writable calibration directory.""" @@ -286,11 +940,21 @@ def run_experiment( point_init: np.ndarray, control: ControlPar, start_cals: List[Calibration], + reference_cals: List[Calibration], + reference_geometry_points: np.ndarray | None, + geometry_export_threshold: float | None, source_case_dir: Path, output_dir: Path | None, ) -> ExperimentResult: """Execute one experiment and collect metrics and optional outputs.""" - working_cals = [clone_calibration(cal) for cal in start_cals] + working_cals = build_experiment_start_calibrations( + spec, + start_cals=start_cals, + reference_cals=reference_cals, + ) + fixed_camera_indices = tuple( + cast(List[int] | None, spec.ba_kwargs.get("fixed_camera_indices")) or [] + ) baseline_rms = reprojection_rms(observed_pixels, point_init, working_cals, control) baseline_ray = mean_ray_convergence(observed_pixels, working_cals, control) @@ -326,6 +990,14 @@ def run_experiment( spec.ba_kwargs.get("optimize_extrinsics", True), ), optimize_points=cast(bool, spec.ba_kwargs.get("optimize_points", True)), + known_points=cast( + Dict[int, np.ndarray] | None, + spec.ba_kwargs.get("known_points"), + ), + known_point_sigmas=cast( + float | np.ndarray | None, + spec.ba_kwargs.get("known_point_sigmas"), + ), ftol=cast(float | None, spec.ba_kwargs.get("ftol")), xtol=cast(float | None, spec.ba_kwargs.get("xtol")), gtol=cast(float | None, spec.ba_kwargs.get("gtol")), @@ -379,9 +1051,15 @@ def run_experiment( int | None, spec.ba_kwargs.get("intrinsic_max_nfev"), ), - intrinsic_ftol=cast(float | None, spec.ba_kwargs.get("intrinsic_ftol", 1e-12)), - intrinsic_xtol=cast(float | None, spec.ba_kwargs.get("intrinsic_xtol", 1e-12)), - intrinsic_gtol=cast(float | None, spec.ba_kwargs.get("intrinsic_gtol", 1e-12)), + intrinsic_ftol=cast( + float | None, spec.ba_kwargs.get("intrinsic_ftol", 1e-12) + ), + intrinsic_xtol=cast( + float | None, spec.ba_kwargs.get("intrinsic_xtol", 1e-12) + ), + intrinsic_gtol=cast( + float | None, spec.ba_kwargs.get("intrinsic_gtol", 1e-12) + ), pose_optimize_points=cast( bool, spec.ba_kwargs.get("pose_optimize_points", True), @@ -398,6 +1076,24 @@ def run_experiment( bool, spec.ba_kwargs.get("reject_on_ray_convergence", True), ), + known_points=cast( + Dict[int, np.ndarray] | None, + spec.ba_kwargs.get("known_points"), + ), + known_point_sigmas=cast( + float | np.ndarray | None, + spec.ba_kwargs.get("known_point_sigmas"), + ), + geometry_reference_points=reference_geometry_points, + geometry_reference_cals=reference_cals, + geometry_guard_mode=cast( + str, + spec.ba_kwargs.get("geometry_guard_mode", "off"), + ), + geometry_guard_threshold=cast( + float | None, + spec.ba_kwargs.get("geometry_guard_threshold"), + ), ) success = True final_rms = cast(float, summary["final_reprojection_rms"]) @@ -407,9 +1103,27 @@ def run_experiment( raise ValueError(f"Unknown experiment mode: {spec.mode}") duration_sec = perf_counter() - start + camera_position_shifts, camera_angle_shifts = calibration_pose_shifts( + working_cals, + refined_cals, + ) + geometry_projection_drift = calibration_body_projection_drift( + reference_cals, + refined_cals, + control, + reference_geometry_points, + ) + export_blocked, export_note = should_block_export_on_geometry( + geometry_projection_drift, + geometry_export_threshold, + ) + if export_note: + notes = f"{notes}; {export_note}" if notes else export_note + if export_blocked: + success = False cal_dir = None - if output_dir is not None: + if output_dir is not None and not export_blocked: case_out_dir = output_dir / spec.name cal_dir = ensure_output_case_layout(source_case_dir, case_out_dir) write_calibration_folder(refined_cals, cal_dir) @@ -423,6 +1137,10 @@ def run_experiment( + "\n", encoding="utf-8", ) + (case_out_dir / "geometry_check.txt").write_text( + format_projection_drift(geometry_projection_drift) + "\n", + encoding="utf-8", + ) return ExperimentResult( name=spec.name, @@ -435,6 +1153,12 @@ def run_experiment( final_ray_convergence=final_ray, notes=notes, cal_dir=cal_dir, + fixed_camera_indices=fixed_camera_indices, + camera_position_shifts=camera_position_shifts, + camera_angle_shifts=camera_angle_shifts, + geometry_projection_drift=geometry_projection_drift, + refined_cals=refined_cals, + refined_points=refined_points, ) @@ -469,6 +1193,51 @@ def format_results(results: Iterable[ExperimentResult]) -> str: for index, value in enumerate(row): widths[index] = max(widths[index], len(value)) + def render_row(values: List[str]) -> str: + return " ".join( + value.ljust(widths[index]) for index, value in enumerate(values) + ) + + separator = " ".join("-" * width for width in widths) + lines = [render_row(list(headers)), separator] + lines.extend(render_row(row) for row in data) + return "\n".join(lines) + + +def format_fixed_camera_diagnostics( + diagnostics: Sequence[FixedCameraDiagnostic], +) -> str: + """Render a compact plain-text summary of anchor-pair diagnostics.""" + headers = ( + "fixed_pair", + "rms_after", + "ray_after", + "fixed_pos_shift", + "fixed_ang_shift", + "mean_free_pos", + "max_free_pos", + "mean_free_ang", + "notes", + ) + data = [ + [ + f"{item.fixed_camera_indices[0] + 1},{item.fixed_camera_indices[1] + 1}", + f"{item.final_rms:.6f}", + f"{item.final_ray_convergence:.6f}", + f"{item.fixed_position_shift:.6e}", + f"{item.fixed_angle_shift:.6e}", + f"{item.mean_free_position_shift:.6f}", + f"{item.max_free_position_shift:.6f}", + f"{item.mean_free_angle_shift:.6f}", + item.notes, + ] + for item in diagnostics + ] + widths = [len(header) for header in headers] + for row in data: + for index, value in enumerate(row): + widths[index] = max(widths[index], len(value)) + def render_row(values: List[str]) -> str: return " ".join(value.ljust(widths[index]) for index, value in enumerate(values)) @@ -478,6 +1247,103 @@ def render_row(values: List[str]) -> str: return "\n".join(lines) +def summarize_anchor_participation( + diagnostics: Sequence[FixedCameraDiagnostic], + num_cams: int, +) -> str: + """Aggregate anchor-pair diagnostics by camera participation.""" + headers = ( + "camera", + "pair_count", + "avg_rms", + "avg_ray", + "avg_free_pos", + "avg_free_ang", + ) + rows = [] + for camera_index in range(num_cams): + participating = [ + item for item in diagnostics if camera_index in item.fixed_camera_indices + ] + if not participating: + continue + rows.append( + [ + str(camera_index + 1), + str(len(participating)), + f"{np.mean([item.final_rms for item in participating]):.6f}", + f"{np.mean([item.final_ray_convergence for item in participating]):.6f}", + f"{np.mean([item.mean_free_position_shift for item in participating]):.6f}", + f"{np.mean([item.mean_free_angle_shift for item in participating]):.6f}", + ] + ) + + widths = [len(header) for header in headers] + for row in rows: + for index, value in enumerate(row): + widths[index] = max(widths[index], len(value)) + + def render_row(values: List[str]) -> str: + return " ".join(value.ljust(widths[index]) for index, value in enumerate(values)) + + separator = " ".join("-" * width for width in widths) + lines = [render_row(list(headers)), separator] + lines.extend(render_row(row) for row in rows) + return "\n".join(lines) + + +def find_experiment_spec( + experiments: Sequence[ExperimentSpec], + name: str, +) -> ExperimentSpec: + """Return one experiment by name.""" + for spec in experiments: + if spec.name == name: + return spec + raise ValueError(f"Unknown experiment: {name}") + + +def run_fixed_camera_pair_diagnostics( + spec: ExperimentSpec, + *, + observed_pixels: np.ndarray, + point_init: np.ndarray, + control: ControlPar, + start_cals: List[Calibration], + reference_cals: List[Calibration], + reference_geometry_points: np.ndarray | None, + geometry_export_threshold: float | None, + source_case_dir: Path, +) -> List[FixedCameraDiagnostic]: + """Run one experiment over every two-camera anchor pair and summarize results.""" + diagnostic_results = [] + for fixed_pair in all_fixed_camera_pairs(len(start_cals)): + pair_kwargs = dict(spec.ba_kwargs) + pair_kwargs["fixed_camera_indices"] = list(fixed_pair) + pair_spec = ExperimentSpec( + name=f"{spec.name}_fixed_{fixed_pair[0] + 1}_{fixed_pair[1] + 1}", + description=f"{spec.description} with cameras {fixed_pair[0] + 1} and {fixed_pair[1] + 1} fixed", + mode=spec.mode, + ba_kwargs=pair_kwargs, + ) + diagnostic_results.append( + run_experiment( + pair_spec, + observed_pixels=observed_pixels, + point_init=point_init, + control=control, + start_cals=start_cals, + reference_cals=reference_cals, + reference_geometry_points=reference_geometry_points, + geometry_export_threshold=geometry_export_threshold, + source_case_dir=source_case_dir, + output_dir=None, + ) + ) + + return summarize_fixed_camera_diagnostics(diagnostic_results) + + def parse_args(argv: Iterable[str] | None = None) -> argparse.Namespace: """Parse command-line arguments for the BA demo.""" parser = argparse.ArgumentParser( @@ -519,6 +1385,64 @@ def parse_args(argv: Iterable[str] | None = None) -> argparse.Namespace: action="store_true", help="Run comparisons but do not write updated calibration folders.", ) + parser.add_argument( + "--known-points", + type=int, + default=12, + help="Use N evenly spaced input 3D points as soft geometry anchors; 0 disables constrained presets.", + ) + parser.add_argument( + "--known-point-sigma", + type=float, + default=0.25, + help="Standard deviation for each anchored 3D coordinate in object-space units.", + ) + parser.add_argument( + "--diagnose-fixed-pairs", + action="store_true", + help="Run one experiment across every two-camera fixed pair and print anchor-pair diagnostics.", + ) + parser.add_argument( + "--diagnostic-experiment", + type=str, + default="guarded_two_step", + help="Experiment name to use with --diagnose-fixed-pairs.", + ) + parser.add_argument( + "--diagnose-epipolar", + action="store_true", + help="Compare pairwise epipolar consistency before and after the selected diagnostic experiment.", + ) + parser.add_argument( + "--diagnose-quadruplets", + action="store_true", + help="Compare leave-one-camera-out quadruplet stability before and after the selected diagnostic experiment.", + ) + parser.add_argument( + "--epipolar-curve-points", + type=int, + default=64, + help="Number of points sampled along each epipolar curve for the diagnostic distance calculation.", + ) + parser.add_argument( + "--geometry-guard-mode", + type=str, + choices=("auto", "off", "soft", "hard"), + default="auto", + help="Guarded-BA geometry acceptance mode. 'auto' uses 'hard' when cal_ori.par exposes known 3D target points.", + ) + parser.add_argument( + "--geometry-guard-threshold", + type=float, + default=2.5, + help="Maximum allowed calibration-body projection drift in pixels for hard geometry guards.", + ) + parser.add_argument( + "--geometry-export-threshold", + type=float, + default=2.5, + help="Do not write output case folders whose final calibration-body drift exceeds this many pixels; 0 disables export blocking.", + ) return parser.parse_args(list(argv) if argv is not None else None) @@ -536,6 +1460,16 @@ def main(argv: Iterable[str] | None = None) -> int: ) true_cals = load_calibrations(case_dir, num_cams) start_cals = perturb_calibrations(true_cals, args.perturbation_scale) + reference_geometry_points = load_reference_geometry_points(case_dir, num_cams) + if args.geometry_guard_mode == "auto": + geometry_guard_mode = "hard" if reference_geometry_points is not None else "off" + else: + geometry_guard_mode = args.geometry_guard_mode + geometry_export_threshold = ( + args.geometry_export_threshold if reference_geometry_points is not None else None + ) + known_points = build_known_point_constraints(point_init, args.known_points) + known_point_sigmas = args.known_point_sigma if known_points else None output_dir = None if args.skip_write else args.output_dir.resolve() if output_dir is not None: @@ -543,12 +1477,126 @@ def main(argv: Iterable[str] | None = None) -> int: print(f"Case: {case_dir}") print(f"Cameras: {num_cams}") - print(f"Observations: {observed_pixels.shape[0]} points across {observed_pixels.shape[1]} cameras") + print( + f"Observations: {observed_pixels.shape[0]} points across {observed_pixels.shape[1]} cameras" + ) + if known_points: + print( + f"Known-point anchors: {len(known_points)} (sigma={args.known_point_sigma:.3f})" + ) + else: + print("Known-point anchors: disabled") + if reference_geometry_points is not None: + print( + f"Geometry guard: mode={geometry_guard_mode}, guard_threshold={args.geometry_guard_threshold:.3f}px, export_threshold={args.geometry_export_threshold:.3f}px" + ) + else: + print("Geometry guard: unavailable for this case (no known 3D target file found)") print(f"Output folders: {output_dir if output_dir is not None else 'disabled'}") print() + experiments = default_experiments( + known_points=known_points or None, + known_point_sigmas=known_point_sigmas, + perturbation_scale=args.perturbation_scale, + geometry_guard_mode=geometry_guard_mode, + geometry_guard_threshold=args.geometry_guard_threshold, + ) + + if args.diagnose_fixed_pairs: + spec = find_experiment_spec(experiments, args.diagnostic_experiment) + diagnostics = run_fixed_camera_pair_diagnostics( + spec, + observed_pixels=observed_pixels, + point_init=point_init, + control=control, + start_cals=start_cals, + reference_cals=true_cals, + reference_geometry_points=reference_geometry_points, + geometry_export_threshold=geometry_export_threshold, + source_case_dir=case_dir, + ) + print( + f"Fixed-pair diagnostics for {spec.name}: {spec.description}" + ) + print(format_fixed_camera_diagnostics(diagnostics)) + print() + print("Anchor participation summary") + print(summarize_anchor_participation(diagnostics, num_cams)) + return 0 + + if args.diagnose_epipolar or args.diagnose_quadruplets: + spec = find_experiment_spec(experiments, args.diagnostic_experiment) + diagnostic_start_cals = build_experiment_start_calibrations( + spec, + start_cals=start_cals, + reference_cals=true_cals, + ) + diagnostic_result = run_experiment( + spec, + observed_pixels=observed_pixels, + point_init=point_init, + control=control, + start_cals=start_cals, + reference_cals=true_cals, + reference_geometry_points=reference_geometry_points, + geometry_export_threshold=geometry_export_threshold, + source_case_dir=case_dir, + output_dir=None, + ) + if diagnostic_result.refined_cals is None: + raise ValueError("Diagnostic experiment did not return refined calibrations") + + print( + f"Diagnostics for {spec.name}: {spec.description}" + ) + print( + f"Result RMS: {diagnostic_result.final_rms:.6f} px, ray: {diagnostic_result.final_ray_convergence:.6f}, notes: {diagnostic_result.notes}" + ) + print() + + if args.diagnose_epipolar: + vpar = read_volume_par(case_dir / "parameters/criteria.par") + baseline_epipolar = summarize_epipolar_consistency( + observed_pixels, + diagnostic_start_cals, + control, + vpar, + num_curve_points=args.epipolar_curve_points, + ) + final_epipolar = summarize_epipolar_consistency( + observed_pixels, + diagnostic_result.refined_cals, + control, + vpar, + num_curve_points=args.epipolar_curve_points, + ) + print(f"Epipolar diagnostics against criteria.par eps0={vpar.eps0:.6f}") + print("Baseline") + print(format_epipolar_diagnostics(baseline_epipolar)) + print() + print("Final") + print(format_epipolar_diagnostics(final_epipolar)) + print() + + if args.diagnose_quadruplets: + baseline_quadruplets = summarize_quadruplet_sensitivity( + observed_pixels, + diagnostic_start_cals, + control, + ) + final_quadruplets = summarize_quadruplet_sensitivity( + observed_pixels, + diagnostic_result.refined_cals, + control, + ) + print("Quadruplet leave-one-camera-out sensitivity") + print(format_quadruplet_sensitivity(baseline_quadruplets, final_quadruplets)) + + return 0 + results = [] - for spec in default_experiments(): + for spec in experiments: print(f"Running {spec.name}: {spec.description}") result = run_experiment( spec, @@ -556,6 +1604,9 @@ def main(argv: Iterable[str] | None = None) -> int: point_init=point_init, control=control, start_cals=start_cals, + reference_cals=true_cals, + reference_geometry_points=reference_geometry_points, + geometry_export_threshold=geometry_export_threshold, source_case_dir=case_dir, output_dir=output_dir, ) @@ -570,4 +1621,4 @@ def main(argv: Iterable[str] | None = None) -> int: if __name__ == "__main__": - raise SystemExit(main()) \ No newline at end of file + raise SystemExit(main()) diff --git a/openptv_python/generate_synthetic_cavity_case.py b/openptv_python/generate_synthetic_cavity_case.py new file mode 100644 index 0000000..585d520 --- /dev/null +++ b/openptv_python/generate_synthetic_cavity_case.py @@ -0,0 +1,518 @@ +"""Generate a deterministic synthetic case modeled after test_cavity.""" + +from __future__ import annotations + +import argparse +import json +import shutil +from dataclasses import dataclass +from pathlib import Path +from typing import Iterable, List, Sequence, cast + +import numpy as np + +from .calibration import Calibration, read_calibration, write_calibration +from .imgcoord import image_coordinates +from .orientation import ( + external_calibration, + full_calibration, + initialize_bundle_adjustment_points, + match_detection_to_ref, +) +from .parameters import ControlPar, OrientPar, VolumePar, read_volume_par +from .sortgrid import read_sortgrid_par +from .tracking_frame_buf import Pathinfo, Target, n_tupel_dtype, write_path_frame, write_targets +from .trafo import arr_metric_to_pixel + +DEFAULT_SOURCE_CASE = Path("tests/testing_fodder/test_cavity") +DEFAULT_OUTPUT_CASE = Path("tests/testing_fodder/test_cavity_synthetic") +DEFAULT_SEED = 20260306 +FRAME_NUMBERS = (10001, 10002) + + +@dataclass(frozen=True) +class SyntheticCaseSummary: + """Metadata describing the generated synthetic case.""" + + seed: int + num_calibration_points: int + num_frames: int + particles_per_frame: int + calibration_position_errors: List[float] + calibration_angle_errors: List[float] + + +def clone_calibration(cal: Calibration) -> Calibration: + """Return a detached calibration copy.""" + return Calibration( + ext_par=cal.ext_par.copy(), + int_par=cal.int_par.copy(), + glass_par=cal.glass_par.copy(), + added_par=cal.added_par.copy(), + mmlut=cal.mmlut, + mmlut_data=cal.mmlut_data, + ) + + +def make_target(x: float, y: float, pnr: int) -> Target: + """Create a simple synthetic target record.""" + return Target( + pnr=pnr, + x=float(x), + y=float(y), + n=21, + nx=5, + ny=5, + sumg=5000, + tnr=-1, + ) + + +def camera_points_in_bounds(pixel_points: np.ndarray, cpar: ControlPar, margin: float = 8.0) -> np.ndarray: + """Return a mask of points lying safely inside the sensor bounds.""" + return ( + (pixel_points[:, 0] >= margin) + & (pixel_points[:, 0] <= cpar.imx - margin) + & (pixel_points[:, 1] >= margin) + & (pixel_points[:, 1] <= cpar.imy - margin) + ) + + +def z_bounds_at_x(x_coord: float, vpar: VolumePar) -> tuple[float, float]: + """Interpolate the admissible z range for one x coordinate.""" + x0, x1 = vpar.x_lay + zmin0, zmin1 = vpar.z_min_lay + zmax0, zmax1 = vpar.z_max_lay + if x1 == x0: + return zmin0, zmax0 + weight = (x_coord - x0) / (x1 - x0) + z_min = zmin0 + weight * (zmin1 - zmin0) + z_max = zmax0 + weight * (zmax1 - zmax0) + return float(z_min), float(z_max) + + +def project_pixels(points_3d: np.ndarray, cals: Sequence[Calibration], cpar: ControlPar) -> np.ndarray: + """Project 3D points into all cameras in pixel coordinates.""" + observed = np.empty((points_3d.shape[0], len(cals), 2), dtype=np.float64) + for cam_index, cal in enumerate(cals): + observed[:, cam_index, :] = arr_metric_to_pixel( + image_coordinates(points_3d, cal, cpar.mm), + cpar, + ) + return observed + + +def select_visible_points( + candidate_points: np.ndarray, + cals: Sequence[Calibration], + cpar: ControlPar, + count: int, +) -> np.ndarray: + """Return the first points that project inside every camera image.""" + projected = project_pixels(candidate_points, cals, cpar) + visibility = np.ones(candidate_points.shape[0], dtype=bool) + for cam_index in range(len(cals)): + visibility &= camera_points_in_bounds(projected[:, cam_index, :], cpar) + visible_points = candidate_points[visibility] + if visible_points.shape[0] < count: + raise ValueError("Not enough visible points for the requested synthetic case") + return visible_points[:count] + + +def build_calibration_body(vpar: VolumePar, cals: Sequence[Calibration], cpar: ControlPar) -> np.ndarray: + """Build a structured 3D calibration body visible in all cameras.""" + xs = np.linspace(vpar.x_lay[0] + 4.0, vpar.x_lay[1] - 4.0, 8) + ys = np.linspace(-16.0, 16.0, 6) + candidates = [] + for x_coord in xs: + z_min, z_max = z_bounds_at_x(float(x_coord), vpar) + if z_max - z_min < 10.0: + continue + zs = np.linspace(z_min + 4.0, z_max - 4.0, 4) + for y_coord in ys: + for z_coord in zs: + candidates.append([float(x_coord), float(y_coord), float(z_coord)]) + candidate_points = np.asarray(candidates, dtype=np.float64) + return select_visible_points(candidate_points, cals, cpar, count=48) + + +def generate_particle_cloud( + rng: np.random.Generator, + vpar: VolumePar, + cals: Sequence[Calibration], + cpar: ControlPar, + count: int, +) -> np.ndarray: + """Sample random 3D particles inside the observed volume and keep visible quadruplets.""" + accepted: List[np.ndarray] = [] + while len(accepted) < count: + x_coord = float(rng.uniform(vpar.x_lay[0] + 2.0, vpar.x_lay[1] - 2.0)) + z_min, z_max = z_bounds_at_x(x_coord, vpar) + y_coord = float(rng.uniform(-18.0, 18.0)) + z_coord = float(rng.uniform(z_min + 2.0, z_max - 2.0)) + point = np.asarray([[x_coord, y_coord, z_coord]], dtype=np.float64) + projected = project_pixels(point, cals, cpar)[0] + if all(camera_points_in_bounds(projected[None, cam_index, :], cpar)[0] for cam_index in range(len(cals))): + accepted.append(point[0]) + return np.asarray(accepted, dtype=np.float64) + + +def shuffled_targets_from_pixels( + pixel_points: np.ndarray, + rng: np.random.Generator, + noise_sigma: float, +) -> tuple[List[Target], np.ndarray]: + """Shuffle projected pixels into a target list and return point-to-target indices.""" + noisy_pixels = pixel_points + rng.normal(0.0, noise_sigma, size=pixel_points.shape) + order = rng.permutation(pixel_points.shape[0]) + targets = [make_target(noisy_pixels[target_index, 0], noisy_pixels[target_index, 1], list_index) for list_index, target_index in enumerate(order)] + point_to_target = np.empty(pixel_points.shape[0], dtype=np.int32) + for list_index, target_index in enumerate(order): + point_to_target[target_index] = list_index + return targets, point_to_target + + +def write_calibration_body_points(points: np.ndarray, output_file: Path) -> None: + """Write a calblock-compatible calibration body file.""" + lines = [f"{index + 1:11d}{point[0]:11.3f}{point[1]:11.3f}{point[2]:11.3f}" for index, point in enumerate(points)] + output_file.write_text("\n".join(lines) + "\n", encoding="utf-8") + + +def perturb_calibration_for_recovery(cal: Calibration, camera_index: int) -> Calibration: + """Create a deterministic seed calibration for full_calibration recovery.""" + trial = clone_calibration(cal) + position_deltas = [ + np.array([1.0, -0.8, 0.4]), + np.array([-0.7, 0.5, -0.3]), + np.array([0.6, 0.4, -0.5]), + np.array([-0.5, -0.6, 0.4]), + ] + angle_deltas = [ + np.array([0.006, -0.004, 0.003]), + np.array([-0.005, 0.004, -0.003]), + np.array([0.004, 0.003, -0.002]), + np.array([-0.004, -0.003, 0.002]), + ] + trial.set_pos(trial.get_pos() + position_deltas[camera_index]) + trial.set_angles(trial.get_angles() + angle_deltas[camera_index]) + return trial + + +def select_external_seed_subset(ref_points: np.ndarray) -> np.ndarray: + """Pick a well-spread subset of reference points for external calibration.""" + mins = np.argmin(ref_points, axis=0) + maxs = np.argmax(ref_points, axis=0) + center = np.argmin(np.linalg.norm(ref_points - np.mean(ref_points, axis=0), axis=1)) + indices = [] + for index in np.concatenate([mins, maxs, np.array([center])]): + if int(index) not in indices: + indices.append(int(index)) + return np.asarray(indices[:6], dtype=np.int32) + + +def recover_calibrations_from_body( + truth_cals: Sequence[Calibration], + ref_points: np.ndarray, + calibration_targets_dir: Path, + output_cal_dir: Path, + truth_cal_dir: Path, + cpar: ControlPar, + orient_par: OrientPar, + sortgrid_eps: int, + rng: np.random.Generator, +) -> tuple[List[Calibration], List[float], List[float]]: + """Recover working calibrations from synthetic calibration-body targets.""" + recovered = [] + position_errors = [] + angle_errors = [] + + seed_indices = select_external_seed_subset(ref_points) + + for camera_index, truth_cal in enumerate(truth_cals, start=1): + projected = arr_metric_to_pixel(image_coordinates(ref_points, truth_cal, cpar.mm), cpar) + targets, _ = shuffled_targets_from_pixels(projected, rng, noise_sigma=0.0) + write_targets( + targets, + len(targets), + str(calibration_targets_dir / f"cam{camera_index}.%05d"), + 1, + ) + + seed = perturb_calibration_for_recovery(truth_cal, camera_index - 1) + external_calibration( + seed, + ref_points[seed_indices], + projected[seed_indices], + cpar, + ) + sorted_targets = match_detection_to_ref(seed, ref_points, targets, cpar, sortgrid_eps) + full_calibration( + seed, + ref_points, + cast(np.ndarray, sorted_targets), + cpar, + orient_par, + ) + recovered.append(seed) + + write_calibration( + seed, + output_cal_dir / f"cam{camera_index}.tif.ori", + output_cal_dir / f"cam{camera_index}.tif.addpar", + ) + write_calibration( + truth_cal, + truth_cal_dir / f"cam{camera_index}.tif.ori", + truth_cal_dir / f"cam{camera_index}.tif.addpar", + ) + + position_errors.append(float(np.linalg.norm(seed.get_pos() - truth_cal.get_pos()))) + angle_errors.append(float(np.linalg.norm(seed.get_angles() - truth_cal.get_angles()))) + + return recovered, position_errors, angle_errors + + +def build_frame_targets_and_paths( + frame_points: np.ndarray, + cals: Sequence[Calibration], + cpar: ControlPar, + rng: np.random.Generator, +) -> tuple[list[list[Target]], np.recarray, list[Pathinfo]]: + """Create target files plus rt_is-compatible correspondences for one frame.""" + projected = project_pixels(frame_points, cals, cpar) + per_camera_targets = [] + per_camera_mapping = [] + for cam_index in range(len(cals)): + targets, point_to_target = shuffled_targets_from_pixels(projected[:, cam_index, :], rng, noise_sigma=0.08) + per_camera_targets.append(targets) + per_camera_mapping.append(point_to_target) + + observed_pixels = np.empty_like(projected) + for point_index in range(frame_points.shape[0]): + for cam_index in range(len(cals)): + target_index = per_camera_mapping[cam_index][point_index] + observed_pixels[point_index, cam_index, 0] = per_camera_targets[cam_index][target_index].x + observed_pixels[point_index, cam_index, 1] = per_camera_targets[cam_index][target_index].y + + initial_points, _ = initialize_bundle_adjustment_points(observed_pixels, list(cals), cpar) + cor_buf = np.recarray((frame_points.shape[0],), dtype=n_tupel_dtype) + path_buf = [Pathinfo() for _ in range(frame_points.shape[0])] + for point_index in range(frame_points.shape[0]): + cor_buf[point_index].p = np.array( + [per_camera_mapping[cam_index][point_index] for cam_index in range(len(cals))], + dtype=np.int32, + ) + cor_buf[point_index].corr = 1.0 + path_buf[point_index].x = initial_points[point_index] + return per_camera_targets, cor_buf, path_buf + + +def write_sequence_file(sequence_path: Path, frame_numbers: Sequence[int]) -> None: + """Write a sequence.par file consistent with img_orig target files.""" + lines = [ + "img_orig/cam1.%05d", + "img_orig/cam2.%05d", + "img_orig/cam3.%05d", + "img_orig/cam4.%05d", + str(frame_numbers[0]), + str(frame_numbers[-1]), + ] + sequence_path.write_text("\n".join(lines) + "\n", encoding="utf-8") + + +def prepare_output_case(source_case: Path, output_case: Path) -> None: + """Copy the source structure and clear generated data directories.""" + shutil.copytree(source_case, output_case, dirs_exist_ok=True) + for name in ("cal", "img_orig", "res_orig", "ground_truth", "calibration_targets"): + path = output_case / name + if path.exists(): + shutil.rmtree(path) + (output_case / "cal").mkdir(parents=True, exist_ok=True) + (output_case / "img_orig").mkdir(parents=True, exist_ok=True) + (output_case / "res_orig").mkdir(parents=True, exist_ok=True) + (output_case / "ground_truth" / "cal").mkdir(parents=True, exist_ok=True) + (output_case / "ground_truth" / "particles").mkdir(parents=True, exist_ok=True) + (output_case / "calibration_targets").mkdir(parents=True, exist_ok=True) + + +def write_case_readme(output_case: Path) -> None: + """Document the generated synthetic case contents.""" + text = """# Synthetic Cavity Case + +This case is generated deterministically from the geometry of `test_cavity`, but all observations come from known ground truth. + +Contents: + +- `cal/`: working calibrations recovered from synthetic calibration-body targets using `full_calibration`. +- `ground_truth/cal/`: exact camera models used to project the synthetic data. +- `ground_truth/calibration_body_points.txt`: known 3D calibration-body points. +- `calibration_targets/`: synthetic target files for that calibration body. +- `img_orig/`: synthetic particle target files for two frames. +- `res_orig/`: synthetic `rt_is`, `ptv_is`, and `added` files for those frames. +- `ground_truth/particles/`: exact 3D particle coordinates per frame. +- `ground_truth/manifest.json`: generation seed and calibration-recovery errors. +""" + (output_case / "README.md").write_text(text, encoding="utf-8") + + +def generate_synthetic_case( + source_case: Path, + output_case: Path, + *, + seed: int, + particles_per_frame: int, +) -> SyntheticCaseSummary: + """Generate the synthetic cavity-like case on disk.""" + rng = np.random.default_rng(seed) + prepare_output_case(source_case, output_case) + write_case_readme(output_case) + + control = ControlPar(4).from_file(source_case / "parameters/ptv.par") + vpar = read_volume_par(source_case / "parameters/criteria.par") + orient_par = OrientPar().from_file(source_case / "parameters/orient.par") + sortgrid_eps = read_sortgrid_par(source_case / "parameters/sortgrid.par") + truth_cals = [ + read_calibration( + source_case / f"cal/cam{camera_index}.tif.ori", + source_case / f"cal/cam{camera_index}.tif.addpar", + ) + for camera_index in range(1, 5) + ] + + calibration_body = build_calibration_body(vpar, truth_cals, control) + write_calibration_body_points( + calibration_body, + output_case / "ground_truth/calibration_body_points.txt", + ) + shutil.copy2( + output_case / "ground_truth/calibration_body_points.txt", + output_case / "cal/calblock.txt", + ) + + recovered_cals, position_errors, angle_errors = recover_calibrations_from_body( + truth_cals, + calibration_body, + output_case / "calibration_targets", + output_case / "cal", + output_case / "ground_truth/cal", + control, + orient_par, + sortgrid_eps, + rng, + ) + + write_sequence_file(output_case / "parameters/sequence.par", FRAME_NUMBERS) + + for frame_number in FRAME_NUMBERS: + frame_points = generate_particle_cloud( + rng, + vpar, + truth_cals, + control, + particles_per_frame, + ) + np.savetxt( + output_case / "ground_truth/particles" / f"frame_{frame_number}.txt", + frame_points, + fmt="%.6f", + header="x y z", + comments="", + ) + + per_camera_targets, cor_buf, path_buf = build_frame_targets_and_paths( + frame_points, + recovered_cals, + control, + rng, + ) + for camera_index, targets in enumerate(per_camera_targets, start=1): + write_targets( + targets, + len(targets), + str(output_case / "img_orig" / f"cam{camera_index}.%05d"), + frame_number, + ) + + write_path_frame( + cor_buf, + path_buf, + len(path_buf), + str(output_case / "res_orig" / "rt_is"), + str(output_case / "res_orig" / "ptv_is"), + str(output_case / "res_orig" / "added"), + frame_number, + ) + + manifest = SyntheticCaseSummary( + seed=seed, + num_calibration_points=int(calibration_body.shape[0]), + num_frames=len(FRAME_NUMBERS), + particles_per_frame=particles_per_frame, + calibration_position_errors=position_errors, + calibration_angle_errors=angle_errors, + ) + (output_case / "ground_truth/manifest.json").write_text( + json.dumps(manifest.__dict__, indent=2) + "\n", + encoding="utf-8", + ) + return manifest + + +def parse_args(argv: Iterable[str] | None = None) -> argparse.Namespace: + """Parse command-line arguments for synthetic-case generation.""" + parser = argparse.ArgumentParser( + description="Generate a synthetic bundle-adjustment case modeled after test_cavity.", + ) + parser.add_argument( + "--source-case", + type=Path, + default=DEFAULT_SOURCE_CASE, + help="Empirical case used as the geometric template.", + ) + parser.add_argument( + "--output-case", + type=Path, + default=DEFAULT_OUTPUT_CASE, + help="Destination case folder to populate.", + ) + parser.add_argument( + "--seed", + type=int, + default=DEFAULT_SEED, + help="Random seed for deterministic particle generation.", + ) + parser.add_argument( + "--particles-per-frame", + type=int, + default=96, + help="Number of fully observed particles to synthesize per frame.", + ) + return parser.parse_args(list(argv) if argv is not None else None) + + +def main(argv: Iterable[str] | None = None) -> int: + """Generate the synthetic case and print a compact summary.""" + args = parse_args(argv) + summary = generate_synthetic_case( + args.source_case.resolve(), + args.output_case.resolve(), + seed=args.seed, + particles_per_frame=args.particles_per_frame, + ) + print(f"Wrote synthetic case to {args.output_case.resolve()}") + print(f"Calibration points: {summary.num_calibration_points}") + print(f"Frames: {summary.num_frames}") + print(f"Particles per frame: {summary.particles_per_frame}") + print( + "Calibration recovery position errors: " + + ", ".join(f"{value:.6f}" for value in summary.calibration_position_errors) + ) + print( + "Calibration recovery angle errors: " + + ", ".join(f"{value:.6f}" for value in summary.calibration_angle_errors) + ) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) \ No newline at end of file diff --git a/openptv_python/orientation.py b/openptv_python/orientation.py index b86c0ad..0e22115 100644 --- a/openptv_python/orientation.py +++ b/openptv_python/orientation.py @@ -1369,11 +1369,15 @@ def _bundle_adjustment_jacobian_sparsity( point_offset: int, optimize_points: bool, active_priors: List[Tuple[int, int, float, float]], + active_known_points: List[Tuple[int, np.ndarray, np.ndarray]], ) -> scipy.sparse.csr_matrix: """Build the Jacobian sparsity pattern for finite-difference bundle adjustment.""" num_observation_residuals = int(np.count_nonzero(obs_mask) * 2) num_prior_residuals = len(active_priors) - total_residuals = num_observation_residuals + num_prior_residuals + num_known_point_residuals = len(active_known_points) * 3 + total_residuals = ( + num_observation_residuals + num_prior_residuals + num_known_point_residuals + ) total_parameters = point_offset + (obs_mask.shape[0] * 3 if optimize_points else 0) sparsity = scipy.sparse.lil_matrix( @@ -1401,14 +1405,61 @@ def _bundle_adjustment_jacobian_sparsity( row += 2 - for cam_index, param_index, _sigma, _base_value in active_priors: + for cam_index, param_index, _prior_sigma, _base_value in active_priors: cam_start = cam_index * camera_block_size sparsity[row, cam_start + param_index] = 1 row += 1 + if optimize_points: + for point_index, _target, _point_sigma in active_known_points: + point_start = point_offset + point_index * 3 + sparsity[row : row + 3, point_start : point_start + 3] = 1 + row += 3 + return sparsity.tocsr() +def _normalize_known_point_constraints( + num_points: int, + known_points: Optional[Dict[int, np.ndarray]], + known_point_sigmas: Optional[float | np.ndarray], +) -> List[Tuple[int, np.ndarray, np.ndarray]]: + """Normalize known-point priors into indexed target and sigma vectors.""" + if known_points is None: + return [] + + sigma_source: float | np.ndarray + sigma_source = 1.0 if known_point_sigmas is None else known_point_sigmas + sigma_array = np.asarray(sigma_source, dtype=np.float64) + + constraints = [] + for point_index in sorted(known_points): + if point_index < 0 or point_index >= num_points: + raise ValueError("known_points contains an out-of-range point index") + + target = np.asarray(known_points[point_index], dtype=np.float64) + if target.shape != (3,): + raise ValueError("Each known_points entry must have shape (3,)") + + if sigma_array.ndim == 0: + sigma = np.full(3, float(sigma_array), dtype=np.float64) + elif sigma_array.shape == (3,): + sigma = sigma_array.copy() + elif sigma_array.shape == (num_points, 3): + sigma = sigma_array[point_index].copy() + else: + raise ValueError( + "known_point_sigmas must be a scalar, shape (3,), or shape (num_points, 3)" + ) + + if np.any(sigma <= 0): + raise ValueError("known_point_sigmas must be strictly positive") + + constraints.append((point_index, target, sigma)) + + return constraints + + def multi_camera_bundle_adjustment( observed_pixels: np.ndarray, cals: List[Calibration], @@ -1425,6 +1476,8 @@ def multi_camera_bundle_adjustment( max_nfev: Optional[int] = None, optimize_extrinsics: bool = True, optimize_points: bool = True, + known_points: Optional[Dict[int, np.ndarray]] = None, + known_point_sigmas: Optional[float | np.ndarray] = None, ftol: Optional[float] = None, xtol: Optional[float] = None, gtol: Optional[float] = None, @@ -1474,6 +1527,10 @@ def multi_camera_bundle_adjustment( raise ValueError( "Bundle adjustment must optimize points or at least one camera" ) + if known_points is not None and not optimize_points: + raise ValueError( + "known_points constraints require optimize_points=True in the current implementation" + ) # Refining both 3D points and camera poses from image observations has a similarity # gauge. One fixed camera removes global translation/rotation, but scale still drifts @@ -1551,6 +1608,12 @@ def multi_camera_bundle_adjustment( continue active_priors.append((cam_index, param_index, sigma, base_value)) + active_known_points = _normalize_known_point_constraints( + num_points, + known_points, + known_point_sigmas, + ) + camera_lower, camera_upper = _expand_parameter_limits( parameter_names, parameter_bounds, @@ -1592,7 +1655,11 @@ def unpack_parameters(params: np.ndarray) -> Tuple[List[Calibration], np.ndarray def residual_vector(params: np.ndarray) -> np.ndarray: trial_cals, points = unpack_parameters(params) - residuals = np.empty(int(np.count_nonzero(obs_mask) * 2) + len(active_priors)) + residuals = np.empty( + int(np.count_nonzero(obs_mask) * 2) + + len(active_priors) + + 3 * len(active_known_points) + ) row = 0 for cam, point_indices in enumerate(observation_point_indices): @@ -1615,6 +1682,10 @@ def residual_vector(params: np.ndarray) -> np.ndarray: residuals[row] = (value - base_value) / sigma row += 1 + for point_index, target, point_sigma in active_known_points: + residuals[row : row + 3] = (points[point_index] - target) / point_sigma + row += 3 + return residuals initial_cals, initial_points = unpack_parameters(x0) @@ -1639,6 +1710,7 @@ def residual_vector(params: np.ndarray) -> np.ndarray: point_offset, optimize_points, active_priors, + active_known_points, ) if ftol is not None: least_squares_kwargs["ftol"] = ftol @@ -1663,6 +1735,9 @@ def residual_vector(params: np.ndarray) -> np.ndarray: observed_pixels, refined_points, refined_cals, cpar ) result["optimized_camera_indices"] = optimized_cam_indices + result["known_point_indices"] = [ + point_index for point_index, _, _ in active_known_points + ] return refined_cals, refined_points, result @@ -1691,10 +1766,80 @@ def guarded_two_step_bundle_adjustment( intrinsic_gtol: Optional[float] = 1e-12, pose_optimize_points: bool = True, intrinsic_optimize_points: bool = True, + known_points: Optional[Dict[int, np.ndarray]] = None, + known_point_sigmas: Optional[float | np.ndarray] = None, + geometry_reference_points: Optional[np.ndarray] = None, + geometry_reference_cals: Optional[List[Calibration]] = None, + geometry_guard_mode: str = "off", + geometry_guard_threshold: Optional[float] = None, reject_worse_solution: bool = True, reject_on_ray_convergence: bool = True, ) -> Tuple[List[Calibration], np.ndarray, Dict[str, object]]: """Run pose-only BA then tightly constrained intrinsics BA with acceptance checks.""" + + def projection_drift_summaries( + reference_cals: List[Calibration], + candidate_cals: List[Calibration], + reference_points: Optional[np.ndarray], + ) -> Optional[List[Dict[str, float]]]: + if reference_points is None: + return None + + summaries: List[Dict[str, float]] = [] + for camera_index, (reference_cal, candidate_cal) in enumerate( + zip(reference_cals, candidate_cals), + start=1, + ): + reference_pixels = [] + candidate_pixels = [] + for point in reference_points: + ref_x, ref_y = img_coord(point, reference_cal, cpar.mm) + cand_x, cand_y = img_coord(point, candidate_cal, cpar.mm) + reference_pixels.append(metric_to_pixel(ref_x, ref_y, cpar)) + candidate_pixels.append(metric_to_pixel(cand_x, cand_y, cpar)) + + displacement = np.linalg.norm( + np.asarray(candidate_pixels) - np.asarray(reference_pixels), + axis=1, + ) + summaries.append( + { + "camera_index": float(camera_index), + "mean_distance": float(displacement.mean()), + "p95_distance": float(np.percentile(displacement, 95.0)), + "max_distance": float(displacement.max()), + } + ) + + return summaries + + def max_projection_drift( + summaries: Optional[List[Dict[str, float]]], + ) -> Optional[float]: + if not summaries: + return None + return max(item["max_distance"] for item in summaries) + + def geometry_stage_ok( + candidate_metric: Optional[float], + baseline_metric: Optional[float], + ) -> bool: + if geometry_guard_mode == "off" or candidate_metric is None: + return True + if geometry_guard_mode == "soft": + if baseline_metric is None: + return True + return candidate_metric <= baseline_metric + 1e-12 + if geometry_guard_mode == "hard": + if geometry_guard_threshold is None or geometry_guard_threshold <= 0: + raise ValueError( + "geometry_guard_threshold must be positive when geometry_guard_mode='hard'" + ) + return candidate_metric <= geometry_guard_threshold + raise ValueError( + "geometry_guard_mode must be one of 'off', 'soft', or 'hard'" + ) + base_cals = [_clone_calibration(cal) for cal in cals] if point_init is None: base_points, _ = initialize_bundle_adjustment_points( @@ -1707,6 +1852,15 @@ def guarded_two_step_bundle_adjustment( baseline_rms = reprojection_rms(observed_pixels, base_points, base_cals, cpar) baseline_ray_convergence = mean_ray_convergence(observed_pixels, base_cals, cpar) + if geometry_reference_cals is None: + geometry_reference_cals = [_clone_calibration(cal) for cal in base_cals] + + baseline_geometry = projection_drift_summaries( + geometry_reference_cals, + base_cals, + geometry_reference_points, + ) + baseline_geometry_max = max_projection_drift(baseline_geometry) pose_cals, pose_points, pose_result = multi_camera_bundle_adjustment( observed_pixels, @@ -1722,9 +1876,17 @@ def guarded_two_step_bundle_adjustment( max_nfev=pose_max_nfev, optimize_extrinsics=True, optimize_points=pose_optimize_points, + known_points=known_points, + known_point_sigmas=known_point_sigmas, ) pose_rms = reprojection_rms(observed_pixels, pose_points, pose_cals, cpar) pose_ray_convergence = mean_ray_convergence(observed_pixels, pose_cals, cpar) + pose_geometry = projection_drift_summaries( + geometry_reference_cals, + pose_cals, + geometry_reference_points, + ) + pose_geometry_max = max_projection_drift(pose_geometry) intrinsic_fixed = list(range(len(cals))) intrinsic_cals, intrinsic_points, intrinsic_result = multi_camera_bundle_adjustment( @@ -1741,6 +1903,8 @@ def guarded_two_step_bundle_adjustment( max_nfev=intrinsic_max_nfev, optimize_extrinsics=False, optimize_points=intrinsic_optimize_points, + known_points=known_points, + known_point_sigmas=known_point_sigmas, ftol=intrinsic_ftol, xtol=intrinsic_xtol, gtol=intrinsic_gtol, @@ -1751,6 +1915,12 @@ def guarded_two_step_bundle_adjustment( intrinsic_ray_convergence = mean_ray_convergence( observed_pixels, intrinsic_cals, cpar ) + intrinsic_geometry = projection_drift_summaries( + geometry_reference_cals, + intrinsic_cals, + geometry_reference_points, + ) + intrinsic_geometry_max = max_projection_drift(intrinsic_geometry) accepted_stage = "intrinsics" final_cals = intrinsic_cals @@ -1762,10 +1932,17 @@ def guarded_two_step_bundle_adjustment( not reject_on_ray_convergence or pose_ray_convergence <= baseline_ray_convergence ) + pose_geometry_ok = geometry_stage_ok(pose_geometry_max, baseline_geometry_max) + pose_ok = pose_ok and pose_geometry_ok intrinsic_ok = intrinsic_rms <= pose_rms and ( not reject_on_ray_convergence or intrinsic_ray_convergence <= pose_ray_convergence ) + intrinsic_geometry_ok = geometry_stage_ok( + intrinsic_geometry_max, + pose_geometry_max, + ) + intrinsic_ok = intrinsic_ok and intrinsic_geometry_ok if reject_worse_solution: if not pose_ok: @@ -1786,14 +1963,24 @@ def guarded_two_step_bundle_adjustment( "baseline_mean_ray_convergence": baseline_ray_convergence, "baseline_cals": base_cals, "baseline_points": base_points, + "baseline_geometry": baseline_geometry, + "baseline_geometry_max": baseline_geometry_max, "pose_reprojection_rms": pose_rms, "pose_mean_ray_convergence": pose_ray_convergence, "pose_cals": pose_cals, "pose_points": pose_points, + "pose_geometry": pose_geometry, + "pose_geometry_max": pose_geometry_max, + "pose_geometry_ok": pose_geometry_ok, "intrinsic_reprojection_rms": intrinsic_rms, "intrinsic_mean_ray_convergence": intrinsic_ray_convergence, "intrinsic_cals": intrinsic_cals, "intrinsic_points": intrinsic_points, + "intrinsic_geometry": intrinsic_geometry, + "intrinsic_geometry_max": intrinsic_geometry_max, + "intrinsic_geometry_ok": intrinsic_geometry_ok, + "geometry_guard_mode": geometry_guard_mode, + "geometry_guard_threshold": geometry_guard_threshold, "accepted_stage": accepted_stage, "final_reprojection_rms": final_rms, "final_mean_ray_convergence": final_ray_convergence, diff --git a/tests/test_bundle_adjustment.py b/tests/test_bundle_adjustment.py index 2ca4d70..fad2c68 100644 --- a/tests/test_bundle_adjustment.py +++ b/tests/test_bundle_adjustment.py @@ -237,6 +237,171 @@ def test_bundle_adjustment_rejects_scale_ambiguous_configuration(self): max_nfev=10, ) + def test_multi_camera_bundle_adjustment_known_points_constrain_geometry(self): + points = np.array( + [ + [-10.0, -10.0, 0.0], + [10.0, -10.0, 1.0], + [-10.0, 10.0, -1.0], + [10.0, 10.0, 0.5], + [0.0, 0.0, 3.0], + [6.0, -4.0, -2.0], + ], + dtype=float, + ) + observed_pixels = np.empty((len(points), 4, 2), dtype=float) + for cam_num, cal in enumerate(self.true_cals): + observed_pixels[:, cam_num, :] = arr_metric_to_pixel( + image_coordinates(points, cal, self.control.mm), self.control + ) + + point_init = points.copy() + point_init[:2] += np.array([[3.0, -2.0, 1.5], [-2.5, 1.0, -1.0]]) + start_cals = self.perturb_calibrations(self.true_cals) + ba_kwargs = { + "point_init": point_init, + "fixed_camera_indices": [0, 1], + "loss": "linear", + "method": "trf", + "prior_sigmas": { + "x0": 1.0, + "y0": 1.0, + "z0": 1.0, + "omega": 0.01, + "phi": 0.01, + "kappa": 0.01, + }, + "max_nfev": 8, + } + + unconstrained_cals, unconstrained_points, unconstrained_result = ( + multi_camera_bundle_adjustment( + observed_pixels, + start_cals, + self.control, + OrientPar(), + **ba_kwargs, + ) + ) + constrained_cals, constrained_points, constrained_result = ( + multi_camera_bundle_adjustment( + observed_pixels, + self.perturb_calibrations(self.true_cals), + self.control, + OrientPar(), + known_points={0: points[0], 1: points[1]}, + known_point_sigmas=1e-3, + **ba_kwargs, + ) + ) + + unconstrained_error = float( + np.mean(np.linalg.norm(unconstrained_points[:2] - points[:2], axis=1)) + ) + constrained_error = float( + np.mean(np.linalg.norm(constrained_points[:2] - points[:2], axis=1)) + ) + + self.assertLess(constrained_error, unconstrained_error) + self.assertLess(constrained_error, 1e-2) + self.assertIn(0, constrained_result["known_point_indices"]) + self.assertIn(1, constrained_result["known_point_indices"]) + self.assertLess( + reprojection_rms( + observed_pixels, + constrained_points, + constrained_cals, + self.control, + ), + unconstrained_result["initial_reprojection_rms"], + ) + + def test_fixed_camera_indices_preserve_selected_camera_poses(self): + points = np.array( + [ + [-10.0, -10.0, 0.0], + [10.0, -10.0, 1.0], + [-10.0, 10.0, -1.0], + [10.0, 10.0, 0.5], + [0.0, 0.0, 3.0], + [6.0, -4.0, -2.0], + ], + dtype=float, + ) + observed_pixels = np.empty((len(points), 4, 2), dtype=float) + for cam_num, cal in enumerate(self.true_cals): + observed_pixels[:, cam_num, :] = arr_metric_to_pixel( + image_coordinates(points, cal, self.control.mm), self.control + ) + + start_cals = self.perturb_calibrations(self.true_cals) + fixed_indices = [0, 1] + refined_cals, _, result = multi_camera_bundle_adjustment( + observed_pixels, + start_cals, + self.control, + OrientPar(), + point_init=points, + fixed_camera_indices=fixed_indices, + loss="linear", + method="lm", + prior_sigmas={ + "x0": 1.0, + "y0": 1.0, + "z0": 1.0, + "omega": 0.01, + "phi": 0.01, + "kappa": 0.01, + }, + max_nfev=50, + ) + + self.assertTrue(result.success, msg=result.message) + for cam_index in fixed_indices: + np.testing.assert_allclose( + refined_cals[cam_index].get_pos(), + start_cals[cam_index].get_pos(), + atol=1e-12, + ) + np.testing.assert_allclose( + refined_cals[cam_index].get_angles(), + start_cals[cam_index].get_angles(), + atol=1e-12, + ) + self.assertEqual(result["optimized_camera_indices"], [2, 3]) + self.assertLess( + result["final_reprojection_rms"], result["initial_reprojection_rms"] + ) + + def test_known_points_require_point_optimization(self): + points = np.array( + [ + [-10.0, -10.0, 0.0], + [10.0, -10.0, 1.0], + [-10.0, 10.0, -1.0], + [10.0, 10.0, 0.5], + ], + dtype=float, + ) + observed_pixels = np.empty((len(points), 4, 2), dtype=float) + for cam_num, cal in enumerate(self.true_cals): + observed_pixels[:, cam_num, :] = arr_metric_to_pixel( + image_coordinates(points, cal, self.control.mm), self.control + ) + + with self.assertRaises(ValueError): + multi_camera_bundle_adjustment( + observed_pixels, + self.perturb_calibrations(self.true_cals), + self.control, + OrientPar(), + point_init=points, + fixed_camera_indices=[0, 1], + optimize_points=False, + known_points={0: points[0]}, + known_point_sigmas=1e-3, + ) + def test_guarded_two_step_bundle_adjustment_rejects_bad_intrinsics_stage(self): points = np.array( [ @@ -332,6 +497,9 @@ def test_guarded_two_step_bundle_adjustment_rejects_bad_intrinsics_stage(self): ) self.assertEqual(mocked_adjustment.call_count, 2) + for call in mocked_adjustment.call_args_list: + self.assertIsNone(call.kwargs.get("known_points")) + self.assertIsNone(call.kwargs.get("known_point_sigmas")) self.assertEqual(summary["accepted_stage"], "pose") self.assertLess( summary["pose_reprojection_rms"], summary["baseline_reprojection_rms"] @@ -430,6 +598,269 @@ def test_guarded_two_step_bundle_adjustment_preserves_pose_when_intrinsics_are_t ) self.assertEqual(final_points.shape, points.shape) + def test_guarded_two_step_bundle_adjustment_passes_known_point_constraints(self): + points = np.array( + [ + [-10.0, -10.0, 0.0], + [10.0, -10.0, 1.0], + [-10.0, 10.0, -1.0], + [10.0, 10.0, 0.5], + ], + dtype=float, + ) + observed_pixels = np.empty((len(points), 4, 2), dtype=float) + for cam_num, cal in enumerate(self.true_cals): + observed_pixels[:, cam_num, :] = arr_metric_to_pixel( + image_coordinates(points, cal, self.control.mm), self.control + ) + + intrinsics = OrientPar() + intrinsics.k1flag = 1 + intrinsics.p1flag = 1 + intrinsics.p2flag = 1 + known_points = {0: points[0], 1: points[1]} + + with patch( + "openptv_python.orientation.multi_camera_bundle_adjustment", + side_effect=[ + (self.true_cals, points.copy(), {"success": True}), + (self.true_cals, points.copy(), {"success": True}), + ], + ) as mocked_adjustment: + guarded_two_step_bundle_adjustment( + observed_pixels, + self.lightly_perturb_calibrations(self.true_cals), + self.control, + OrientPar(), + intrinsics, + point_init=points, + fixed_camera_indices=[0, 1], + known_points=known_points, + known_point_sigmas=1e-3, + ) + + self.assertEqual(mocked_adjustment.call_count, 2) + for call in mocked_adjustment.call_args_list: + self.assertEqual(call.kwargs["known_points"], known_points) + self.assertEqual(call.kwargs["known_point_sigmas"], 1e-3) + + def test_guarded_two_step_bundle_adjustment_rejects_on_hard_geometry_guard(self): + points = np.array( + [ + [-10.0, -10.0, 0.0], + [10.0, -10.0, 1.0], + [-10.0, 10.0, -1.0], + [10.0, 10.0, 0.5], + ], + dtype=float, + ) + observed_pixels = np.zeros((len(points), 4, 2), dtype=float) + start_cals = self.lightly_perturb_calibrations(self.true_cals) + pose_cals = [self.clone_calibration(cal) for cal in self.true_cals] + bad_intrinsic_cals = [self.clone_calibration(cal) for cal in self.true_cals] + bad_intrinsic_cals[2].set_pos( + bad_intrinsic_cals[2].get_pos() + np.array([20.0, 0.0, 0.0]) + ) + bad_intrinsic_cals[2].set_angles( + bad_intrinsic_cals[2].get_angles() + np.array([0.0, 0.05, 0.0]) + ) + + intrinsics = OrientPar() + intrinsics.k1flag = 1 + intrinsics.p1flag = 1 + intrinsics.p2flag = 1 + + with patch( + "openptv_python.orientation.multi_camera_bundle_adjustment", + side_effect=[ + (pose_cals, points.copy(), {"success": True}), + (bad_intrinsic_cals, points.copy(), {"success": True}), + ], + ), patch( + "openptv_python.orientation.reprojection_rms", + side_effect=[10.0, 5.0, 4.0], + ), patch( + "openptv_python.orientation.mean_ray_convergence", + side_effect=[3.0, 2.0, 1.0], + ), patch( + "openptv_python.orientation.img_coord", + side_effect=lambda point, cal, _mm: ( + float(point[0] + cal.get_pos()[0]), + float(point[1] + cal.get_pos()[1]), + ), + ), patch( + "openptv_python.orientation.metric_to_pixel", + side_effect=lambda x, y, _cpar: np.array([x, y], dtype=float), + ): + final_cals, _, summary = guarded_two_step_bundle_adjustment( + observed_pixels, + start_cals, + self.control, + OrientPar(), + intrinsics, + point_init=points, + fixed_camera_indices=[0, 1], + geometry_reference_points=points, + geometry_reference_cals=self.true_cals, + geometry_guard_mode="hard", + geometry_guard_threshold=1.0, + ) + + self.assertEqual(summary["accepted_stage"], "pose") + self.assertTrue(summary["pose_geometry_ok"]) + self.assertFalse(summary["intrinsic_geometry_ok"]) + self.assertGreater(summary["intrinsic_geometry_max"], 1.0) + np.testing.assert_allclose(final_cals[2].get_pos(), pose_cals[2].get_pos()) + + def test_guarded_two_step_bundle_adjustment_rejects_on_soft_geometry_guard(self): + points = np.array( + [ + [-10.0, -10.0, 0.0], + [10.0, -10.0, 1.0], + [-10.0, 10.0, -1.0], + [10.0, 10.0, 0.5], + ], + dtype=float, + ) + observed_pixels = np.zeros((len(points), 4, 2), dtype=float) + start_cals = self.lightly_perturb_calibrations(self.true_cals) + pose_cals = [self.clone_calibration(cal) for cal in self.true_cals] + bad_intrinsic_cals = [self.clone_calibration(cal) for cal in self.true_cals] + bad_intrinsic_cals[3].set_pos( + bad_intrinsic_cals[3].get_pos() + np.array([2.0, 0.0, 0.0]) + ) + + intrinsics = OrientPar() + intrinsics.k1flag = 1 + intrinsics.p1flag = 1 + intrinsics.p2flag = 1 + + with patch( + "openptv_python.orientation.multi_camera_bundle_adjustment", + side_effect=[ + (pose_cals, points.copy(), {"success": True}), + (bad_intrinsic_cals, points.copy(), {"success": True}), + ], + ), patch( + "openptv_python.orientation.reprojection_rms", + side_effect=[10.0, 5.0, 4.0], + ), patch( + "openptv_python.orientation.mean_ray_convergence", + side_effect=[3.0, 2.0, 1.0], + ), patch( + "openptv_python.orientation.img_coord", + side_effect=lambda point, cal, _mm: ( + float(point[0] + cal.get_pos()[0]), + float(point[1] + cal.get_pos()[1]), + ), + ), patch( + "openptv_python.orientation.metric_to_pixel", + side_effect=lambda x, y, _cpar: np.array([x, y], dtype=float), + ): + final_cals, _, summary = guarded_two_step_bundle_adjustment( + observed_pixels, + start_cals, + self.control, + OrientPar(), + intrinsics, + point_init=points, + fixed_camera_indices=[0, 1], + geometry_reference_points=points, + geometry_reference_cals=self.true_cals, + geometry_guard_mode="soft", + ) + + self.assertEqual(summary["accepted_stage"], "pose") + self.assertTrue(summary["pose_geometry_ok"]) + self.assertFalse(summary["intrinsic_geometry_ok"]) + self.assertGreater( + summary["intrinsic_geometry_max"], + summary["pose_geometry_max"], + ) + np.testing.assert_allclose(final_cals[3].get_pos(), pose_cals[3].get_pos()) + + def test_cavity_intrinsics_only_improves_from_intrinsic_perturbation(self): + cavity_dir = Path("tests/testing_fodder/test_cavity") + control = ControlPar(4).from_file(cavity_dir / "parameters/ptv.par") + true_cals = [ + read_calibration( + cavity_dir / f"cal/cam{cam_num}.tif.ori", + cavity_dir / f"cal/cam{cam_num}.tif.addpar", + ) + for cam_num in range(1, 5) + ] + + cor_buf, path_buf = read_path_frame( + str(cavity_dir / "res_orig/rt_is"), + "", + "", + 10001, + ) + targets = [ + read_targets(str(cavity_dir / f"img_orig/cam{cam_num}.%05d"), 10001) + for cam_num in range(1, 5) + ] + subset = [ + pt_num for pt_num, corres in enumerate(cor_buf) if np.all(corres.p >= 0) + ][:24] + observed_pixels = np.full((len(subset), 4, 2), np.nan, dtype=float) + point_init = np.empty((len(subset), 3), dtype=float) + for out_num, pt_num in enumerate(subset): + point_init[out_num] = path_buf[pt_num].x + for cam in range(4): + target_index = cor_buf[pt_num].p[cam] + observed_pixels[out_num, cam, 0] = targets[cam][target_index].x + observed_pixels[out_num, cam, 1] = targets[cam][target_index].y + + start_cals = [self.clone_calibration(cal) for cal in true_cals] + for cal in start_cals: + cal.added_par[0] += 2e-5 + cal.added_par[3] += 8e-5 + cal.added_par[4] -= 6e-5 + + before_rms = reprojection_rms(observed_pixels, point_init, start_cals, control) + intrinsics = OrientPar() + intrinsics.k1flag = 1 + intrinsics.p1flag = 1 + intrinsics.p2flag = 1 + + refined_cals, refined_points, result = multi_camera_bundle_adjustment( + observed_pixels, + start_cals, + control, + intrinsics, + point_init=point_init.copy(), + fixed_camera_indices=[0, 1, 2, 3], + optimize_extrinsics=False, + optimize_points=False, + loss="linear", + method="trf", + prior_sigmas={ + "k1": 5e-5, + "p1": 1e-4, + "p2": 1e-4, + }, + parameter_bounds={ + "k1": (-5e-5, 5e-5), + "p1": (-2e-4, 2e-4), + "p2": (-2e-4, 2e-4), + }, + max_nfev=40, + ) + after_rms = reprojection_rms( + observed_pixels, + refined_points, + refined_cals, + control, + ) + + self.assertTrue(result.success, msg=result.message) + self.assertLess(after_rms, before_rms - 0.1) + for refined, start in zip(refined_cals, start_cals): + np.testing.assert_allclose(refined.get_pos(), start.get_pos(), atol=1e-12) + np.testing.assert_allclose(refined.get_angles(), start.get_angles(), atol=1e-12) + np.testing.assert_allclose(refined_points, point_init) + if __name__ == "__main__": unittest.main() diff --git a/tests/test_demo_bundle_adjustment.py b/tests/test_demo_bundle_adjustment.py new file mode 100644 index 0000000..dcdb78e --- /dev/null +++ b/tests/test_demo_bundle_adjustment.py @@ -0,0 +1,240 @@ +import unittest +from pathlib import Path + +import numpy as np + +from openptv_python.demo_bundle_adjustment import ( + ExperimentResult, + all_fixed_camera_pairs, + build_experiment_start_calibrations, + build_known_point_constraints, + calibration_body_projection_drift, + default_experiments, + format_quadruplet_sensitivity, + load_calibrations, + load_reference_geometry_points, + perturb_calibrations, + summarize_epipolar_consistency, + summarize_fixed_camera_diagnostics, + summarize_quadruplet_sensitivity, +) +from openptv_python.calibration import read_calibration +from openptv_python.parameters import ControlPar, read_volume_par +from openptv_python.tracking_frame_buf import read_path_frame, read_targets + + +class TestBundleAdjustmentDemo(unittest.TestCase): + def test_build_known_point_constraints_returns_copied_subset(self): + point_init = np.arange(30, dtype=float).reshape(10, 3) + + known_points = build_known_point_constraints(point_init, 4) + + self.assertEqual(sorted(known_points), [0, 3, 6, 9]) + np.testing.assert_allclose(known_points[6], point_init[6]) + + point_init[6, 0] = -999.0 + self.assertNotEqual(known_points[6][0], point_init[6, 0]) + + def test_default_experiments_adds_known_point_presets(self): + known_points = {0: np.array([0.0, 0.0, 0.0]), 5: np.array([1.0, 2.0, 3.0])} + + experiments = default_experiments( + known_points=known_points, + known_point_sigmas=0.25, + ) + + names = [spec.name for spec in experiments] + self.assertIn("intrinsics_only", names) + self.assertIn("pose_trf_known_points", names) + self.assertIn("guarded_two_step_known_points", names) + + intrinsics_only = next( + spec for spec in experiments if spec.name == "intrinsics_only" + ) + self.assertFalse(intrinsics_only.ba_kwargs["optimize_extrinsics"]) + self.assertFalse(intrinsics_only.ba_kwargs["optimize_points"]) + self.assertEqual( + intrinsics_only.ba_kwargs["fixed_camera_indices"], + [0, 1, 2, 3], + ) + self.assertTrue(intrinsics_only.ba_kwargs["use_reference_cals"]) + self.assertTrue(intrinsics_only.ba_kwargs["perturb_intrinsics_only"]) + self.assertEqual(intrinsics_only.ba_kwargs["intrinsic_perturbation_scale"], 1.0) + + pose_spec = next( + spec for spec in experiments if spec.name == "pose_trf_known_points" + ) + guarded_spec = next( + spec + for spec in experiments + if spec.name == "guarded_two_step_known_points" + ) + self.assertIs(pose_spec.ba_kwargs["known_points"], known_points) + self.assertEqual(pose_spec.ba_kwargs["known_point_sigmas"], 0.25) + self.assertIs(guarded_spec.ba_kwargs["known_points"], known_points) + self.assertEqual(guarded_spec.ba_kwargs["known_point_sigmas"], 0.25) + self.assertEqual(guarded_spec.ba_kwargs["geometry_guard_mode"], "off") + self.assertIsNone(guarded_spec.ba_kwargs["geometry_guard_threshold"]) + + def test_default_experiments_accepts_geometry_guard_configuration(self): + experiments = default_experiments( + perturbation_scale=1.0, + geometry_guard_mode="hard", + geometry_guard_threshold=2.5, + ) + + guarded_spec = next( + spec for spec in experiments if spec.name == "guarded_two_step" + ) + + self.assertEqual(guarded_spec.ba_kwargs["geometry_guard_mode"], "hard") + self.assertEqual(guarded_spec.ba_kwargs["geometry_guard_threshold"], 2.5) + + def test_pose_demo_keeps_fixed_cameras_on_reference_geometry(self): + cavity_dir = Path("tests/testing_fodder/test_cavity") + control = ControlPar(4).from_file(cavity_dir / "parameters/ptv.par") + true_cals = load_calibrations(cavity_dir, 4) + start_cals = perturb_calibrations(true_cals, 1.0) + spec = next( + experiment + for experiment in default_experiments(perturbation_scale=1.0) + if experiment.name == "pose_trf_linear" + ) + + working_cals = build_experiment_start_calibrations( + spec, + start_cals=start_cals, + reference_cals=true_cals, + ) + geometry_points = load_reference_geometry_points(cavity_dir, 4) + drift = calibration_body_projection_drift( + true_cals, + working_cals, + control, + geometry_points, + ) + + self.assertIsNotNone(drift) + np.testing.assert_allclose(working_cals[0].get_pos(), true_cals[0].get_pos()) + np.testing.assert_allclose(working_cals[1].get_pos(), true_cals[1].get_pos()) + np.testing.assert_allclose( + working_cals[0].get_angles(), + true_cals[0].get_angles(), + ) + np.testing.assert_allclose( + working_cals[1].get_angles(), + true_cals[1].get_angles(), + ) + self.assertLess(drift[0].max_distance, 1e-9) + self.assertLess(drift[1].max_distance, 1e-9) + self.assertGreater(drift[2].max_distance, 0.1) + + def test_all_fixed_camera_pairs_returns_all_unique_pairs(self): + self.assertEqual( + all_fixed_camera_pairs(4), + [(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)], + ) + + def test_summarize_fixed_camera_diagnostics_orders_by_fixed_drift_then_rms(self): + results = [ + ExperimentResult( + name="run_b", + description="", + duration_sec=0.2, + success=True, + initial_rms=4.0, + final_rms=1.3, + baseline_ray_convergence=2.0, + final_ray_convergence=1.1, + notes="b", + cal_dir=None, + fixed_camera_indices=(0, 2), + camera_position_shifts=[0.0, 0.8, 0.0, 0.6], + camera_angle_shifts=[0.0, 0.02, 0.0, 0.01], + refined_cals=None, + refined_points=None, + ), + ExperimentResult( + name="run_a", + description="", + duration_sec=0.1, + success=True, + initial_rms=4.0, + final_rms=1.2, + baseline_ray_convergence=2.0, + final_ray_convergence=1.0, + notes="a", + cal_dir=None, + fixed_camera_indices=(0, 1), + camera_position_shifts=[0.0, 0.0, 0.5, 0.4], + camera_angle_shifts=[0.0, 0.0, 0.01, 0.02], + refined_cals=None, + refined_points=None, + ), + ] + + diagnostics = summarize_fixed_camera_diagnostics(results) + + self.assertEqual(diagnostics[0].fixed_camera_indices, (0, 1)) + self.assertEqual(diagnostics[1].fixed_camera_indices, (0, 2)) + self.assertEqual(diagnostics[0].fixed_position_shift, 0.0) + self.assertAlmostEqual(diagnostics[0].mean_free_position_shift, 0.45) + + def test_epipolar_and_quadruplet_diagnostics_detect_perturbation(self): + cavity_dir = Path("tests/testing_fodder/test_cavity") + control = ControlPar(4).from_file(cavity_dir / "parameters/ptv.par") + vpar = read_volume_par(cavity_dir / "parameters/criteria.par") + cals = [ + read_calibration( + cavity_dir / f"cal/cam{cam_num}.tif.ori", + cavity_dir / f"cal/cam{cam_num}.tif.addpar", + ) + for cam_num in range(1, 5) + ] + + cor_buf, path_buf = read_path_frame(str(cavity_dir / "res_orig/rt_is"), "", "", 10001) + targets = [ + read_targets(str(cavity_dir / f"img_orig/cam{cam_num}.%05d"), 10001) + for cam_num in range(1, 5) + ] + subset = [ + pt_num for pt_num, corres in enumerate(cor_buf) if np.all(corres.p >= 0) + ][:8] + observed_pixels = np.full((len(subset), 4, 2), np.nan, dtype=float) + for out_num, pt_num in enumerate(subset): + for cam in range(4): + target_index = cor_buf[pt_num].p[cam] + observed_pixels[out_num, cam, 0] = targets[cam][target_index].x + observed_pixels[out_num, cam, 1] = targets[cam][target_index].y + + baseline_epipolar = summarize_epipolar_consistency( + observed_pixels, + cals, + control, + vpar, + num_curve_points=16, + ) + baseline_quad = summarize_quadruplet_sensitivity(observed_pixels, cals, control) + + perturbed = observed_pixels.copy() + perturbed[0, 1, 0] += 25.0 + perturbed[0, 1, 1] -= 15.0 + perturbed_epipolar = summarize_epipolar_consistency( + perturbed, + cals, + control, + vpar, + num_curve_points=16, + ) + perturbed_quad = summarize_quadruplet_sensitivity(perturbed, cals, control) + + self.assertGreater( + max(item.max_distance for item in perturbed_epipolar), + max(item.max_distance for item in baseline_epipolar), + ) + self.assertGreater(perturbed_quad.max_spread, baseline_quad.max_spread) + self.assertIn("baseline", format_quadruplet_sensitivity(baseline_quad, perturbed_quad)) + + +if __name__ == "__main__": + unittest.main() \ No newline at end of file diff --git a/tests/test_synthetic_cavity_case.py b/tests/test_synthetic_cavity_case.py new file mode 100644 index 0000000..f2ca0bb --- /dev/null +++ b/tests/test_synthetic_cavity_case.py @@ -0,0 +1,270 @@ +from pathlib import Path + +import numpy as np + +from openptv_python.calibration import read_calibration +from openptv_python.demo_bundle_adjustment import load_case_observations +from openptv_python.generate_synthetic_cavity_case import ( + DEFAULT_OUTPUT_CASE, + project_pixels, +) +from openptv_python.orientation import multi_camera_bundle_adjustment, reprojection_rms +from openptv_python.parameters import ControlPar, OrientPar, SequencePar + + +def test_synthetic_cavity_case_exists_and_is_coherent(): + case_dir = Path(DEFAULT_OUTPUT_CASE) + assert case_dir.exists() + assert (case_dir / "ground_truth/manifest.json").exists() + assert (case_dir / "ground_truth/calibration_body_points.txt").exists() + + truth_cals = [ + read_calibration( + case_dir / f"ground_truth/cal/cam{camera_index}.tif.ori", + case_dir / f"ground_truth/cal/cam{camera_index}.tif.addpar", + ) + for camera_index in range(1, 5) + ] + working_cals = [ + read_calibration( + case_dir / f"cal/cam{camera_index}.tif.ori", + case_dir / f"cal/cam{camera_index}.tif.addpar", + ) + for camera_index in range(1, 5) + ] + + position_errors = [ + np.linalg.norm(working.get_pos() - truth.get_pos()) + for working, truth in zip(working_cals, truth_cals) + ] + angle_errors = [ + np.linalg.norm(working.get_angles() - truth.get_angles()) + for working, truth in zip(working_cals, truth_cals) + ] + assert max(position_errors) < 0.2 + assert max(angle_errors) < 0.01 + + control, observed_pixels, point_init = load_case_observations( + case_dir, + 4, + max_frames=1, + max_points_per_frame=16, + ) + assert observed_pixels.shape == (16, 4, 2) + rms = reprojection_rms(observed_pixels, point_init, working_cals, control) + assert rms < 0.5 + + +def load_truth_particles( + case_dir: Path, + *, + max_frames: int | None, + max_points_per_frame: int | None, +) -> np.ndarray: + """Load the exact synthetic particle coordinates in sequence order.""" + seq = SequencePar.from_file(case_dir / "parameters/sequence.par", 4) + frames = list(range(seq.first, seq.last + 1)) + if max_frames is not None: + frames = frames[:max_frames] + + truth_batches = [] + for frame in frames: + frame_points = np.loadtxt( + case_dir / "ground_truth/particles" / f"frame_{frame}.txt", + skiprows=1, + ) + if frame_points.ndim == 1: + frame_points = frame_points.reshape(1, 3) + if max_points_per_frame is not None: + frame_points = frame_points[:max_points_per_frame] + truth_batches.append(frame_points) + + return np.concatenate(truth_batches, axis=0) + + +def perturb_free_cameras(truth_cals): + """Perturb only the cameras that remain free during BA.""" + deltas = { + 2: (np.array([0.9, -0.6, 0.4]), np.array([0.008, -0.006, 0.004])), + 3: (np.array([-0.7, 0.5, -0.3]), np.array([-0.007, 0.005, -0.004])), + } + perturbed = [] + for camera_index, cal in enumerate(truth_cals): + trial = cal.__class__( + ext_par=cal.ext_par.copy(), + int_par=cal.int_par.copy(), + glass_par=cal.glass_par.copy(), + added_par=cal.added_par.copy(), + mmlut=cal.mmlut, + mmlut_data=cal.mmlut_data, + ) + if camera_index in deltas: + pos_delta, angle_delta = deltas[camera_index] + trial.set_pos(trial.get_pos() + pos_delta) + trial.set_angles(trial.get_angles() + angle_delta) + perturbed.append(trial) + return perturbed + + +def perturb_intrinsics(truth_cals): + """Perturb a small subset of intrinsic parameters while keeping poses fixed.""" + perturbed = [] + for cal in truth_cals: + trial = cal.__class__( + ext_par=cal.ext_par.copy(), + int_par=cal.int_par.copy(), + glass_par=cal.glass_par.copy(), + added_par=cal.added_par.copy(), + mmlut=cal.mmlut, + mmlut_data=cal.mmlut_data, + ) + trial.added_par[0] += 2e-5 + trial.added_par[3] += 8e-5 + trial.added_par[4] -= 6e-5 + perturbed.append(trial) + return perturbed + + +def test_synthetic_case_bundle_adjustment_recovers_ground_truth_from_controlled_perturbation(): + case_dir = Path(DEFAULT_OUTPUT_CASE) + control = ControlPar(4).from_file(case_dir / "parameters/ptv.par") + truth_cals = [ + read_calibration( + case_dir / f"ground_truth/cal/cam{camera_index}.tif.ori", + case_dir / f"ground_truth/cal/cam{camera_index}.tif.addpar", + ) + for camera_index in range(1, 5) + ] + truth_points = load_truth_particles( + case_dir, + max_frames=1, + max_points_per_frame=24, + ) + observed_pixels = project_pixels(truth_points, truth_cals, control) + + start_cals = perturb_free_cameras(truth_cals) + point_init = truth_points + np.array([0.35, -0.25, 0.18]) + + initial_camera_errors = [ + np.linalg.norm(start_cals[camera_index].get_pos() - truth_cals[camera_index].get_pos()) + for camera_index in (2, 3) + ] + initial_angle_errors = [ + np.linalg.norm(start_cals[camera_index].get_angles() - truth_cals[camera_index].get_angles()) + for camera_index in (2, 3) + ] + initial_point_error = float( + np.mean(np.linalg.norm(point_init - truth_points, axis=1)) + ) + + refined_cals, refined_points, result = multi_camera_bundle_adjustment( + observed_pixels, + start_cals, + control, + OrientPar(), + point_init=point_init, + fixed_camera_indices=[0, 1], + loss="linear", + method="lm", + prior_sigmas={ + "x0": 1.0, + "y0": 1.0, + "z0": 1.0, + "omega": 0.01, + "phi": 0.01, + "kappa": 0.01, + }, + max_nfev=100, + ) + + final_camera_errors = [ + np.linalg.norm(refined_cals[camera_index].get_pos() - truth_cals[camera_index].get_pos()) + for camera_index in (2, 3) + ] + final_angle_errors = [ + np.linalg.norm(refined_cals[camera_index].get_angles() - truth_cals[camera_index].get_angles()) + for camera_index in (2, 3) + ] + final_point_error = float( + np.mean(np.linalg.norm(refined_points - truth_points, axis=1)) + ) + + assert bool(result.success), result.message + assert max(final_camera_errors) < max(initial_camera_errors) * 0.01 + assert max(final_camera_errors) < 0.01 + assert max(final_angle_errors) < max(initial_angle_errors) * 0.01 + assert max(final_angle_errors) < 1e-4 + assert final_point_error < initial_point_error * 1e-3 + assert final_point_error < 1e-3 + assert result["final_reprojection_rms"] < 1e-3 + + +def test_synthetic_case_intrinsics_only_recovers_ground_truth_from_controlled_perturbation(): + case_dir = Path(DEFAULT_OUTPUT_CASE) + control = ControlPar(4).from_file(case_dir / "parameters/ptv.par") + truth_cals = [ + read_calibration( + case_dir / f"ground_truth/cal/cam{camera_index}.tif.ori", + case_dir / f"ground_truth/cal/cam{camera_index}.tif.addpar", + ) + for camera_index in range(1, 5) + ] + truth_points = load_truth_particles( + case_dir, + max_frames=1, + max_points_per_frame=24, + ) + observed_pixels = project_pixels(truth_points, truth_cals, control) + start_cals = perturb_intrinsics(truth_cals) + + intrinsics = OrientPar() + intrinsics.k1flag = 1 + intrinsics.p1flag = 1 + intrinsics.p2flag = 1 + + refined_cals, refined_points, result = multi_camera_bundle_adjustment( + observed_pixels, + start_cals, + control, + intrinsics, + point_init=truth_points.copy(), + fixed_camera_indices=[0, 1, 2, 3], + optimize_extrinsics=False, + optimize_points=False, + loss="linear", + method="trf", + prior_sigmas={ + "k1": 5e-5, + "p1": 1e-4, + "p2": 1e-4, + }, + parameter_bounds={ + "k1": (-5e-5, 5e-5), + "p1": (-2e-4, 2e-4), + "p2": (-2e-4, 2e-4), + }, + max_nfev=40, + ) + + k1_errors = [ + abs(refined.added_par[0] - truth.added_par[0]) + for refined, truth in zip(refined_cals, truth_cals) + ] + p1_errors = [ + abs(refined.added_par[3] - truth.added_par[3]) + for refined, truth in zip(refined_cals, truth_cals) + ] + p2_errors = [ + abs(refined.added_par[4] - truth.added_par[4]) + for refined, truth in zip(refined_cals, truth_cals) + ] + + assert bool(result.success), result.message + assert np.max(np.linalg.norm(refined_points - truth_points, axis=1)) == 0.0 + for refined, start in zip(refined_cals, start_cals): + np.testing.assert_allclose(refined.get_pos(), start.get_pos(), atol=1e-12) + np.testing.assert_allclose(refined.get_angles(), start.get_angles(), atol=1e-12) + assert max(k1_errors) < 1.5e-5 + assert max(p1_errors) < 1.5e-5 + assert max(p2_errors) < 3.0e-5 + assert result["final_reprojection_rms"] < result["initial_reprojection_rms"] * 0.2 \ No newline at end of file diff --git a/tests/testing_fodder/test_cavity_synthetic/README.md b/tests/testing_fodder/test_cavity_synthetic/README.md new file mode 100644 index 0000000..d1babae --- /dev/null +++ b/tests/testing_fodder/test_cavity_synthetic/README.md @@ -0,0 +1,14 @@ +# Synthetic Cavity Case + +This case is generated deterministically from the geometry of `test_cavity`, but all observations come from known ground truth. + +Contents: + +- `cal/`: working calibrations recovered from synthetic calibration-body targets using `full_calibration`. +- `ground_truth/cal/`: exact camera models used to project the synthetic data. +- `ground_truth/calibration_body_points.txt`: known 3D calibration-body points. +- `calibration_targets/`: synthetic target files for that calibration body. +- `img_orig/`: synthetic particle target files for two frames. +- `res_orig/`: synthetic `rt_is`, `ptv_is`, and `added` files for those frames. +- `ground_truth/particles/`: exact 3D particle coordinates per frame. +- `ground_truth/manifest.json`: generation seed and calibration-recovery errors. diff --git a/tests/testing_fodder/test_cavity_synthetic/cal/calblock.txt b/tests/testing_fodder/test_cavity_synthetic/cal/calblock.txt new file mode 100644 index 0000000..87bfe1f --- /dev/null +++ b/tests/testing_fodder/test_cavity_synthetic/cal/calblock.txt @@ -0,0 +1,48 @@ + 1 -36.000 -16.000 -16.000 + 2 -36.000 -16.000 -5.333 + 3 -36.000 -16.000 5.333 + 4 -36.000 -16.000 16.000 + 5 -36.000 -9.600 -16.000 + 6 -36.000 -9.600 -5.333 + 7 -36.000 -9.600 5.333 + 8 -36.000 -9.600 16.000 + 9 -36.000 -3.200 -16.000 + 10 -36.000 -3.200 -5.333 + 11 -36.000 -3.200 5.333 + 12 -36.000 -3.200 16.000 + 13 -36.000 3.200 -16.000 + 14 -36.000 3.200 -5.333 + 15 -36.000 3.200 5.333 + 16 -36.000 3.200 16.000 + 17 -36.000 9.600 -16.000 + 18 -36.000 9.600 -5.333 + 19 -36.000 9.600 5.333 + 20 -36.000 9.600 16.000 + 21 -36.000 16.000 -16.000 + 22 -36.000 16.000 -5.333 + 23 -36.000 16.000 5.333 + 24 -36.000 16.000 16.000 + 25 -25.714 -16.000 -16.000 + 26 -25.714 -16.000 -5.333 + 27 -25.714 -16.000 5.333 + 28 -25.714 -16.000 16.000 + 29 -25.714 -9.600 -16.000 + 30 -25.714 -9.600 -5.333 + 31 -25.714 -9.600 5.333 + 32 -25.714 -9.600 16.000 + 33 -25.714 -3.200 -16.000 + 34 -25.714 -3.200 -5.333 + 35 -25.714 -3.200 5.333 + 36 -25.714 -3.200 16.000 + 37 -25.714 3.200 -16.000 + 38 -25.714 3.200 -5.333 + 39 -25.714 3.200 5.333 + 40 -25.714 3.200 16.000 + 41 -25.714 9.600 -16.000 + 42 -25.714 9.600 -5.333 + 43 -25.714 9.600 5.333 + 44 -25.714 9.600 16.000 + 45 -25.714 16.000 -16.000 + 46 -25.714 16.000 -5.333 + 47 -25.714 16.000 5.333 + 48 -25.714 16.000 16.000 diff --git a/tests/testing_fodder/test_cavity_synthetic/cal/cam1.tif.addpar b/tests/testing_fodder/test_cavity_synthetic/cal/cam1.tif.addpar new file mode 100644 index 0000000..2916c6a --- /dev/null +++ b/tests/testing_fodder/test_cavity_synthetic/cal/cam1.tif.addpar @@ -0,0 +1 @@ +0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 1.00000000 0.00000000 diff --git a/tests/testing_fodder/test_cavity_synthetic/cal/cam1.tif.ori b/tests/testing_fodder/test_cavity_synthetic/cal/cam1.tif.ori new file mode 100644 index 0000000..2700f81 --- /dev/null +++ b/tests/testing_fodder/test_cavity_synthetic/cal/cam1.tif.ori @@ -0,0 +1,11 @@ +82.96897532 12.21372353 -569.03076947 +-56.54284096 2.97360259 56.53126707 + +-0.9857736 -0.0171549 0.1672010 +-0.0164254 0.9998486 0.0057447 +-0.1672743 0.0029167 -0.9859061 + +0.0000 0.0000 +70.0000 + +0.000000000000000 0.000000000000000 -125.000000000000000 diff --git a/tests/testing_fodder/test_cavity_synthetic/cal/cam2.tif.addpar b/tests/testing_fodder/test_cavity_synthetic/cal/cam2.tif.addpar new file mode 100644 index 0000000..2916c6a --- /dev/null +++ b/tests/testing_fodder/test_cavity_synthetic/cal/cam2.tif.addpar @@ -0,0 +1 @@ +0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 1.00000000 0.00000000 diff --git a/tests/testing_fodder/test_cavity_synthetic/cal/cam2.tif.ori b/tests/testing_fodder/test_cavity_synthetic/cal/cam2.tif.ori new file mode 100644 index 0000000..85cd577 --- /dev/null +++ b/tests/testing_fodder/test_cavity_synthetic/cal/cam2.tif.ori @@ -0,0 +1,11 @@ +-128.26443576 26.36339680 -572.94429259 +0.03174008 -2.91375837 -0.02126352 + +-0.9739376 -0.0207125 -0.2258683 +-0.0284175 0.9991180 0.0309147 +0.2250288 0.0365276 -0.9736672 + +0.0000 0.0000 +70.0000 + +0.000000000000000 0.000000000000000 -125.000000000000000 diff --git a/tests/testing_fodder/test_cavity_synthetic/cal/cam3.tif.addpar b/tests/testing_fodder/test_cavity_synthetic/cal/cam3.tif.addpar new file mode 100644 index 0000000..2916c6a --- /dev/null +++ b/tests/testing_fodder/test_cavity_synthetic/cal/cam3.tif.addpar @@ -0,0 +1 @@ +0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 1.00000000 0.00000000 diff --git a/tests/testing_fodder/test_cavity_synthetic/cal/cam3.tif.ori b/tests/testing_fodder/test_cavity_synthetic/cal/cam3.tif.ori new file mode 100644 index 0000000..bcf3c16 --- /dev/null +++ b/tests/testing_fodder/test_cavity_synthetic/cal/cam3.tif.ori @@ -0,0 +1,11 @@ +-110.35331896 73.30467692 584.86753878 +-0.11168165 -0.19747520 -0.02792006 + +0.9801829 0.0273739 -0.1961942 +-0.0058853 0.9939932 0.1092836 +0.1980072 -0.1059633 0.9744562 + +0.0000 0.0000 +70.0000 + +0.000000000000000 0.000000000000000 125.000000000000000 diff --git a/tests/testing_fodder/test_cavity_synthetic/cal/cam4.tif.addpar b/tests/testing_fodder/test_cavity_synthetic/cal/cam4.tif.addpar new file mode 100644 index 0000000..2916c6a --- /dev/null +++ b/tests/testing_fodder/test_cavity_synthetic/cal/cam4.tif.addpar @@ -0,0 +1 @@ +0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 1.00000000 0.00000000 diff --git a/tests/testing_fodder/test_cavity_synthetic/cal/cam4.tif.ori b/tests/testing_fodder/test_cavity_synthetic/cal/cam4.tif.ori new file mode 100644 index 0000000..3a95f4a --- /dev/null +++ b/tests/testing_fodder/test_cavity_synthetic/cal/cam4.tif.ori @@ -0,0 +1,11 @@ +126.01664951 68.15570699 573.11474392 +-0.11944273 0.23908264 0.00944663 + +0.9715123 -0.0091778 0.2368115 +-0.0188378 0.9930975 0.1157695 +-0.2362394 -0.1169325 0.9646335 + +0.0000 0.0000 +70.0000 + +0.000000000000000 0.000000000000000 125.000000000000000 diff --git a/tests/testing_fodder/test_cavity_synthetic/calibration_targets/cam1.00001_targets b/tests/testing_fodder/test_cavity_synthetic/calibration_targets/cam1.00001_targets new file mode 100644 index 0000000..fc9d449 --- /dev/null +++ b/tests/testing_fodder/test_cavity_synthetic/calibration_targets/cam1.00001_targets @@ -0,0 +1,49 @@ +48 + 0 871.4976 574.4239 21 5 5 5000 -1 + 1 968.7627 780.1996 21 5 5 5000 -1 + 2 838.1284 503.3898 21 5 5 5000 -1 + 3 856.3282 643.2051 21 5 5 5000 -1 + 4 841.5711 710.1669 21 5 5 5000 -1 + 5 824.8968 639.3359 21 5 5 5000 -1 + 6 965.2590 571.2681 21 5 5 5000 -1 + 7 842.7487 779.0878 21 5 5 5000 -1 + 8 931.1273 569.2818 21 5 5 5000 -1 + 9 949.1127 638.9936 21 5 5 5000 -1 + 10 980.7167 431.1059 21 5 5 5000 -1 + 11 872.6715 645.2169 21 5 5 5000 -1 + 12 951.4404 776.4492 21 5 5 5000 -1 + 13 837.0107 434.4505 21 5 5 5000 -1 + 14 966.4111 640.9148 21 5 5 5000 -1 + 15 857.5034 713.0480 21 5 5 5000 -1 + 16 946.8500 501.5247 21 5 5 5000 -1 + 17 984.1661 642.8867 21 5 5 5000 -1 + 18 963.0019 431.9533 21 5 5 5000 -1 + 19 875.0614 786.7928 21 5 5 5000 -1 + 20 985.3463 713.4687 21 5 5 5000 -1 + 21 983.0011 572.3006 21 5 5 5000 -1 + 22 840.4085 641.2454 21 5 5 5000 -1 + 23 930.0169 501.4367 21 5 5 5000 -1 + 24 981.8513 501.7078 21 5 5 5000 -1 + 25 947.9733 570.2621 21 5 5 5000 -1 + 26 934.5580 772.7941 21 5 5 5000 -1 + 27 858.6931 782.8897 21 5 5 5000 -1 + 28 950.2685 707.7218 21 5 5 5000 -1 + 29 822.6427 503.2771 21 5 5 5000 -1 + 30 932.2542 637.1213 21 5 5 5000 -1 + 31 823.7620 571.3093 21 5 5 5000 -1 + 32 986.5417 784.0488 21 5 5 5000 -1 + 33 873.8594 716.0057 21 5 5 5000 -1 + 34 839.2610 572.3206 21 5 5 5000 -1 + 35 826.0468 707.3596 21 5 5 5000 -1 + 36 870.3378 503.6243 21 5 5 5000 -1 + 37 964.1225 501.6151 21 5 5 5000 -1 + 38 933.3978 704.9578 21 5 5 5000 -1 + 39 821.5386 435.2366 21 5 5 5000 -1 + 40 928.9232 433.5833 21 5 5 5000 -1 + 41 967.5790 710.5578 21 5 5 5000 -1 + 42 869.1919 432.8156 21 5 5 5000 -1 + 43 854.0215 503.5055 21 5 5 5000 -1 + 44 945.7430 432.7788 21 5 5 5000 -1 + 45 855.1676 573.3584 21 5 5 5000 -1 + 46 827.2123 775.3831 21 5 5 5000 -1 + 47 852.8899 433.6438 21 5 5 5000 -1 diff --git a/tests/testing_fodder/test_cavity_synthetic/calibration_targets/cam2.00001_targets b/tests/testing_fodder/test_cavity_synthetic/calibration_targets/cam2.00001_targets new file mode 100644 index 0000000..9f839c6 --- /dev/null +++ b/tests/testing_fodder/test_cavity_synthetic/calibration_targets/cam2.00001_targets @@ -0,0 +1,49 @@ +48 + 0 948.1816 578.7394 21 5 5 5000 -1 + 1 864.4369 504.9010 21 5 5 5000 -1 + 2 868.0688 641.8695 21 5 5 5000 -1 + 3 961.9179 574.9674 21 5 5 5000 -1 + 4 953.6742 790.3353 21 5 5 5000 -1 + 5 990.0954 635.5653 21 5 5 5000 -1 + 6 993.5576 771.1674 21 5 5 5000 -1 + 7 991.8334 703.3832 21 5 5 5000 -1 + 8 986.5779 499.8175 21 5 5 5000 -1 + 9 951.8562 719.8431 21 5 5 5000 -1 + 10 944.4550 437.4601 21 5 5 5000 -1 + 11 849.6602 507.6854 21 5 5 5000 -1 + 12 958.2539 435.5523 21 5 5 5000 -1 + 13 988.3436 567.7109 21 5 5 5000 -1 + 14 946.3248 508.1229 21 5 5 5000 -1 + 15 967.3140 783.7800 21 5 5 5000 -1 + 16 971.6972 433.6937 21 5 5 5000 -1 + 17 869.8626 710.2959 21 5 5 5000 -1 + 18 856.9919 785.0576 21 5 5 5000 -1 + 19 847.7908 438.2377 21 5 5 5000 -1 + 20 832.5949 440.1710 21 5 5 5000 -1 + 21 836.3833 580.8684 21 5 5 5000 -1 + 22 838.2562 651.1507 21 5 5 5000 -1 + 23 963.7299 644.6104 21 5 5 5000 -1 + 24 884.1750 704.9572 21 5 5 5000 -1 + 25 965.5285 714.2138 21 5 5 5000 -1 + 26 877.0319 434.5177 21 5 5 5000 -1 + 27 882.4120 637.4041 21 5 5 5000 -1 + 28 984.7982 431.8824 21 5 5 5000 -1 + 29 840.1151 721.3919 21 5 5 5000 -1 + 30 973.5059 502.5146 21 5 5 5000 -1 + 31 878.8406 502.1870 21 5 5 5000 -1 + 32 978.8499 708.7289 21 5 5 5000 -1 + 33 885.9230 772.4768 21 5 5 5000 -1 + 34 871.6416 778.6871 21 5 5 5000 -1 + 35 855.1806 715.7723 21 5 5 5000 -1 + 36 866.2603 573.4054 21 5 5 5000 -1 + 37 960.0927 505.2822 21 5 5 5000 -1 + 38 880.6339 569.8149 21 5 5 5000 -1 + 39 950.0253 649.3120 21 5 5 5000 -1 + 40 851.5149 577.0887 21 5 5 5000 -1 + 41 980.6042 777.3928 21 5 5 5000 -1 + 42 853.3550 646.4502 21 5 5 5000 -1 + 43 841.9600 791.5943 21 5 5 5000 -1 + 44 862.5984 436.3539 21 5 5 5000 -1 + 45 977.0822 640.0297 21 5 5 5000 -1 + 46 834.4962 510.5427 21 5 5 5000 -1 + 47 975.3009 571.2924 21 5 5 5000 -1 diff --git a/tests/testing_fodder/test_cavity_synthetic/calibration_targets/cam3.00001_targets b/tests/testing_fodder/test_cavity_synthetic/calibration_targets/cam3.00001_targets new file mode 100644 index 0000000..cc7b54d --- /dev/null +++ b/tests/testing_fodder/test_cavity_synthetic/calibration_targets/cam3.00001_targets @@ -0,0 +1,49 @@ +48 + 0 378.6979 544.2027 21 5 5 5000 -1 + 1 281.0768 487.8945 21 5 5 5000 -1 + 2 272.4208 614.0183 21 5 5 5000 -1 + 3 274.3605 747.3727 21 5 5 5000 -1 + 4 262.1260 604.7482 21 5 5 5000 -1 + 5 406.0988 834.1688 21 5 5 5000 -1 + 6 367.0961 535.8339 21 5 5 5000 -1 + 7 265.0610 802.1153 21 5 5 5000 -1 + 8 390.5938 552.7836 21 5 5 5000 -1 + 9 392.2514 687.6544 21 5 5 5000 -1 + 10 292.8589 564.6654 21 5 5 5000 -1 + 11 273.3871 680.7657 21 5 5 5000 -1 + 12 380.3742 677.3935 21 5 5 5000 -1 + 13 382.0843 810.0263 21 5 5 5000 -1 + 14 264.0754 736.4600 21 5 5 5000 -1 + 15 381.2250 743.7784 21 5 5 5000 -1 + 16 261.1623 538.6863 21 5 5 5000 -1 + 17 368.7889 667.3850 21 5 5 5000 -1 + 18 283.9384 691.1156 21 5 5 5000 -1 + 19 391.4185 620.2926 21 5 5 5000 -1 + 20 402.7949 561.5846 21 5 5 5000 -1 + 21 285.8799 825.8643 21 5 5 5000 -1 + 22 282.0239 555.7848 21 5 5 5000 -1 + 23 293.8065 633.2751 21 5 5 5000 -1 + 24 403.6092 629.9570 21 5 5 5000 -1 + 25 377.8726 477.3912 21 5 5 5000 -1 + 26 404.4314 698.1768 21 5 5 5000 -1 + 27 370.5168 798.3971 21 5 5 5000 -1 + 28 369.6485 732.9571 21 5 5 5000 -1 + 29 366.2630 469.8495 21 5 5 5000 -1 + 30 275.3408 813.8420 21 5 5 5000 -1 + 31 284.9058 758.5614 21 5 5 5000 -1 + 32 389.7774 485.1246 21 5 5 5000 -1 + 33 393.9415 821.9466 21 5 5 5000 -1 + 34 263.0970 670.6719 21 5 5 5000 -1 + 35 282.9777 623.5243 21 5 5 5000 -1 + 36 294.7605 701.7313 21 5 5 5000 -1 + 37 291.9178 495.8995 21 5 5 5000 -1 + 38 260.2061 472.4834 21 5 5 5000 -1 + 39 405.2613 766.2466 21 5 5 5000 -1 + 40 401.9884 493.0570 21 5 5 5000 -1 + 41 271.4617 547.1278 21 5 5 5000 -1 + 42 379.5318 610.8692 21 5 5 5000 -1 + 43 367.9381 601.6781 21 5 5 5000 -1 + 44 295.7209 770.0364 21 5 5 5000 -1 + 45 393.0924 754.8715 21 5 5 5000 -1 + 46 270.5096 480.0916 21 5 5 5000 -1 + 47 296.6876 838.1932 21 5 5 5000 -1 diff --git a/tests/testing_fodder/test_cavity_synthetic/calibration_targets/cam4.00001_targets b/tests/testing_fodder/test_cavity_synthetic/calibration_targets/cam4.00001_targets new file mode 100644 index 0000000..a709b5e --- /dev/null +++ b/tests/testing_fodder/test_cavity_synthetic/calibration_targets/cam4.00001_targets @@ -0,0 +1,49 @@ +48 + 0 452.3771 641.8168 21 5 5 5000 -1 + 1 308.5756 728.1646 21 5 5 5000 -1 + 2 386.5472 469.4421 21 5 5 5000 -1 + 3 447.1862 379.0409 21 5 5 5000 -1 + 4 328.7642 650.9696 21 5 5 5000 -1 + 5 389.4610 605.7483 21 5 5 5000 -1 + 6 280.2059 468.8952 21 5 5 5000 -1 + 7 325.8714 518.8007 21 5 5 5000 -1 + 8 390.8869 673.6586 21 5 5 5000 -1 + 9 385.0587 401.0409 21 5 5 5000 -1 + 10 433.6599 718.2991 21 5 5 5000 -1 + 11 351.2782 705.7634 21 5 5 5000 -1 + 12 322.8866 386.0242 21 5 5 5000 -1 + 13 449.8258 510.7250 21 5 5 5000 -1 + 14 324.3906 452.4898 21 5 5 5000 -1 + 15 407.7031 461.0836 21 5 5 5000 -1 + 16 392.2924 741.4106 21 5 5 5000 -1 + 17 413.2227 729.7171 21 5 5 5000 -1 + 18 330.1767 716.8329 21 5 5 5000 -1 + 19 428.3542 452.9245 21 5 5 5000 -1 + 20 305.6135 594.6883 21 5 5 5000 -1 + 21 283.3762 604.6504 21 5 5 5000 -1 + 22 453.6201 707.1476 21 5 5 5000 -1 + 23 286.4594 739.7665 21 5 5 5000 -1 + 24 429.7131 519.4949 21 5 5 5000 -1 + 25 281.8021 536.8540 21 5 5 5000 -1 + 26 304.0990 527.7199 21 5 5 5000 -1 + 27 406.2699 393.5298 21 5 5 5000 -1 + 28 431.0502 585.9122 21 5 5 5000 -1 + 29 426.9734 386.1985 21 5 5 5000 -1 + 30 284.9286 672.2871 21 5 5 5000 -1 + 31 349.9202 640.6814 21 5 5 5000 -1 + 32 410.5051 595.7112 21 5 5 5000 -1 + 33 409.1148 528.4765 21 5 5 5000 -1 + 34 345.7087 444.5746 21 5 5 5000 -1 + 35 278.5874 400.7713 21 5 5 5000 -1 + 36 388.0145 537.6770 21 5 5 5000 -1 + 37 301.0022 393.3095 21 5 5 5000 -1 + 38 344.2578 378.9098 21 5 5 5000 -1 + 39 348.5393 575.4577 21 5 5 5000 -1 + 40 451.1124 576.3435 21 5 5 5000 -1 + 41 411.8743 662.7906 21 5 5 5000 -1 + 42 327.3292 584.9598 21 5 5 5000 -1 + 43 432.3657 652.1794 21 5 5 5000 -1 + 44 307.1056 661.5023 21 5 5 5000 -1 + 45 448.5172 444.9584 21 5 5 5000 -1 + 46 347.1361 510.0895 21 5 5 5000 -1 + 47 302.5620 460.5945 21 5 5 5000 -1 diff --git a/tests/testing_fodder/test_cavity_synthetic/ground_truth/cal/cam1.tif.addpar b/tests/testing_fodder/test_cavity_synthetic/ground_truth/cal/cam1.tif.addpar new file mode 100644 index 0000000..2916c6a --- /dev/null +++ b/tests/testing_fodder/test_cavity_synthetic/ground_truth/cal/cam1.tif.addpar @@ -0,0 +1 @@ +0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 1.00000000 0.00000000 diff --git a/tests/testing_fodder/test_cavity_synthetic/ground_truth/cal/cam1.tif.ori b/tests/testing_fodder/test_cavity_synthetic/ground_truth/cal/cam1.tif.ori new file mode 100644 index 0000000..2700f81 --- /dev/null +++ b/tests/testing_fodder/test_cavity_synthetic/ground_truth/cal/cam1.tif.ori @@ -0,0 +1,11 @@ +82.96897532 12.21372353 -569.03076947 +-56.54284096 2.97360259 56.53126707 + +-0.9857736 -0.0171549 0.1672010 +-0.0164254 0.9998486 0.0057447 +-0.1672743 0.0029167 -0.9859061 + +0.0000 0.0000 +70.0000 + +0.000000000000000 0.000000000000000 -125.000000000000000 diff --git a/tests/testing_fodder/test_cavity_synthetic/ground_truth/cal/cam2.tif.addpar b/tests/testing_fodder/test_cavity_synthetic/ground_truth/cal/cam2.tif.addpar new file mode 100644 index 0000000..2916c6a --- /dev/null +++ b/tests/testing_fodder/test_cavity_synthetic/ground_truth/cal/cam2.tif.addpar @@ -0,0 +1 @@ +0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 1.00000000 0.00000000 diff --git a/tests/testing_fodder/test_cavity_synthetic/ground_truth/cal/cam2.tif.ori b/tests/testing_fodder/test_cavity_synthetic/ground_truth/cal/cam2.tif.ori new file mode 100644 index 0000000..85cd577 --- /dev/null +++ b/tests/testing_fodder/test_cavity_synthetic/ground_truth/cal/cam2.tif.ori @@ -0,0 +1,11 @@ +-128.26443576 26.36339680 -572.94429259 +0.03174008 -2.91375837 -0.02126352 + +-0.9739376 -0.0207125 -0.2258683 +-0.0284175 0.9991180 0.0309147 +0.2250288 0.0365276 -0.9736672 + +0.0000 0.0000 +70.0000 + +0.000000000000000 0.000000000000000 -125.000000000000000 diff --git a/tests/testing_fodder/test_cavity_synthetic/ground_truth/cal/cam3.tif.addpar b/tests/testing_fodder/test_cavity_synthetic/ground_truth/cal/cam3.tif.addpar new file mode 100644 index 0000000..2916c6a --- /dev/null +++ b/tests/testing_fodder/test_cavity_synthetic/ground_truth/cal/cam3.tif.addpar @@ -0,0 +1 @@ +0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 1.00000000 0.00000000 diff --git a/tests/testing_fodder/test_cavity_synthetic/ground_truth/cal/cam3.tif.ori b/tests/testing_fodder/test_cavity_synthetic/ground_truth/cal/cam3.tif.ori new file mode 100644 index 0000000..bcf3c16 --- /dev/null +++ b/tests/testing_fodder/test_cavity_synthetic/ground_truth/cal/cam3.tif.ori @@ -0,0 +1,11 @@ +-110.35331896 73.30467692 584.86753878 +-0.11168165 -0.19747520 -0.02792006 + +0.9801829 0.0273739 -0.1961942 +-0.0058853 0.9939932 0.1092836 +0.1980072 -0.1059633 0.9744562 + +0.0000 0.0000 +70.0000 + +0.000000000000000 0.000000000000000 125.000000000000000 diff --git a/tests/testing_fodder/test_cavity_synthetic/ground_truth/cal/cam4.tif.addpar b/tests/testing_fodder/test_cavity_synthetic/ground_truth/cal/cam4.tif.addpar new file mode 100644 index 0000000..2916c6a --- /dev/null +++ b/tests/testing_fodder/test_cavity_synthetic/ground_truth/cal/cam4.tif.addpar @@ -0,0 +1 @@ +0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 1.00000000 0.00000000 diff --git a/tests/testing_fodder/test_cavity_synthetic/ground_truth/cal/cam4.tif.ori b/tests/testing_fodder/test_cavity_synthetic/ground_truth/cal/cam4.tif.ori new file mode 100644 index 0000000..3a95f4a --- /dev/null +++ b/tests/testing_fodder/test_cavity_synthetic/ground_truth/cal/cam4.tif.ori @@ -0,0 +1,11 @@ +126.01664951 68.15570699 573.11474392 +-0.11944273 0.23908264 0.00944663 + +0.9715123 -0.0091778 0.2368115 +-0.0188378 0.9930975 0.1157695 +-0.2362394 -0.1169325 0.9646335 + +0.0000 0.0000 +70.0000 + +0.000000000000000 0.000000000000000 125.000000000000000 diff --git a/tests/testing_fodder/test_cavity_synthetic/ground_truth/calibration_body_points.txt b/tests/testing_fodder/test_cavity_synthetic/ground_truth/calibration_body_points.txt new file mode 100644 index 0000000..87bfe1f --- /dev/null +++ b/tests/testing_fodder/test_cavity_synthetic/ground_truth/calibration_body_points.txt @@ -0,0 +1,48 @@ + 1 -36.000 -16.000 -16.000 + 2 -36.000 -16.000 -5.333 + 3 -36.000 -16.000 5.333 + 4 -36.000 -16.000 16.000 + 5 -36.000 -9.600 -16.000 + 6 -36.000 -9.600 -5.333 + 7 -36.000 -9.600 5.333 + 8 -36.000 -9.600 16.000 + 9 -36.000 -3.200 -16.000 + 10 -36.000 -3.200 -5.333 + 11 -36.000 -3.200 5.333 + 12 -36.000 -3.200 16.000 + 13 -36.000 3.200 -16.000 + 14 -36.000 3.200 -5.333 + 15 -36.000 3.200 5.333 + 16 -36.000 3.200 16.000 + 17 -36.000 9.600 -16.000 + 18 -36.000 9.600 -5.333 + 19 -36.000 9.600 5.333 + 20 -36.000 9.600 16.000 + 21 -36.000 16.000 -16.000 + 22 -36.000 16.000 -5.333 + 23 -36.000 16.000 5.333 + 24 -36.000 16.000 16.000 + 25 -25.714 -16.000 -16.000 + 26 -25.714 -16.000 -5.333 + 27 -25.714 -16.000 5.333 + 28 -25.714 -16.000 16.000 + 29 -25.714 -9.600 -16.000 + 30 -25.714 -9.600 -5.333 + 31 -25.714 -9.600 5.333 + 32 -25.714 -9.600 16.000 + 33 -25.714 -3.200 -16.000 + 34 -25.714 -3.200 -5.333 + 35 -25.714 -3.200 5.333 + 36 -25.714 -3.200 16.000 + 37 -25.714 3.200 -16.000 + 38 -25.714 3.200 -5.333 + 39 -25.714 3.200 5.333 + 40 -25.714 3.200 16.000 + 41 -25.714 9.600 -16.000 + 42 -25.714 9.600 -5.333 + 43 -25.714 9.600 5.333 + 44 -25.714 9.600 16.000 + 45 -25.714 16.000 -16.000 + 46 -25.714 16.000 -5.333 + 47 -25.714 16.000 5.333 + 48 -25.714 16.000 16.000 diff --git a/tests/testing_fodder/test_cavity_synthetic/ground_truth/manifest.json b/tests/testing_fodder/test_cavity_synthetic/ground_truth/manifest.json new file mode 100644 index 0000000..7822255 --- /dev/null +++ b/tests/testing_fodder/test_cavity_synthetic/ground_truth/manifest.json @@ -0,0 +1,18 @@ +{ + "seed": 20260306, + "num_calibration_points": 48, + "num_frames": 2, + "particles_per_frame": 96, + "calibration_position_errors": [ + 6.159861524155277e-13, + 1.608167152903132e-13, + 3.024609420349515e-13, + 5.895364129370234e-13 + ], + "calibration_angle_errors": [ + 8.881784197001252e-16, + 5.834553579603124e-16, + 5.215837820588841e-16, + 1.0547703616262308e-15 + ] +} diff --git a/tests/testing_fodder/test_cavity_synthetic/ground_truth/particles/frame_10001.txt b/tests/testing_fodder/test_cavity_synthetic/ground_truth/particles/frame_10001.txt new file mode 100644 index 0000000..ae9c38f --- /dev/null +++ b/tests/testing_fodder/test_cavity_synthetic/ground_truth/particles/frame_10001.txt @@ -0,0 +1,97 @@ +x y z +-9.004835 2.927509 -16.750501 +-7.649701 -9.018886 -6.178007 +19.349357 -17.385223 6.662090 +-32.732274 -5.765123 14.241608 +18.427344 10.835289 -10.594021 +36.951029 -5.346760 -1.165943 +-19.621321 -4.575388 4.475528 +8.585080 -1.119442 -11.452552 +30.312216 4.566054 -10.316668 +12.794282 -2.151459 -15.999535 +11.809191 -14.957691 3.005617 +-7.655285 -6.144198 0.624520 +-19.068770 3.149961 7.943458 +28.939217 11.428574 -1.552253 +10.937000 10.566049 1.146079 +-0.768273 5.705635 -4.286561 +15.322519 2.297270 -5.129089 +7.538092 0.333324 -12.397181 +10.428159 -14.451338 12.244161 +32.601978 -7.993106 4.920415 +-27.517787 8.319280 15.709022 +-7.154215 -15.115500 -3.731479 +-31.368099 7.717670 -17.866159 +22.598320 -8.817379 12.636423 +36.231206 -4.618367 -8.056307 +-18.729931 2.424087 6.254237 +36.209999 -12.163057 15.531557 +-17.262215 -4.645539 10.520935 +6.419334 -0.959543 16.232084 +-11.470633 -0.255429 -1.876678 +12.559839 1.016427 1.206560 +-28.187416 17.609123 17.732456 +18.252515 8.257974 -16.527974 +-31.636808 -0.820526 5.061469 +6.812190 -0.394813 -14.351415 +-35.520951 -12.285168 -12.965790 +-29.442653 -4.617777 -4.978286 +-29.122712 -17.082813 17.548087 +32.182994 -2.536154 4.209082 +-0.282648 13.600975 -14.131718 +13.390442 -8.510309 12.465171 +-34.501915 8.109042 -16.661865 +33.677706 4.877333 6.326409 +-28.002603 -2.311969 10.166887 +3.042524 -7.115969 -7.297907 +-37.930244 -1.532696 1.885356 +4.302062 10.273079 4.915763 +-0.892994 16.166421 12.892843 +13.431346 17.192127 13.881468 +13.840243 13.303489 10.878883 +34.570359 -8.232063 12.521135 +-15.390811 -8.777840 12.243990 +20.291769 2.781038 17.071646 +-3.226106 -2.078560 -13.231801 +21.466940 -15.699072 2.734152 +-32.602179 14.495653 -0.909771 +-18.858722 -1.436360 -8.804947 +24.958894 6.645643 16.307594 +2.695383 -17.606863 -11.001925 +-3.660997 15.604463 -2.850515 +15.987679 9.649215 -12.308764 +-10.749616 -6.337697 17.834690 +-31.505738 15.428744 -6.667206 +-26.744478 -14.993761 2.014296 +13.082148 -13.197912 10.331149 +17.057399 17.978989 7.922039 +-6.345041 -10.606396 -8.910120 +18.102808 6.131316 -4.288700 +-21.812015 9.252024 -10.632501 +-37.248133 16.716369 -6.914447 +22.396524 -6.294372 -3.303686 +15.227467 16.499758 -0.489815 +-36.240958 -0.053516 9.971913 +-0.212349 16.489640 -11.296255 +-7.593310 -13.629282 1.593718 +6.131071 -2.066680 -11.936591 +-8.136932 1.289874 1.422768 +33.396159 -4.391515 -13.318549 +-33.770236 -6.056359 -1.743087 +10.085961 12.123055 8.622585 +-2.668577 13.010837 7.796477 +-27.546173 -15.981290 -5.856841 +20.229369 11.768749 16.963026 +-36.907787 2.306576 -8.385496 +19.405988 11.197590 3.730228 +20.652808 4.800233 15.667643 +3.467258 8.710355 16.456719 +-5.559737 -9.969548 -11.017521 +2.171960 -7.275603 -2.604426 +22.965897 -5.975890 -5.598953 +-5.378142 9.603184 -14.608550 +-12.524768 13.787659 17.565771 +18.813132 -5.988243 12.042472 +25.931019 -9.206367 6.292844 +-30.575895 8.928450 -5.360647 +-20.195398 -7.880231 12.880970 diff --git a/tests/testing_fodder/test_cavity_synthetic/ground_truth/particles/frame_10002.txt b/tests/testing_fodder/test_cavity_synthetic/ground_truth/particles/frame_10002.txt new file mode 100644 index 0000000..83be55f --- /dev/null +++ b/tests/testing_fodder/test_cavity_synthetic/ground_truth/particles/frame_10002.txt @@ -0,0 +1,97 @@ +x y z +33.104389 -10.872280 -3.755599 +-1.276461 -5.540703 17.832293 +-25.617460 -7.420437 -8.547085 +-32.062522 9.466744 13.178667 +21.836250 7.907779 10.121107 +21.179789 8.109552 9.814606 +31.385881 4.361555 3.674192 +-22.379271 -14.704716 -11.036177 +-23.189485 4.767033 2.928617 +13.896577 5.172793 -12.451611 +3.079689 8.252285 2.371386 +-17.687921 -11.045332 -4.754318 +-4.776302 6.331159 6.150901 +34.351268 7.247273 15.833020 +16.039927 -2.150842 -8.151144 +37.414321 4.708428 -5.019521 +32.666564 -3.417784 11.893482 +-9.516009 -0.494849 -16.737998 +-36.749104 -12.132857 16.428506 +14.457588 7.266229 14.867945 +30.353869 -16.077797 -17.141300 +16.916110 15.004697 4.988611 +-17.859179 11.878296 -2.318000 +-32.028579 2.280382 10.542283 +-25.811409 -7.345323 2.584927 +1.803145 9.787628 -2.689545 +-23.763177 -0.429052 5.081737 +-18.961480 12.670967 17.110970 +-37.283036 -14.578460 -16.903897 +-6.163134 14.977071 10.381251 +34.833451 -11.171844 -16.317059 +-23.519053 -16.019391 0.962569 +31.478736 -10.411195 16.816865 +-10.907518 17.044802 -11.058911 +2.334175 -15.093639 8.558908 +-24.317235 3.802922 1.280903 +7.175480 -3.602055 15.419935 +-6.726319 11.591488 -15.331991 +-10.643992 0.961783 1.840879 +19.536722 4.372938 5.146245 +11.824139 11.676318 6.375628 +-14.052875 -1.273047 10.673917 +26.943971 6.258145 -14.174347 +-29.036769 15.651018 8.528567 +4.996032 11.961674 -15.893777 +-14.717247 -9.354877 3.118251 +-19.028373 -16.906327 4.682552 +12.763865 11.731536 -17.439620 +22.926399 -3.398060 -17.099254 +34.295453 14.015082 5.315808 +24.851832 -16.628967 -8.383341 +29.023279 7.452149 -12.736070 +-7.359767 -7.519720 5.658102 +-20.519402 -0.269845 -12.585480 +-14.361438 3.189792 13.535694 +-15.745571 -15.728232 -2.461477 +-26.588969 13.215854 -3.715778 +-10.501724 15.680141 -6.367624 +-14.989470 8.752016 1.127607 +26.290043 -1.299356 1.942553 +-11.953441 -3.574083 -1.629490 +23.334035 2.845580 -11.208691 +14.252242 -15.959440 14.148256 +33.551702 13.795561 -1.122564 +8.409184 -14.002681 -0.363069 +7.369943 -6.987929 14.637666 +22.415552 -2.327246 17.901142 +26.404882 -3.567311 5.110050 +-3.493007 -2.379322 6.966128 +-4.118330 9.997989 5.874630 +-1.303455 3.191303 -15.045083 +14.501357 9.043564 -14.158811 +-24.967128 2.443493 7.741993 +-24.209033 13.317319 13.212777 +3.280612 -7.577176 14.078099 +-16.630896 -1.082430 -11.616092 +33.912991 -6.317229 -9.377707 +37.658071 -4.932050 -12.498551 +-23.594928 -8.917137 3.516823 +24.985716 -9.773017 -14.055044 +12.404520 -5.279279 6.966119 +-36.113479 12.051428 -6.693231 +25.487721 -15.211498 -14.045956 +-32.639433 8.783989 0.120439 +-14.524117 16.278829 -7.333158 +26.708617 -17.203784 5.154325 +-28.325314 -16.516442 -15.796798 +13.572443 -16.191496 13.058760 +-11.425392 -2.987008 16.092634 +-35.698364 5.002007 -7.558434 +30.842816 -10.882623 -7.265986 +27.007981 5.269136 12.363942 +26.510122 9.554547 8.970011 +5.368834 -1.445615 2.648482 +-29.545642 15.436410 -9.097935 +-18.287969 2.209223 -16.054032 diff --git a/tests/testing_fodder/test_cavity_synthetic/img_orig/cam1.10001_targets b/tests/testing_fodder/test_cavity_synthetic/img_orig/cam1.10001_targets new file mode 100644 index 0000000..354bcbc --- /dev/null +++ b/tests/testing_fodder/test_cavity_synthetic/img_orig/cam1.10001_targets @@ -0,0 +1,97 @@ +96 + 0 375.8344 420.1521 21 5 5 5000 -1 + 1 646.2484 507.4573 21 5 5 5000 -1 + 2 611.8107 441.8715 21 5 5 5000 -1 + 3 248.1574 570.3210 21 5 5 5000 -1 + 4 978.9761 423.9222 21 5 5 5000 -1 + 5 409.6471 591.7594 21 5 5 5000 -1 + 6 517.8805 501.6333 21 5 5 5000 -1 + 7 681.3554 460.9577 21 5 5 5000 -1 + 8 451.2594 482.5749 21 5 5 5000 -1 + 9 697.7671 613.5248 21 5 5 5000 -1 + 10 422.1037 757.0054 21 5 5 5000 -1 + 11 548.2702 693.2840 21 5 5 5000 -1 + 12 772.2233 691.2423 21 5 5 5000 -1 + 13 860.8439 785.5578 21 5 5 5000 -1 + 14 182.2604 673.8492 21 5 5 5000 -1 + 15 433.9783 604.3410 21 5 5 5000 -1 + 16 169.5545 680.3567 21 5 5 5000 -1 + 17 513.6286 620.9917 21 5 5 5000 -1 + 18 689.9959 580.9569 21 5 5 5000 -1 + 19 657.5025 597.0278 21 5 5 5000 -1 + 20 518.6506 639.2304 21 5 5 5000 -1 + 21 906.3746 509.8786 21 5 5 5000 -1 + 22 285.3244 544.1997 21 5 5 5000 -1 + 23 653.1839 729.8943 21 5 5 5000 -1 + 24 937.3094 671.8229 21 5 5 5000 -1 + 25 588.8598 463.9373 21 5 5 5000 -1 + 26 654.4495 678.2827 21 5 5 5000 -1 + 27 514.5216 518.1966 21 5 5 5000 -1 + 28 819.9923 508.1427 21 5 5 5000 -1 + 29 966.9730 518.3309 21 5 5 5000 -1 + 30 408.5211 470.4200 21 5 5 5000 -1 + 31 844.5909 417.8207 21 5 5 5000 -1 + 32 563.5904 437.1854 21 5 5 5000 -1 + 33 879.2786 782.3961 21 5 5 5000 -1 + 34 975.6279 742.3689 21 5 5 5000 -1 + 35 647.2612 723.5755 21 5 5 5000 -1 + 36 743.8821 657.6862 21 5 5 5000 -1 + 37 448.9352 769.2966 21 5 5 5000 -1 + 38 403.7129 435.3353 21 5 5 5000 -1 + 39 935.1017 523.3286 21 5 5 5000 -1 + 40 449.3900 642.5092 21 5 5 5000 -1 + 41 217.6933 671.7899 21 5 5 5000 -1 + 42 858.3714 769.1733 21 5 5 5000 -1 + 43 358.5581 805.4848 21 5 5 5000 -1 + 44 589.0031 470.6977 21 5 5 5000 -1 + 45 332.0087 687.9430 21 5 5 5000 -1 + 46 217.9418 647.1200 21 5 5 5000 -1 + 47 338.7520 789.2533 21 5 5 5000 -1 + 48 917.2618 438.9172 21 5 5 5000 -1 + 49 787.2889 626.0809 21 5 5 5000 -1 + 50 485.0699 622.2580 21 5 5 5000 -1 + 51 415.7646 705.7300 21 5 5 5000 -1 + 52 327.5697 685.0196 21 5 5 5000 -1 + 53 387.0214 527.0608 21 5 5 5000 -1 + 54 552.3504 693.7950 21 5 5 5000 -1 + 55 187.2712 708.0098 21 5 5 5000 -1 + 56 583.9787 550.8124 21 5 5 5000 -1 + 57 896.0271 657.7583 21 5 5 5000 -1 + 58 213.9278 706.8086 21 5 5 5000 -1 + 59 316.7616 711.4221 21 5 5 5000 -1 + 60 763.9183 582.1183 21 5 5 5000 -1 + 61 654.0924 759.3575 21 5 5 5000 -1 + 62 332.6202 563.4162 21 5 5 5000 -1 + 63 842.1657 516.5745 21 5 5 5000 -1 + 64 449.9840 499.5648 21 5 5 5000 -1 + 65 408.1494 510.8354 21 5 5 5000 -1 + 66 902.9993 614.4171 21 5 5 5000 -1 + 67 920.0565 449.1931 21 5 5 5000 -1 + 68 974.8591 621.2660 21 5 5 5000 -1 + 69 409.2044 428.5535 21 5 5 5000 -1 + 70 355.1761 494.3036 21 5 5 5000 -1 + 71 558.3701 810.3811 21 5 5 5000 -1 + 72 255.1604 493.3458 21 5 5 5000 -1 + 73 980.1604 581.1237 21 5 5 5000 -1 + 74 443.4447 778.3303 21 5 5 5000 -1 + 75 943.6367 604.6330 21 5 5 5000 -1 + 76 377.5283 550.0026 21 5 5 5000 -1 + 77 857.3801 630.2844 21 5 5 5000 -1 + 78 656.3258 777.5241 21 5 5 5000 -1 + 79 666.0868 675.6030 21 5 5 5000 -1 + 80 168.1267 750.1217 21 5 5 5000 -1 + 81 198.6544 566.0490 21 5 5 5000 -1 + 82 335.6563 584.9485 21 5 5 5000 -1 + 83 357.5730 680.0513 21 5 5 5000 -1 + 84 379.1089 498.1361 21 5 5 5000 -1 + 85 503.2387 612.9579 21 5 5 5000 -1 + 86 777.3090 657.6464 21 5 5 5000 -1 + 87 285.8202 718.0797 21 5 5 5000 -1 + 88 663.6613 711.3493 21 5 5 5000 -1 + 89 765.0167 574.0378 21 5 5 5000 -1 + 90 334.7219 488.2299 21 5 5 5000 -1 + 91 623.0618 637.3091 21 5 5 5000 -1 + 92 901.3486 665.2433 21 5 5 5000 -1 + 93 584.4045 432.0303 21 5 5 5000 -1 + 94 490.9732 629.1366 21 5 5 5000 -1 + 95 722.5358 702.0648 21 5 5 5000 -1 diff --git a/tests/testing_fodder/test_cavity_synthetic/img_orig/cam1.10002_targets b/tests/testing_fodder/test_cavity_synthetic/img_orig/cam1.10002_targets new file mode 100644 index 0000000..cc77005 --- /dev/null +++ b/tests/testing_fodder/test_cavity_synthetic/img_orig/cam1.10002_targets @@ -0,0 +1,97 @@ +96 + 0 860.8597 690.0645 21 5 5 5000 -1 + 1 242.8033 742.0198 21 5 5 5000 -1 + 2 645.3623 691.9746 21 5 5 5000 -1 + 3 313.1509 730.1134 21 5 5 5000 -1 + 4 830.1218 566.5592 21 5 5 5000 -1 + 5 674.8081 640.1798 21 5 5 5000 -1 + 6 767.1505 623.0664 21 5 5 5000 -1 + 7 1001.6497 768.2951 21 5 5 5000 -1 + 8 184.5261 539.7623 21 5 5 5000 -1 + 9 326.8991 588.1724 21 5 5 5000 -1 + 10 331.3372 528.0376 21 5 5 5000 -1 + 11 729.9758 514.5280 21 5 5 5000 -1 + 12 478.3433 650.9049 21 5 5 5000 -1 + 13 622.8422 449.0050 21 5 5 5000 -1 + 14 380.4455 452.3846 21 5 5 5000 -1 + 15 846.1979 686.5036 21 5 5 5000 -1 + 16 790.6877 587.0177 21 5 5 5000 -1 + 17 225.8963 571.3950 21 5 5 5000 -1 + 18 565.8083 669.2133 21 5 5 5000 -1 + 19 218.0841 729.7357 21 5 5 5000 -1 + 20 534.1495 523.5112 21 5 5 5000 -1 + 21 700.6702 424.1040 21 5 5 5000 -1 + 22 266.7111 560.0047 21 5 5 5000 -1 + 23 214.9928 741.2451 21 5 5 5000 -1 + 24 209.4115 692.5131 21 5 5 5000 -1 + 25 426.2967 517.3027 21 5 5 5000 -1 + 26 772.7983 790.8386 21 5 5 5000 -1 + 27 533.1575 483.1537 21 5 5 5000 -1 + 28 337.5363 659.0470 21 5 5 5000 -1 + 29 614.6052 542.7083 21 5 5 5000 -1 + 30 553.3884 506.6382 21 5 5 5000 -1 + 31 603.2179 579.3671 21 5 5 5000 -1 + 32 256.8899 803.6003 21 5 5 5000 -1 + 33 431.1311 671.9949 21 5 5 5000 -1 + 34 825.9259 781.4130 21 5 5 5000 -1 + 35 831.1253 771.6404 21 5 5 5000 -1 + 36 707.8076 573.8016 21 5 5 5000 -1 + 37 661.5737 484.9278 21 5 5 5000 -1 + 38 205.0415 749.2965 21 5 5 5000 -1 + 39 207.2717 655.3969 21 5 5 5000 -1 + 40 399.2135 535.8331 21 5 5 5000 -1 + 41 749.9385 472.0116 21 5 5 5000 -1 + 42 601.4362 637.0111 21 5 5 5000 -1 + 43 900.0848 438.9133 21 5 5 5000 -1 + 44 690.3428 439.4448 21 5 5 5000 -1 + 45 821.7596 703.8697 21 5 5 5000 -1 + 46 405.6379 641.6755 21 5 5 5000 -1 + 47 898.1774 580.5424 21 5 5 5000 -1 + 48 406.2375 785.8762 21 5 5 5000 -1 + 49 764.8775 480.0461 21 5 5 5000 -1 + 50 965.2565 551.8385 21 5 5 5000 -1 + 51 477.8669 687.4639 21 5 5 5000 -1 + 52 703.2958 649.7412 21 5 5 5000 -1 + 53 448.6984 487.1080 21 5 5 5000 -1 + 54 274.0462 513.3715 21 5 5 5000 -1 + 55 522.1162 692.9344 21 5 5 5000 -1 + 56 940.9452 731.4275 21 5 5 5000 -1 + 57 279.8162 806.1724 21 5 5 5000 -1 + 58 414.7748 788.6488 21 5 5 5000 -1 + 59 510.9838 629.6314 21 5 5 5000 -1 + 60 770.7013 730.5755 21 5 5 5000 -1 + 61 727.5427 710.6933 21 5 5 5000 -1 + 62 308.7316 791.1004 21 5 5 5000 -1 + 63 539.5054 775.3177 21 5 5 5000 -1 + 64 169.2759 678.6232 21 5 5 5000 -1 + 65 683.8893 599.8833 21 5 5 5000 -1 + 66 313.1566 640.2398 21 5 5 5000 -1 + 67 903.2093 791.7420 21 5 5 5000 -1 + 68 288.5327 551.0993 21 5 5 5000 -1 + 69 432.2212 560.3685 21 5 5 5000 -1 + 70 434.8783 487.6141 21 5 5 5000 -1 + 71 311.3139 804.6544 21 5 5 5000 -1 + 72 323.9571 530.4601 21 5 5 5000 -1 + 73 696.3493 619.0681 21 5 5 5000 -1 + 74 263.9241 537.9141 21 5 5 5000 -1 + 75 967.1390 474.8477 21 5 5 5000 -1 + 76 283.9642 632.6596 21 5 5 5000 -1 + 77 810.3055 463.7940 21 5 5 5000 -1 + 78 919.5498 511.0685 21 5 5 5000 -1 + 79 353.8071 568.8262 21 5 5 5000 -1 + 80 709.1049 622.1943 21 5 5 5000 -1 + 81 827.9544 580.3177 21 5 5 5000 -1 + 82 815.7250 556.1823 21 5 5 5000 -1 + 83 203.7163 468.1730 21 5 5 5000 -1 + 84 165.1945 569.7486 21 5 5 5000 -1 + 85 893.3761 503.5065 21 5 5 5000 -1 + 86 607.3129 503.1196 21 5 5 5000 -1 + 87 860.3538 463.9992 21 5 5 5000 -1 + 88 483.4432 768.2797 21 5 5 5000 -1 + 89 810.5944 613.3286 21 5 5 5000 -1 + 90 280.6155 657.0733 21 5 5 5000 -1 + 91 747.3958 781.5821 21 5 5 5000 -1 + 92 819.6249 611.8175 21 5 5 5000 -1 + 93 867.4060 437.8991 21 5 5 5000 -1 + 94 734.9628 432.2598 21 5 5 5000 -1 + 95 190.8726 466.1689 21 5 5 5000 -1 diff --git a/tests/testing_fodder/test_cavity_synthetic/img_orig/cam2.10001_targets b/tests/testing_fodder/test_cavity_synthetic/img_orig/cam2.10001_targets new file mode 100644 index 0000000..30a4236 --- /dev/null +++ b/tests/testing_fodder/test_cavity_synthetic/img_orig/cam2.10001_targets @@ -0,0 +1,97 @@ +96 + 0 618.0741 447.6873 21 5 5 5000 -1 + 1 461.3457 473.6560 21 5 5 5000 -1 + 2 794.1701 582.8501 21 5 5 5000 -1 + 3 787.0019 656.6384 21 5 5 5000 -1 + 4 927.9882 451.9688 21 5 5 5000 -1 + 5 877.8434 769.8525 21 5 5 5000 -1 + 6 992.8946 623.1884 21 5 5 5000 -1 + 7 479.5620 632.7341 21 5 5 5000 -1 + 8 618.3607 514.8427 21 5 5 5000 -1 + 9 655.4889 587.5935 21 5 5 5000 -1 + 10 418.7260 676.0386 21 5 5 5000 -1 + 11 397.1375 518.7701 21 5 5 5000 -1 + 12 893.8202 530.6038 21 5 5 5000 -1 + 13 475.4788 701.1495 21 5 5 5000 -1 + 14 460.6774 605.7070 21 5 5 5000 -1 + 15 419.9942 595.0488 21 5 5 5000 -1 + 16 549.9201 504.7039 21 5 5 5000 -1 + 17 578.4861 517.9962 21 5 5 5000 -1 + 18 567.2678 440.6961 21 5 5 5000 -1 + 19 474.5412 504.1939 21 5 5 5000 -1 + 20 476.0205 751.3415 21 5 5 5000 -1 + 21 644.0292 731.5034 21 5 5 5000 -1 + 22 588.3394 554.8647 21 5 5 5000 -1 + 23 200.6881 673.6497 21 5 5 5000 -1 + 24 253.9263 575.8776 21 5 5 5000 -1 + 25 951.2766 747.7179 21 5 5 5000 -1 + 26 469.8182 432.3301 21 5 5 5000 -1 + 27 821.7790 689.1858 21 5 5 5000 -1 + 28 336.4629 713.6641 21 5 5 5000 -1 + 29 875.9144 784.8495 21 5 5 5000 -1 + 30 218.6113 673.1322 21 5 5 5000 -1 + 31 406.5789 798.0531 21 5 5 5000 -1 + 32 929.4726 525.1296 21 5 5 5000 -1 + 33 374.5235 506.0889 21 5 5 5000 -1 + 34 954.4599 664.0590 21 5 5 5000 -1 + 35 488.3977 617.4253 21 5 5 5000 -1 + 36 632.1976 725.9010 21 5 5 5000 -1 + 37 382.3029 705.8540 21 5 5 5000 -1 + 38 506.8627 763.1257 21 5 5 5000 -1 + 39 743.0110 460.8084 21 5 5 5000 -1 + 40 408.2172 489.9704 21 5 5 5000 -1 + 41 706.4791 615.9647 21 5 5 5000 -1 + 42 626.0396 473.4660 21 5 5 5000 -1 + 43 410.5473 583.2721 21 5 5 5000 -1 + 44 365.5632 535.4209 21 5 5 5000 -1 + 45 550.7381 619.5980 21 5 5 5000 -1 + 46 208.1010 678.4508 21 5 5 5000 -1 + 47 496.1810 485.6062 21 5 5 5000 -1 + 48 984.4418 604.3545 21 5 5 5000 -1 + 49 929.6213 615.3979 21 5 5 5000 -1 + 50 253.5656 567.5984 21 5 5 5000 -1 + 51 800.2336 574.3438 21 5 5 5000 -1 + 52 901.7834 513.9099 21 5 5 5000 -1 + 53 968.0020 585.6998 21 5 5 5000 -1 + 54 547.2336 809.3676 21 5 5 5000 -1 + 55 675.2023 757.9408 21 5 5 5000 -1 + 56 425.2402 442.6313 21 5 5 5000 -1 + 57 774.5333 630.0685 21 5 5 5000 -1 + 58 504.7245 642.6918 21 5 5 5000 -1 + 59 377.0038 782.9868 21 5 5 5000 -1 + 60 908.3522 443.3606 21 5 5 5000 -1 + 61 563.7229 472.4733 21 5 5 5000 -1 + 62 261.2080 701.7261 21 5 5 5000 -1 + 63 676.1558 598.6884 21 5 5 5000 -1 + 64 265.4675 702.5067 21 5 5 5000 -1 + 65 898.5433 629.9684 21 5 5 5000 -1 + 66 904.6987 416.7035 21 5 5 5000 -1 + 67 360.9775 544.2168 21 5 5 5000 -1 + 68 601.3971 641.8319 21 5 5 5000 -1 + 69 252.0067 741.1245 21 5 5 5000 -1 + 70 894.0836 661.1577 21 5 5 5000 -1 + 71 427.1133 646.7213 21 5 5 5000 -1 + 72 671.9737 678.5201 21 5 5 5000 -1 + 73 564.2231 693.7888 21 5 5 5000 -1 + 74 342.4155 684.7072 21 5 5 5000 -1 + 75 266.8087 645.6198 21 5 5 5000 -1 + 76 392.0076 498.8912 21 5 5 5000 -1 + 77 945.1123 674.3233 21 5 5 5000 -1 + 78 969.4552 427.7753 21 5 5 5000 -1 + 79 546.9427 694.4386 21 5 5 5000 -1 + 80 492.5124 625.8281 21 5 5 5000 -1 + 81 403.5543 562.5852 21 5 5 5000 -1 + 82 662.3829 776.9546 21 5 5 5000 -1 + 83 661.8655 712.6434 21 5 5 5000 -1 + 84 897.5884 515.3627 21 5 5 5000 -1 + 85 391.3936 554.5739 21 5 5 5000 -1 + 86 476.4418 773.7865 21 5 5 5000 -1 + 87 771.2045 699.6710 21 5 5 5000 -1 + 88 800.3488 513.8674 21 5 5 5000 -1 + 89 353.1047 686.9816 21 5 5 5000 -1 + 90 283.8743 499.8924 21 5 5 5000 -1 + 91 730.8034 672.0422 21 5 5 5000 -1 + 92 421.7273 425.9355 21 5 5 5000 -1 + 93 802.9814 657.9943 21 5 5 5000 -1 + 94 615.0185 439.6153 21 5 5 5000 -1 + 95 923.8115 782.6436 21 5 5 5000 -1 diff --git a/tests/testing_fodder/test_cavity_synthetic/img_orig/cam2.10002_targets b/tests/testing_fodder/test_cavity_synthetic/img_orig/cam2.10002_targets new file mode 100644 index 0000000..da90835 --- /dev/null +++ b/tests/testing_fodder/test_cavity_synthetic/img_orig/cam2.10002_targets @@ -0,0 +1,97 @@ +96 + 0 769.7391 484.1639 21 5 5 5000 -1 + 1 846.3159 612.7942 21 5 5 5000 -1 + 2 328.8022 798.1662 21 5 5 5000 -1 + 3 680.7273 431.7176 21 5 5 5000 -1 + 4 835.7399 558.1289 21 5 5 5000 -1 + 5 842.7132 781.7787 21 5 5 5000 -1 + 6 883.9474 443.9277 21 5 5 5000 -1 + 7 638.8456 505.5406 21 5 5 5000 -1 + 8 633.3983 665.6366 21 5 5 5000 -1 + 9 301.3729 721.7104 21 5 5 5000 -1 + 10 319.1517 662.5398 21 5 5 5000 -1 + 11 505.1882 492.1243 21 5 5 5000 -1 + 12 812.9201 774.8767 21 5 5 5000 -1 + 13 870.7146 796.7865 21 5 5 5000 -1 + 14 665.8212 451.3612 21 5 5 5000 -1 + 15 243.6297 737.3371 21 5 5 5000 -1 + 16 860.0949 467.7653 21 5 5 5000 -1 + 17 391.9735 636.2890 21 5 5 5000 -1 + 18 1000.8311 730.0702 21 5 5 5000 -1 + 19 866.3903 687.5156 21 5 5 5000 -1 + 20 755.1090 781.6284 21 5 5 5000 -1 + 21 931.2854 513.2801 21 5 5 5000 -1 + 22 562.9371 511.3371 21 5 5 5000 -1 + 23 333.0473 560.1979 21 5 5 5000 -1 + 24 259.6545 738.9506 21 5 5 5000 -1 + 25 684.2632 445.8580 21 5 5 5000 -1 + 26 538.3248 630.0578 21 5 5 5000 -1 + 27 712.9872 651.1903 21 5 5 5000 -1 + 28 409.2960 525.3205 21 5 5 5000 -1 + 29 786.3470 618.6491 21 5 5 5000 -1 + 30 271.7728 573.3383 21 5 5 5000 -1 + 31 542.3616 647.6969 21 5 5 5000 -1 + 32 647.2434 544.1877 21 5 5 5000 -1 + 33 199.2495 747.4881 21 5 5 5000 -1 + 34 558.7706 526.7348 21 5 5 5000 -1 + 35 800.6126 789.5737 21 5 5 5000 -1 + 36 734.1857 637.6042 21 5 5 5000 -1 + 37 939.8595 580.1340 21 5 5 5000 -1 + 38 476.8271 781.1508 21 5 5 5000 -1 + 39 327.6722 655.1411 21 5 5 5000 -1 + 40 902.0079 438.7968 21 5 5 5000 -1 + 41 408.3486 643.9726 21 5 5 5000 -1 + 42 758.9025 572.9825 21 5 5 5000 -1 + 43 474.3809 491.3810 21 5 5 5000 -1 + 44 380.2929 532.3563 21 5 5 5000 -1 + 45 245.4703 799.9631 21 5 5 5000 -1 + 46 844.7702 704.3536 21 5 5 5000 -1 + 47 464.2374 535.9334 21 5 5 5000 -1 + 48 176.4888 679.3093 21 5 5 5000 -1 + 49 420.5701 496.6241 21 5 5 5000 -1 + 50 261.1077 545.4145 21 5 5 5000 -1 + 51 750.9400 710.4175 21 5 5 5000 -1 + 52 809.3001 471.1188 21 5 5 5000 -1 + 53 265.4175 540.1946 21 5 5 5000 -1 + 54 637.7322 636.5080 21 5 5 5000 -1 + 55 395.3298 570.2385 21 5 5 5000 -1 + 56 324.8764 593.1372 21 5 5 5000 -1 + 57 420.1380 566.5084 21 5 5 5000 -1 + 58 745.7189 517.5005 21 5 5 5000 -1 + 59 966.1119 775.0092 21 5 5 5000 -1 + 60 955.2639 556.2102 21 5 5 5000 -1 + 61 540.1592 683.5670 21 5 5 5000 -1 + 62 752.7766 621.2785 21 5 5 5000 -1 + 63 662.0418 624.8679 21 5 5 5000 -1 + 64 958.8049 478.8634 21 5 5 5000 -1 + 65 242.5592 472.3497 21 5 5 5000 -1 + 66 192.2297 573.8294 21 5 5 5000 -1 + 67 305.8502 729.7687 21 5 5 5000 -1 + 68 505.4023 765.2125 21 5 5 5000 -1 + 69 630.9036 492.9898 21 5 5 5000 -1 + 70 581.0601 689.1271 21 5 5 5000 -1 + 71 576.1024 585.6281 21 5 5 5000 -1 + 72 941.9797 502.9645 21 5 5 5000 -1 + 73 746.3841 627.7416 21 5 5 5000 -1 + 74 471.7310 778.1799 21 5 5 5000 -1 + 75 418.6106 457.7037 21 5 5 5000 -1 + 76 861.9663 580.9197 21 5 5 5000 -1 + 77 756.3994 593.3718 21 5 5 5000 -1 + 78 330.2214 516.2498 21 5 5 5000 -1 + 79 583.7373 770.8770 21 5 5 5000 -1 + 80 277.7047 651.8552 21 5 5 5000 -1 + 81 845.6356 568.6782 21 5 5 5000 -1 + 82 321.7933 632.4274 21 5 5 5000 -1 + 83 770.9647 732.2370 21 5 5 5000 -1 + 84 858.2886 463.8604 21 5 5 5000 -1 + 85 386.1482 530.1523 21 5 5 5000 -1 + 86 302.2976 788.2662 21 5 5 5000 -1 + 87 703.4472 601.4629 21 5 5 5000 -1 + 88 474.6496 669.9736 21 5 5 5000 -1 + 89 236.8296 475.7488 21 5 5 5000 -1 + 90 280.2614 558.2461 21 5 5 5000 -1 + 91 677.3564 690.9606 21 5 5 5000 -1 + 92 848.9565 693.7717 21 5 5 5000 -1 + 93 222.1465 691.9188 21 5 5 5000 -1 + 94 725.5306 438.6246 21 5 5 5000 -1 + 95 320.8137 799.8329 21 5 5 5000 -1 diff --git a/tests/testing_fodder/test_cavity_synthetic/img_orig/cam3.10001_targets b/tests/testing_fodder/test_cavity_synthetic/img_orig/cam3.10001_targets new file mode 100644 index 0000000..0b89808 --- /dev/null +++ b/tests/testing_fodder/test_cavity_synthetic/img_orig/cam3.10001_targets @@ -0,0 +1,97 @@ +96 + 0 601.1951 651.4577 21 5 5 5000 -1 + 1 634.4300 512.3544 21 5 5 5000 -1 + 2 531.3491 645.3990 21 5 5 5000 -1 + 3 660.1068 482.3405 21 5 5 5000 -1 + 4 810.2614 507.0327 21 5 5 5000 -1 + 5 509.6486 749.8499 21 5 5 5000 -1 + 6 316.2159 483.9984 21 5 5 5000 -1 + 7 537.3049 598.9933 21 5 5 5000 -1 + 8 487.3316 705.0246 21 5 5 5000 -1 + 9 986.9809 661.1049 21 5 5 5000 -1 + 10 413.2921 542.4686 21 5 5 5000 -1 + 11 768.0404 518.7716 21 5 5 5000 -1 + 12 822.8365 515.4662 21 5 5 5000 -1 + 13 383.2713 575.4773 21 5 5 5000 -1 + 14 544.2481 514.3070 21 5 5 5000 -1 + 15 329.3526 664.2474 21 5 5 5000 -1 + 16 455.1836 699.0406 21 5 5 5000 -1 + 17 285.5170 662.5145 21 5 5 5000 -1 + 18 795.3380 527.0164 21 5 5 5000 -1 + 19 310.7868 498.2070 21 5 5 5000 -1 + 20 328.0234 552.5324 21 5 5 5000 -1 + 21 855.8265 522.2116 21 5 5 5000 -1 + 22 782.3461 627.6848 21 5 5 5000 -1 + 23 710.8756 624.3716 21 5 5 5000 -1 + 24 778.5715 794.7443 21 5 5 5000 -1 + 25 876.4030 798.6034 21 5 5 5000 -1 + 26 576.0529 743.7657 21 5 5 5000 -1 + 27 1025.2020 683.5462 21 5 5 5000 -1 + 28 307.4076 555.3027 21 5 5 5000 -1 + 29 665.3759 810.1510 21 5 5 5000 -1 + 30 464.2685 621.0520 21 5 5 5000 -1 + 31 765.0976 529.0827 21 5 5 5000 -1 + 32 711.5007 562.7207 21 5 5 5000 -1 + 33 566.1235 730.7761 21 5 5 5000 -1 + 34 671.0377 713.1794 21 5 5 5000 -1 + 35 697.5500 649.4582 21 5 5 5000 -1 + 36 565.4032 728.1858 21 5 5 5000 -1 + 37 377.4293 477.7895 21 5 5 5000 -1 + 38 875.3373 695.7392 21 5 5 5000 -1 + 39 968.1170 663.7444 21 5 5 5000 -1 + 40 272.2414 767.0136 21 5 5 5000 -1 + 41 379.2427 808.1782 21 5 5 5000 -1 + 42 576.3276 531.3339 21 5 5 5000 -1 + 43 942.6595 576.3269 21 5 5 5000 -1 + 44 927.4254 734.0532 21 5 5 5000 -1 + 45 886.5028 526.2590 21 5 5 5000 -1 + 46 810.7316 468.3697 21 5 5 5000 -1 + 47 1026.5429 727.2904 21 5 5 5000 -1 + 48 803.0444 783.9324 21 5 5 5000 -1 + 49 609.1388 477.4080 21 5 5 5000 -1 + 50 800.5768 608.1963 21 5 5 5000 -1 + 51 570.1772 631.3547 21 5 5 5000 -1 + 52 632.7028 461.5746 21 5 5 5000 -1 + 53 276.3314 553.1060 21 5 5 5000 -1 + 54 722.9814 639.6951 21 5 5 5000 -1 + 55 260.1671 620.8580 21 5 5 5000 -1 + 56 574.6840 707.7886 21 5 5 5000 -1 + 57 342.2636 693.7450 21 5 5 5000 -1 + 58 373.5259 683.7214 21 5 5 5000 -1 + 59 673.3538 706.5930 21 5 5 5000 -1 + 60 861.5122 821.2559 21 5 5 5000 -1 + 61 943.8137 513.3068 21 5 5 5000 -1 + 62 259.7618 670.8607 21 5 5 5000 -1 + 63 992.6786 717.8784 21 5 5 5000 -1 + 64 700.9374 630.0679 21 5 5 5000 -1 + 65 864.3589 708.6399 21 5 5 5000 -1 + 66 838.4354 455.0407 21 5 5 5000 -1 + 67 934.4910 578.3175 21 5 5 5000 -1 + 68 637.7565 578.4273 21 5 5 5000 -1 + 69 577.3542 786.4521 21 5 5 5000 -1 + 70 1005.8137 670.0663 21 5 5 5000 -1 + 71 459.8716 742.7320 21 5 5 5000 -1 + 72 446.9913 653.8770 21 5 5 5000 -1 + 73 300.6148 713.3311 21 5 5 5000 -1 + 74 466.0028 627.0809 21 5 5 5000 -1 + 75 574.9761 795.8757 21 5 5 5000 -1 + 76 758.5607 644.9335 21 5 5 5000 -1 + 77 1048.8948 770.5914 21 5 5 5000 -1 + 78 581.2234 734.9929 21 5 5 5000 -1 + 79 371.7600 848.8552 21 5 5 5000 -1 + 80 328.0621 726.0840 21 5 5 5000 -1 + 81 362.4486 809.8779 21 5 5 5000 -1 + 82 811.6552 537.1180 21 5 5 5000 -1 + 83 829.8878 568.7902 21 5 5 5000 -1 + 84 255.8207 471.8265 21 5 5 5000 -1 + 85 806.2112 465.0901 21 5 5 5000 -1 + 86 809.2206 737.2640 21 5 5 5000 -1 + 87 742.5455 664.0010 21 5 5 5000 -1 + 88 889.1032 598.5749 21 5 5 5000 -1 + 89 778.7694 800.0437 21 5 5 5000 -1 + 90 628.2620 489.4467 21 5 5 5000 -1 + 91 887.7784 621.1251 21 5 5 5000 -1 + 92 877.3362 690.0333 21 5 5 5000 -1 + 93 1005.8675 585.8276 21 5 5 5000 -1 + 94 702.6856 536.9321 21 5 5 5000 -1 + 95 904.3450 737.4220 21 5 5 5000 -1 diff --git a/tests/testing_fodder/test_cavity_synthetic/img_orig/cam3.10002_targets b/tests/testing_fodder/test_cavity_synthetic/img_orig/cam3.10002_targets new file mode 100644 index 0000000..525f173 --- /dev/null +++ b/tests/testing_fodder/test_cavity_synthetic/img_orig/cam3.10002_targets @@ -0,0 +1,97 @@ +96 + 0 532.6351 633.9436 21 5 5 5000 -1 + 1 250.8356 787.0811 21 5 5 5000 -1 + 2 412.1016 657.5044 21 5 5 5000 -1 + 3 414.8066 600.8104 21 5 5 5000 -1 + 4 409.8474 789.3017 21 5 5 5000 -1 + 5 273.0792 593.2234 21 5 5 5000 -1 + 6 859.6604 594.2071 21 5 5 5000 -1 + 7 858.0347 653.4859 21 5 5 5000 -1 + 8 616.8260 542.7744 21 5 5 5000 -1 + 9 977.6394 728.8559 21 5 5 5000 -1 + 10 738.7007 782.5199 21 5 5 5000 -1 + 11 561.6139 510.5491 21 5 5 5000 -1 + 12 902.3706 557.0739 21 5 5 5000 -1 + 13 782.3715 521.1949 21 5 5 5000 -1 + 14 601.4075 494.4655 21 5 5 5000 -1 + 15 803.3232 650.9165 21 5 5 5000 -1 + 16 777.7403 532.1760 21 5 5 5000 -1 + 17 812.5750 817.8395 21 5 5 5000 -1 + 18 707.0074 732.4712 21 5 5 5000 -1 + 19 344.0368 804.8922 21 5 5 5000 -1 + 20 925.5311 545.4898 21 5 5 5000 -1 + 21 544.6249 635.9762 21 5 5 5000 -1 + 22 497.8947 555.3517 21 5 5 5000 -1 + 23 889.0664 774.6374 21 5 5 5000 -1 + 24 411.9579 816.4832 21 5 5 5000 -1 + 25 686.4609 556.3958 21 5 5 5000 -1 + 26 463.4136 520.6149 21 5 5 5000 -1 + 27 990.8399 488.0587 21 5 5 5000 -1 + 28 1022.8472 577.2381 21 5 5 5000 -1 + 29 749.4334 690.7269 21 5 5 5000 -1 + 30 268.6146 520.5932 21 5 5 5000 -1 + 31 892.0950 795.1996 21 5 5 5000 -1 + 32 378.3315 718.0449 21 5 5 5000 -1 + 33 1002.8964 755.2967 21 5 5 5000 -1 + 34 923.4060 648.2880 21 5 5 5000 -1 + 35 520.8061 624.3591 21 5 5 5000 -1 + 36 415.1377 519.3158 21 5 5 5000 -1 + 37 331.8918 562.4556 21 5 5 5000 -1 + 38 610.7249 581.7134 21 5 5 5000 -1 + 39 821.2357 816.5273 21 5 5 5000 -1 + 40 832.2455 484.0968 21 5 5 5000 -1 + 41 330.5331 636.5947 21 5 5 5000 -1 + 42 425.5100 638.6136 21 5 5 5000 -1 + 43 679.3516 503.6131 21 5 5 5000 -1 + 44 750.5699 725.5231 21 5 5 5000 -1 + 45 980.4212 686.7744 21 5 5 5000 -1 + 46 463.1901 828.1096 21 5 5 5000 -1 + 47 953.5139 735.9423 21 5 5 5000 -1 + 48 883.7427 720.0533 21 5 5 5000 -1 + 49 475.5395 527.7797 21 5 5 5000 -1 + 50 555.6905 691.2134 21 5 5 5000 -1 + 51 933.5614 814.8643 21 5 5 5000 -1 + 52 711.2456 656.6839 21 5 5 5000 -1 + 53 288.5335 797.7969 21 5 5 5000 -1 + 54 1010.1143 490.1842 21 5 5 5000 -1 + 55 1030.2365 568.8392 21 5 5 5000 -1 + 56 930.0960 674.3395 21 5 5 5000 -1 + 57 491.3334 470.2510 21 5 5 5000 -1 + 58 664.8168 716.4996 21 5 5 5000 -1 + 59 334.0524 481.6249 21 5 5 5000 -1 + 60 401.4066 609.7590 21 5 5 5000 -1 + 61 625.9921 673.4063 21 5 5 5000 -1 + 62 823.5307 573.5371 21 5 5 5000 -1 + 63 689.7797 805.6602 21 5 5 5000 -1 + 64 413.0519 745.0033 21 5 5 5000 -1 + 65 948.6914 588.7258 21 5 5 5000 -1 + 66 755.1783 502.6599 21 5 5 5000 -1 + 67 523.8921 458.7800 21 5 5 5000 -1 + 68 790.3149 698.3434 21 5 5 5000 -1 + 69 358.2153 492.1084 21 5 5 5000 -1 + 70 774.5568 572.9953 21 5 5 5000 -1 + 71 977.7703 589.7398 21 5 5 5000 -1 + 72 665.9507 536.6031 21 5 5 5000 -1 + 73 871.2669 595.1664 21 5 5 5000 -1 + 74 1011.9525 668.6675 21 5 5 5000 -1 + 75 388.7270 728.6234 21 5 5 5000 -1 + 76 937.4524 541.4403 21 5 5 5000 -1 + 77 464.8992 756.5053 21 5 5 5000 -1 + 78 370.8768 508.0194 21 5 5 5000 -1 + 79 932.1215 778.6366 21 5 5 5000 -1 + 80 520.6845 668.8115 21 5 5 5000 -1 + 81 444.0039 609.6972 21 5 5 5000 -1 + 82 911.2410 674.8136 21 5 5 5000 -1 + 83 402.1527 630.2024 21 5 5 5000 -1 + 84 982.1991 738.7768 21 5 5 5000 -1 + 85 466.5098 646.7376 21 5 5 5000 -1 + 86 534.2196 476.0819 21 5 5 5000 -1 + 87 527.2560 680.2951 21 5 5 5000 -1 + 88 312.0425 559.1544 21 5 5 5000 -1 + 89 617.7815 595.5488 21 5 5 5000 -1 + 90 505.0907 746.1432 21 5 5 5000 -1 + 91 884.2659 558.7127 21 5 5 5000 -1 + 92 1005.7662 677.2316 21 5 5 5000 -1 + 93 584.5114 727.0614 21 5 5 5000 -1 + 94 488.4349 806.8121 21 5 5 5000 -1 + 95 891.4657 560.8911 21 5 5 5000 -1 diff --git a/tests/testing_fodder/test_cavity_synthetic/img_orig/cam4.10001_targets b/tests/testing_fodder/test_cavity_synthetic/img_orig/cam4.10001_targets new file mode 100644 index 0000000..7dcfafc --- /dev/null +++ b/tests/testing_fodder/test_cavity_synthetic/img_orig/cam4.10001_targets @@ -0,0 +1,97 @@ +96 + 0 371.1196 391.2138 21 5 5 5000 -1 + 1 474.9862 610.0136 21 5 5 5000 -1 + 2 597.6101 546.5350 21 5 5 5000 -1 + 3 453.6529 653.0015 21 5 5 5000 -1 + 4 864.6529 637.0581 21 5 5 5000 -1 + 5 712.3557 634.0705 21 5 5 5000 -1 + 6 368.8663 482.8186 21 5 5 5000 -1 + 7 841.7468 569.0040 21 5 5 5000 -1 + 8 569.3261 559.7342 21 5 5 5000 -1 + 9 377.2535 590.5126 21 5 5 5000 -1 + 10 777.4997 551.7058 21 5 5 5000 -1 + 11 776.4110 726.6524 21 5 5 5000 -1 + 12 807.3044 711.6490 21 5 5 5000 -1 + 13 872.0125 547.2544 21 5 5 5000 -1 + 14 795.3412 449.4344 21 5 5 5000 -1 + 15 948.9002 666.5594 21 5 5 5000 -1 + 16 1015.5719 505.0770 21 5 5 5000 -1 + 17 647.1144 652.9336 21 5 5 5000 -1 + 18 673.2737 569.1282 21 5 5 5000 -1 + 19 479.2300 537.9232 21 5 5 5000 -1 + 20 651.7231 446.8463 21 5 5 5000 -1 + 21 321.1093 630.4111 21 5 5 5000 -1 + 22 292.8827 573.9318 21 5 5 5000 -1 + 23 605.3369 624.2477 21 5 5 5000 -1 + 24 851.6995 532.3502 21 5 5 5000 -1 + 25 781.8181 545.9167 21 5 5 5000 -1 + 26 487.8907 616.7814 21 5 5 5000 -1 + 27 354.2999 754.0260 21 5 5 5000 -1 + 28 416.1187 717.2082 21 5 5 5000 -1 + 29 348.6402 404.8706 21 5 5 5000 -1 + 30 1052.4921 596.9914 21 5 5 5000 -1 + 31 343.1853 617.9563 21 5 5 5000 -1 + 32 810.6442 427.6203 21 5 5 5000 -1 + 33 877.5139 523.9321 21 5 5 5000 -1 + 34 520.3918 426.3728 21 5 5 5000 -1 + 35 323.2252 525.5892 21 5 5 5000 -1 + 36 379.4957 459.5222 21 5 5 5000 -1 + 37 682.6625 495.8980 21 5 5 5000 -1 + 38 806.6610 663.7339 21 5 5 5000 -1 + 39 890.7500 438.0686 21 5 5 5000 -1 + 40 868.1364 449.0504 21 5 5 5000 -1 + 41 897.6575 460.5341 21 5 5 5000 -1 + 42 392.9366 600.5280 21 5 5 5000 -1 + 43 801.3003 387.7036 21 5 5 5000 -1 + 44 880.7569 445.1378 21 5 5 5000 -1 + 45 506.6770 565.1171 21 5 5 5000 -1 + 46 841.9756 385.2812 21 5 5 5000 -1 + 47 698.0027 377.5394 21 5 5 5000 -1 + 48 648.6748 392.4996 21 5 5 5000 -1 + 49 692.3970 481.1043 21 5 5 5000 -1 + 50 732.6081 733.0883 21 5 5 5000 -1 + 51 504.8848 662.4884 21 5 5 5000 -1 + 52 617.5721 647.5609 21 5 5 5000 -1 + 53 619.4533 713.4411 21 5 5 5000 -1 + 54 933.4753 619.8574 21 5 5 5000 -1 + 55 719.7978 455.1511 21 5 5 5000 -1 + 56 395.7412 462.5909 21 5 5 5000 -1 + 57 362.2571 459.2965 21 5 5 5000 -1 + 58 542.5172 642.7826 21 5 5 5000 -1 + 59 350.1138 569.9481 21 5 5 5000 -1 + 60 848.7971 375.0646 21 5 5 5000 -1 + 61 477.8698 452.6349 21 5 5 5000 -1 + 62 408.8665 715.5445 21 5 5 5000 -1 + 63 879.4677 753.3787 21 5 5 5000 -1 + 64 767.0927 571.1121 21 5 5 5000 -1 + 65 1030.2600 516.1408 21 5 5 5000 -1 + 66 725.5919 585.4083 21 5 5 5000 -1 + 67 619.3515 514.0184 21 5 5 5000 -1 + 68 1017.6796 593.9076 21 5 5 5000 -1 + 69 349.3044 670.9336 21 5 5 5000 -1 + 70 922.8250 504.8421 21 5 5 5000 -1 + 71 635.7461 661.3223 21 5 5 5000 -1 + 72 472.4459 531.7421 21 5 5 5000 -1 + 73 924.3041 625.4896 21 5 5 5000 -1 + 74 879.2912 492.6368 21 5 5 5000 -1 + 75 650.7620 397.7189 21 5 5 5000 -1 + 76 813.5699 551.1108 21 5 5 5000 -1 + 77 292.7349 565.8417 21 5 5 5000 -1 + 78 1075.9157 604.7165 21 5 5 5000 -1 + 79 605.6755 703.6866 21 5 5 5000 -1 + 80 1021.8978 653.2126 21 5 5 5000 -1 + 81 791.4794 561.8865 21 5 5 5000 -1 + 82 870.1937 449.3173 21 5 5 5000 -1 + 83 1047.6793 709.9529 21 5 5 5000 -1 + 84 355.5144 385.0012 21 5 5 5000 -1 + 85 702.3578 405.6451 21 5 5 5000 -1 + 86 988.9832 439.0539 21 5 5 5000 -1 + 87 641.4369 427.8650 21 5 5 5000 -1 + 88 904.8187 668.6487 21 5 5 5000 -1 + 89 805.2202 722.1173 21 5 5 5000 -1 + 90 774.5907 438.3923 21 5 5 5000 -1 + 91 313.3519 377.5902 21 5 5 5000 -1 + 92 1033.4481 663.8778 21 5 5 5000 -1 + 93 1075.3874 619.2595 21 5 5 5000 -1 + 94 907.2865 731.1871 21 5 5 5000 -1 + 95 728.7928 627.9919 21 5 5 5000 -1 diff --git a/tests/testing_fodder/test_cavity_synthetic/img_orig/cam4.10002_targets b/tests/testing_fodder/test_cavity_synthetic/img_orig/cam4.10002_targets new file mode 100644 index 0000000..6035436 --- /dev/null +++ b/tests/testing_fodder/test_cavity_synthetic/img_orig/cam4.10002_targets @@ -0,0 +1,97 @@ +96 + 0 334.2275 542.1888 21 5 5 5000 -1 + 1 891.2837 483.1754 21 5 5 5000 -1 + 2 1025.2487 498.6441 21 5 5 5000 -1 + 3 1038.1758 674.8786 21 5 5 5000 -1 + 4 971.8808 708.4972 21 5 5 5000 -1 + 5 695.0504 513.2339 21 5 5 5000 -1 + 6 395.5890 389.3562 21 5 5 5000 -1 + 7 1037.2597 416.7359 21 5 5 5000 -1 + 8 697.3035 728.0674 21 5 5 5000 -1 + 9 430.9612 518.8569 21 5 5 5000 -1 + 10 444.0904 725.5541 21 5 5 5000 -1 + 11 943.7696 521.7831 21 5 5 5000 -1 + 12 774.7428 707.7518 21 5 5 5000 -1 + 13 630.1668 458.4806 21 5 5 5000 -1 + 14 502.4029 431.6898 21 5 5 5000 -1 + 15 1026.5036 714.9234 21 5 5 5000 -1 + 16 332.8651 498.3158 21 5 5 5000 -1 + 17 529.5336 719.8969 21 5 5 5000 -1 + 18 538.0450 604.9717 21 5 5 5000 -1 + 19 642.2064 635.3505 21 5 5 5000 -1 + 20 1004.5148 472.5241 21 5 5 5000 -1 + 21 326.5564 468.3127 21 5 5 5000 -1 + 22 614.9613 549.1795 21 5 5 5000 -1 + 23 811.9840 496.0940 21 5 5 5000 -1 + 24 954.8356 604.9586 21 5 5 5000 -1 + 25 957.7703 577.8421 21 5 5 5000 -1 + 26 950.5054 516.6514 21 5 5 5000 -1 + 27 852.0788 405.2528 21 5 5 5000 -1 + 28 365.8497 399.3532 21 5 5 5000 -1 + 29 696.9691 654.0124 21 5 5 5000 -1 + 30 814.1284 745.6007 21 5 5 5000 -1 + 31 571.0638 550.2134 21 5 5 5000 -1 + 32 636.8897 590.8675 21 5 5 5000 -1 + 33 528.6536 659.2827 21 5 5 5000 -1 + 34 897.9311 485.7766 21 5 5 5000 -1 + 35 564.5914 594.4487 21 5 5 5000 -1 + 36 496.5539 549.5979 21 5 5 5000 -1 + 37 994.4852 691.7361 21 5 5 5000 -1 + 38 706.0343 454.3036 21 5 5 5000 -1 + 39 510.9494 536.4765 21 5 5 5000 -1 + 40 808.7484 746.6436 21 5 5 5000 -1 + 41 1037.3282 414.1878 21 5 5 5000 -1 + 42 1013.1069 610.7966 21 5 5 5000 -1 + 43 804.2246 623.5048 21 5 5 5000 -1 + 44 412.2172 538.6797 21 5 5 5000 -1 + 45 894.3188 603.6603 21 5 5 5000 -1 + 46 759.5054 421.7247 21 5 5 5000 -1 + 47 587.8198 371.7142 21 5 5 5000 -1 + 48 406.8884 427.7058 21 5 5 5000 -1 + 49 524.7095 521.3157 21 5 5 5000 -1 + 50 430.2780 566.5758 21 5 5 5000 -1 + 51 415.6498 636.3913 21 5 5 5000 -1 + 52 477.6941 699.4492 21 5 5 5000 -1 + 53 347.5675 465.2593 21 5 5 5000 -1 + 54 957.8693 729.2962 21 5 5 5000 -1 + 55 735.3755 613.1903 21 5 5 5000 -1 + 56 966.0259 651.6222 21 5 5 5000 -1 + 57 959.6943 750.7472 21 5 5 5000 -1 + 58 415.4845 416.3501 21 5 5 5000 -1 + 59 864.0649 576.3931 21 5 5 5000 -1 + 60 427.4184 712.1307 21 5 5 5000 -1 + 61 599.8341 643.6205 21 5 5 5000 -1 + 62 796.4586 441.5889 21 5 5 5000 -1 + 63 736.3515 577.8625 21 5 5 5000 -1 + 64 855.1442 453.8349 21 5 5 5000 -1 + 65 1084.1237 508.4847 21 5 5 5000 -1 + 66 638.8138 425.5643 21 5 5 5000 -1 + 67 599.9554 408.7322 21 5 5 5000 -1 + 68 483.1244 739.5292 21 5 5 5000 -1 + 69 1009.2949 519.0909 21 5 5 5000 -1 + 70 520.1012 581.5095 21 5 5 5000 -1 + 71 438.9838 626.4676 21 5 5 5000 -1 + 72 584.1435 389.2201 21 5 5 5000 -1 + 73 984.8710 483.8782 21 5 5 5000 -1 + 74 739.2653 648.7865 21 5 5 5000 -1 + 75 841.6733 423.2225 21 5 5 5000 -1 + 76 948.0194 582.0485 21 5 5 5000 -1 + 77 1018.6098 670.9641 21 5 5 5000 -1 + 78 436.9171 654.1660 21 5 5 5000 -1 + 79 325.5123 426.1727 21 5 5 5000 -1 + 80 513.1246 668.6761 21 5 5 5000 -1 + 81 544.7242 382.0995 21 5 5 5000 -1 + 82 453.9529 437.8067 21 5 5 5000 -1 + 83 881.0844 519.3747 21 5 5 5000 -1 + 84 534.1729 559.1430 21 5 5 5000 -1 + 85 525.9841 467.2229 21 5 5 5000 -1 + 86 439.3035 510.1743 21 5 5 5000 -1 + 87 339.9972 690.3312 21 5 5 5000 -1 + 88 847.0584 495.4915 21 5 5 5000 -1 + 89 276.9638 699.2297 21 5 5 5000 -1 + 90 711.4458 474.6552 21 5 5 5000 -1 + 91 1096.4783 603.9216 21 5 5 5000 -1 + 92 1053.3147 621.0458 21 5 5 5000 -1 + 93 1071.7062 665.2577 21 5 5 5000 -1 + 94 949.2438 467.2970 21 5 5 5000 -1 + 95 623.5276 497.6648 21 5 5 5000 -1 diff --git a/tests/testing_fodder/test_cavity_synthetic/parameters/cal_ori.par b/tests/testing_fodder/test_cavity_synthetic/parameters/cal_ori.par new file mode 100644 index 0000000..9dece9c --- /dev/null +++ b/tests/testing_fodder/test_cavity_synthetic/parameters/cal_ori.par @@ -0,0 +1,12 @@ +cal/target_on_a_side.txt +cal/cam1.tif +cal/cam1.tif.ori +cal/cam2.tif +cal/cam2.tif.ori +cal/cam3.tif +cal/cam3.tif.ori +cal/cam4.tif +cal/cam4.tif.ori +1 +1 +0 diff --git a/tests/testing_fodder/test_cavity_synthetic/parameters/criteria.par b/tests/testing_fodder/test_cavity_synthetic/parameters/criteria.par new file mode 100644 index 0000000..67cc8fa --- /dev/null +++ b/tests/testing_fodder/test_cavity_synthetic/parameters/criteria.par @@ -0,0 +1,12 @@ +-40 +-20 +20 +40 +-20 +20 +0.02 +0.02 +0.02 +0.02 +33 +0.2 diff --git a/tests/testing_fodder/test_cavity_synthetic/parameters/detect_plate.par b/tests/testing_fodder/test_cavity_synthetic/parameters/detect_plate.par new file mode 100644 index 0000000..1c67d03 --- /dev/null +++ b/tests/testing_fodder/test_cavity_synthetic/parameters/detect_plate.par @@ -0,0 +1,13 @@ +40 +40 +40 +40 +500 +25 +400 +5 +50 +5 +50 +100 +3 diff --git a/tests/testing_fodder/test_cavity_synthetic/parameters/dumbbell.par b/tests/testing_fodder/test_cavity_synthetic/parameters/dumbbell.par new file mode 100644 index 0000000..ea931f3 --- /dev/null +++ b/tests/testing_fodder/test_cavity_synthetic/parameters/dumbbell.par @@ -0,0 +1,6 @@ +3.000000 +25.000000 +0.050000 +1.000000 +1 +500 diff --git a/tests/testing_fodder/test_cavity_synthetic/parameters/examine.par b/tests/testing_fodder/test_cavity_synthetic/parameters/examine.par new file mode 100644 index 0000000..aa47d0d --- /dev/null +++ b/tests/testing_fodder/test_cavity_synthetic/parameters/examine.par @@ -0,0 +1,2 @@ +0 +0 diff --git a/tests/testing_fodder/test_cavity_synthetic/parameters/man_ori.par b/tests/testing_fodder/test_cavity_synthetic/parameters/man_ori.par new file mode 100644 index 0000000..45acff6 --- /dev/null +++ b/tests/testing_fodder/test_cavity_synthetic/parameters/man_ori.par @@ -0,0 +1,16 @@ +3 +5 +72 +73 +3 +5 +72 +73 +1 +5 +71 +73 +1 +5 +71 +73 diff --git a/tests/testing_fodder/test_cavity_synthetic/parameters/multi_planes.par b/tests/testing_fodder/test_cavity_synthetic/parameters/multi_planes.par new file mode 100644 index 0000000..9b30724 --- /dev/null +++ b/tests/testing_fodder/test_cavity_synthetic/parameters/multi_planes.par @@ -0,0 +1,4 @@ +3 +img/calib_a_cam +img/calib_b_cam +img/calib_c_cam diff --git a/tests/testing_fodder/test_cavity_synthetic/parameters/orient.par b/tests/testing_fodder/test_cavity_synthetic/parameters/orient.par new file mode 100644 index 0000000..66f4ca4 --- /dev/null +++ b/tests/testing_fodder/test_cavity_synthetic/parameters/orient.par @@ -0,0 +1,12 @@ +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 diff --git a/tests/testing_fodder/test_cavity_synthetic/parameters/pft_version b/tests/testing_fodder/test_cavity_synthetic/parameters/pft_version new file mode 100644 index 0000000..00750ed --- /dev/null +++ b/tests/testing_fodder/test_cavity_synthetic/parameters/pft_version @@ -0,0 +1 @@ +3 diff --git a/tests/testing_fodder/test_cavity_synthetic/parameters/pft_version.par b/tests/testing_fodder/test_cavity_synthetic/parameters/pft_version.par new file mode 100644 index 0000000..573541a --- /dev/null +++ b/tests/testing_fodder/test_cavity_synthetic/parameters/pft_version.par @@ -0,0 +1 @@ +0 diff --git a/tests/testing_fodder/test_cavity_synthetic/parameters/ptv.par b/tests/testing_fodder/test_cavity_synthetic/parameters/ptv.par new file mode 100644 index 0000000..49c0f6f --- /dev/null +++ b/tests/testing_fodder/test_cavity_synthetic/parameters/ptv.par @@ -0,0 +1,21 @@ +4 +img/cam1.10003 +cal/cam1.tif +img/cam2.10003 +cal/cam2.tif +img/cam3.10003 +cal/cam3.tif +img/cam4.10003 +cal/cam4.tif +1 +0 +1 +1280 +1024 +0.012 +0.012 +0 +1 +1.33 +1.46 +6 diff --git a/tests/testing_fodder/test_cavity_synthetic/parameters/sequence.par b/tests/testing_fodder/test_cavity_synthetic/parameters/sequence.par new file mode 100644 index 0000000..9d9b85c --- /dev/null +++ b/tests/testing_fodder/test_cavity_synthetic/parameters/sequence.par @@ -0,0 +1,6 @@ +img_orig/cam1.%05d +img_orig/cam2.%05d +img_orig/cam3.%05d +img_orig/cam4.%05d +10001 +10002 diff --git a/tests/testing_fodder/test_cavity_synthetic/parameters/shaking.par b/tests/testing_fodder/test_cavity_synthetic/parameters/shaking.par new file mode 100644 index 0000000..06d8de2 --- /dev/null +++ b/tests/testing_fodder/test_cavity_synthetic/parameters/shaking.par @@ -0,0 +1,4 @@ +10000 +10004 +10 +5 diff --git a/tests/testing_fodder/test_cavity_synthetic/parameters/sortgrid.par b/tests/testing_fodder/test_cavity_synthetic/parameters/sortgrid.par new file mode 100644 index 0000000..209e3ef --- /dev/null +++ b/tests/testing_fodder/test_cavity_synthetic/parameters/sortgrid.par @@ -0,0 +1 @@ +20 diff --git a/tests/testing_fodder/test_cavity_synthetic/parameters/targ_rec.par b/tests/testing_fodder/test_cavity_synthetic/parameters/targ_rec.par new file mode 100644 index 0000000..7800c5a --- /dev/null +++ b/tests/testing_fodder/test_cavity_synthetic/parameters/targ_rec.par @@ -0,0 +1,13 @@ +9 +9 +9 +11 +100 +4 +500 +2 +100 +2 +100 +150 +2 diff --git a/tests/testing_fodder/test_cavity_synthetic/parameters/track.par b/tests/testing_fodder/test_cavity_synthetic/parameters/track.par new file mode 100644 index 0000000..31706ba --- /dev/null +++ b/tests/testing_fodder/test_cavity_synthetic/parameters/track.par @@ -0,0 +1,9 @@ +-5.5 +5.5 +-5.5 +5.5 +-5.5 +5.5 +110 +1.0 +0 diff --git a/tests/testing_fodder/test_cavity_synthetic/parameters/unsharp_mask.par b/tests/testing_fodder/test_cavity_synthetic/parameters/unsharp_mask.par new file mode 100644 index 0000000..48082f7 --- /dev/null +++ b/tests/testing_fodder/test_cavity_synthetic/parameters/unsharp_mask.par @@ -0,0 +1 @@ +12 diff --git a/tests/testing_fodder/test_cavity_synthetic/parameters_Run1.yaml b/tests/testing_fodder/test_cavity_synthetic/parameters_Run1.yaml new file mode 100644 index 0000000..9c58258 --- /dev/null +++ b/tests/testing_fodder/test_cavity_synthetic/parameters_Run1.yaml @@ -0,0 +1,174 @@ +num_cams: 4 +plugins: + available_tracking: + - default + available_sequence: + - default + selected_tracking: default + selected_sequence: default +cal_ori: + chfield: 0 + fixp_name: cal/target_on_a_side.txt + img_cal_name: + - cal/cam1.tif + - cal/cam2.tif + - cal/cam3.tif + - cal/cam4.tif + img_ori: + - cal/cam1.tif.ori + - cal/cam2.tif.ori + - cal/cam3.tif.ori + - cal/cam4.tif.ori + pair_flag: true + tiff_flag: true + cal_splitter: false +criteria: + X_lay: + - -40 + - 40 + Zmax_lay: + - 20 + - 20 + Zmin_lay: + - -20 + - -20 + cn: 0.02 + cnx: 0.02 + cny: 0.02 + corrmin: 33.0 + csumg: 0.02 + eps0: 0.2 +detect_plate: + gvth_1: 40 + gvth_2: 40 + gvth_3: 40 + gvth_4: 40 + max_npix: 400 + max_npix_x: 50 + max_npix_y: 50 + min_npix: 25 + min_npix_x: 5 + min_npix_y: 5 + size_cross: 3 + sum_grey: 100 + tol_dis: 500 +dumbbell: + dumbbell_eps: 3.0 + dumbbell_gradient_descent: 0.05 + dumbbell_niter: 500 + dumbbell_penalty_weight: 1.0 + dumbbell_scale: 25.0 + dumbbell_step: 1 +examine: + Combine_Flag: false + Examine_Flag: false +man_ori: + nr: + - 3 + - 5 + - 72 + - 73 + - 3 + - 5 + - 72 + - 73 + - 1 + - 5 + - 71 + - 73 + - 1 + - 5 + - 71 + - 73 +multi_planes: + n_planes: 3 + plane_name: + - img/calib_a_cam + - img/calib_b_cam + - img/calib_c_cam +orient: + cc: 0 + interf: 0 + k1: 0 + k2: 0 + k3: 0 + p1: 0 + p2: 0 + pnfo: 0 + scale: 0 + shear: 0 + xh: 0 + yh: 0 +pft_version: + Existing_Target: 0 +ptv: + allcam_flag: false + chfield: 0 + hp_flag: true + img_cal: + - cal/cam1.tif + - cal/cam2.tif + - cal/cam3.tif + - cal/cam4.tif + img_name: + - img/cam1.10003 + - img/cam2.10003 + - img/cam3.10003 + - img/cam4.10003 + imx: 1280 + imy: 1024 + mmp_d: 6.0 + mmp_n1: 1.0 + mmp_n2: 1.33 + mmp_n3: 1.46 + pix_x: 0.012 + pix_y: 0.012 + tiff_flag: true + splitter: false +sequence: + base_name: + - img/cam1.%05d + - img/cam2.%05d + - img/cam3.%05d + - img/cam4.%05d + first: 10001 + last: 10004 +shaking: + shaking_first_frame: 10000 + shaking_last_frame: 10004 + shaking_max_num_frames: 5 + shaking_max_num_points: 10 +sortgrid: + radius: 20 +targ_rec: + cr_sz: 2 + disco: 100 + gvthres: + - 9 + - 9 + - 9 + - 11 + nnmax: 500 + nnmin: 4 + nxmax: 100 + nxmin: 2 + nymax: 100 + nymin: 2 + sumg_min: 150 +track: + angle: 110.0 + dacc: 1.0 + dvxmax: 5.5 + dvxmin: -5.5 + dvymax: 5.5 + dvymin: -5.5 + dvzmax: 5.5 + dvzmin: -5.5 + flagNewParticles: false +masking: + mask_flag: false + mask_base_name: '' +unsharp_mask: + flag: false + size: 3 + strength: 1.0 diff --git a/tests/testing_fodder/test_cavity_synthetic/res_orig/added.10001 b/tests/testing_fodder/test_cavity_synthetic/res_orig/added.10001 new file mode 100644 index 0000000..61f6b2c --- /dev/null +++ b/tests/testing_fodder/test_cavity_synthetic/res_orig/added.10001 @@ -0,0 +1,97 @@ +96 +-1 -2 -9.015 2.927 -16.720 2 +-1 -2 -7.645 -9.020 -6.160 2 +-1 -2 19.352 -17.386 6.728 2 +-1 -2 -32.744 -5.758 14.272 2 +-1 -2 18.431 10.832 -10.569 2 +-1 -2 36.949 -5.349 -1.172 2 +-1 -2 -19.617 -4.575 4.417 2 +-1 -2 8.582 -1.118 -11.440 2 +-1 -2 30.311 4.564 -10.305 2 +-1 -2 12.792 -2.158 -16.020 2 +-1 -2 11.807 -14.954 3.007 2 +-1 -2 -7.646 -6.150 0.638 2 +-1 -2 -19.070 3.150 7.961 2 +-1 -2 28.940 11.433 -1.501 2 +-1 -2 10.934 10.566 1.142 2 +-1 -2 -0.768 5.709 -4.230 2 +-1 -2 15.329 2.295 -5.081 2 +-1 -2 7.532 0.326 -12.395 2 +-1 -2 10.426 -14.451 12.228 2 +-1 -2 32.607 -7.990 4.928 2 +-1 -2 -27.516 8.319 15.666 2 +-1 -2 -7.156 -15.113 -3.747 2 +-1 -2 -31.370 7.721 -17.849 2 +-1 -2 22.606 -8.820 12.624 2 +-1 -2 36.226 -4.620 -8.021 2 +-1 -2 -18.723 2.422 6.242 2 +-1 -2 36.212 -12.161 15.533 2 +-1 -2 -17.260 -4.648 10.483 2 +-1 -2 6.424 -0.957 16.235 2 +-1 -2 -11.474 -0.256 -1.879 2 +-1 -2 12.559 1.021 1.212 2 +-1 -2 -28.189 17.607 17.740 2 +-1 -2 18.265 8.257 -16.566 2 +-1 -2 -31.630 -0.820 5.041 2 +-1 -2 6.814 -0.387 -14.351 2 +-1 -2 -35.523 -12.285 -13.003 2 +-1 -2 -29.445 -4.622 -4.994 2 +-1 -2 -29.126 -17.089 17.515 2 +-1 -2 32.183 -2.534 4.235 2 +-1 -2 -0.283 13.600 -14.159 2 +-1 -2 13.389 -8.503 12.506 2 +-1 -2 -34.498 8.116 -16.657 2 +-1 -2 33.681 4.878 6.307 2 +-1 -2 -28.009 -2.306 10.164 2 +-1 -2 3.044 -7.118 -7.259 2 +-1 -2 -37.934 -1.535 1.828 2 +-1 -2 4.302 10.272 4.930 2 +-1 -2 -0.895 16.169 12.916 2 +-1 -2 13.444 17.195 13.852 2 +-1 -2 13.841 13.308 10.860 2 +-1 -2 34.572 -8.234 12.524 2 +-1 -2 -15.387 -8.779 12.254 2 +-1 -2 20.289 2.773 17.085 2 +-1 -2 -3.234 -2.076 -13.225 2 +-1 -2 21.468 -15.696 2.713 2 +-1 -2 -32.606 14.493 -0.933 2 +-1 -2 -18.860 -1.441 -8.808 2 +-1 -2 24.961 6.651 16.351 2 +-1 -2 2.688 -17.609 -10.999 2 +-1 -2 -3.655 15.607 -2.903 2 +-1 -2 15.982 9.643 -12.345 2 +-1 -2 -10.752 -6.333 17.852 2 +-1 -2 -31.511 15.417 -6.714 2 +-1 -2 -26.751 -15.001 2.024 2 +-1 -2 13.083 -13.194 10.341 2 +-1 -2 17.056 17.982 7.902 2 +-1 -2 -6.340 -10.603 -8.938 2 +-1 -2 18.118 6.123 -4.304 2 +-1 -2 -21.811 9.253 -10.665 2 +-1 -2 -37.246 16.707 -6.957 2 +-1 -2 22.396 -6.297 -3.288 2 +-1 -2 15.233 16.496 -0.485 2 +-1 -2 -36.242 -0.055 9.997 2 +-1 -2 -0.222 16.488 -11.317 2 +-1 -2 -7.595 -13.632 1.601 2 +-1 -2 6.135 -2.063 -11.965 2 +-1 -2 -8.137 1.294 1.428 2 +-1 -2 33.390 -4.392 -13.338 2 +-1 -2 -33.770 -6.056 -1.755 2 +-1 -2 10.093 12.128 8.628 2 +-1 -2 -2.671 13.008 7.810 2 +-1 -2 -27.554 -15.977 -5.876 2 +-1 -2 20.226 11.765 16.945 2 +-1 -2 -36.900 2.302 -8.408 2 +-1 -2 19.409 11.193 3.726 2 +-1 -2 20.649 4.804 15.698 2 +-1 -2 3.480 8.709 16.416 2 +-1 -2 -5.562 -9.971 -10.975 2 +-1 -2 2.171 -7.285 -2.609 2 +-1 -2 22.975 -5.970 -5.607 2 +-1 -2 -5.377 9.600 -14.614 2 +-1 -2 -12.528 13.790 17.585 2 +-1 -2 18.812 -5.990 12.050 2 +-1 -2 25.926 -9.208 6.305 2 +-1 -2 -30.580 8.931 -5.441 2 +-1 -2 -20.187 -7.884 12.874 2 diff --git a/tests/testing_fodder/test_cavity_synthetic/res_orig/added.10002 b/tests/testing_fodder/test_cavity_synthetic/res_orig/added.10002 new file mode 100644 index 0000000..a6d62af --- /dev/null +++ b/tests/testing_fodder/test_cavity_synthetic/res_orig/added.10002 @@ -0,0 +1,97 @@ +96 +-1 -2 33.100 -10.874 -3.731 2 +-1 -2 -1.282 -5.544 17.803 2 +-1 -2 -25.620 -7.420 -8.545 2 +-1 -2 -32.080 9.467 13.174 2 +-1 -2 21.834 7.900 10.078 2 +-1 -2 21.174 8.109 9.754 2 +-1 -2 31.382 4.365 3.668 2 +-1 -2 -22.386 -14.708 -11.052 2 +-1 -2 -23.193 4.761 2.856 2 +-1 -2 13.888 5.177 -12.495 2 +-1 -2 3.080 8.242 2.359 2 +-1 -2 -17.690 -11.043 -4.786 2 +-1 -2 -4.770 6.324 6.119 2 +-1 -2 34.353 7.246 15.820 2 +-1 -2 16.035 -2.154 -8.124 2 +-1 -2 37.414 4.711 -4.991 2 +-1 -2 32.666 -3.420 11.879 2 +-1 -2 -9.519 -0.495 -16.794 2 +-1 -2 -36.751 -12.133 16.401 2 +-1 -2 14.459 7.267 14.869 2 +-1 -2 30.360 -16.077 -17.149 2 +-1 -2 16.930 15.001 4.989 2 +-1 -2 -17.861 11.878 -2.326 2 +-1 -2 -32.018 2.278 10.487 2 +-1 -2 -25.811 -7.346 2.617 2 +-1 -2 1.807 9.784 -2.707 2 +-1 -2 -23.763 -0.427 5.064 2 +-1 -2 -18.965 12.662 17.085 2 +-1 -2 -37.281 -14.578 -16.865 2 +-1 -2 -6.163 14.971 10.397 2 +-1 -2 34.834 -11.174 -16.321 2 +-1 -2 -23.527 -16.023 0.975 2 +-1 -2 31.479 -10.415 16.823 2 +-1 -2 -10.904 17.047 -10.996 2 +-1 -2 2.338 -15.091 8.570 2 +-1 -2 -24.309 3.802 1.276 2 +-1 -2 7.183 -3.599 15.415 2 +-1 -2 -6.721 11.602 -15.325 2 +-1 -2 -10.654 0.968 1.899 2 +-1 -2 19.542 4.372 5.107 2 +-1 -2 11.825 11.680 6.344 2 +-1 -2 -14.056 -1.276 10.696 2 +-1 -2 26.936 6.253 -14.169 2 +-1 -2 -29.039 15.648 8.516 2 +-1 -2 4.990 11.953 -15.933 2 +-1 -2 -14.723 -9.350 3.145 2 +-1 -2 -19.024 -16.906 4.685 2 +-1 -2 12.761 11.736 -17.431 2 +-1 -2 22.932 -3.406 -17.093 2 +-1 -2 34.301 14.013 5.332 2 +-1 -2 24.846 -16.626 -8.352 2 +-1 -2 29.010 7.454 -12.738 2 +-1 -2 -7.365 -7.525 5.655 2 +-1 -2 -20.517 -0.265 -12.633 2 +-1 -2 -14.358 3.194 13.554 2 +-1 -2 -15.743 -15.729 -2.462 2 +-1 -2 -26.586 13.217 -3.685 2 +-1 -2 -10.498 15.683 -6.299 2 +-1 -2 -15.002 8.755 1.199 2 +-1 -2 26.292 -1.299 1.988 2 +-1 -2 -11.952 -3.570 -1.630 2 +-1 -2 23.324 2.842 -11.248 2 +-1 -2 14.251 -15.957 14.159 2 +-1 -2 33.547 13.799 -1.132 2 +-1 -2 8.409 -14.003 -0.366 2 +-1 -2 7.368 -6.988 14.633 2 +-1 -2 22.414 -2.333 17.837 2 +-1 -2 26.398 -3.567 5.101 2 +-1 -2 -3.495 -2.383 6.971 2 +-1 -2 -4.115 9.992 5.818 2 +-1 -2 -1.304 3.194 -14.999 2 +-1 -2 14.499 9.047 -14.126 2 +-1 -2 -24.969 2.440 7.757 2 +-1 -2 -24.215 13.318 13.233 2 +-1 -2 3.281 -7.571 14.055 2 +-1 -2 -16.631 -1.079 -11.642 2 +-1 -2 33.910 -6.317 -9.369 2 +-1 -2 37.664 -4.931 -12.510 2 +-1 -2 -23.598 -8.924 3.462 2 +-1 -2 24.986 -9.769 -14.015 2 +-1 -2 12.407 -5.281 6.950 2 +-1 -2 -36.110 12.047 -6.674 2 +-1 -2 25.488 -15.206 -14.025 2 +-1 -2 -32.643 8.787 0.154 2 +-1 -2 -14.534 16.274 -7.342 2 +-1 -2 26.704 -17.204 5.115 2 +-1 -2 -28.329 -16.516 -15.822 2 +-1 -2 13.571 -16.186 13.031 2 +-1 -2 -11.429 -2.984 16.068 2 +-1 -2 -35.700 5.003 -7.533 2 +-1 -2 30.834 -10.881 -7.255 2 +-1 -2 27.009 5.267 12.371 2 +-1 -2 26.504 9.561 9.008 2 +-1 -2 5.367 -1.449 2.623 2 +-1 -2 -29.551 15.440 -9.108 2 +-1 -2 -18.286 2.204 -16.040 2 diff --git a/tests/testing_fodder/test_cavity_synthetic/res_orig/ptv_is.10001 b/tests/testing_fodder/test_cavity_synthetic/res_orig/ptv_is.10001 new file mode 100644 index 0000000..bb18c55 --- /dev/null +++ b/tests/testing_fodder/test_cavity_synthetic/res_orig/ptv_is.10001 @@ -0,0 +1,97 @@ +96 +-1 -2 -9.015 2.927 -16.720 +-1 -2 -7.645 -9.020 -6.160 +-1 -2 19.352 -17.386 6.728 +-1 -2 -32.744 -5.758 14.272 +-1 -2 18.431 10.832 -10.569 +-1 -2 36.949 -5.349 -1.172 +-1 -2 -19.617 -4.575 4.417 +-1 -2 8.582 -1.118 -11.440 +-1 -2 30.311 4.564 -10.305 +-1 -2 12.792 -2.158 -16.020 +-1 -2 11.807 -14.954 3.007 +-1 -2 -7.646 -6.150 0.638 +-1 -2 -19.070 3.150 7.961 +-1 -2 28.940 11.433 -1.501 +-1 -2 10.934 10.566 1.142 +-1 -2 -0.768 5.709 -4.230 +-1 -2 15.329 2.295 -5.081 +-1 -2 7.532 0.326 -12.395 +-1 -2 10.426 -14.451 12.228 +-1 -2 32.607 -7.990 4.928 +-1 -2 -27.516 8.319 15.666 +-1 -2 -7.156 -15.113 -3.747 +-1 -2 -31.370 7.721 -17.849 +-1 -2 22.606 -8.820 12.624 +-1 -2 36.226 -4.620 -8.021 +-1 -2 -18.723 2.422 6.242 +-1 -2 36.212 -12.161 15.533 +-1 -2 -17.260 -4.648 10.483 +-1 -2 6.424 -0.957 16.235 +-1 -2 -11.474 -0.256 -1.879 +-1 -2 12.559 1.021 1.212 +-1 -2 -28.189 17.607 17.740 +-1 -2 18.265 8.257 -16.566 +-1 -2 -31.630 -0.820 5.041 +-1 -2 6.814 -0.387 -14.351 +-1 -2 -35.523 -12.285 -13.003 +-1 -2 -29.445 -4.622 -4.994 +-1 -2 -29.126 -17.089 17.515 +-1 -2 32.183 -2.534 4.235 +-1 -2 -0.283 13.600 -14.159 +-1 -2 13.389 -8.503 12.506 +-1 -2 -34.498 8.116 -16.657 +-1 -2 33.681 4.878 6.307 +-1 -2 -28.009 -2.306 10.164 +-1 -2 3.044 -7.118 -7.259 +-1 -2 -37.934 -1.535 1.828 +-1 -2 4.302 10.272 4.930 +-1 -2 -0.895 16.169 12.916 +-1 -2 13.444 17.195 13.852 +-1 -2 13.841 13.308 10.860 +-1 -2 34.572 -8.234 12.524 +-1 -2 -15.387 -8.779 12.254 +-1 -2 20.289 2.773 17.085 +-1 -2 -3.234 -2.076 -13.225 +-1 -2 21.468 -15.696 2.713 +-1 -2 -32.606 14.493 -0.933 +-1 -2 -18.860 -1.441 -8.808 +-1 -2 24.961 6.651 16.351 +-1 -2 2.688 -17.609 -10.999 +-1 -2 -3.655 15.607 -2.903 +-1 -2 15.982 9.643 -12.345 +-1 -2 -10.752 -6.333 17.852 +-1 -2 -31.511 15.417 -6.714 +-1 -2 -26.751 -15.001 2.024 +-1 -2 13.083 -13.194 10.341 +-1 -2 17.056 17.982 7.902 +-1 -2 -6.340 -10.603 -8.938 +-1 -2 18.118 6.123 -4.304 +-1 -2 -21.811 9.253 -10.665 +-1 -2 -37.246 16.707 -6.957 +-1 -2 22.396 -6.297 -3.288 +-1 -2 15.233 16.496 -0.485 +-1 -2 -36.242 -0.055 9.997 +-1 -2 -0.222 16.488 -11.317 +-1 -2 -7.595 -13.632 1.601 +-1 -2 6.135 -2.063 -11.965 +-1 -2 -8.137 1.294 1.428 +-1 -2 33.390 -4.392 -13.338 +-1 -2 -33.770 -6.056 -1.755 +-1 -2 10.093 12.128 8.628 +-1 -2 -2.671 13.008 7.810 +-1 -2 -27.554 -15.977 -5.876 +-1 -2 20.226 11.765 16.945 +-1 -2 -36.900 2.302 -8.408 +-1 -2 19.409 11.193 3.726 +-1 -2 20.649 4.804 15.698 +-1 -2 3.480 8.709 16.416 +-1 -2 -5.562 -9.971 -10.975 +-1 -2 2.171 -7.285 -2.609 +-1 -2 22.975 -5.970 -5.607 +-1 -2 -5.377 9.600 -14.614 +-1 -2 -12.528 13.790 17.585 +-1 -2 18.812 -5.990 12.050 +-1 -2 25.926 -9.208 6.305 +-1 -2 -30.580 8.931 -5.441 +-1 -2 -20.187 -7.884 12.874 diff --git a/tests/testing_fodder/test_cavity_synthetic/res_orig/ptv_is.10002 b/tests/testing_fodder/test_cavity_synthetic/res_orig/ptv_is.10002 new file mode 100644 index 0000000..ce22d4a --- /dev/null +++ b/tests/testing_fodder/test_cavity_synthetic/res_orig/ptv_is.10002 @@ -0,0 +1,97 @@ +96 +-1 -2 33.100 -10.874 -3.731 +-1 -2 -1.282 -5.544 17.803 +-1 -2 -25.620 -7.420 -8.545 +-1 -2 -32.080 9.467 13.174 +-1 -2 21.834 7.900 10.078 +-1 -2 21.174 8.109 9.754 +-1 -2 31.382 4.365 3.668 +-1 -2 -22.386 -14.708 -11.052 +-1 -2 -23.193 4.761 2.856 +-1 -2 13.888 5.177 -12.495 +-1 -2 3.080 8.242 2.359 +-1 -2 -17.690 -11.043 -4.786 +-1 -2 -4.770 6.324 6.119 +-1 -2 34.353 7.246 15.820 +-1 -2 16.035 -2.154 -8.124 +-1 -2 37.414 4.711 -4.991 +-1 -2 32.666 -3.420 11.879 +-1 -2 -9.519 -0.495 -16.794 +-1 -2 -36.751 -12.133 16.401 +-1 -2 14.459 7.267 14.869 +-1 -2 30.360 -16.077 -17.149 +-1 -2 16.930 15.001 4.989 +-1 -2 -17.861 11.878 -2.326 +-1 -2 -32.018 2.278 10.487 +-1 -2 -25.811 -7.346 2.617 +-1 -2 1.807 9.784 -2.707 +-1 -2 -23.763 -0.427 5.064 +-1 -2 -18.965 12.662 17.085 +-1 -2 -37.281 -14.578 -16.865 +-1 -2 -6.163 14.971 10.397 +-1 -2 34.834 -11.174 -16.321 +-1 -2 -23.527 -16.023 0.975 +-1 -2 31.479 -10.415 16.823 +-1 -2 -10.904 17.047 -10.996 +-1 -2 2.338 -15.091 8.570 +-1 -2 -24.309 3.802 1.276 +-1 -2 7.183 -3.599 15.415 +-1 -2 -6.721 11.602 -15.325 +-1 -2 -10.654 0.968 1.899 +-1 -2 19.542 4.372 5.107 +-1 -2 11.825 11.680 6.344 +-1 -2 -14.056 -1.276 10.696 +-1 -2 26.936 6.253 -14.169 +-1 -2 -29.039 15.648 8.516 +-1 -2 4.990 11.953 -15.933 +-1 -2 -14.723 -9.350 3.145 +-1 -2 -19.024 -16.906 4.685 +-1 -2 12.761 11.736 -17.431 +-1 -2 22.932 -3.406 -17.093 +-1 -2 34.301 14.013 5.332 +-1 -2 24.846 -16.626 -8.352 +-1 -2 29.010 7.454 -12.738 +-1 -2 -7.365 -7.525 5.655 +-1 -2 -20.517 -0.265 -12.633 +-1 -2 -14.358 3.194 13.554 +-1 -2 -15.743 -15.729 -2.462 +-1 -2 -26.586 13.217 -3.685 +-1 -2 -10.498 15.683 -6.299 +-1 -2 -15.002 8.755 1.199 +-1 -2 26.292 -1.299 1.988 +-1 -2 -11.952 -3.570 -1.630 +-1 -2 23.324 2.842 -11.248 +-1 -2 14.251 -15.957 14.159 +-1 -2 33.547 13.799 -1.132 +-1 -2 8.409 -14.003 -0.366 +-1 -2 7.368 -6.988 14.633 +-1 -2 22.414 -2.333 17.837 +-1 -2 26.398 -3.567 5.101 +-1 -2 -3.495 -2.383 6.971 +-1 -2 -4.115 9.992 5.818 +-1 -2 -1.304 3.194 -14.999 +-1 -2 14.499 9.047 -14.126 +-1 -2 -24.969 2.440 7.757 +-1 -2 -24.215 13.318 13.233 +-1 -2 3.281 -7.571 14.055 +-1 -2 -16.631 -1.079 -11.642 +-1 -2 33.910 -6.317 -9.369 +-1 -2 37.664 -4.931 -12.510 +-1 -2 -23.598 -8.924 3.462 +-1 -2 24.986 -9.769 -14.015 +-1 -2 12.407 -5.281 6.950 +-1 -2 -36.110 12.047 -6.674 +-1 -2 25.488 -15.206 -14.025 +-1 -2 -32.643 8.787 0.154 +-1 -2 -14.534 16.274 -7.342 +-1 -2 26.704 -17.204 5.115 +-1 -2 -28.329 -16.516 -15.822 +-1 -2 13.571 -16.186 13.031 +-1 -2 -11.429 -2.984 16.068 +-1 -2 -35.700 5.003 -7.533 +-1 -2 30.834 -10.881 -7.255 +-1 -2 27.009 5.267 12.371 +-1 -2 26.504 9.561 9.008 +-1 -2 5.367 -1.449 2.623 +-1 -2 -29.551 15.440 -9.108 +-1 -2 -18.286 2.204 -16.040 diff --git a/tests/testing_fodder/test_cavity_synthetic/res_orig/rt_is.10001 b/tests/testing_fodder/test_cavity_synthetic/res_orig/rt_is.10001 new file mode 100644 index 0000000..c8f93e6 --- /dev/null +++ b/tests/testing_fodder/test_cavity_synthetic/res_orig/rt_is.10001 @@ -0,0 +1,97 @@ +96 +1 -9.015 2.927 -16.720 18 9 7 67 +2 -7.645 -9.020 -6.160 88 83 33 52 +3 19.352 -17.386 6.728 43 31 60 63 +4 -32.744 -5.758 14.272 92 34 80 21 +5 18.431 10.832 -10.569 84 33 12 39 +6 36.949 -5.349 -1.172 16 46 27 93 +7 -19.617 -4.575 4.417 86 93 16 1 +8 8.582 -1.118 -11.440 94 7 54 81 +9 30.311 4.564 -10.305 3 24 43 16 +10 12.792 -2.158 -16.020 40 71 76 7 +11 11.807 -14.954 3.007 74 86 24 89 +12 -7.646 -6.150 0.638 26 72 56 23 +13 -19.070 3.150 7.961 89 51 30 72 +14 28.940 11.433 -1.501 72 90 61 86 +15 10.934 10.566 1.142 64 19 31 14 +16 -0.768 5.709 -4.230 56 22 68 37 +17 15.329 2.295 -5.081 5 15 50 24 +18 7.532 0.326 -12.395 85 35 23 25 +19 10.426 -14.451 12.228 37 38 89 11 +20 32.607 -7.990 4.928 58 64 63 80 +21 -27.516 8.319 15.666 63 84 13 6 +22 -7.156 -15.113 -3.747 78 82 75 53 +23 -31.370 7.721 -17.849 39 12 28 56 +24 22.606 -8.820 12.624 59 37 95 88 +25 36.226 -4.620 -8.021 14 23 70 78 +26 -18.723 2.422 6.242 60 2 74 19 +27 36.212 -12.161 15.533 80 69 77 83 +28 -17.260 -4.648 10.483 36 3 8 26 +29 6.424 -0.957 16.235 50 45 87 66 +30 -11.474 -0.256 -1.879 9 41 2 8 +31 12.559 1.021 1.212 15 14 22 76 +32 -28.189 17.607 17.740 31 66 37 84 +33 18.265 8.257 -16.566 53 44 82 41 +34 -31.630 -0.820 5.041 66 49 15 59 +35 6.814 -0.387 -14.351 17 80 64 10 +36 -35.523 -12.285 -13.003 34 25 40 69 +37 -29.445 -4.622 -4.994 57 70 57 42 +38 -29.126 -17.089 17.515 13 95 79 27 +39 32.183 -2.534 4.235 46 75 9 68 +40 -0.283 13.600 -14.159 25 61 90 85 +41 13.389 -8.503 12.506 51 13 86 38 +42 -34.498 8.116 -16.657 29 32 53 57 +43 33.681 4.878 6.307 81 50 93 65 +44 -28.009 -2.306 10.164 77 65 58 9 +45 3.044 -7.118 -7.259 11 79 59 95 +46 -37.934 -1.535 1.828 68 6 62 22 +47 4.302 10.272 4.930 6 16 94 55 +48 -0.895 16.169 12.916 32 94 3 75 +49 13.444 17.195 13.852 69 26 46 43 +50 13.841 13.308 10.860 30 1 4 32 +51 34.572 -8.234 12.524 55 62 47 92 +52 -15.387 -8.779 12.254 95 87 5 51 +53 20.289 2.773 17.085 82 43 91 13 +54 -3.234 -2.076 -13.225 91 68 0 18 +55 21.468 -15.696 2.713 47 59 25 94 +56 -32.606 14.493 -0.933 67 4 19 29 +57 -18.860 -1.441 -8.808 49 57 72 45 +58 24.961 6.651 16.351 22 67 67 70 +59 2.688 -17.609 -10.999 71 54 29 50 +60 -3.655 15.607 -2.903 2 0 49 48 +61 15.982 9.643 -12.345 65 11 18 40 +62 -10.752 -6.333 17.852 79 91 36 58 +63 -31.511 15.417 -6.714 48 60 6 0 +64 -26.751 -15.001 2.024 42 5 41 62 +65 13.083 -13.194 10.341 10 20 48 12 +66 17.056 17.982 7.902 0 92 66 60 +67 -6.340 -10.603 -8.938 23 21 26 71 +68 18.118 6.123 -4.304 76 85 83 74 +69 -21.811 9.253 -10.665 28 88 10 61 +70 -37.246 16.707 -6.957 4 78 84 91 +71 22.396 -6.297 -3.288 45 89 38 73 +72 15.233 16.496 -0.485 38 56 85 46 +73 -36.242 -0.055 9.997 75 48 17 77 +74 -0.222 16.488 -11.317 93 18 52 47 +75 -7.595 -13.632 1.601 61 55 69 79 +76 6.135 -2.063 -11.965 20 58 35 64 +77 -8.137 1.294 1.428 19 63 51 2 +78 33.390 -4.392 -13.338 41 30 39 30 +79 -33.770 -6.056 -1.755 24 77 73 31 +80 10.093 12.128 8.628 8 47 11 90 +81 -2.671 13.008 7.810 44 42 1 87 +82 -27.554 -15.977 -5.876 33 29 81 28 +83 20.226 11.765 16.945 90 40 45 82 +84 -36.900 2.302 -8.408 73 53 55 35 +85 19.409 11.193 3.726 70 76 21 44 +86 20.649 4.804 15.698 62 81 88 33 +87 3.480 8.709 16.416 27 17 32 49 +88 -5.562 -9.971 -10.975 35 36 78 17 +89 2.171 -7.285 -2.609 54 73 34 5 +90 22.975 -5.970 -5.607 52 74 92 54 +91 -5.377 9.600 -14.614 1 8 42 20 +92 -12.528 13.790 17.585 7 39 14 34 +93 18.812 -5.990 12.050 83 10 65 4 +94 25.926 -9.208 6.305 87 28 44 15 +95 -30.580 8.931 -5.441 21 52 20 36 +96 -20.187 -7.884 12.874 12 27 71 3 diff --git a/tests/testing_fodder/test_cavity_synthetic/res_orig/rt_is.10002 b/tests/testing_fodder/test_cavity_synthetic/res_orig/rt_is.10002 new file mode 100644 index 0000000..26064a4 --- /dev/null +++ b/tests/testing_fodder/test_cavity_synthetic/res_orig/rt_is.10002 @@ -0,0 +1,97 @@ +96 +1 33.100 -10.874 -3.731 23 15 84 3 +2 -1.282 -5.544 17.803 18 8 58 19 +3 -25.620 -7.420 -8.545 0 92 32 71 +4 -32.080 9.467 13.174 85 72 37 21 +5 21.834 7.900 10.078 72 44 95 34 +6 21.174 8.109 9.754 10 85 91 1 +7 31.382 4.365 3.668 17 30 71 69 +8 -22.386 -14.708 -11.052 35 12 4 52 +9 -23.193 4.761 2.856 82 4 3 86 +10 13.888 5.177 -12.495 69 57 70 88 +11 3.080 8.242 2.359 20 34 25 90 +12 -17.690 -11.043 -4.786 60 83 77 80 +13 -4.770 6.324 6.119 29 32 38 95 +14 34.353 7.246 15.820 8 53 55 2 +15 16.035 -2.154 -8.124 46 41 15 59 +16 37.414 4.711 -4.991 84 66 28 65 +17 32.666 -3.420 11.879 39 80 92 42 +18 -9.519 -0.495 -16.794 73 63 0 22 +19 -36.751 -12.133 16.401 56 18 53 89 +20 14.459 7.267 14.869 40 47 62 23 +21 30.360 -16.077 -17.149 32 45 79 15 +22 16.930 15.001 4.989 14 75 40 27 +23 -17.861 11.878 -2.326 49 0 26 14 +24 -32.018 2.278 10.487 47 37 41 0 +25 -25.811 -7.346 2.617 15 19 75 51 +26 1.807 9.784 -2.707 30 22 72 38 +27 -23.763 -0.427 5.064 92 1 2 50 +28 -18.965 12.662 17.085 41 52 49 82 +29 -37.281 -14.578 -16.865 7 59 1 87 +30 -6.163 14.971 10.397 13 14 14 67 +31 34.834 -11.174 -16.321 38 33 9 93 +32 -23.527 -16.023 0.975 34 5 24 10 +33 31.479 -10.415 16.823 19 9 33 37 +34 -10.904 17.047 -10.996 21 3 67 47 +35 2.338 -15.091 8.570 63 79 63 8 +36 -24.309 3.802 1.276 4 81 60 9 +37 7.183 -3.599 15.415 12 31 29 55 +38 -6.721 11.602 -15.325 37 69 11 66 +39 -10.654 0.968 1.899 65 87 21 31 +40 19.542 4.372 5.107 79 55 6 83 +41 11.825 11.680 6.344 70 43 13 62 +42 -14.056 -1.276 10.696 80 62 80 70 +43 26.936 6.253 -14.169 68 90 12 73 +44 -29.039 15.648 8.516 93 40 69 28 +45 4.990 11.953 -15.933 27 11 43 46 +46 -14.723 -9.350 3.145 61 51 90 33 +47 -19.024 -16.906 4.685 26 35 46 68 +48 12.761 11.736 -17.431 53 49 66 75 +49 22.932 -3.406 -17.093 28 10 7 76 +50 34.301 14.013 5.332 95 65 54 7 +51 24.846 -16.626 -8.352 71 95 31 54 +52 29.010 7.454 -12.738 74 50 20 20 +53 -7.365 -7.525 5.655 2 91 93 61 +54 -20.517 -0.265 -12.633 89 29 42 36 +55 -14.358 3.194 13.554 36 42 35 39 +56 -15.743 -15.729 -2.462 91 20 94 17 +57 -26.586 13.217 -3.685 87 16 78 58 +58 -10.498 15.683 -6.299 44 25 86 72 +59 -15.002 8.755 1.199 11 58 22 85 +60 26.292 -1.299 1.988 76 82 34 25 +61 -11.952 -3.570 -1.630 52 27 87 35 +62 23.324 2.842 -11.248 9 56 73 11 +63 14.251 -15.957 14.159 48 74 39 30 +64 33.547 13.799 -1.132 83 89 27 41 +65 8.409 -14.003 -0.366 88 68 10 12 +66 7.368 -6.988 14.633 51 61 44 74 +67 22.414 -2.333 17.837 66 17 82 45 +68 26.398 -3.567 5.101 90 39 56 24 +69 -3.495 -2.383 6.971 42 54 61 32 +70 -4.115 9.992 5.818 86 7 8 13 +71 -1.304 3.194 -14.999 31 71 89 5 +72 14.499 9.047 -14.126 25 28 16 64 +73 -24.969 2.440 7.757 81 76 83 44 +74 -24.215 13.318 13.233 77 84 36 48 +75 3.281 -7.571 14.055 55 70 18 29 +76 -16.631 -1.079 -11.642 6 73 85 84 +77 33.910 -6.317 -9.369 24 93 45 92 +78 37.664 -4.931 -12.510 64 48 74 91 +79 -23.598 -8.924 3.462 45 46 64 78 +80 24.986 -9.769 -14.015 3 67 48 56 +81 12.407 -5.281 6.950 33 88 68 43 +82 -36.110 12.047 -6.674 75 64 30 79 +83 25.488 -15.206 -14.025 62 86 23 4 +84 -32.643 8.787 0.154 78 21 88 53 +85 -14.534 16.274 -7.342 94 94 57 81 +86 26.704 -17.204 5.115 57 2 51 57 +87 -28.329 -16.516 -15.822 67 13 19 60 +88 13.571 -16.186 13.031 58 38 17 40 +89 -11.429 -2.984 16.068 5 36 50 18 +90 -35.700 5.003 -7.533 50 60 5 16 +91 30.834 -10.881 -7.255 1 24 47 77 +92 27.009 5.267 12.371 22 23 65 26 +93 26.504 9.561 9.008 54 78 76 94 +94 5.367 -1.449 2.623 59 26 52 63 +95 -29.551 15.440 -9.108 43 6 59 6 +96 -18.286 2.204 -16.040 16 77 81 49 From 74fe803996a504ca4265491ba02ec13c7a84678d Mon Sep 17 00:00:00 2001 From: Alex Liberzon Date: Sat, 7 Mar 2026 01:17:57 +0200 Subject: [PATCH 09/27] updated to have different order of releasing cameras --- .gitignore | 1 + docs/bundle_adjustment.md | 26 ++ openptv_python/demo_bundle_adjustment.py | 449 ++++++++++++++++++++++- openptv_python/orientation.py | 337 +++++++++++++++-- tests/test_bundle_adjustment.py | 302 +++++++++++++++ tests/test_demo_bundle_adjustment.py | 50 +++ 6 files changed, 1126 insertions(+), 39 deletions(-) diff --git a/.gitignore b/.gitignore index 9727a52..5194d8e 100644 --- a/.gitignore +++ b/.gitignore @@ -472,3 +472,4 @@ $RECYCLE.BIN/ # End of https://www.toptal.com/developers/gitignore/api/python,jupyternotebooks,vim,visualstudiocode,pycharm,emacs,linux,macos,windows .cruft.json /.tmp +/tmp diff --git a/docs/bundle_adjustment.md b/docs/bundle_adjustment.md index ee39fc0..59919d5 100644 --- a/docs/bundle_adjustment.md +++ b/docs/bundle_adjustment.md @@ -126,6 +126,7 @@ The current routines report and the demo prints: 1. `initial_reprojection_rms` and `final_reprojection_rms`. 1. Per-camera reprojection RMS for `multi_camera_bundle_adjustment`. 1. `baseline_mean_ray_convergence`, `pose_mean_ray_convergence`, and `final_mean_ray_convergence` for guarded runs. +1. `baseline_correspondence_rate`, `pose_correspondence_rate`, and `intrinsic_correspondence_rate` for guarded runs when original quadruplet identities are available. 1. Runtime in seconds for each experiment. 1. `known_point_indices` for constrained runs in `multi_camera_bundle_adjustment`. @@ -165,6 +166,9 @@ The command-line options are: 1. `--geometry-guard-mode {auto,off,soft,hard}`: control whether guarded two-step BA rejects stages that move a known 3D calibration target too far from a trusted reference calibration. `auto` enables `hard` when `parameters/cal_ori.par` points to a known 3D target file such as `cal/target_on_a_side.txt`. 1. `--geometry-guard-threshold S`: pixel threshold used by `hard` geometry guards. 1. `--geometry-export-threshold S`: refuse to write output case folders whose final calibration-body drift exceeds `S` pixels. Use `0` to disable export blocking. +1. `--correspondence-guard-mode {auto,off,soft,hard}`: control whether guarded two-step BA rejects stages that replace too many original quadruplet target identities after reprojection and nearest-target reassignment. `auto` derives a threshold from the trusted reference calibration when tracking data is available. +1. `--correspondence-guard-threshold S`: replacement-rate threshold used by `hard` correspondence guards. +1. `--correspondence-export-threshold S`: refuse to write output case folders whose final correspondence replacement rate exceeds `S`. Use `0` to disable correspondence-based export blocking. ### Included Demo Presets @@ -182,6 +186,12 @@ When a case exposes known 3D calibration-target points through `cal_ori.par`, th 1. Output case folders are written only if the final calibration stays within the configured export drift threshold. 1. Cases without a known 3D target file still run normally; the geometry guard simply stays off unless you explicitly enable it another way. +When a case also exposes original tracked quadruplets and per-camera target files, the demo adds a correspondence-preserving workflow on top: + +1. Guarded presets can compare original target identities with the identities implied by the refined calibration. +1. `auto` correspondence mode derives a threshold from the trusted reference calibration and rejects later stages that replace materially more quadruplets than that reference already does. +1. Output case folders are skipped if the final replacement rate exceeds the configured correspondence export threshold. + If `--known-points` is greater than zero, the demo also compares two constrained presets: 1. `pose_trf_known_points`: pose-only bundle adjustment with soft 3D anchors. @@ -236,6 +246,17 @@ python -m openptv_python.demo_bundle_adjustment \ --output-dir .tmp/demo_bundle_adjustment_runs_soft ``` +Keep geometry blocking but also enforce a hard correspondence replacement limit derived from the trusted reference calibration: + +```bash +python -m openptv_python.demo_bundle_adjustment \ + tests/testing_fodder/test_cavity \ + --max-frames 2 \ + --max-points-per-frame 80 \ + --correspondence-guard-mode auto \ + --output-dir .tmp/demo_bundle_adjustment_runs_correspondence +``` + Run without writing calibration folders: ```bash @@ -290,12 +311,15 @@ tmp/bundle_adjustment_demo/ ... calibration_delta.txt geometry_check.txt + correspondence_check.txt ``` `calibration_delta.txt` is generated with `openptv_python.calibration_compare` and shows camera-by-camera parameter differences relative to the source case. `geometry_check.txt` reports per-camera drift of the known 3D calibration-body projections relative to the trusted reference calibration. If the final drift exceeds `--geometry-export-threshold`, the demo does not write that experiment's case folder at all. +`correspondence_check.txt` reports how many original quadruplet target identities are replaced by the refined calibration after nearest-target reassignment. If the final replacement rate exceeds `--correspondence-export-threshold`, the demo does not write that experiment's case folder at all. + ## Choosing Options These are the main tradeoffs when selecting settings. @@ -353,6 +377,8 @@ This split is useful because the two diagnostics answer different questions: If epipolar distance stays bad while reprojection improves, the next step is usually not to add an epipolar term blindly. First identify whether a subset of camera pairs or quadruplets is driving the inconsistency, then decide whether to reject or downweight those observations. +The correspondence guard is aimed at a related failure mode: a calibration can reduce RMS by drifting until many refined projections land closer to different detected targets than the original quadruplet used. In that case, the solution is numerically fitting the images better while no longer preserving the same tracked structure. The new replacement-rate check gives you an explicit acceptance and export criterion for that situation. + ### Priors and Bounds 1. `prior_sigmas` softly regularize motion away from the starting calibration. diff --git a/openptv_python/demo_bundle_adjustment.py b/openptv_python/demo_bundle_adjustment.py index ebc95f6..6aca494 100644 --- a/openptv_python/demo_bundle_adjustment.py +++ b/openptv_python/demo_bundle_adjustment.py @@ -17,7 +17,7 @@ format_calibration_comparison, ) from .epi import epipolar_curve -from .imgcoord import img_coord +from .imgcoord import image_coordinates, img_coord from .orientation import ( guarded_two_step_bundle_adjustment, initialize_bundle_adjustment_points, @@ -36,7 +36,7 @@ ) from .sortgrid import read_calblock as read_sortgrid_calblock from .tracking_frame_buf import read_path_frame, read_targets -from .trafo import metric_to_pixel +from .trafo import arr_metric_to_pixel, metric_to_pixel @dataclass(frozen=True) @@ -67,6 +67,7 @@ class ExperimentResult: camera_position_shifts: List[float] camera_angle_shifts: List[float] geometry_projection_drift: List[ProjectionDriftSummary] | None = None + correspondence_replacement: CorrespondenceReplacementSummary | None = None refined_cals: List[Calibration] | None = None refined_points: np.ndarray | None = None @@ -122,6 +123,27 @@ class ProjectionDriftSummary: max_distance: float +@dataclass(frozen=True) +class CorrespondenceReplacementSummary: + """How strongly a calibration would replace the original quadruplet identities.""" + + replacement_rate: float + camera_change_rates: List[float] + mean_nearest_distance: float + p95_nearest_distance: float + max_nearest_distance: float + + +@dataclass(frozen=True) +class ObservationTrackingData: + """Original correspondence identities and candidate detections for a case.""" + + original_target_ids: np.ndarray + point_frame_indices: np.ndarray + frame_target_pixels: List[List[np.ndarray]] + reference_replacement_rate: float | None = None + + def clone_calibration(cal: Calibration) -> Calibration: """Return a detached copy of a calibration object.""" return Calibration( @@ -377,6 +399,166 @@ def load_case_observations( ) +def summarize_correspondence_replacements( + points: np.ndarray, + cals: Sequence[Calibration], + control: ControlPar, + tracking_data: ObservationTrackingData | None, +) -> CorrespondenceReplacementSummary | None: + """Measure how often reprojections switch to different target identities.""" + if tracking_data is None: + return None + + projected_pixels = np.empty((points.shape[0], len(cals), 2), dtype=float) + for camera_index, cal in enumerate(cals): + projected_pixels[:, camera_index, :] = arr_metric_to_pixel( + image_coordinates(points, cal, control.mm), + control, + ) + + replacement_ids = np.empty_like(tracking_data.original_target_ids) + nearest_distances = np.empty_like(tracking_data.original_target_ids, dtype=float) + for point_index in range(points.shape[0]): + frame_targets = tracking_data.frame_target_pixels[ + int(tracking_data.point_frame_indices[point_index]) + ] + for camera_index in range(len(cals)): + deltas = frame_targets[camera_index] - projected_pixels[point_index, camera_index] + squared_distances = np.sum(deltas * deltas, axis=1) + nearest_index = int(np.argmin(squared_distances)) + replacement_ids[point_index, camera_index] = nearest_index + nearest_distances[point_index, camera_index] = float( + np.sqrt(squared_distances[nearest_index]) + ) + + changed_mask = np.any( + replacement_ids != tracking_data.original_target_ids, + axis=1, + ) + camera_change_rates = [ + float( + np.mean( + replacement_ids[:, camera_index] + != tracking_data.original_target_ids[:, camera_index] + ) + ) + for camera_index in range(len(cals)) + ] + return CorrespondenceReplacementSummary( + replacement_rate=float(np.mean(changed_mask)), + camera_change_rates=camera_change_rates, + mean_nearest_distance=float(np.mean(nearest_distances)), + p95_nearest_distance=float(np.percentile(nearest_distances, 95.0)), + max_nearest_distance=float(np.max(nearest_distances)), + ) + + +def load_case_tracking_data( + case_dir: Path, + num_cams: int, + *, + max_frames: int | None, + max_points_per_frame: int | None, + control: ControlPar, + reference_cals: Sequence[Calibration], +) -> ObservationTrackingData | None: + """Load original target identities and candidate detections for replacement guards.""" + seq = SequencePar.from_file(case_dir / "parameters/sequence.par", num_cams) + frames = list(range(seq.first, seq.last + 1)) + if max_frames is not None: + frames = frames[:max_frames] + + original_target_ids = [] + point_frame_indices = [] + frame_target_pixels: List[List[np.ndarray]] = [] + reference_points = [] + for frame_index, frame in enumerate(frames): + cor_buf, path_buf = read_path_frame( + str(case_dir / "res_orig/rt_is"), + "", + "", + frame, + ) + targets = [ + read_targets(str(case_dir / f"img_orig/cam{cam_num}.%05d"), frame) + for cam_num in range(1, num_cams + 1) + ] + subset = [ + point_num + for point_num, corres in enumerate(cor_buf) + if np.all(corres.p[:num_cams] >= 0) + ] + if max_points_per_frame is not None: + subset = subset[:max_points_per_frame] + + for point_num in subset: + original_target_ids.append(cor_buf[point_num].p[:num_cams].copy()) + point_frame_indices.append(frame_index) + reference_points.append(path_buf[point_num].x.copy()) + + frame_target_pixels.append( + [np.asarray([[target.x, target.y] for target in cam_targets], dtype=float) for cam_targets in targets] + ) + + if not original_target_ids: + return None + + tracking_data = ObservationTrackingData( + original_target_ids=np.asarray(original_target_ids, dtype=int), + point_frame_indices=np.asarray(point_frame_indices, dtype=int), + frame_target_pixels=frame_target_pixels, + ) + reference_summary = summarize_correspondence_replacements( + np.asarray(reference_points, dtype=float), + reference_cals, + control, + tracking_data, + ) + return ObservationTrackingData( + original_target_ids=tracking_data.original_target_ids, + point_frame_indices=tracking_data.point_frame_indices, + frame_target_pixels=tracking_data.frame_target_pixels, + reference_replacement_rate=( + None if reference_summary is None else reference_summary.replacement_rate + ), + ) + + +def format_correspondence_replacement( + summary: CorrespondenceReplacementSummary | None, +) -> str: + """Render a compact correspondence-replacement summary.""" + if summary is None: + return "No correspondence replacement data available." + + camera_rates = " ".join( + f"cam{camera_index + 1}={100.0 * rate:.1f}%" + for camera_index, rate in enumerate(summary.camera_change_rates) + ) + return ( + f"quad_replacement_rate={100.0 * summary.replacement_rate:.1f}%\n" + f"camera_change_rates: {camera_rates}\n" + f"nearest_distance_px: mean={summary.mean_nearest_distance:.3f} " + f"p95={summary.p95_nearest_distance:.3f} max={summary.max_nearest_distance:.3f}" + ) + + +def should_block_export_on_correspondence( + summary: CorrespondenceReplacementSummary | None, + threshold: float | None, +) -> tuple[bool, str]: + """Return whether an export should be blocked by correspondence churn.""" + if summary is None or threshold is None or threshold <= 0: + return False, "" + if summary.replacement_rate <= threshold: + return False, "" + return ( + True, + "correspondence_export_blocked=" + f"replacement_rate={summary.replacement_rate:.3f}>{threshold:.3f}", + ) + + def build_known_point_constraints( point_init: np.ndarray, count: int, @@ -720,15 +902,46 @@ def summarize_fixed_camera_diagnostics( return diagnostics +def normalize_staged_release_order( + staged_release_order: Sequence[int] | None, + num_cams: int, +) -> List[int]: + """Return a validated zero-based staged camera release order.""" + if staged_release_order is None: + order = list(range(num_cams)) + else: + order = [int(camera_index) for camera_index in staged_release_order] + + if len(order) != num_cams: + raise ValueError( + f"staged_release_order must contain exactly {num_cams} cameras" + ) + if sorted(order) != list(range(num_cams)): + raise ValueError( + "staged_release_order must be a permutation of zero-based camera indices" + ) + return order + + def default_experiments( *, + num_cams: int = 4, known_points: Dict[int, np.ndarray] | None = None, known_point_sigmas: float | np.ndarray | None = None, perturbation_scale: float = 1.0, + staged_release_order: Sequence[int] | None = None, + pose_stage_ray_slack: float = 0.0, geometry_guard_mode: str = "off", geometry_guard_threshold: float | None = None, + correspondence_guard_mode: str = "off", + correspondence_guard_threshold: float | None = None, + correspondence_guard_reference_rate: float | None = None, ) -> List[ExperimentSpec]: """Return a set of representative BA configurations.""" + staged_order = normalize_staged_release_order(staged_release_order, num_cams) + staged_fixed = [ + camera_index for camera_index in range(num_cams) if camera_index != staged_order[0] + ] pose_priors = { "x0": 0.5, "y0": 0.5, @@ -831,7 +1044,7 @@ def default_experiments( mode="multi", ba_kwargs={ "orient_par": intrinsics_only, - "fixed_camera_indices": [0, 1, 2, 3], + "fixed_camera_indices": list(range(num_cams)), "loss": "linear", "method": "trf", "prior_sigmas": { @@ -868,6 +1081,32 @@ def default_experiments( "intrinsic_max_nfev": 4, "geometry_guard_mode": geometry_guard_mode, "geometry_guard_threshold": geometry_guard_threshold, + "correspondence_guard_mode": correspondence_guard_mode, + "correspondence_guard_threshold": correspondence_guard_threshold, + "correspondence_guard_reference_rate": correspondence_guard_reference_rate, + }, + ), + ExperimentSpec( + name="guarded_stagewise_release", + description="Guarded BA that releases one camera at a time before tightly constrained intrinsics", + mode="guarded", + ba_kwargs={ + "pose_orient_par": OrientPar(), + "intrinsic_orient_par": intrinsics, + "fixed_camera_indices": staged_fixed, + "pose_release_camera_order": staged_order, + "pose_stage_ray_slack": pose_stage_ray_slack, + "pose_prior_sigmas": pose_priors, + "pose_parameter_bounds": pose_bounds, + "pose_max_nfev": 8, + "intrinsic_prior_sigmas": tight_intrinsic_priors, + "intrinsic_parameter_bounds": tight_intrinsic_bounds, + "intrinsic_max_nfev": 4, + "geometry_guard_mode": geometry_guard_mode, + "geometry_guard_threshold": geometry_guard_threshold, + "correspondence_guard_mode": correspondence_guard_mode, + "correspondence_guard_threshold": correspondence_guard_threshold, + "correspondence_guard_reference_rate": correspondence_guard_reference_rate, }, ), ] @@ -909,6 +1148,34 @@ def default_experiments( "known_point_sigmas": known_point_sigmas, "geometry_guard_mode": geometry_guard_mode, "geometry_guard_threshold": geometry_guard_threshold, + "correspondence_guard_mode": correspondence_guard_mode, + "correspondence_guard_threshold": correspondence_guard_threshold, + "correspondence_guard_reference_rate": correspondence_guard_reference_rate, + }, + ), + ExperimentSpec( + name="guarded_stagewise_release_known_points", + description="Stagewise guarded BA with soft known-point geometry anchors", + mode="guarded", + ba_kwargs={ + "pose_orient_par": OrientPar(), + "intrinsic_orient_par": intrinsics, + "fixed_camera_indices": staged_fixed, + "pose_release_camera_order": staged_order, + "pose_stage_ray_slack": pose_stage_ray_slack, + "pose_prior_sigmas": pose_priors, + "pose_parameter_bounds": pose_bounds, + "pose_max_nfev": 8, + "intrinsic_prior_sigmas": tight_intrinsic_priors, + "intrinsic_parameter_bounds": tight_intrinsic_bounds, + "intrinsic_max_nfev": 4, + "known_points": known_points, + "known_point_sigmas": known_point_sigmas, + "geometry_guard_mode": geometry_guard_mode, + "geometry_guard_threshold": geometry_guard_threshold, + "correspondence_guard_mode": correspondence_guard_mode, + "correspondence_guard_threshold": correspondence_guard_threshold, + "correspondence_guard_reference_rate": correspondence_guard_reference_rate, }, ), ] @@ -942,7 +1209,9 @@ def run_experiment( start_cals: List[Calibration], reference_cals: List[Calibration], reference_geometry_points: np.ndarray | None, + tracking_data: ObservationTrackingData | None, geometry_export_threshold: float | None, + correspondence_export_threshold: float | None, source_case_dir: Path, output_dir: Path | None, ) -> ExperimentResult: @@ -1020,6 +1289,14 @@ def run_experiment( List[int] | None, spec.ba_kwargs.get("fixed_camera_indices"), ), + pose_release_camera_order=cast( + List[int] | None, + spec.ba_kwargs.get("pose_release_camera_order"), + ), + pose_stage_ray_slack=cast( + float, + spec.ba_kwargs.get("pose_stage_ray_slack", 0.0), + ), pose_prior_sigmas=cast( Dict[str, float] | None, spec.ba_kwargs.get("pose_prior_sigmas"), @@ -1094,6 +1371,27 @@ def run_experiment( float | None, spec.ba_kwargs.get("geometry_guard_threshold"), ), + correspondence_original_ids=( + None if tracking_data is None else tracking_data.original_target_ids + ), + correspondence_point_frame_indices=( + None if tracking_data is None else tracking_data.point_frame_indices + ), + correspondence_frame_target_pixels=( + None if tracking_data is None else tracking_data.frame_target_pixels + ), + correspondence_guard_mode=cast( + str, + spec.ba_kwargs.get("correspondence_guard_mode", "off"), + ), + correspondence_guard_threshold=cast( + float | None, + spec.ba_kwargs.get("correspondence_guard_threshold"), + ), + correspondence_guard_reference_rate=cast( + float | None, + spec.ba_kwargs.get("correspondence_guard_reference_rate"), + ), ) success = True final_rms = cast(float, summary["final_reprojection_rms"]) @@ -1113,17 +1411,29 @@ def run_experiment( control, reference_geometry_points, ) + correspondence_replacement = summarize_correspondence_replacements( + refined_points, + refined_cals, + control, + tracking_data, + ) export_blocked, export_note = should_block_export_on_geometry( geometry_projection_drift, geometry_export_threshold, ) + correspondence_blocked, correspondence_note = should_block_export_on_correspondence( + correspondence_replacement, + correspondence_export_threshold, + ) if export_note: notes = f"{notes}; {export_note}" if notes else export_note - if export_blocked: + if correspondence_note: + notes = f"{notes}; {correspondence_note}" if notes else correspondence_note + if export_blocked or correspondence_blocked: success = False cal_dir = None - if output_dir is not None and not export_blocked: + if output_dir is not None and not (export_blocked or correspondence_blocked): case_out_dir = output_dir / spec.name cal_dir = ensure_output_case_layout(source_case_dir, case_out_dir) write_calibration_folder(refined_cals, cal_dir) @@ -1141,6 +1451,10 @@ def run_experiment( format_projection_drift(geometry_projection_drift) + "\n", encoding="utf-8", ) + (case_out_dir / "correspondence_check.txt").write_text( + format_correspondence_replacement(correspondence_replacement) + "\n", + encoding="utf-8", + ) return ExperimentResult( name=spec.name, @@ -1157,6 +1471,7 @@ def run_experiment( camera_position_shifts=camera_position_shifts, camera_angle_shifts=camera_angle_shifts, geometry_projection_drift=geometry_projection_drift, + correspondence_replacement=correspondence_replacement, refined_cals=refined_cals, refined_points=refined_points, ) @@ -1312,7 +1627,9 @@ def run_fixed_camera_pair_diagnostics( start_cals: List[Calibration], reference_cals: List[Calibration], reference_geometry_points: np.ndarray | None, + tracking_data: ObservationTrackingData | None, geometry_export_threshold: float | None, + correspondence_export_threshold: float | None, source_case_dir: Path, ) -> List[FixedCameraDiagnostic]: """Run one experiment over every two-camera anchor pair and summarize results.""" @@ -1335,7 +1652,9 @@ def run_fixed_camera_pair_diagnostics( start_cals=start_cals, reference_cals=reference_cals, reference_geometry_points=reference_geometry_points, + tracking_data=tracking_data, geometry_export_threshold=geometry_export_threshold, + correspondence_export_threshold=correspondence_export_threshold, source_case_dir=source_case_dir, output_dir=None, ) @@ -1440,9 +1759,40 @@ def parse_args(argv: Iterable[str] | None = None) -> argparse.Namespace: parser.add_argument( "--geometry-export-threshold", type=float, - default=2.5, + default=None, help="Do not write output case folders whose final calibration-body drift exceeds this many pixels; 0 disables export blocking.", ) + parser.add_argument( + "--correspondence-guard-mode", + type=str, + choices=("auto", "off", "soft", "hard"), + default="auto", + help="Guarded-BA correspondence acceptance mode. 'auto' uses a hard replacement-rate guard when original quadruplet identities are available.", + ) + parser.add_argument( + "--correspondence-guard-threshold", + type=float, + default=None, + help="Maximum allowed quadruplet replacement rate for hard correspondence guards. If omitted, the demo derives a case-specific threshold from the trusted reference calibration.", + ) + parser.add_argument( + "--correspondence-export-threshold", + type=float, + default=None, + help="Do not write output case folders whose final quadruplet replacement rate exceeds this threshold; 0 disables correspondence-based export blocking.", + ) + parser.add_argument( + "--staged-release-order", + type=str, + default=None, + help="Comma-separated 1-based camera release order for staged guarded presets, for example '1,2,3,4'.", + ) + parser.add_argument( + "--staged-ray-slack", + type=float, + default=0.0, + help="Allow staged guarded pose-release steps to worsen mean ray convergence by up to this amount relative to the last accepted stage.", + ) return parser.parse_args(list(argv) if argv is not None else None) @@ -1461,12 +1811,68 @@ def main(argv: Iterable[str] | None = None) -> int: true_cals = load_calibrations(case_dir, num_cams) start_cals = perturb_calibrations(true_cals, args.perturbation_scale) reference_geometry_points = load_reference_geometry_points(case_dir, num_cams) + tracking_data = load_case_tracking_data( + case_dir, + num_cams, + max_frames=args.max_frames, + max_points_per_frame=args.max_points_per_frame, + control=control, + reference_cals=true_cals, + ) if args.geometry_guard_mode == "auto": geometry_guard_mode = "hard" if reference_geometry_points is not None else "off" else: geometry_guard_mode = args.geometry_guard_mode geometry_export_threshold = ( - args.geometry_export_threshold if reference_geometry_points is not None else None + ( + args.geometry_export_threshold + if args.geometry_export_threshold is not None + else args.geometry_guard_threshold + ) + if reference_geometry_points is not None + else None + ) + if args.correspondence_guard_mode == "auto": + correspondence_guard_mode = "hard" if tracking_data is not None else "off" + else: + correspondence_guard_mode = args.correspondence_guard_mode + if tracking_data is not None and tracking_data.reference_replacement_rate is not None: + auto_correspondence_threshold = min( + 0.25, + max(0.05, tracking_data.reference_replacement_rate + 0.02), + ) + else: + auto_correspondence_threshold = None + correspondence_guard_threshold = ( + args.correspondence_guard_threshold + if args.correspondence_guard_threshold is not None + else auto_correspondence_threshold + ) + correspondence_export_threshold = ( + ( + args.correspondence_export_threshold + if args.correspondence_export_threshold is not None + else correspondence_guard_threshold + ) + if tracking_data is not None + else None + ) + if args.staged_release_order is None: + staged_release_order = list(range(num_cams)) + else: + try: + staged_release_order = [ + int(token.strip()) - 1 + for token in args.staged_release_order.split(",") + if token.strip() + ] + except ValueError as exc: + raise ValueError( + "--staged-release-order must be a comma-separated list of integers" + ) from exc + staged_release_order = normalize_staged_release_order( + staged_release_order, + num_cams, ) known_points = build_known_point_constraints(point_init, args.known_points) known_point_sigmas = args.known_point_sigma if known_points else None @@ -1488,19 +1894,40 @@ def main(argv: Iterable[str] | None = None) -> int: print("Known-point anchors: disabled") if reference_geometry_points is not None: print( - f"Geometry guard: mode={geometry_guard_mode}, guard_threshold={args.geometry_guard_threshold:.3f}px, export_threshold={args.geometry_export_threshold:.3f}px" + f"Geometry guard: mode={geometry_guard_mode}, guard_threshold={args.geometry_guard_threshold:.3f}px, export_threshold={geometry_export_threshold:.3f}px" ) else: print("Geometry guard: unavailable for this case (no known 3D target file found)") + if tracking_data is not None and tracking_data.reference_replacement_rate is not None: + print( + "Correspondence guard: " + f"mode={correspondence_guard_mode}, reference_rate={tracking_data.reference_replacement_rate:.3f}, " + f"guard_threshold={correspondence_guard_threshold:.3f}, export_threshold={correspondence_export_threshold:.3f}" + ) + else: + print("Correspondence guard: unavailable for this case") + print( + "Staged guarded release: " + f"order={[camera_index + 1 for camera_index in staged_release_order]}, " + f"ray_slack={args.staged_ray_slack:.6f}" + ) print(f"Output folders: {output_dir if output_dir is not None else 'disabled'}") print() experiments = default_experiments( + num_cams=num_cams, known_points=known_points or None, known_point_sigmas=known_point_sigmas, perturbation_scale=args.perturbation_scale, + staged_release_order=staged_release_order, + pose_stage_ray_slack=args.staged_ray_slack, geometry_guard_mode=geometry_guard_mode, geometry_guard_threshold=args.geometry_guard_threshold, + correspondence_guard_mode=correspondence_guard_mode, + correspondence_guard_threshold=correspondence_guard_threshold, + correspondence_guard_reference_rate=( + None if tracking_data is None else tracking_data.reference_replacement_rate + ), ) if args.diagnose_fixed_pairs: @@ -1513,7 +1940,9 @@ def main(argv: Iterable[str] | None = None) -> int: start_cals=start_cals, reference_cals=true_cals, reference_geometry_points=reference_geometry_points, + tracking_data=tracking_data, geometry_export_threshold=geometry_export_threshold, + correspondence_export_threshold=correspondence_export_threshold, source_case_dir=case_dir, ) print( @@ -1540,7 +1969,9 @@ def main(argv: Iterable[str] | None = None) -> int: start_cals=start_cals, reference_cals=true_cals, reference_geometry_points=reference_geometry_points, + tracking_data=tracking_data, geometry_export_threshold=geometry_export_threshold, + correspondence_export_threshold=correspondence_export_threshold, source_case_dir=case_dir, output_dir=None, ) @@ -1606,7 +2037,9 @@ def main(argv: Iterable[str] | None = None) -> int: start_cals=start_cals, reference_cals=true_cals, reference_geometry_points=reference_geometry_points, + tracking_data=tracking_data, geometry_export_threshold=geometry_export_threshold, + correspondence_export_threshold=correspondence_export_threshold, source_case_dir=case_dir, output_dir=output_dir, ) diff --git a/openptv_python/orientation.py b/openptv_python/orientation.py index 0e22115..ada532e 100644 --- a/openptv_python/orientation.py +++ b/openptv_python/orientation.py @@ -1,6 +1,6 @@ """Functions for the orientation of the camera.""" -from typing import Dict, List, Optional, Tuple +from typing import Dict, List, Optional, Sequence, Tuple, cast import numpy as np import scipy @@ -1751,6 +1751,8 @@ def guarded_two_step_bundle_adjustment( *, point_init: Optional[np.ndarray] = None, fixed_camera_indices: Optional[List[int]] = None, + pose_release_camera_order: Optional[List[int]] = None, + pose_stage_ray_slack: float = 0.0, pose_prior_sigmas: Optional[Dict[str, float]] = None, pose_parameter_bounds: Optional[Dict[str, Tuple[float, float]]] = None, pose_loss: str = "linear", @@ -1772,11 +1774,20 @@ def guarded_two_step_bundle_adjustment( geometry_reference_cals: Optional[List[Calibration]] = None, geometry_guard_mode: str = "off", geometry_guard_threshold: Optional[float] = None, + correspondence_original_ids: Optional[np.ndarray] = None, + correspondence_point_frame_indices: Optional[np.ndarray] = None, + correspondence_frame_target_pixels: Optional[Sequence[Sequence[np.ndarray]]] = None, + correspondence_guard_mode: str = "off", + correspondence_guard_threshold: Optional[float] = None, + correspondence_guard_reference_rate: Optional[float] = None, reject_worse_solution: bool = True, reject_on_ray_convergence: bool = True, ) -> Tuple[List[Calibration], np.ndarray, Dict[str, object]]: """Run pose-only BA then tightly constrained intrinsics BA with acceptance checks.""" + if pose_stage_ray_slack < 0: + raise ValueError("pose_stage_ray_slack must be non-negative") + def projection_drift_summaries( reference_cals: List[Calibration], candidate_cals: List[Calibration], @@ -1840,6 +1851,88 @@ def geometry_stage_ok( "geometry_guard_mode must be one of 'off', 'soft', or 'hard'" ) + def correspondence_replacement_summary( + candidate_points: np.ndarray, + candidate_cals: List[Calibration], + ) -> Optional[Dict[str, object]]: + if ( + correspondence_original_ids is None + or correspondence_point_frame_indices is None + or correspondence_frame_target_pixels is None + ): + return None + + projected_pixels = np.empty( + (candidate_points.shape[0], len(candidate_cals), 2), + dtype=np.float64, + ) + for camera_index, cal in enumerate(candidate_cals): + projected_pixels[:, camera_index, :] = arr_metric_to_pixel( + image_coordinates(candidate_points, cal, cpar.mm), + cpar, + ) + + replacement_ids = np.empty_like(correspondence_original_ids) + nearest_distances = np.empty_like( + correspondence_original_ids, + dtype=np.float64, + ) + for point_index in range(candidate_points.shape[0]): + frame_targets = correspondence_frame_target_pixels[ + int(correspondence_point_frame_indices[point_index]) + ] + for camera_index in range(len(candidate_cals)): + deltas = frame_targets[camera_index] - projected_pixels[point_index, camera_index] + squared_distances = np.sum(deltas * deltas, axis=1) + nearest_index = int(np.argmin(squared_distances)) + replacement_ids[point_index, camera_index] = nearest_index + nearest_distances[point_index, camera_index] = float( + np.sqrt(squared_distances[nearest_index]) + ) + + changed_mask = np.any( + replacement_ids != correspondence_original_ids, + axis=1, + ) + camera_change_rates = [ + float( + np.mean( + replacement_ids[:, camera_index] + != correspondence_original_ids[:, camera_index] + ) + ) + for camera_index in range(len(candidate_cals)) + ] + return { + "replacement_rate": float(np.mean(changed_mask)), + "camera_change_rates": camera_change_rates, + "mean_nearest_distance": float(np.mean(nearest_distances)), + "p95_nearest_distance": float(np.percentile(nearest_distances, 95.0)), + "max_nearest_distance": float(np.max(nearest_distances)), + } + + def correspondence_stage_ok( + candidate_rate: Optional[float], + prior_rate: Optional[float], + ) -> bool: + if correspondence_guard_mode == "off" or candidate_rate is None: + return True + if correspondence_guard_mode == "soft": + if correspondence_guard_reference_rate is not None: + return candidate_rate <= correspondence_guard_reference_rate + 1e-12 + if prior_rate is None: + return True + return candidate_rate <= prior_rate + 1e-12 + if correspondence_guard_mode == "hard": + if correspondence_guard_threshold is None or correspondence_guard_threshold <= 0: + raise ValueError( + "correspondence_guard_threshold must be positive when correspondence_guard_mode='hard'" + ) + return candidate_rate <= correspondence_guard_threshold + raise ValueError( + "correspondence_guard_mode must be one of 'off', 'soft', or 'hard'" + ) + base_cals = [_clone_calibration(cal) for cal in cals] if point_init is None: base_points, _ = initialize_bundle_adjustment_points( @@ -1861,32 +1954,190 @@ def geometry_stage_ok( geometry_reference_points, ) baseline_geometry_max = max_projection_drift(baseline_geometry) - - pose_cals, pose_points, pose_result = multi_camera_bundle_adjustment( - observed_pixels, + baseline_correspondence = correspondence_replacement_summary( + base_points, base_cals, - cpar, - pose_orient_par, - point_init=base_points, - fixed_camera_indices=fixed_camera_indices, - loss=pose_loss, - method=pose_method, - prior_sigmas=pose_prior_sigmas, - parameter_bounds=pose_parameter_bounds, - max_nfev=pose_max_nfev, - optimize_extrinsics=True, - optimize_points=pose_optimize_points, - known_points=known_points, - known_point_sigmas=known_point_sigmas, ) - pose_rms = reprojection_rms(observed_pixels, pose_points, pose_cals, cpar) - pose_ray_convergence = mean_ray_convergence(observed_pixels, pose_cals, cpar) - pose_geometry = projection_drift_summaries( - geometry_reference_cals, - pose_cals, - geometry_reference_points, + baseline_correspondence_rate = ( + None + if baseline_correspondence is None + else cast(float, baseline_correspondence["replacement_rate"]) ) - pose_geometry_max = max_projection_drift(pose_geometry) + + pose_stage_summaries: List[Dict[str, object]] = [] + pose_result: object + pose_ok: bool + if pose_release_camera_order is None: + pose_cals, pose_points, pose_result = multi_camera_bundle_adjustment( + observed_pixels, + base_cals, + cpar, + pose_orient_par, + point_init=base_points, + fixed_camera_indices=fixed_camera_indices, + loss=pose_loss, + method=pose_method, + prior_sigmas=pose_prior_sigmas, + parameter_bounds=pose_parameter_bounds, + max_nfev=pose_max_nfev, + optimize_extrinsics=True, + optimize_points=pose_optimize_points, + known_points=known_points, + known_point_sigmas=known_point_sigmas, + ) + pose_rms = reprojection_rms(observed_pixels, pose_points, pose_cals, cpar) + pose_ray_convergence = mean_ray_convergence(observed_pixels, pose_cals, cpar) + pose_geometry = projection_drift_summaries( + geometry_reference_cals, + pose_cals, + geometry_reference_points, + ) + pose_geometry_max = max_projection_drift(pose_geometry) + pose_correspondence = correspondence_replacement_summary( + pose_points, + pose_cals, + ) + pose_correspondence_rate = ( + None + if pose_correspondence is None + else cast(float, pose_correspondence["replacement_rate"]) + ) + pose_ok = pose_rms <= baseline_rms and ( + not reject_on_ray_convergence + or pose_ray_convergence <= baseline_ray_convergence + ) + pose_geometry_ok = geometry_stage_ok(pose_geometry_max, baseline_geometry_max) + pose_ok = pose_ok and pose_geometry_ok + pose_correspondence_ok = correspondence_stage_ok( + pose_correspondence_rate, + baseline_correspondence_rate, + ) + pose_ok = pose_ok and pose_correspondence_ok + else: + release_order = [int(camera_index) for camera_index in pose_release_camera_order] + if not release_order: + raise ValueError("pose_release_camera_order must not be empty") + if len(set(release_order)) != len(release_order): + raise ValueError("pose_release_camera_order must not contain duplicates") + if any(camera_index < 0 or camera_index >= len(cals) for camera_index in release_order): + raise ValueError( + "pose_release_camera_order contains an out-of-range camera index" + ) + + current_cals = [_clone_calibration(cal) for cal in base_cals] + current_points = np.asarray(base_points, dtype=np.float64).copy() + current_rms = baseline_rms + current_ray_convergence = baseline_ray_convergence + current_geometry = baseline_geometry + current_geometry_max = baseline_geometry_max + current_correspondence = baseline_correspondence + current_correspondence_rate = baseline_correspondence_rate + pose_result = {"staged": True, "stages": []} + pose_ok = False + pose_geometry_ok = True + pose_correspondence_ok = True + released_cameras: List[int] = [] + + for stage_index, released_camera in enumerate(release_order, start=1): + released_cameras.append(released_camera) + stage_fixed = [ + camera_index + for camera_index in range(len(cals)) + if camera_index not in released_cameras + ] + stage_cals, stage_points, stage_result = multi_camera_bundle_adjustment( + observed_pixels, + current_cals, + cpar, + pose_orient_par, + point_init=current_points, + fixed_camera_indices=stage_fixed, + loss=pose_loss, + method=pose_method, + prior_sigmas=pose_prior_sigmas, + parameter_bounds=pose_parameter_bounds, + max_nfev=pose_max_nfev, + optimize_extrinsics=True, + optimize_points=pose_optimize_points, + known_points=known_points, + known_point_sigmas=known_point_sigmas, + ) + stage_rms = reprojection_rms(observed_pixels, stage_points, stage_cals, cpar) + stage_ray_convergence = mean_ray_convergence( + observed_pixels, stage_cals, cpar + ) + stage_geometry = projection_drift_summaries( + geometry_reference_cals, + stage_cals, + geometry_reference_points, + ) + stage_geometry_max = max_projection_drift(stage_geometry) + stage_correspondence = correspondence_replacement_summary( + stage_points, + stage_cals, + ) + stage_correspondence_rate = ( + None + if stage_correspondence is None + else cast(float, stage_correspondence["replacement_rate"]) + ) + stage_geometry_ok = geometry_stage_ok( + stage_geometry_max, + current_geometry_max, + ) + stage_correspondence_ok = correspondence_stage_ok( + stage_correspondence_rate, + current_correspondence_rate, + ) + stage_ok = stage_rms <= current_rms and ( + not reject_on_ray_convergence + or stage_ray_convergence + <= current_ray_convergence + pose_stage_ray_slack + ) + stage_ok = stage_ok and stage_geometry_ok and stage_correspondence_ok + pose_stage_summaries.append( + { + "stage_index": stage_index, + "released_camera_index": released_camera, + "free_camera_indices": released_cameras.copy(), + "fixed_camera_indices": stage_fixed, + "reprojection_rms": stage_rms, + "mean_ray_convergence": stage_ray_convergence, + "geometry": stage_geometry, + "geometry_max": stage_geometry_max, + "geometry_ok": stage_geometry_ok, + "correspondence": stage_correspondence, + "correspondence_rate": stage_correspondence_rate, + "correspondence_ok": stage_correspondence_ok, + "accepted": stage_ok or not reject_worse_solution, + "result": stage_result, + } + ) + cast(List[object], pose_result["stages"]).append(stage_result) + + if reject_worse_solution and not stage_ok: + break + + current_cals = stage_cals + current_points = stage_points + current_rms = stage_rms + current_ray_convergence = stage_ray_convergence + current_geometry = stage_geometry + current_geometry_max = stage_geometry_max + current_correspondence = stage_correspondence + current_correspondence_rate = stage_correspondence_rate + pose_ok = True + pose_geometry_ok = stage_geometry_ok + pose_correspondence_ok = stage_correspondence_ok + + pose_cals = current_cals + pose_points = current_points + pose_rms = current_rms + pose_ray_convergence = current_ray_convergence + pose_geometry = current_geometry + pose_geometry_max = current_geometry_max + pose_correspondence = current_correspondence + pose_correspondence_rate = current_correspondence_rate intrinsic_fixed = list(range(len(cals))) intrinsic_cals, intrinsic_points, intrinsic_result = multi_camera_bundle_adjustment( @@ -1921,19 +2172,21 @@ def geometry_stage_ok( geometry_reference_points, ) intrinsic_geometry_max = max_projection_drift(intrinsic_geometry) + intrinsic_correspondence = correspondence_replacement_summary( + intrinsic_points, + intrinsic_cals, + ) + intrinsic_correspondence_rate = ( + None + if intrinsic_correspondence is None + else cast(float, intrinsic_correspondence["replacement_rate"]) + ) accepted_stage = "intrinsics" final_cals = intrinsic_cals final_points = intrinsic_points final_rms = intrinsic_rms final_ray_convergence = intrinsic_ray_convergence - - pose_ok = pose_rms <= baseline_rms and ( - not reject_on_ray_convergence - or pose_ray_convergence <= baseline_ray_convergence - ) - pose_geometry_ok = geometry_stage_ok(pose_geometry_max, baseline_geometry_max) - pose_ok = pose_ok and pose_geometry_ok intrinsic_ok = intrinsic_rms <= pose_rms and ( not reject_on_ray_convergence or intrinsic_ray_convergence <= pose_ray_convergence @@ -1943,6 +2196,11 @@ def geometry_stage_ok( pose_geometry_max, ) intrinsic_ok = intrinsic_ok and intrinsic_geometry_ok + intrinsic_correspondence_ok = correspondence_stage_ok( + intrinsic_correspondence_rate, + pose_correspondence_rate, + ) + intrinsic_ok = intrinsic_ok and intrinsic_correspondence_ok if reject_worse_solution: if not pose_ok: @@ -1965,6 +2223,8 @@ def geometry_stage_ok( "baseline_points": base_points, "baseline_geometry": baseline_geometry, "baseline_geometry_max": baseline_geometry_max, + "baseline_correspondence": baseline_correspondence, + "baseline_correspondence_rate": baseline_correspondence_rate, "pose_reprojection_rms": pose_rms, "pose_mean_ray_convergence": pose_ray_convergence, "pose_cals": pose_cals, @@ -1972,6 +2232,15 @@ def geometry_stage_ok( "pose_geometry": pose_geometry, "pose_geometry_max": pose_geometry_max, "pose_geometry_ok": pose_geometry_ok, + "pose_release_camera_order": pose_release_camera_order, + "pose_stage_ray_slack": pose_stage_ray_slack, + "pose_stage_summaries": pose_stage_summaries, + "accepted_pose_stage_count": len( + [summary for summary in pose_stage_summaries if summary["accepted"]] + ), + "pose_correspondence": pose_correspondence, + "pose_correspondence_rate": pose_correspondence_rate, + "pose_correspondence_ok": pose_correspondence_ok, "intrinsic_reprojection_rms": intrinsic_rms, "intrinsic_mean_ray_convergence": intrinsic_ray_convergence, "intrinsic_cals": intrinsic_cals, @@ -1979,8 +2248,14 @@ def geometry_stage_ok( "intrinsic_geometry": intrinsic_geometry, "intrinsic_geometry_max": intrinsic_geometry_max, "intrinsic_geometry_ok": intrinsic_geometry_ok, + "intrinsic_correspondence": intrinsic_correspondence, + "intrinsic_correspondence_rate": intrinsic_correspondence_rate, + "intrinsic_correspondence_ok": intrinsic_correspondence_ok, "geometry_guard_mode": geometry_guard_mode, "geometry_guard_threshold": geometry_guard_threshold, + "correspondence_guard_mode": correspondence_guard_mode, + "correspondence_guard_threshold": correspondence_guard_threshold, + "correspondence_guard_reference_rate": correspondence_guard_reference_rate, "accepted_stage": accepted_stage, "final_reprojection_rms": final_rms, "final_mean_ray_convergence": final_ray_convergence, diff --git a/tests/test_bundle_adjustment.py b/tests/test_bundle_adjustment.py index fad2c68..a146b70 100644 --- a/tests/test_bundle_adjustment.py +++ b/tests/test_bundle_adjustment.py @@ -644,6 +644,162 @@ def test_guarded_two_step_bundle_adjustment_passes_known_point_constraints(self) self.assertEqual(call.kwargs["known_points"], known_points) self.assertEqual(call.kwargs["known_point_sigmas"], 1e-3) + def test_guarded_two_step_bundle_adjustment_supports_staged_camera_release(self): + points = np.array( + [ + [-10.0, -10.0, 0.0], + [10.0, -10.0, 1.0], + [-10.0, 10.0, -1.0], + [10.0, 10.0, 0.5], + ], + dtype=float, + ) + observed_pixels = np.empty((len(points), 4, 2), dtype=float) + for cam_num, cal in enumerate(self.true_cals): + observed_pixels[:, cam_num, :] = arr_metric_to_pixel( + image_coordinates(points, cal, self.control.mm), self.control + ) + + start_cals = self.lightly_perturb_calibrations(self.true_cals) + intrinsics = OrientPar() + intrinsics.k1flag = 1 + intrinsics.p1flag = 1 + intrinsics.p2flag = 1 + + staged_returns = [ + (self.true_cals, points.copy(), {"success": True, "stage": f"pose_{idx}"}) + for idx in range(4) + ] + [ + (self.true_cals, points.copy(), {"success": True, "stage": "intrinsics"}) + ] + + with patch( + "openptv_python.orientation.multi_camera_bundle_adjustment", + side_effect=staged_returns, + ) as mocked_adjustment, patch( + "openptv_python.orientation.reprojection_rms", + side_effect=[10.0, 9.0, 8.0, 7.0, 6.0, 5.0], + ), patch( + "openptv_python.orientation.mean_ray_convergence", + side_effect=[6.0, 5.0, 4.0, 3.0, 2.0, 1.0], + ): + _, final_points, summary = guarded_two_step_bundle_adjustment( + observed_pixels, + start_cals, + self.control, + OrientPar(), + intrinsics, + point_init=points, + pose_release_camera_order=[0, 1, 2, 3], + pose_prior_sigmas={ + "x0": 0.5, + "y0": 0.5, + "z0": 0.5, + "omega": 0.005, + "phi": 0.005, + "kappa": 0.005, + }, + pose_parameter_bounds={ + "x0": (-2.0, 2.0), + "y0": (-2.0, 2.0), + "z0": (-2.0, 2.0), + "omega": (-0.02, 0.02), + "phi": (-0.02, 0.02), + "kappa": (-0.02, 0.02), + }, + pose_max_nfev=20, + intrinsic_max_nfev=10, + ) + + self.assertEqual(mocked_adjustment.call_count, 5) + fixed_sequences = [ + call.kwargs.get("fixed_camera_indices") + for call in mocked_adjustment.call_args_list + ] + self.assertEqual( + fixed_sequences, + [[1, 2, 3], [2, 3], [3], [], [0, 1, 2, 3]], + ) + self.assertEqual(summary["pose_release_camera_order"], [0, 1, 2, 3]) + self.assertEqual(summary["accepted_pose_stage_count"], 4) + self.assertEqual(summary["accepted_stage"], "intrinsics") + self.assertEqual(len(summary["pose_stage_summaries"]), 4) + self.assertEqual( + [stage["released_camera_index"] for stage in summary["pose_stage_summaries"]], + [0, 1, 2, 3], + ) + self.assertEqual(final_points.shape, points.shape) + + def test_guarded_two_step_bundle_adjustment_stagewise_ray_slack_allows_near_miss(self): + points = np.array( + [ + [-10.0, -10.0, 0.0], + [10.0, -10.0, 1.0], + [-10.0, 10.0, -1.0], + [10.0, 10.0, 0.5], + ], + dtype=float, + ) + observed_pixels = np.empty((len(points), 4, 2), dtype=float) + for cam_num, cal in enumerate(self.true_cals): + observed_pixels[:, cam_num, :] = arr_metric_to_pixel( + image_coordinates(points, cal, self.control.mm), self.control + ) + + start_cals = self.lightly_perturb_calibrations(self.true_cals) + intrinsics = OrientPar() + intrinsics.k1flag = 1 + intrinsics.p1flag = 1 + intrinsics.p2flag = 1 + + staged_returns = [ + (self.true_cals, points.copy(), {"success": True, "stage": f"pose_{idx}"}) + for idx in range(4) + ] + [ + (self.true_cals, points.copy(), {"success": True, "stage": "intrinsics"}) + ] + + with patch( + "openptv_python.orientation.multi_camera_bundle_adjustment", + side_effect=staged_returns, + ), patch( + "openptv_python.orientation.reprojection_rms", + side_effect=[10.0, 9.0, 8.0, 7.0, 6.0, 5.5], + ), patch( + "openptv_python.orientation.mean_ray_convergence", + side_effect=[6.0, 5.0, 4.0, 4.0005, 3.5, 3.4], + ): + _, _, summary = guarded_two_step_bundle_adjustment( + observed_pixels, + start_cals, + self.control, + OrientPar(), + intrinsics, + point_init=points, + pose_release_camera_order=[0, 1, 2, 3], + pose_stage_ray_slack=1e-3, + pose_prior_sigmas={ + "x0": 0.5, + "y0": 0.5, + "z0": 0.5, + "omega": 0.005, + "phi": 0.005, + "kappa": 0.005, + }, + pose_parameter_bounds={ + "x0": (-2.0, 2.0), + "y0": (-2.0, 2.0), + "z0": (-2.0, 2.0), + "omega": (-0.02, 0.02), + "phi": (-0.02, 0.02), + "kappa": (-0.02, 0.02), + }, + ) + + self.assertEqual(summary["accepted_pose_stage_count"], 4) + self.assertEqual(summary["pose_stage_ray_slack"], 1e-3) + self.assertTrue(summary["pose_stage_summaries"][2]["accepted"]) + def test_guarded_two_step_bundle_adjustment_rejects_on_hard_geometry_guard(self): points = np.array( [ @@ -779,6 +935,152 @@ def test_guarded_two_step_bundle_adjustment_rejects_on_soft_geometry_guard(self) ) np.testing.assert_allclose(final_cals[3].get_pos(), pose_cals[3].get_pos()) + def test_guarded_two_step_bundle_adjustment_rejects_on_hard_correspondence_guard(self): + points = np.array( + [ + [0.0, 0.0, 0.0], + [20.0, 0.0, 0.0], + [40.0, 0.0, 0.0], + [60.0, 0.0, 0.0], + ], + dtype=float, + ) + observed_pixels = np.zeros((len(points), 4, 2), dtype=float) + pose_cals = [self.clone_calibration(cal) for cal in self.true_cals] + for cal in pose_cals: + cal.set_pos(np.zeros(3, dtype=float)) + cal.set_angles(np.zeros(3, dtype=float)) + start_cals = [self.clone_calibration(cal) for cal in pose_cals] + bad_intrinsic_cals = [self.clone_calibration(cal) for cal in self.true_cals] + for cal in bad_intrinsic_cals: + cal.set_pos(np.zeros(3, dtype=float)) + cal.set_angles(np.zeros(3, dtype=float)) + bad_intrinsic_cals[2].set_pos( + bad_intrinsic_cals[2].get_pos() + np.array([25.0, 0.0, 0.0]) + ) + + intrinsics = OrientPar() + intrinsics.k1flag = 1 + intrinsics.p1flag = 1 + intrinsics.p2flag = 1 + target_sets = [ + np.asarray([[point[0], point[1]] for point in points], dtype=float) + for _ in range(4) + ] + + with patch( + "openptv_python.orientation.multi_camera_bundle_adjustment", + side_effect=[ + (pose_cals, points.copy(), {"success": True}), + (bad_intrinsic_cals, points.copy(), {"success": True}), + ], + ), patch( + "openptv_python.orientation.reprojection_rms", + side_effect=[10.0, 5.0, 4.0], + ), patch( + "openptv_python.orientation.mean_ray_convergence", + side_effect=[3.0, 2.0, 1.0], + ), patch( + "openptv_python.orientation.image_coordinates", + side_effect=lambda pts, cal, _mm: pts[:, :2] + cal.get_pos()[:2], + ), patch( + "openptv_python.orientation.arr_metric_to_pixel", + side_effect=lambda coords, _cpar: coords, + ): + final_cals, _, summary = guarded_two_step_bundle_adjustment( + observed_pixels, + start_cals, + self.control, + OrientPar(), + intrinsics, + point_init=points, + fixed_camera_indices=[0, 1], + correspondence_original_ids=np.tile(np.arange(len(points))[:, None], (1, 4)), + correspondence_point_frame_indices=np.zeros(len(points), dtype=int), + correspondence_frame_target_pixels=[target_sets], + correspondence_guard_mode="hard", + correspondence_guard_threshold=0.2, + ) + + self.assertEqual(summary["accepted_stage"], "pose") + self.assertTrue(summary["pose_correspondence_ok"]) + self.assertFalse(summary["intrinsic_correspondence_ok"]) + self.assertGreater(summary["intrinsic_correspondence_rate"], 0.2) + np.testing.assert_allclose(final_cals[2].get_pos(), pose_cals[2].get_pos()) + + def test_guarded_two_step_bundle_adjustment_rejects_on_soft_correspondence_guard(self): + points = np.array( + [ + [0.0, 0.0, 0.0], + [20.0, 0.0, 0.0], + [40.0, 0.0, 0.0], + [60.0, 0.0, 0.0], + ], + dtype=float, + ) + observed_pixels = np.zeros((len(points), 4, 2), dtype=float) + pose_cals = [self.clone_calibration(cal) for cal in self.true_cals] + for cal in pose_cals: + cal.set_pos(np.zeros(3, dtype=float)) + cal.set_angles(np.zeros(3, dtype=float)) + start_cals = [self.clone_calibration(cal) for cal in pose_cals] + bad_intrinsic_cals = [self.clone_calibration(cal) for cal in self.true_cals] + for cal in bad_intrinsic_cals: + cal.set_pos(np.zeros(3, dtype=float)) + cal.set_angles(np.zeros(3, dtype=float)) + bad_intrinsic_cals[3].set_pos( + bad_intrinsic_cals[3].get_pos() + np.array([25.0, 0.0, 0.0]) + ) + + intrinsics = OrientPar() + intrinsics.k1flag = 1 + intrinsics.p1flag = 1 + intrinsics.p2flag = 1 + target_sets = [ + np.asarray([[point[0], point[1]] for point in points], dtype=float) + for _ in range(4) + ] + + with patch( + "openptv_python.orientation.multi_camera_bundle_adjustment", + side_effect=[ + (pose_cals, points.copy(), {"success": True}), + (bad_intrinsic_cals, points.copy(), {"success": True}), + ], + ), patch( + "openptv_python.orientation.reprojection_rms", + side_effect=[10.0, 5.0, 4.0], + ), patch( + "openptv_python.orientation.mean_ray_convergence", + side_effect=[3.0, 2.0, 1.0], + ), patch( + "openptv_python.orientation.image_coordinates", + side_effect=lambda pts, cal, _mm: pts[:, :2] + cal.get_pos()[:2], + ), patch( + "openptv_python.orientation.arr_metric_to_pixel", + side_effect=lambda coords, _cpar: coords, + ): + final_cals, _, summary = guarded_two_step_bundle_adjustment( + observed_pixels, + start_cals, + self.control, + OrientPar(), + intrinsics, + point_init=points, + fixed_camera_indices=[0, 1], + correspondence_original_ids=np.tile(np.arange(len(points))[:, None], (1, 4)), + correspondence_point_frame_indices=np.zeros(len(points), dtype=int), + correspondence_frame_target_pixels=[target_sets], + correspondence_guard_mode="soft", + correspondence_guard_reference_rate=0.0, + ) + + self.assertEqual(summary["accepted_stage"], "pose") + self.assertTrue(summary["pose_correspondence_ok"]) + self.assertFalse(summary["intrinsic_correspondence_ok"]) + self.assertGreater(summary["intrinsic_correspondence_rate"], 0.0) + np.testing.assert_allclose(final_cals[3].get_pos(), pose_cals[3].get_pos()) + def test_cavity_intrinsics_only_improves_from_intrinsic_perturbation(self): cavity_dir = Path("tests/testing_fodder/test_cavity") control = ControlPar(4).from_file(cavity_dir / "parameters/ptv.par") diff --git a/tests/test_demo_bundle_adjustment.py b/tests/test_demo_bundle_adjustment.py index dcdb78e..cd21f0e 100644 --- a/tests/test_demo_bundle_adjustment.py +++ b/tests/test_demo_bundle_adjustment.py @@ -13,6 +13,7 @@ format_quadruplet_sensitivity, load_calibrations, load_reference_geometry_points, + normalize_staged_release_order, perturb_calibrations, summarize_epipolar_consistency, summarize_fixed_camera_diagnostics, @@ -39,6 +40,7 @@ def test_default_experiments_adds_known_point_presets(self): known_points = {0: np.array([0.0, 0.0, 0.0]), 5: np.array([1.0, 2.0, 3.0])} experiments = default_experiments( + num_cams=4, known_points=known_points, known_point_sigmas=0.25, ) @@ -47,6 +49,8 @@ def test_default_experiments_adds_known_point_presets(self): self.assertIn("intrinsics_only", names) self.assertIn("pose_trf_known_points", names) self.assertIn("guarded_two_step_known_points", names) + self.assertIn("guarded_stagewise_release", names) + self.assertIn("guarded_stagewise_release_known_points", names) intrinsics_only = next( spec for spec in experiments if spec.name == "intrinsics_only" @@ -69,26 +73,72 @@ def test_default_experiments_adds_known_point_presets(self): for spec in experiments if spec.name == "guarded_two_step_known_points" ) + staged_spec = next( + spec + for spec in experiments + if spec.name == "guarded_stagewise_release" + ) + staged_known_spec = next( + spec + for spec in experiments + if spec.name == "guarded_stagewise_release_known_points" + ) self.assertIs(pose_spec.ba_kwargs["known_points"], known_points) self.assertEqual(pose_spec.ba_kwargs["known_point_sigmas"], 0.25) self.assertIs(guarded_spec.ba_kwargs["known_points"], known_points) self.assertEqual(guarded_spec.ba_kwargs["known_point_sigmas"], 0.25) + self.assertEqual(staged_spec.ba_kwargs["fixed_camera_indices"], [1, 2, 3]) + self.assertEqual(staged_spec.ba_kwargs["pose_release_camera_order"], [0, 1, 2, 3]) + self.assertEqual(staged_spec.ba_kwargs["pose_stage_ray_slack"], 0.0) + self.assertIs(staged_known_spec.ba_kwargs["known_points"], known_points) + self.assertEqual(staged_known_spec.ba_kwargs["known_point_sigmas"], 0.25) self.assertEqual(guarded_spec.ba_kwargs["geometry_guard_mode"], "off") self.assertIsNone(guarded_spec.ba_kwargs["geometry_guard_threshold"]) + self.assertEqual(guarded_spec.ba_kwargs["correspondence_guard_mode"], "off") + self.assertIsNone(guarded_spec.ba_kwargs["correspondence_guard_threshold"]) + self.assertIsNone(guarded_spec.ba_kwargs["correspondence_guard_reference_rate"]) def test_default_experiments_accepts_geometry_guard_configuration(self): experiments = default_experiments( + num_cams=4, perturbation_scale=1.0, + staged_release_order=[2, 0, 1, 3], + pose_stage_ray_slack=0.002, geometry_guard_mode="hard", geometry_guard_threshold=2.5, + correspondence_guard_mode="hard", + correspondence_guard_threshold=0.18, + correspondence_guard_reference_rate=0.15625, ) guarded_spec = next( spec for spec in experiments if spec.name == "guarded_two_step" ) + staged_spec = next( + spec for spec in experiments if spec.name == "guarded_stagewise_release" + ) self.assertEqual(guarded_spec.ba_kwargs["geometry_guard_mode"], "hard") self.assertEqual(guarded_spec.ba_kwargs["geometry_guard_threshold"], 2.5) + self.assertEqual(guarded_spec.ba_kwargs["correspondence_guard_mode"], "hard") + self.assertEqual(guarded_spec.ba_kwargs["correspondence_guard_threshold"], 0.18) + self.assertEqual( + guarded_spec.ba_kwargs["correspondence_guard_reference_rate"], + 0.15625, + ) + self.assertEqual(staged_spec.ba_kwargs["pose_release_camera_order"], [2, 0, 1, 3]) + self.assertEqual(staged_spec.ba_kwargs["fixed_camera_indices"], [0, 1, 3]) + self.assertEqual(staged_spec.ba_kwargs["pose_stage_ray_slack"], 0.002) + + def test_normalize_staged_release_order_validates_camera_permutation(self): + self.assertEqual(normalize_staged_release_order(None, 4), [0, 1, 2, 3]) + self.assertEqual(normalize_staged_release_order([2, 0, 1, 3], 4), [2, 0, 1, 3]) + + with self.assertRaises(ValueError): + normalize_staged_release_order([0, 1, 1, 3], 4) + + with self.assertRaises(ValueError): + normalize_staged_release_order([0, 1, 2], 4) def test_pose_demo_keeps_fixed_cameras_on_reference_geometry(self): cavity_dir = Path("tests/testing_fodder/test_cavity") From 9a34c9429a9d50a53c675afb5d415794d88ea0f5 Mon Sep 17 00:00:00 2001 From: Alex Liberzon Date: Sat, 7 Mar 2026 01:52:33 +0200 Subject: [PATCH 10/27] updated bundle adjustment and docs /memories/repo/openptv-python-2026-03-07-handoff.md. --- Makefile | 7 +- docs/bundle_adjustment.md | 11 +- openptv_python/demo_bundle_adjustment.py | 171 +++++++++--- .../generate_synthetic_cavity_case.py | 85 ++++-- openptv_python/orientation.py | 28 +- tests/test_bundle_adjustment.py | 259 ++++++++++-------- tests/test_demo_bundle_adjustment.py | 28 +- tests/test_synthetic_cavity_case.py | 20 +- 8 files changed, 405 insertions(+), 204 deletions(-) diff --git a/Makefile b/Makefile index 895ed00..7cee62b 100644 --- a/Makefile +++ b/Makefile @@ -2,17 +2,18 @@ PROJECT := openptv_python CONDA := conda CONDAFLAGS := COV_REPORT := html +PYTHON ?= .venv/bin/python default: qa unit-tests type-check qa: - pre-commit run --all-files + $(PYTHON) -m pre_commit run --all-files unit-tests: - python -m pytest -vv --cov=. --cov-report=$(COV_REPORT) --doctest-glob="*.md" --doctest-glob="*.rst" + $(PYTHON) -m pytest -vv --cov=. --cov-report=$(COV_REPORT) --doctest-glob="*.md" --doctest-glob="*.rst" type-check: - python -m mypy . + $(PYTHON) -m mypy . conda-env-update: $(CONDA) install -y -c conda-forge conda-merge diff --git a/docs/bundle_adjustment.md b/docs/bundle_adjustment.md index 59919d5..779f30e 100644 --- a/docs/bundle_adjustment.md +++ b/docs/bundle_adjustment.md @@ -50,11 +50,12 @@ where $\\theta_j$ denotes the active calibration parameters for camera $j$. The solver minimizes a robust or linear least-squares objective $$ -\min_{\Theta, X} -\sum_{i,j} \rho\left(\left\|W r_{ij}\right\|^2\right) -+ \sum_k \left(\frac{p_k - p_{k,0}}{\sigma_k}\right)^2 -+ \sum_{m \in \mathcal{M}} \left\|\frac{X_m - X_m^*}{\sigma^{(X)}_m}\right\|^2. -$$ +\\min\_{\\Theta, X} +\\sum\_{i,j} \\rho\\left(\\left|W r\_{ij}\\right|^2\\right) + +- \\sum_k \\left(\\frac{p_k - p\_{k,0}}{\\sigma_k}\\right)^2 +- \\sum\_{m \\in \\mathcal{M}} \\left|\\frac{X_m - X_m^\*}{\\sigma^{(X)}\_m}\\right|^2. + $$ with: diff --git a/openptv_python/demo_bundle_adjustment.py b/openptv_python/demo_bundle_adjustment.py index 6aca494..4b44b0a 100644 --- a/openptv_python/demo_bundle_adjustment.py +++ b/openptv_python/demo_bundle_adjustment.py @@ -22,7 +22,6 @@ guarded_two_step_bundle_adjustment, initialize_bundle_adjustment_points, mean_ray_convergence, - metric_observations_from_pixels, multi_camera_bundle_adjustment, reprojection_rms, ) @@ -171,7 +170,9 @@ def perturb_calibrations(cals: List[Calibration], scale: float) -> List[Calibrat return perturbed -def perturb_intrinsic_parameters(cals: List[Calibration], scale: float) -> List[Calibration]: +def perturb_intrinsic_parameters( + cals: List[Calibration], scale: float +) -> List[Calibration]: """Apply deterministic distortion perturbations while keeping poses fixed.""" perturbed = [clone_calibration(cal) for cal in cals] deltas = [ @@ -216,7 +217,9 @@ def build_experiment_start_calibrations( for camera_index in fixed_camera_indices: if camera_index < 0 or camera_index >= len(working_cals): continue - working_cals[camera_index].set_pos(reference_cals[camera_index].get_pos().copy()) + working_cals[camera_index].set_pos( + reference_cals[camera_index].get_pos().copy() + ) working_cals[camera_index].set_angles( reference_cals[camera_index].get_angles().copy() ) @@ -295,7 +298,10 @@ def format_projection_drift(summaries: Sequence[ProjectionDriftSummary] | None) lines = ["camera mean_px p95_px max_px", "------ ------- ------ ------"] for item in summaries: lines.append( - f"{item.camera_index:>6} {item.mean_distance:>7.3f} {item.p95_distance:>6.3f} {item.max_distance:>6.3f}" + ( + f"{item.camera_index:>6} {item.mean_distance:>7.3f} " + f"{item.p95_distance:>6.3f} {item.max_distance:>6.3f}" + ) ) return "\n".join(lines) @@ -321,7 +327,10 @@ def should_block_export_on_geometry( if drift_max is None or drift_max <= threshold_px: return False, "" - return True, f"geometry_export_blocked=max_drift={drift_max:.3f}px>{threshold_px:.3f}px" + return ( + True, + f"geometry_export_blocked=max_drift={drift_max:.3f}px>{threshold_px:.3f}px", + ) def discover_num_cams(cal_dir: Path) -> int: @@ -423,7 +432,10 @@ def summarize_correspondence_replacements( int(tracking_data.point_frame_indices[point_index]) ] for camera_index in range(len(cals)): - deltas = frame_targets[camera_index] - projected_pixels[point_index, camera_index] + deltas = ( + frame_targets[camera_index] + - projected_pixels[point_index, camera_index] + ) squared_distances = np.sum(deltas * deltas, axis=1) nearest_index = int(np.argmin(squared_distances)) replacement_ids[point_index, camera_index] = nearest_index @@ -497,7 +509,12 @@ def load_case_tracking_data( reference_points.append(path_buf[point_num].x.copy()) frame_target_pixels.append( - [np.asarray([[target.x, target.y] for target in cam_targets], dtype=float) for cam_targets in targets] + [ + np.asarray( + [[target.x, target.y] for target in cam_targets], dtype=float + ) + for cam_targets in targets + ] ) if not original_target_ids: @@ -731,7 +748,12 @@ def format_epipolar_diagnostics( f"{item.p95_distance:.6f}", f"{item.max_distance:.6f}", ] - row.extend([f"{100.0 * item.acceptance_rates[threshold]:.1f}%" for threshold in thresholds]) + row.extend( + [ + f"{100.0 * item.acceptance_rates[threshold]:.1f}%" + for threshold in thresholds + ] + ) data.append(row) widths = [len(header) for header in headers] @@ -740,7 +762,9 @@ def format_epipolar_diagnostics( widths[index] = max(widths[index], len(value)) def render_row(values: List[str]) -> str: - return " ".join(value.ljust(widths[index]) for index, value in enumerate(values)) + return " ".join( + value.ljust(widths[index]) for index, value in enumerate(values) + ) separator = " ".join("-" * width for width in widths) lines = [render_row(headers), separator] @@ -760,7 +784,9 @@ def summarize_quadruplet_sensitivity( full_mask = np.all(np.isfinite(observed_pixels), axis=(1, 2)) full_indices = np.flatnonzero(full_mask) if full_indices.size == 0: - raise ValueError("No fully observed quadruplets available for sensitivity analysis") + raise ValueError( + "No fully observed quadruplets available for sensitivity analysis" + ) full_points, full_ray_convergence = initialize_bundle_adjustment_points( observed_pixels[full_indices], @@ -774,7 +800,9 @@ def summarize_quadruplet_sensitivity( subset_points = [] subset_rays = [] for omitted_camera in range(observed_pixels.shape[1]): - keep = [cam for cam in range(observed_pixels.shape[1]) if cam != omitted_camera] + keep = [ + cam for cam in range(observed_pixels.shape[1]) if cam != omitted_camera + ] subset_observed = observed_pixels[point_index : point_index + 1, keep, :] subset_cals = [cals[cam] for cam in keep] subset_point, subset_ray = initialize_bundle_adjustment_points( @@ -800,7 +828,9 @@ def summarize_quadruplet_sensitivity( max_spread=float(np.max(spreads)), mean_full_ray_convergence=float(np.mean(full_ray_convergence)), mean_leave_one_out_ray_convergence=float(np.mean(leave_one_out_rays)), - worst_point_indices=[int(full_indices[index]) for index in worst_order.tolist()], + worst_point_indices=[ + int(full_indices[index]) for index in worst_order.tolist() + ], ) @@ -842,7 +872,9 @@ def format_quadruplet_sensitivity( widths[index] = max(widths[index], len(value)) def render_row(values: List[str]) -> str: - return " ".join(value.ljust(widths[index]) for index, value in enumerate(values)) + return " ".join( + value.ljust(widths[index]) for index, value in enumerate(values) + ) separator = " ".join("-" * width for width in widths) lines = [render_row(list(headers)), separator] @@ -857,11 +889,14 @@ def summarize_fixed_camera_diagnostics( diagnostics = [] for result in results: if len(result.fixed_camera_indices) != 2: - raise ValueError("Fixed-camera diagnostics require exactly two fixed cameras") + raise ValueError( + "Fixed-camera diagnostics require exactly two fixed cameras" + ) fixed_indices = set(result.fixed_camera_indices) fixed_position_shifts = [ - result.camera_position_shifts[index] for index in result.fixed_camera_indices + result.camera_position_shifts[index] + for index in result.fixed_camera_indices ] fixed_angle_shifts = [ result.camera_angle_shifts[index] for index in result.fixed_camera_indices @@ -940,7 +975,9 @@ def default_experiments( """Return a set of representative BA configurations.""" staged_order = normalize_staged_release_order(staged_release_order, num_cams) staged_fixed = [ - camera_index for camera_index in range(num_cams) if camera_index != staged_order[0] + camera_index + for camera_index in range(num_cams) + if camera_index != staged_order[0] ] pose_priors = { "x0": 0.5, @@ -1040,7 +1077,10 @@ def default_experiments( ), ExperimentSpec( name="intrinsics_only", - description="Intrinsics-only BA from fixed reference poses with tightly bounded distortion updates", + description=( + "Intrinsics-only BA from fixed reference poses with tightly " + "bounded distortion updates" + ), mode="multi", ba_kwargs={ "orient_par": intrinsics_only, @@ -1554,7 +1594,9 @@ def format_fixed_camera_diagnostics( widths[index] = max(widths[index], len(value)) def render_row(values: List[str]) -> str: - return " ".join(value.ljust(widths[index]) for index, value in enumerate(values)) + return " ".join( + value.ljust(widths[index]) for index, value in enumerate(values) + ) separator = " ".join("-" * width for width in widths) lines = [render_row(list(headers)), separator] @@ -1599,7 +1641,9 @@ def summarize_anchor_participation( widths[index] = max(widths[index], len(value)) def render_row(values: List[str]) -> str: - return " ".join(value.ljust(widths[index]) for index, value in enumerate(values)) + return " ".join( + value.ljust(widths[index]) for index, value in enumerate(values) + ) separator = " ".join("-" * width for width in widths) lines = [render_row(list(headers)), separator] @@ -1735,7 +1779,10 @@ def parse_args(argv: Iterable[str] | None = None) -> argparse.Namespace: parser.add_argument( "--diagnose-quadruplets", action="store_true", - help="Compare leave-one-camera-out quadruplet stability before and after the selected diagnostic experiment.", + help=( + "Compare leave-one-camera-out quadruplet stability before and after " + "the selected diagnostic experiment." + ), ) parser.add_argument( "--epipolar-curve-points", @@ -1748,7 +1795,10 @@ def parse_args(argv: Iterable[str] | None = None) -> argparse.Namespace: type=str, choices=("auto", "off", "soft", "hard"), default="auto", - help="Guarded-BA geometry acceptance mode. 'auto' uses 'hard' when cal_ori.par exposes known 3D target points.", + help=( + "Guarded-BA geometry acceptance mode. 'auto' uses 'hard' when " + "cal_ori.par exposes known 3D target points." + ), ) parser.add_argument( "--geometry-guard-threshold", @@ -1760,38 +1810,60 @@ def parse_args(argv: Iterable[str] | None = None) -> argparse.Namespace: "--geometry-export-threshold", type=float, default=None, - help="Do not write output case folders whose final calibration-body drift exceeds this many pixels; 0 disables export blocking.", + help=( + "Do not write output case folders whose final calibration-body " + "drift exceeds this many pixels; 0 disables export blocking." + ), ) parser.add_argument( "--correspondence-guard-mode", type=str, choices=("auto", "off", "soft", "hard"), default="auto", - help="Guarded-BA correspondence acceptance mode. 'auto' uses a hard replacement-rate guard when original quadruplet identities are available.", + help=( + "Guarded-BA correspondence acceptance mode. 'auto' uses a hard " + "replacement-rate guard when original quadruplet identities are " + "available." + ), ) parser.add_argument( "--correspondence-guard-threshold", type=float, default=None, - help="Maximum allowed quadruplet replacement rate for hard correspondence guards. If omitted, the demo derives a case-specific threshold from the trusted reference calibration.", + help=( + "Maximum allowed quadruplet replacement rate for hard " + "correspondence guards. If omitted, the demo derives a case-" + "specific threshold from the trusted reference calibration." + ), ) parser.add_argument( "--correspondence-export-threshold", type=float, default=None, - help="Do not write output case folders whose final quadruplet replacement rate exceeds this threshold; 0 disables correspondence-based export blocking.", + help=( + "Do not write output case folders whose final quadruplet " + "replacement rate exceeds this threshold; 0 disables " + "correspondence-based export blocking." + ), ) parser.add_argument( "--staged-release-order", type=str, default=None, - help="Comma-separated 1-based camera release order for staged guarded presets, for example '1,2,3,4'.", + help=( + "Comma-separated 1-based camera release order for staged guarded " + "presets, for example '1,2,3,4'." + ), ) parser.add_argument( "--staged-ray-slack", type=float, default=0.0, - help="Allow staged guarded pose-release steps to worsen mean ray convergence by up to this amount relative to the last accepted stage.", + help=( + "Allow staged guarded pose-release steps to worsen mean ray " + "convergence by up to this amount relative to the last accepted " + "stage." + ), ) return parser.parse_args(list(argv) if argv is not None else None) @@ -1836,7 +1908,10 @@ def main(argv: Iterable[str] | None = None) -> int: correspondence_guard_mode = "hard" if tracking_data is not None else "off" else: correspondence_guard_mode = args.correspondence_guard_mode - if tracking_data is not None and tracking_data.reference_replacement_rate is not None: + if ( + tracking_data is not None + and tracking_data.reference_replacement_rate is not None + ): auto_correspondence_threshold = min( 0.25, max(0.05, tracking_data.reference_replacement_rate + 0.02), @@ -1894,15 +1969,25 @@ def main(argv: Iterable[str] | None = None) -> int: print("Known-point anchors: disabled") if reference_geometry_points is not None: print( - f"Geometry guard: mode={geometry_guard_mode}, guard_threshold={args.geometry_guard_threshold:.3f}px, export_threshold={geometry_export_threshold:.3f}px" + "Geometry guard: " + f"mode={geometry_guard_mode}, " + f"guard_threshold={args.geometry_guard_threshold:.3f}px, " + f"export_threshold={geometry_export_threshold:.3f}px" ) else: - print("Geometry guard: unavailable for this case (no known 3D target file found)") - if tracking_data is not None and tracking_data.reference_replacement_rate is not None: + print( + "Geometry guard: unavailable for this case (no known 3D target file found)" + ) + if ( + tracking_data is not None + and tracking_data.reference_replacement_rate is not None + ): print( "Correspondence guard: " - f"mode={correspondence_guard_mode}, reference_rate={tracking_data.reference_replacement_rate:.3f}, " - f"guard_threshold={correspondence_guard_threshold:.3f}, export_threshold={correspondence_export_threshold:.3f}" + f"mode={correspondence_guard_mode}, " + f"reference_rate={tracking_data.reference_replacement_rate:.3f}, " + f"guard_threshold={correspondence_guard_threshold:.3f}, " + f"export_threshold={correspondence_export_threshold:.3f}" ) else: print("Correspondence guard: unavailable for this case") @@ -1945,9 +2030,7 @@ def main(argv: Iterable[str] | None = None) -> int: correspondence_export_threshold=correspondence_export_threshold, source_case_dir=case_dir, ) - print( - f"Fixed-pair diagnostics for {spec.name}: {spec.description}" - ) + print(f"Fixed-pair diagnostics for {spec.name}: {spec.description}") print(format_fixed_camera_diagnostics(diagnostics)) print() print("Anchor participation summary") @@ -1976,13 +2059,15 @@ def main(argv: Iterable[str] | None = None) -> int: output_dir=None, ) if diagnostic_result.refined_cals is None: - raise ValueError("Diagnostic experiment did not return refined calibrations") + raise ValueError( + "Diagnostic experiment did not return refined calibrations" + ) + print(f"Diagnostics for {spec.name}: {spec.description}") print( - f"Diagnostics for {spec.name}: {spec.description}" - ) - print( - f"Result RMS: {diagnostic_result.final_rms:.6f} px, ray: {diagnostic_result.final_ray_convergence:.6f}, notes: {diagnostic_result.notes}" + f"Result RMS: {diagnostic_result.final_rms:.6f} px, " + f"ray: {diagnostic_result.final_ray_convergence:.6f}, " + f"notes: {diagnostic_result.notes}" ) print() @@ -2022,7 +2107,9 @@ def main(argv: Iterable[str] | None = None) -> int: control, ) print("Quadruplet leave-one-camera-out sensitivity") - print(format_quadruplet_sensitivity(baseline_quadruplets, final_quadruplets)) + print( + format_quadruplet_sensitivity(baseline_quadruplets, final_quadruplets) + ) return 0 diff --git a/openptv_python/generate_synthetic_cavity_case.py b/openptv_python/generate_synthetic_cavity_case.py index 585d520..f7cab0a 100644 --- a/openptv_python/generate_synthetic_cavity_case.py +++ b/openptv_python/generate_synthetic_cavity_case.py @@ -21,7 +21,13 @@ ) from .parameters import ControlPar, OrientPar, VolumePar, read_volume_par from .sortgrid import read_sortgrid_par -from .tracking_frame_buf import Pathinfo, Target, n_tupel_dtype, write_path_frame, write_targets +from .tracking_frame_buf import ( + Pathinfo, + Target, + n_tupel_dtype, + write_path_frame, + write_targets, +) from .trafo import arr_metric_to_pixel DEFAULT_SOURCE_CASE = Path("tests/testing_fodder/test_cavity") @@ -68,7 +74,9 @@ def make_target(x: float, y: float, pnr: int) -> Target: ) -def camera_points_in_bounds(pixel_points: np.ndarray, cpar: ControlPar, margin: float = 8.0) -> np.ndarray: +def camera_points_in_bounds( + pixel_points: np.ndarray, cpar: ControlPar, margin: float = 8.0 +) -> np.ndarray: """Return a mask of points lying safely inside the sensor bounds.""" return ( (pixel_points[:, 0] >= margin) @@ -91,7 +99,9 @@ def z_bounds_at_x(x_coord: float, vpar: VolumePar) -> tuple[float, float]: return float(z_min), float(z_max) -def project_pixels(points_3d: np.ndarray, cals: Sequence[Calibration], cpar: ControlPar) -> np.ndarray: +def project_pixels( + points_3d: np.ndarray, cals: Sequence[Calibration], cpar: ControlPar +) -> np.ndarray: """Project 3D points into all cameras in pixel coordinates.""" observed = np.empty((points_3d.shape[0], len(cals), 2), dtype=np.float64) for cam_index, cal in enumerate(cals): @@ -119,7 +129,9 @@ def select_visible_points( return visible_points[:count] -def build_calibration_body(vpar: VolumePar, cals: Sequence[Calibration], cpar: ControlPar) -> np.ndarray: +def build_calibration_body( + vpar: VolumePar, cals: Sequence[Calibration], cpar: ControlPar +) -> np.ndarray: """Build a structured 3D calibration body visible in all cameras.""" xs = np.linspace(vpar.x_lay[0] + 4.0, vpar.x_lay[1] - 4.0, 8) ys = np.linspace(-16.0, 16.0, 6) @@ -152,7 +164,10 @@ def generate_particle_cloud( z_coord = float(rng.uniform(z_min + 2.0, z_max - 2.0)) point = np.asarray([[x_coord, y_coord, z_coord]], dtype=np.float64) projected = project_pixels(point, cals, cpar)[0] - if all(camera_points_in_bounds(projected[None, cam_index, :], cpar)[0] for cam_index in range(len(cals))): + if all( + camera_points_in_bounds(projected[None, cam_index, :], cpar)[0] + for cam_index in range(len(cals)) + ): accepted.append(point[0]) return np.asarray(accepted, dtype=np.float64) @@ -165,7 +180,12 @@ def shuffled_targets_from_pixels( """Shuffle projected pixels into a target list and return point-to-target indices.""" noisy_pixels = pixel_points + rng.normal(0.0, noise_sigma, size=pixel_points.shape) order = rng.permutation(pixel_points.shape[0]) - targets = [make_target(noisy_pixels[target_index, 0], noisy_pixels[target_index, 1], list_index) for list_index, target_index in enumerate(order)] + targets = [ + make_target( + noisy_pixels[target_index, 0], noisy_pixels[target_index, 1], list_index + ) + for list_index, target_index in enumerate(order) + ] point_to_target = np.empty(pixel_points.shape[0], dtype=np.int32) for list_index, target_index in enumerate(order): point_to_target[target_index] = list_index @@ -174,11 +194,16 @@ def shuffled_targets_from_pixels( def write_calibration_body_points(points: np.ndarray, output_file: Path) -> None: """Write a calblock-compatible calibration body file.""" - lines = [f"{index + 1:11d}{point[0]:11.3f}{point[1]:11.3f}{point[2]:11.3f}" for index, point in enumerate(points)] + lines = [ + f"{index + 1:11d}{point[0]:11.3f}{point[1]:11.3f}{point[2]:11.3f}" + for index, point in enumerate(points) + ] output_file.write_text("\n".join(lines) + "\n", encoding="utf-8") -def perturb_calibration_for_recovery(cal: Calibration, camera_index: int) -> Calibration: +def perturb_calibration_for_recovery( + cal: Calibration, camera_index: int +) -> Calibration: """Create a deterministic seed calibration for full_calibration recovery.""" trial = clone_calibration(cal) position_deltas = [ @@ -229,7 +254,9 @@ def recover_calibrations_from_body( seed_indices = select_external_seed_subset(ref_points) for camera_index, truth_cal in enumerate(truth_cals, start=1): - projected = arr_metric_to_pixel(image_coordinates(ref_points, truth_cal, cpar.mm), cpar) + projected = arr_metric_to_pixel( + image_coordinates(ref_points, truth_cal, cpar.mm), cpar + ) targets, _ = shuffled_targets_from_pixels(projected, rng, noise_sigma=0.0) write_targets( targets, @@ -245,7 +272,9 @@ def recover_calibrations_from_body( projected[seed_indices], cpar, ) - sorted_targets = match_detection_to_ref(seed, ref_points, targets, cpar, sortgrid_eps) + sorted_targets = match_detection_to_ref( + seed, ref_points, targets, cpar, sortgrid_eps + ) full_calibration( seed, ref_points, @@ -266,8 +295,12 @@ def recover_calibrations_from_body( truth_cal_dir / f"cam{camera_index}.tif.addpar", ) - position_errors.append(float(np.linalg.norm(seed.get_pos() - truth_cal.get_pos()))) - angle_errors.append(float(np.linalg.norm(seed.get_angles() - truth_cal.get_angles()))) + position_errors.append( + float(np.linalg.norm(seed.get_pos() - truth_cal.get_pos())) + ) + angle_errors.append( + float(np.linalg.norm(seed.get_angles() - truth_cal.get_angles())) + ) return recovered, position_errors, angle_errors @@ -283,7 +316,9 @@ def build_frame_targets_and_paths( per_camera_targets = [] per_camera_mapping = [] for cam_index in range(len(cals)): - targets, point_to_target = shuffled_targets_from_pixels(projected[:, cam_index, :], rng, noise_sigma=0.08) + targets, point_to_target = shuffled_targets_from_pixels( + projected[:, cam_index, :], rng, noise_sigma=0.08 + ) per_camera_targets.append(targets) per_camera_mapping.append(point_to_target) @@ -291,15 +326,24 @@ def build_frame_targets_and_paths( for point_index in range(frame_points.shape[0]): for cam_index in range(len(cals)): target_index = per_camera_mapping[cam_index][point_index] - observed_pixels[point_index, cam_index, 0] = per_camera_targets[cam_index][target_index].x - observed_pixels[point_index, cam_index, 1] = per_camera_targets[cam_index][target_index].y - - initial_points, _ = initialize_bundle_adjustment_points(observed_pixels, list(cals), cpar) + observed_pixels[point_index, cam_index, 0] = per_camera_targets[cam_index][ + target_index + ].x + observed_pixels[point_index, cam_index, 1] = per_camera_targets[cam_index][ + target_index + ].y + + initial_points, _ = initialize_bundle_adjustment_points( + observed_pixels, list(cals), cpar + ) cor_buf = np.recarray((frame_points.shape[0],), dtype=n_tupel_dtype) path_buf = [Pathinfo() for _ in range(frame_points.shape[0])] for point_index in range(frame_points.shape[0]): cor_buf[point_index].p = np.array( - [per_camera_mapping[cam_index][point_index] for cam_index in range(len(cals))], + [ + per_camera_mapping[cam_index][point_index] + for cam_index in range(len(cals)) + ], dtype=np.int32, ) cor_buf[point_index].corr = 1.0 @@ -339,7 +383,8 @@ def write_case_readme(output_case: Path) -> None: """Document the generated synthetic case contents.""" text = """# Synthetic Cavity Case -This case is generated deterministically from the geometry of `test_cavity`, but all observations come from known ground truth. +This case is generated deterministically from the geometry of `test_cavity`, +but all observations come from known ground truth. Contents: @@ -515,4 +560,4 @@ def main(argv: Iterable[str] | None = None) -> int: if __name__ == "__main__": - raise SystemExit(main()) \ No newline at end of file + raise SystemExit(main()) diff --git a/openptv_python/orientation.py b/openptv_python/orientation.py index ada532e..02aa34f 100644 --- a/openptv_python/orientation.py +++ b/openptv_python/orientation.py @@ -1784,7 +1784,6 @@ def guarded_two_step_bundle_adjustment( reject_on_ray_convergence: bool = True, ) -> Tuple[List[Calibration], np.ndarray, Dict[str, object]]: """Run pose-only BA then tightly constrained intrinsics BA with acceptance checks.""" - if pose_stage_ray_slack < 0: raise ValueError("pose_stage_ray_slack must be non-negative") @@ -1847,9 +1846,7 @@ def geometry_stage_ok( "geometry_guard_threshold must be positive when geometry_guard_mode='hard'" ) return candidate_metric <= geometry_guard_threshold - raise ValueError( - "geometry_guard_mode must be one of 'off', 'soft', or 'hard'" - ) + raise ValueError("geometry_guard_mode must be one of 'off', 'soft', or 'hard'") def correspondence_replacement_summary( candidate_points: np.ndarray, @@ -1882,7 +1879,10 @@ def correspondence_replacement_summary( int(correspondence_point_frame_indices[point_index]) ] for camera_index in range(len(candidate_cals)): - deltas = frame_targets[camera_index] - projected_pixels[point_index, camera_index] + deltas = ( + frame_targets[camera_index] + - projected_pixels[point_index, camera_index] + ) squared_distances = np.sum(deltas * deltas, axis=1) nearest_index = int(np.argmin(squared_distances)) replacement_ids[point_index, camera_index] = nearest_index @@ -1924,7 +1924,10 @@ def correspondence_stage_ok( return True return candidate_rate <= prior_rate + 1e-12 if correspondence_guard_mode == "hard": - if correspondence_guard_threshold is None or correspondence_guard_threshold <= 0: + if ( + correspondence_guard_threshold is None + or correspondence_guard_threshold <= 0 + ): raise ValueError( "correspondence_guard_threshold must be positive when correspondence_guard_mode='hard'" ) @@ -2014,12 +2017,17 @@ def correspondence_stage_ok( ) pose_ok = pose_ok and pose_correspondence_ok else: - release_order = [int(camera_index) for camera_index in pose_release_camera_order] + release_order = [ + int(camera_index) for camera_index in pose_release_camera_order + ] if not release_order: raise ValueError("pose_release_camera_order must not be empty") if len(set(release_order)) != len(release_order): raise ValueError("pose_release_camera_order must not contain duplicates") - if any(camera_index < 0 or camera_index >= len(cals) for camera_index in release_order): + if any( + camera_index < 0 or camera_index >= len(cals) + for camera_index in release_order + ): raise ValueError( "pose_release_camera_order contains an out-of-range camera index" ) @@ -2062,7 +2070,9 @@ def correspondence_stage_ok( known_points=known_points, known_point_sigmas=known_point_sigmas, ) - stage_rms = reprojection_rms(observed_pixels, stage_points, stage_cals, cpar) + stage_rms = reprojection_rms( + observed_pixels, stage_points, stage_cals, cpar + ) stage_ray_convergence = mean_ray_convergence( observed_pixels, stage_cals, cpar ) diff --git a/tests/test_bundle_adjustment.py b/tests/test_bundle_adjustment.py index a146b70..4a72c78 100644 --- a/tests/test_bundle_adjustment.py +++ b/tests/test_bundle_adjustment.py @@ -669,19 +669,21 @@ def test_guarded_two_step_bundle_adjustment_supports_staged_camera_release(self) staged_returns = [ (self.true_cals, points.copy(), {"success": True, "stage": f"pose_{idx}"}) for idx in range(4) - ] + [ - (self.true_cals, points.copy(), {"success": True, "stage": "intrinsics"}) - ] - - with patch( - "openptv_python.orientation.multi_camera_bundle_adjustment", - side_effect=staged_returns, - ) as mocked_adjustment, patch( - "openptv_python.orientation.reprojection_rms", - side_effect=[10.0, 9.0, 8.0, 7.0, 6.0, 5.0], - ), patch( - "openptv_python.orientation.mean_ray_convergence", - side_effect=[6.0, 5.0, 4.0, 3.0, 2.0, 1.0], + ] + [(self.true_cals, points.copy(), {"success": True, "stage": "intrinsics"})] + + with ( + patch( + "openptv_python.orientation.multi_camera_bundle_adjustment", + side_effect=staged_returns, + ) as mocked_adjustment, + patch( + "openptv_python.orientation.reprojection_rms", + side_effect=[10.0, 9.0, 8.0, 7.0, 6.0, 5.0], + ), + patch( + "openptv_python.orientation.mean_ray_convergence", + side_effect=[6.0, 5.0, 4.0, 3.0, 2.0, 1.0], + ), ): _, final_points, summary = guarded_two_step_bundle_adjustment( observed_pixels, @@ -725,12 +727,17 @@ def test_guarded_two_step_bundle_adjustment_supports_staged_camera_release(self) self.assertEqual(summary["accepted_stage"], "intrinsics") self.assertEqual(len(summary["pose_stage_summaries"]), 4) self.assertEqual( - [stage["released_camera_index"] for stage in summary["pose_stage_summaries"]], + [ + stage["released_camera_index"] + for stage in summary["pose_stage_summaries"] + ], [0, 1, 2, 3], ) self.assertEqual(final_points.shape, points.shape) - def test_guarded_two_step_bundle_adjustment_stagewise_ray_slack_allows_near_miss(self): + def test_guarded_two_step_bundle_adjustment_stagewise_ray_slack_allows_near_miss( + self, + ): points = np.array( [ [-10.0, -10.0, 0.0], @@ -755,19 +762,21 @@ def test_guarded_two_step_bundle_adjustment_stagewise_ray_slack_allows_near_miss staged_returns = [ (self.true_cals, points.copy(), {"success": True, "stage": f"pose_{idx}"}) for idx in range(4) - ] + [ - (self.true_cals, points.copy(), {"success": True, "stage": "intrinsics"}) - ] + ] + [(self.true_cals, points.copy(), {"success": True, "stage": "intrinsics"})] - with patch( - "openptv_python.orientation.multi_camera_bundle_adjustment", - side_effect=staged_returns, - ), patch( - "openptv_python.orientation.reprojection_rms", - side_effect=[10.0, 9.0, 8.0, 7.0, 6.0, 5.5], - ), patch( - "openptv_python.orientation.mean_ray_convergence", - side_effect=[6.0, 5.0, 4.0, 4.0005, 3.5, 3.4], + with ( + patch( + "openptv_python.orientation.multi_camera_bundle_adjustment", + side_effect=staged_returns, + ), + patch( + "openptv_python.orientation.reprojection_rms", + side_effect=[10.0, 9.0, 8.0, 7.0, 6.0, 5.5], + ), + patch( + "openptv_python.orientation.mean_ray_convergence", + side_effect=[6.0, 5.0, 4.0, 4.0005, 3.5, 3.4], + ), ): _, _, summary = guarded_two_step_bundle_adjustment( observed_pixels, @@ -826,27 +835,33 @@ def test_guarded_two_step_bundle_adjustment_rejects_on_hard_geometry_guard(self) intrinsics.p1flag = 1 intrinsics.p2flag = 1 - with patch( - "openptv_python.orientation.multi_camera_bundle_adjustment", - side_effect=[ - (pose_cals, points.copy(), {"success": True}), - (bad_intrinsic_cals, points.copy(), {"success": True}), - ], - ), patch( - "openptv_python.orientation.reprojection_rms", - side_effect=[10.0, 5.0, 4.0], - ), patch( - "openptv_python.orientation.mean_ray_convergence", - side_effect=[3.0, 2.0, 1.0], - ), patch( - "openptv_python.orientation.img_coord", - side_effect=lambda point, cal, _mm: ( - float(point[0] + cal.get_pos()[0]), - float(point[1] + cal.get_pos()[1]), + with ( + patch( + "openptv_python.orientation.multi_camera_bundle_adjustment", + side_effect=[ + (pose_cals, points.copy(), {"success": True}), + (bad_intrinsic_cals, points.copy(), {"success": True}), + ], + ), + patch( + "openptv_python.orientation.reprojection_rms", + side_effect=[10.0, 5.0, 4.0], + ), + patch( + "openptv_python.orientation.mean_ray_convergence", + side_effect=[3.0, 2.0, 1.0], + ), + patch( + "openptv_python.orientation.img_coord", + side_effect=lambda point, cal, _mm: ( + float(point[0] + cal.get_pos()[0]), + float(point[1] + cal.get_pos()[1]), + ), + ), + patch( + "openptv_python.orientation.metric_to_pixel", + side_effect=lambda x, y, _cpar: np.array([x, y], dtype=float), ), - ), patch( - "openptv_python.orientation.metric_to_pixel", - side_effect=lambda x, y, _cpar: np.array([x, y], dtype=float), ): final_cals, _, summary = guarded_two_step_bundle_adjustment( observed_pixels, @@ -891,27 +906,33 @@ def test_guarded_two_step_bundle_adjustment_rejects_on_soft_geometry_guard(self) intrinsics.p1flag = 1 intrinsics.p2flag = 1 - with patch( - "openptv_python.orientation.multi_camera_bundle_adjustment", - side_effect=[ - (pose_cals, points.copy(), {"success": True}), - (bad_intrinsic_cals, points.copy(), {"success": True}), - ], - ), patch( - "openptv_python.orientation.reprojection_rms", - side_effect=[10.0, 5.0, 4.0], - ), patch( - "openptv_python.orientation.mean_ray_convergence", - side_effect=[3.0, 2.0, 1.0], - ), patch( - "openptv_python.orientation.img_coord", - side_effect=lambda point, cal, _mm: ( - float(point[0] + cal.get_pos()[0]), - float(point[1] + cal.get_pos()[1]), + with ( + patch( + "openptv_python.orientation.multi_camera_bundle_adjustment", + side_effect=[ + (pose_cals, points.copy(), {"success": True}), + (bad_intrinsic_cals, points.copy(), {"success": True}), + ], + ), + patch( + "openptv_python.orientation.reprojection_rms", + side_effect=[10.0, 5.0, 4.0], + ), + patch( + "openptv_python.orientation.mean_ray_convergence", + side_effect=[3.0, 2.0, 1.0], + ), + patch( + "openptv_python.orientation.img_coord", + side_effect=lambda point, cal, _mm: ( + float(point[0] + cal.get_pos()[0]), + float(point[1] + cal.get_pos()[1]), + ), + ), + patch( + "openptv_python.orientation.metric_to_pixel", + side_effect=lambda x, y, _cpar: np.array([x, y], dtype=float), ), - ), patch( - "openptv_python.orientation.metric_to_pixel", - side_effect=lambda x, y, _cpar: np.array([x, y], dtype=float), ): final_cals, _, summary = guarded_two_step_bundle_adjustment( observed_pixels, @@ -935,7 +956,9 @@ def test_guarded_two_step_bundle_adjustment_rejects_on_soft_geometry_guard(self) ) np.testing.assert_allclose(final_cals[3].get_pos(), pose_cals[3].get_pos()) - def test_guarded_two_step_bundle_adjustment_rejects_on_hard_correspondence_guard(self): + def test_guarded_two_step_bundle_adjustment_rejects_on_hard_correspondence_guard( + self, + ): points = np.array( [ [0.0, 0.0, 0.0], @@ -968,24 +991,30 @@ def test_guarded_two_step_bundle_adjustment_rejects_on_hard_correspondence_guard for _ in range(4) ] - with patch( - "openptv_python.orientation.multi_camera_bundle_adjustment", - side_effect=[ - (pose_cals, points.copy(), {"success": True}), - (bad_intrinsic_cals, points.copy(), {"success": True}), - ], - ), patch( - "openptv_python.orientation.reprojection_rms", - side_effect=[10.0, 5.0, 4.0], - ), patch( - "openptv_python.orientation.mean_ray_convergence", - side_effect=[3.0, 2.0, 1.0], - ), patch( - "openptv_python.orientation.image_coordinates", - side_effect=lambda pts, cal, _mm: pts[:, :2] + cal.get_pos()[:2], - ), patch( - "openptv_python.orientation.arr_metric_to_pixel", - side_effect=lambda coords, _cpar: coords, + with ( + patch( + "openptv_python.orientation.multi_camera_bundle_adjustment", + side_effect=[ + (pose_cals, points.copy(), {"success": True}), + (bad_intrinsic_cals, points.copy(), {"success": True}), + ], + ), + patch( + "openptv_python.orientation.reprojection_rms", + side_effect=[10.0, 5.0, 4.0], + ), + patch( + "openptv_python.orientation.mean_ray_convergence", + side_effect=[3.0, 2.0, 1.0], + ), + patch( + "openptv_python.orientation.image_coordinates", + side_effect=lambda pts, cal, _mm: pts[:, :2] + cal.get_pos()[:2], + ), + patch( + "openptv_python.orientation.arr_metric_to_pixel", + side_effect=lambda coords, _cpar: coords, + ), ): final_cals, _, summary = guarded_two_step_bundle_adjustment( observed_pixels, @@ -995,7 +1024,9 @@ def test_guarded_two_step_bundle_adjustment_rejects_on_hard_correspondence_guard intrinsics, point_init=points, fixed_camera_indices=[0, 1], - correspondence_original_ids=np.tile(np.arange(len(points))[:, None], (1, 4)), + correspondence_original_ids=np.tile( + np.arange(len(points))[:, None], (1, 4) + ), correspondence_point_frame_indices=np.zeros(len(points), dtype=int), correspondence_frame_target_pixels=[target_sets], correspondence_guard_mode="hard", @@ -1008,7 +1039,9 @@ def test_guarded_two_step_bundle_adjustment_rejects_on_hard_correspondence_guard self.assertGreater(summary["intrinsic_correspondence_rate"], 0.2) np.testing.assert_allclose(final_cals[2].get_pos(), pose_cals[2].get_pos()) - def test_guarded_two_step_bundle_adjustment_rejects_on_soft_correspondence_guard(self): + def test_guarded_two_step_bundle_adjustment_rejects_on_soft_correspondence_guard( + self, + ): points = np.array( [ [0.0, 0.0, 0.0], @@ -1041,24 +1074,30 @@ def test_guarded_two_step_bundle_adjustment_rejects_on_soft_correspondence_guard for _ in range(4) ] - with patch( - "openptv_python.orientation.multi_camera_bundle_adjustment", - side_effect=[ - (pose_cals, points.copy(), {"success": True}), - (bad_intrinsic_cals, points.copy(), {"success": True}), - ], - ), patch( - "openptv_python.orientation.reprojection_rms", - side_effect=[10.0, 5.0, 4.0], - ), patch( - "openptv_python.orientation.mean_ray_convergence", - side_effect=[3.0, 2.0, 1.0], - ), patch( - "openptv_python.orientation.image_coordinates", - side_effect=lambda pts, cal, _mm: pts[:, :2] + cal.get_pos()[:2], - ), patch( - "openptv_python.orientation.arr_metric_to_pixel", - side_effect=lambda coords, _cpar: coords, + with ( + patch( + "openptv_python.orientation.multi_camera_bundle_adjustment", + side_effect=[ + (pose_cals, points.copy(), {"success": True}), + (bad_intrinsic_cals, points.copy(), {"success": True}), + ], + ), + patch( + "openptv_python.orientation.reprojection_rms", + side_effect=[10.0, 5.0, 4.0], + ), + patch( + "openptv_python.orientation.mean_ray_convergence", + side_effect=[3.0, 2.0, 1.0], + ), + patch( + "openptv_python.orientation.image_coordinates", + side_effect=lambda pts, cal, _mm: pts[:, :2] + cal.get_pos()[:2], + ), + patch( + "openptv_python.orientation.arr_metric_to_pixel", + side_effect=lambda coords, _cpar: coords, + ), ): final_cals, _, summary = guarded_two_step_bundle_adjustment( observed_pixels, @@ -1068,7 +1107,9 @@ def test_guarded_two_step_bundle_adjustment_rejects_on_soft_correspondence_guard intrinsics, point_init=points, fixed_camera_indices=[0, 1], - correspondence_original_ids=np.tile(np.arange(len(points))[:, None], (1, 4)), + correspondence_original_ids=np.tile( + np.arange(len(points))[:, None], (1, 4) + ), correspondence_point_frame_indices=np.zeros(len(points), dtype=int), correspondence_frame_target_pixels=[target_sets], correspondence_guard_mode="soft", @@ -1160,7 +1201,9 @@ def test_cavity_intrinsics_only_improves_from_intrinsic_perturbation(self): self.assertLess(after_rms, before_rms - 0.1) for refined, start in zip(refined_cals, start_cals): np.testing.assert_allclose(refined.get_pos(), start.get_pos(), atol=1e-12) - np.testing.assert_allclose(refined.get_angles(), start.get_angles(), atol=1e-12) + np.testing.assert_allclose( + refined.get_angles(), start.get_angles(), atol=1e-12 + ) np.testing.assert_allclose(refined_points, point_init) diff --git a/tests/test_demo_bundle_adjustment.py b/tests/test_demo_bundle_adjustment.py index cd21f0e..c68978f 100644 --- a/tests/test_demo_bundle_adjustment.py +++ b/tests/test_demo_bundle_adjustment.py @@ -3,6 +3,7 @@ import numpy as np +from openptv_python.calibration import read_calibration from openptv_python.demo_bundle_adjustment import ( ExperimentResult, all_fixed_camera_pairs, @@ -19,7 +20,6 @@ summarize_fixed_camera_diagnostics, summarize_quadruplet_sensitivity, ) -from openptv_python.calibration import read_calibration from openptv_python.parameters import ControlPar, read_volume_par from openptv_python.tracking_frame_buf import read_path_frame, read_targets @@ -69,14 +69,10 @@ def test_default_experiments_adds_known_point_presets(self): spec for spec in experiments if spec.name == "pose_trf_known_points" ) guarded_spec = next( - spec - for spec in experiments - if spec.name == "guarded_two_step_known_points" + spec for spec in experiments if spec.name == "guarded_two_step_known_points" ) staged_spec = next( - spec - for spec in experiments - if spec.name == "guarded_stagewise_release" + spec for spec in experiments if spec.name == "guarded_stagewise_release" ) staged_known_spec = next( spec @@ -88,7 +84,9 @@ def test_default_experiments_adds_known_point_presets(self): self.assertIs(guarded_spec.ba_kwargs["known_points"], known_points) self.assertEqual(guarded_spec.ba_kwargs["known_point_sigmas"], 0.25) self.assertEqual(staged_spec.ba_kwargs["fixed_camera_indices"], [1, 2, 3]) - self.assertEqual(staged_spec.ba_kwargs["pose_release_camera_order"], [0, 1, 2, 3]) + self.assertEqual( + staged_spec.ba_kwargs["pose_release_camera_order"], [0, 1, 2, 3] + ) self.assertEqual(staged_spec.ba_kwargs["pose_stage_ray_slack"], 0.0) self.assertIs(staged_known_spec.ba_kwargs["known_points"], known_points) self.assertEqual(staged_known_spec.ba_kwargs["known_point_sigmas"], 0.25) @@ -126,7 +124,9 @@ def test_default_experiments_accepts_geometry_guard_configuration(self): guarded_spec.ba_kwargs["correspondence_guard_reference_rate"], 0.15625, ) - self.assertEqual(staged_spec.ba_kwargs["pose_release_camera_order"], [2, 0, 1, 3]) + self.assertEqual( + staged_spec.ba_kwargs["pose_release_camera_order"], [2, 0, 1, 3] + ) self.assertEqual(staged_spec.ba_kwargs["fixed_camera_indices"], [0, 1, 3]) self.assertEqual(staged_spec.ba_kwargs["pose_stage_ray_slack"], 0.002) @@ -242,7 +242,9 @@ def test_epipolar_and_quadruplet_diagnostics_detect_perturbation(self): for cam_num in range(1, 5) ] - cor_buf, path_buf = read_path_frame(str(cavity_dir / "res_orig/rt_is"), "", "", 10001) + cor_buf, path_buf = read_path_frame( + str(cavity_dir / "res_orig/rt_is"), "", "", 10001 + ) targets = [ read_targets(str(cavity_dir / f"img_orig/cam{cam_num}.%05d"), 10001) for cam_num in range(1, 5) @@ -283,8 +285,10 @@ def test_epipolar_and_quadruplet_diagnostics_detect_perturbation(self): max(item.max_distance for item in baseline_epipolar), ) self.assertGreater(perturbed_quad.max_spread, baseline_quad.max_spread) - self.assertIn("baseline", format_quadruplet_sensitivity(baseline_quad, perturbed_quad)) + self.assertIn( + "baseline", format_quadruplet_sensitivity(baseline_quad, perturbed_quad) + ) if __name__ == "__main__": - unittest.main() \ No newline at end of file + unittest.main() diff --git a/tests/test_synthetic_cavity_case.py b/tests/test_synthetic_cavity_case.py index f2ca0bb..50f0d87 100644 --- a/tests/test_synthetic_cavity_case.py +++ b/tests/test_synthetic_cavity_case.py @@ -146,11 +146,16 @@ def test_synthetic_case_bundle_adjustment_recovers_ground_truth_from_controlled_ point_init = truth_points + np.array([0.35, -0.25, 0.18]) initial_camera_errors = [ - np.linalg.norm(start_cals[camera_index].get_pos() - truth_cals[camera_index].get_pos()) + np.linalg.norm( + start_cals[camera_index].get_pos() - truth_cals[camera_index].get_pos() + ) for camera_index in (2, 3) ] initial_angle_errors = [ - np.linalg.norm(start_cals[camera_index].get_angles() - truth_cals[camera_index].get_angles()) + np.linalg.norm( + start_cals[camera_index].get_angles() + - truth_cals[camera_index].get_angles() + ) for camera_index in (2, 3) ] initial_point_error = float( @@ -178,11 +183,16 @@ def test_synthetic_case_bundle_adjustment_recovers_ground_truth_from_controlled_ ) final_camera_errors = [ - np.linalg.norm(refined_cals[camera_index].get_pos() - truth_cals[camera_index].get_pos()) + np.linalg.norm( + refined_cals[camera_index].get_pos() - truth_cals[camera_index].get_pos() + ) for camera_index in (2, 3) ] final_angle_errors = [ - np.linalg.norm(refined_cals[camera_index].get_angles() - truth_cals[camera_index].get_angles()) + np.linalg.norm( + refined_cals[camera_index].get_angles() + - truth_cals[camera_index].get_angles() + ) for camera_index in (2, 3) ] final_point_error = float( @@ -267,4 +277,4 @@ def test_synthetic_case_intrinsics_only_recovers_ground_truth_from_controlled_pe assert max(k1_errors) < 1.5e-5 assert max(p1_errors) < 1.5e-5 assert max(p2_errors) < 3.0e-5 - assert result["final_reprojection_rms"] < result["initial_reprojection_rms"] * 0.2 \ No newline at end of file + assert result["final_reprojection_rms"] < result["initial_reprojection_rms"] * 0.2 From 95f9fa3e1ff21581ba0e4e1ad494368b49755ef9 Mon Sep 17 00:00:00 2001 From: Alex Liberzon Date: Sat, 7 Mar 2026 13:38:13 +0200 Subject: [PATCH 11/27] smaller steps bundle adjustment --- openptv_python/demo_bundle_adjustment.py | 432 +++++++++++++++++++++ openptv_python/orientation.py | 463 ++++++++++++++++------- tests/test_bundle_adjustment.py | 175 +++++++++ tests/test_demo_bundle_adjustment.py | 112 ++++++ 4 files changed, 1054 insertions(+), 128 deletions(-) diff --git a/openptv_python/demo_bundle_adjustment.py b/openptv_python/demo_bundle_adjustment.py index 4b44b0a..5eac602 100644 --- a/openptv_python/demo_bundle_adjustment.py +++ b/openptv_python/demo_bundle_adjustment.py @@ -995,6 +995,145 @@ def default_experiments( "phi": (-0.02, 0.02), "kappa": (-0.02, 0.02), } + conservative_pose_stage_configs = [ + { + "prior_sigmas": { + "x0": 0.05, + "y0": 0.05, + "z0": 0.05, + "omega": 5e-4, + "phi": 5e-4, + "kappa": 5e-4, + }, + "parameter_bounds": { + "x0": (-0.1, 0.1), + "y0": (-0.1, 0.1), + "z0": (-0.1, 0.1), + "omega": (-0.002, 0.002), + "phi": (-0.002, 0.002), + "kappa": (-0.002, 0.002), + }, + "max_nfev": 4, + "optimize_points": False, + "x_scale": { + "x0": 0.02, + "y0": 0.02, + "z0": 0.02, + "omega": 2e-4, + "phi": 2e-4, + "kappa": 2e-4, + }, + }, + { + "prior_sigmas": { + "x0": 0.1, + "y0": 0.1, + "z0": 0.1, + "omega": 1e-3, + "phi": 1e-3, + "kappa": 1e-3, + }, + "parameter_bounds": { + "x0": (-0.2, 0.2), + "y0": (-0.2, 0.2), + "z0": (-0.2, 0.2), + "omega": (-0.004, 0.004), + "phi": (-0.004, 0.004), + "kappa": (-0.004, 0.004), + }, + "max_nfev": 4, + "optimize_points": True, + "x_scale": { + "x0": 0.03, + "y0": 0.03, + "z0": 0.03, + "omega": 3e-4, + "phi": 3e-4, + "kappa": 3e-4, + }, + }, + { + "prior_sigmas": pose_priors, + "parameter_bounds": pose_bounds, + "max_nfev": 8, + "optimize_points": True, + "x_scale": { + "x0": 0.05, + "y0": 0.05, + "z0": 0.05, + "omega": 5e-4, + "phi": 5e-4, + "kappa": 5e-4, + "points": 0.1, + }, + }, + ] + intrinsics_first_pose_stage_configs = [ + { + "prior_sigmas": { + "x0": 0.02, + "y0": 0.02, + "z0": 0.02, + "omega": 2e-4, + "phi": 2e-4, + "kappa": 2e-4, + }, + "parameter_bounds": { + "x0": (-0.05, 0.05), + "y0": (-0.05, 0.05), + "z0": (-0.05, 0.05), + "omega": (-0.001, 0.001), + "phi": (-0.001, 0.001), + "kappa": (-0.001, 0.001), + }, + "max_nfev": 4, + "optimize_points": False, + "x_scale": { + "x0": 0.01, + "y0": 0.01, + "z0": 0.01, + "omega": 1e-4, + "phi": 1e-4, + "kappa": 1e-4, + }, + }, + { + "prior_sigmas": { + "x0": 0.05, + "y0": 0.05, + "z0": 0.05, + "omega": 5e-4, + "phi": 5e-4, + "kappa": 5e-4, + }, + "parameter_bounds": { + "x0": (-0.1, 0.1), + "y0": (-0.1, 0.1), + "z0": (-0.1, 0.1), + "omega": (-0.002, 0.002), + "phi": (-0.002, 0.002), + "kappa": (-0.002, 0.002), + }, + "max_nfev": 4, + "optimize_points": True, + "x_scale": { + "x0": 0.02, + "y0": 0.02, + "z0": 0.02, + "omega": 2e-4, + "phi": 2e-4, + "kappa": 2e-4, + "points": 0.1, + }, + }, + ] + known_point_pose_stage_configs = [ + { + **stage_config, + "optimize_points": True, + } + for stage_config in conservative_pose_stage_configs + ] tight_intrinsic_priors = { "k1": 1e-12, "k2": 1e-12, @@ -1116,6 +1255,16 @@ def default_experiments( "pose_prior_sigmas": pose_priors, "pose_parameter_bounds": pose_bounds, "pose_max_nfev": 8, + "pose_x_scale": { + "x0": 0.05, + "y0": 0.05, + "z0": 0.05, + "omega": 5e-4, + "phi": 5e-4, + "kappa": 5e-4, + "points": 0.1, + }, + "pose_stage_configs": conservative_pose_stage_configs, "intrinsic_prior_sigmas": tight_intrinsic_priors, "intrinsic_parameter_bounds": tight_intrinsic_bounds, "intrinsic_max_nfev": 4, @@ -1139,6 +1288,69 @@ def default_experiments( "pose_prior_sigmas": pose_priors, "pose_parameter_bounds": pose_bounds, "pose_max_nfev": 8, + "pose_x_scale": { + "x0": 0.05, + "y0": 0.05, + "z0": 0.05, + "omega": 5e-4, + "phi": 5e-4, + "kappa": 5e-4, + "points": 0.1, + }, + "pose_stage_configs": conservative_pose_stage_configs, + "intrinsic_prior_sigmas": tight_intrinsic_priors, + "intrinsic_parameter_bounds": tight_intrinsic_bounds, + "intrinsic_max_nfev": 4, + "geometry_guard_mode": geometry_guard_mode, + "geometry_guard_threshold": geometry_guard_threshold, + "correspondence_guard_mode": correspondence_guard_mode, + "correspondence_guard_threshold": correspondence_guard_threshold, + "correspondence_guard_reference_rate": correspondence_guard_reference_rate, + }, + ), + ExperimentSpec( + name="intrinsics_first_guarded_stagewise_release", + description=( + "Intrinsics-only warm start followed by a tiny guarded " + "stagewise pose release" + ), + mode="intrinsics_then_guarded", + ba_kwargs={ + "warmstart_orient_par": intrinsics_only, + "warmstart_fixed_camera_indices": list(range(num_cams)), + "warmstart_loss": "linear", + "warmstart_method": "trf", + "warmstart_prior_sigmas": { + "k1": 5e-5, + "p1": 1e-4, + "p2": 1e-4, + }, + "warmstart_parameter_bounds": { + "k1": (-5e-5, 5e-5), + "p1": (-2e-4, 2e-4), + "p2": (-2e-4, 2e-4), + }, + "warmstart_max_nfev": 40, + "warmstart_optimize_extrinsics": False, + "warmstart_optimize_points": False, + "pose_orient_par": OrientPar(), + "intrinsic_orient_par": intrinsics, + "fixed_camera_indices": staged_fixed, + "pose_release_camera_order": staged_order, + "pose_stage_ray_slack": pose_stage_ray_slack, + "pose_prior_sigmas": pose_priors, + "pose_parameter_bounds": pose_bounds, + "pose_max_nfev": 4, + "pose_x_scale": { + "x0": 0.02, + "y0": 0.02, + "z0": 0.02, + "omega": 2e-4, + "phi": 2e-4, + "kappa": 2e-4, + "points": 0.1, + }, + "pose_stage_configs": intrinsics_first_pose_stage_configs, "intrinsic_prior_sigmas": tight_intrinsic_priors, "intrinsic_parameter_bounds": tight_intrinsic_bounds, "intrinsic_max_nfev": 4, @@ -1181,6 +1393,16 @@ def default_experiments( "pose_prior_sigmas": pose_priors, "pose_parameter_bounds": pose_bounds, "pose_max_nfev": 8, + "pose_x_scale": { + "x0": 0.05, + "y0": 0.05, + "z0": 0.05, + "omega": 5e-4, + "phi": 5e-4, + "kappa": 5e-4, + "points": 0.1, + }, + "pose_stage_configs": known_point_pose_stage_configs, "intrinsic_prior_sigmas": tight_intrinsic_priors, "intrinsic_parameter_bounds": tight_intrinsic_bounds, "intrinsic_max_nfev": 4, @@ -1206,6 +1428,16 @@ def default_experiments( "pose_prior_sigmas": pose_priors, "pose_parameter_bounds": pose_bounds, "pose_max_nfev": 8, + "pose_x_scale": { + "x0": 0.05, + "y0": 0.05, + "z0": 0.05, + "omega": 5e-4, + "phi": 5e-4, + "kappa": 5e-4, + "points": 0.1, + }, + "pose_stage_configs": known_point_pose_stage_configs, "intrinsic_prior_sigmas": tight_intrinsic_priors, "intrinsic_parameter_bounds": tight_intrinsic_bounds, "intrinsic_max_nfev": 4, @@ -1307,6 +1539,10 @@ def run_experiment( float | np.ndarray | None, spec.ba_kwargs.get("known_point_sigmas"), ), + x_scale=cast( + float | Sequence[float] | Dict[str, float] | None, + spec.ba_kwargs.get("x_scale"), + ), ftol=cast(float | None, spec.ba_kwargs.get("ftol")), xtol=cast(float | None, spec.ba_kwargs.get("xtol")), gtol=cast(float | None, spec.ba_kwargs.get("gtol")), @@ -1348,6 +1584,14 @@ def run_experiment( pose_loss=cast(str, spec.ba_kwargs.get("pose_loss", "linear")), pose_method=cast(str, spec.ba_kwargs.get("pose_method", "trf")), pose_max_nfev=cast(int | None, spec.ba_kwargs.get("pose_max_nfev")), + pose_x_scale=cast( + float | Sequence[float] | Dict[str, float] | None, + spec.ba_kwargs.get("pose_x_scale"), + ), + pose_stage_configs=cast( + Sequence[Dict[str, object]] | None, + spec.ba_kwargs.get("pose_stage_configs"), + ), intrinsic_prior_sigmas=cast( Dict[str, float] | None, spec.ba_kwargs.get("intrinsic_prior_sigmas"), @@ -1368,6 +1612,10 @@ def run_experiment( int | None, spec.ba_kwargs.get("intrinsic_max_nfev"), ), + intrinsic_x_scale=cast( + float | Sequence[float] | Dict[str, float] | None, + spec.ba_kwargs.get("intrinsic_x_scale"), + ), intrinsic_ftol=cast( float | None, spec.ba_kwargs.get("intrinsic_ftol", 1e-12) ), @@ -1437,6 +1685,190 @@ def run_experiment( final_rms = cast(float, summary["final_reprojection_rms"]) final_ray = cast(float, summary["final_mean_ray_convergence"]) notes = f"accepted_stage={summary['accepted_stage']}" + elif spec.mode == "intrinsics_then_guarded": + warmstart_orient_par = cast(OrientPar, spec.ba_kwargs["warmstart_orient_par"]) + warmstart_cals, warmstart_points, warmstart_result = multi_camera_bundle_adjustment( + observed_pixels, + working_cals, + control, + warmstart_orient_par, + point_init=point_init, + fix_first_camera=cast(bool, spec.ba_kwargs.get("fix_first_camera", True)), + fixed_camera_indices=cast( + List[int] | None, + spec.ba_kwargs.get("warmstart_fixed_camera_indices"), + ), + loss=cast(str, spec.ba_kwargs.get("warmstart_loss", "linear")), + method=cast(str, spec.ba_kwargs.get("warmstart_method", "trf")), + prior_sigmas=cast( + Dict[str, float] | None, + spec.ba_kwargs.get("warmstart_prior_sigmas"), + ), + parameter_bounds=cast( + Dict[str, tuple[float, float]] | None, + spec.ba_kwargs.get("warmstart_parameter_bounds"), + ), + max_nfev=cast(int | None, spec.ba_kwargs.get("warmstart_max_nfev")), + optimize_extrinsics=cast( + bool, + spec.ba_kwargs.get("warmstart_optimize_extrinsics", False), + ), + optimize_points=cast( + bool, + spec.ba_kwargs.get("warmstart_optimize_points", False), + ), + x_scale=cast( + float | Sequence[float] | Dict[str, float] | None, + spec.ba_kwargs.get("warmstart_x_scale"), + ), + ) + pose_orient_par = cast(OrientPar, spec.ba_kwargs["pose_orient_par"]) + intrinsic_orient_par = cast(OrientPar, spec.ba_kwargs["intrinsic_orient_par"]) + refined_cals, refined_points, summary = guarded_two_step_bundle_adjustment( + observed_pixels, + warmstart_cals, + control, + pose_orient_par, + intrinsic_orient_par, + point_init=warmstart_points, + fixed_camera_indices=cast( + List[int] | None, + spec.ba_kwargs.get("fixed_camera_indices"), + ), + pose_release_camera_order=cast( + List[int] | None, + spec.ba_kwargs.get("pose_release_camera_order"), + ), + pose_stage_ray_slack=cast( + float, + spec.ba_kwargs.get("pose_stage_ray_slack", 0.0), + ), + pose_prior_sigmas=cast( + Dict[str, float] | None, + spec.ba_kwargs.get("pose_prior_sigmas"), + ), + pose_parameter_bounds=cast( + Dict[str, tuple[float, float]] | None, + spec.ba_kwargs.get("pose_parameter_bounds"), + ), + pose_loss=cast(str, spec.ba_kwargs.get("pose_loss", "linear")), + pose_method=cast(str, spec.ba_kwargs.get("pose_method", "trf")), + pose_max_nfev=cast(int | None, spec.ba_kwargs.get("pose_max_nfev")), + pose_x_scale=cast( + float | Sequence[float] | Dict[str, float] | None, + spec.ba_kwargs.get("pose_x_scale"), + ), + pose_stage_configs=cast( + Sequence[Dict[str, object]] | None, + spec.ba_kwargs.get("pose_stage_configs"), + ), + intrinsic_prior_sigmas=cast( + Dict[str, float] | None, + spec.ba_kwargs.get("intrinsic_prior_sigmas"), + ), + intrinsic_parameter_bounds=cast( + Dict[str, tuple[float, float]] | None, + spec.ba_kwargs.get("intrinsic_parameter_bounds"), + ), + intrinsic_loss=cast( + str, + spec.ba_kwargs.get("intrinsic_loss", "linear"), + ), + intrinsic_method=cast( + str, + spec.ba_kwargs.get("intrinsic_method", "trf"), + ), + intrinsic_max_nfev=cast( + int | None, + spec.ba_kwargs.get("intrinsic_max_nfev"), + ), + intrinsic_x_scale=cast( + float | Sequence[float] | Dict[str, float] | None, + spec.ba_kwargs.get("intrinsic_x_scale"), + ), + intrinsic_ftol=cast( + float | None, spec.ba_kwargs.get("intrinsic_ftol", 1e-12) + ), + intrinsic_xtol=cast( + float | None, spec.ba_kwargs.get("intrinsic_xtol", 1e-12) + ), + intrinsic_gtol=cast( + float | None, spec.ba_kwargs.get("intrinsic_gtol", 1e-12) + ), + pose_optimize_points=cast( + bool, + spec.ba_kwargs.get("pose_optimize_points", True), + ), + intrinsic_optimize_points=cast( + bool, + spec.ba_kwargs.get("intrinsic_optimize_points", True), + ), + reject_worse_solution=cast( + bool, + spec.ba_kwargs.get("reject_worse_solution", True), + ), + reject_on_ray_convergence=cast( + bool, + spec.ba_kwargs.get("reject_on_ray_convergence", True), + ), + known_points=cast( + Dict[int, np.ndarray] | None, + spec.ba_kwargs.get("known_points"), + ), + known_point_sigmas=cast( + float | np.ndarray | None, + spec.ba_kwargs.get("known_point_sigmas"), + ), + geometry_reference_points=reference_geometry_points, + geometry_reference_cals=reference_cals, + geometry_guard_mode=cast( + str, + spec.ba_kwargs.get("geometry_guard_mode", "off"), + ), + geometry_guard_threshold=cast( + float | None, + spec.ba_kwargs.get("geometry_guard_threshold"), + ), + correspondence_original_ids=( + None if tracking_data is None else tracking_data.original_target_ids + ), + correspondence_point_frame_indices=( + None if tracking_data is None else tracking_data.point_frame_indices + ), + correspondence_frame_target_pixels=( + None if tracking_data is None else tracking_data.frame_target_pixels + ), + correspondence_guard_mode=cast( + str, + spec.ba_kwargs.get("correspondence_guard_mode", "off"), + ), + correspondence_guard_threshold=cast( + float | None, + spec.ba_kwargs.get("correspondence_guard_threshold"), + ), + correspondence_guard_reference_rate=cast( + float | None, + spec.ba_kwargs.get("correspondence_guard_reference_rate"), + ), + ) + warmstart_success = bool( + getattr( + warmstart_result, + "success", + cast(Dict[str, object], warmstart_result).get("success", False), + ) + ) + final_rms = cast(float, summary["final_reprojection_rms"]) + final_ray = cast(float, summary["final_mean_ray_convergence"]) + warmstart_rms = float( + cast(Dict[str, object], warmstart_result)["final_reprojection_rms"] + ) + success = warmstart_success + notes = ( + f"warmstart_success={warmstart_success}; " + f"warmstart_rms={warmstart_rms:.6f}; " + f"accepted_stage={summary['accepted_stage']}" + ) else: raise ValueError(f"Unknown experiment mode: {spec.mode}") diff --git a/openptv_python/orientation.py b/openptv_python/orientation.py index 02aa34f..1074deb 100644 --- a/openptv_python/orientation.py +++ b/openptv_python/orientation.py @@ -1361,6 +1361,53 @@ def _expand_parameter_limits( return np.asarray(lower, dtype=np.float64), np.asarray(upper, dtype=np.float64) +def _bundle_adjustment_x_scale( + x_scale: Optional[float | Sequence[float] | Dict[str, float]], + parameter_names: List[str], + optimized_cam_indices: List[int], + optimize_points: bool, + num_points: int, + total_parameters: int, +) -> Optional[float | np.ndarray]: + """Normalize least_squares x_scale inputs to the packed BA parameter vector.""" + if x_scale is None: + return None + + if np.isscalar(x_scale): + value = float(cast(float, x_scale)) + if value <= 0: + raise ValueError("x_scale must be strictly positive") + return value + + if isinstance(x_scale, dict): + values: List[float] = [] + for _cam in optimized_cam_indices: + for name in parameter_names: + value = float(x_scale.get(name, 1.0)) + if value <= 0: + raise ValueError("x_scale entries must be strictly positive") + values.append(value) + + if optimize_points: + point_scale = float(x_scale.get("points", x_scale.get("point", 1.0))) + if point_scale <= 0: + raise ValueError("point x_scale must be strictly positive") + values.extend([point_scale] * (num_points * 3)) + + return np.asarray(values, dtype=np.float64) + + values = np.asarray(x_scale, dtype=np.float64) + if values.ndim != 1: + raise ValueError("x_scale sequence must be one-dimensional") + if values.size != total_parameters: + raise ValueError( + f"x_scale sequence must have length {total_parameters}, got {values.size}" + ) + if np.any(values <= 0): + raise ValueError("x_scale entries must be strictly positive") + return values.copy() + + def _bundle_adjustment_jacobian_sparsity( obs_mask: np.ndarray, num_cams: int, @@ -1478,6 +1525,7 @@ def multi_camera_bundle_adjustment( optimize_points: bool = True, known_points: Optional[Dict[int, np.ndarray]] = None, known_point_sigmas: Optional[float | np.ndarray] = None, + x_scale: Optional[float | Sequence[float] | Dict[str, float]] = None, ftol: Optional[float] = None, xtol: Optional[float] = None, gtol: Optional[float] = None, @@ -1630,6 +1678,15 @@ def multi_camera_bundle_adjustment( else: bounds = (camera_lower, camera_upper) + normalized_x_scale = _bundle_adjustment_x_scale( + x_scale, + parameter_names, + optimized_cam_indices, + optimize_points, + num_points, + x0.size, + ) + def unpack_parameters(params: np.ndarray) -> Tuple[List[Calibration], np.ndarray]: trial_cals = [_clone_calibration(cal) for cal in base_cals] offset = 0 @@ -1701,6 +1758,8 @@ def residual_vector(params: np.ndarray) -> np.ndarray: "max_nfev": max_nfev, "bounds": bounds, } + if normalized_x_scale is not None: + least_squares_kwargs["x_scale"] = normalized_x_scale if method != "lm" and x0.size > 0: least_squares_kwargs["jac_sparsity"] = _bundle_adjustment_jacobian_sparsity( obs_mask, @@ -1758,11 +1817,14 @@ def guarded_two_step_bundle_adjustment( pose_loss: str = "linear", pose_method: str = "trf", pose_max_nfev: Optional[int] = None, + pose_x_scale: Optional[float | Sequence[float] | Dict[str, float]] = None, + pose_stage_configs: Optional[Sequence[Dict[str, object]]] = None, intrinsic_prior_sigmas: Optional[Dict[str, float]] = None, intrinsic_parameter_bounds: Optional[Dict[str, Tuple[float, float]]] = None, intrinsic_loss: str = "linear", intrinsic_method: str = "trf", intrinsic_max_nfev: Optional[int] = None, + intrinsic_x_scale: Optional[float | Sequence[float] | Dict[str, float]] = None, intrinsic_ftol: Optional[float] = 1e-12, intrinsic_xtol: Optional[float] = 1e-12, intrinsic_gtol: Optional[float] = 1e-12, @@ -1787,6 +1849,53 @@ def guarded_two_step_bundle_adjustment( if pose_stage_ray_slack < 0: raise ValueError("pose_stage_ray_slack must be non-negative") + normalized_pose_stage_configs = list(pose_stage_configs or []) + + def pose_stage_variants() -> List[Dict[str, object]]: + if not normalized_pose_stage_configs: + return [ + { + "prior_sigmas": pose_prior_sigmas, + "parameter_bounds": pose_parameter_bounds, + "max_nfev": pose_max_nfev, + "optimize_points": pose_optimize_points, + "x_scale": pose_x_scale, + "loss": pose_loss, + "method": pose_method, + } + ] + + variants: List[Dict[str, object]] = [] + for variant in normalized_pose_stage_configs: + variant_dict = dict(variant) + variants.append( + { + "prior_sigmas": cast( + Optional[Dict[str, float]], + variant_dict.get("prior_sigmas", pose_prior_sigmas), + ), + "parameter_bounds": cast( + Optional[Dict[str, Tuple[float, float]]], + variant_dict.get("parameter_bounds", pose_parameter_bounds), + ), + "max_nfev": cast( + Optional[int], + variant_dict.get("max_nfev", pose_max_nfev), + ), + "optimize_points": cast( + bool, + variant_dict.get("optimize_points", pose_optimize_points), + ), + "x_scale": cast( + Optional[float | Sequence[float] | Dict[str, float]], + variant_dict.get("x_scale", pose_x_scale), + ), + "loss": cast(str, variant_dict.get("loss", pose_loss)), + "method": cast(str, variant_dict.get("method", pose_method)), + } + ) + return variants + def projection_drift_summaries( reference_cals: List[Calibration], candidate_cals: List[Calibration], @@ -1968,54 +2077,129 @@ def correspondence_stage_ok( ) pose_stage_summaries: List[Dict[str, object]] = [] + pose_stage_variants_list = pose_stage_variants() pose_result: object pose_ok: bool if pose_release_camera_order is None: - pose_cals, pose_points, pose_result = multi_camera_bundle_adjustment( - observed_pixels, - base_cals, - cpar, - pose_orient_par, - point_init=base_points, - fixed_camera_indices=fixed_camera_indices, - loss=pose_loss, - method=pose_method, - prior_sigmas=pose_prior_sigmas, - parameter_bounds=pose_parameter_bounds, - max_nfev=pose_max_nfev, - optimize_extrinsics=True, - optimize_points=pose_optimize_points, - known_points=known_points, - known_point_sigmas=known_point_sigmas, - ) - pose_rms = reprojection_rms(observed_pixels, pose_points, pose_cals, cpar) - pose_ray_convergence = mean_ray_convergence(observed_pixels, pose_cals, cpar) - pose_geometry = projection_drift_summaries( - geometry_reference_cals, - pose_cals, - geometry_reference_points, - ) - pose_geometry_max = max_projection_drift(pose_geometry) - pose_correspondence = correspondence_replacement_summary( - pose_points, - pose_cals, - ) - pose_correspondence_rate = ( - None - if pose_correspondence is None - else cast(float, pose_correspondence["replacement_rate"]) - ) - pose_ok = pose_rms <= baseline_rms and ( - not reject_on_ray_convergence - or pose_ray_convergence <= baseline_ray_convergence - ) - pose_geometry_ok = geometry_stage_ok(pose_geometry_max, baseline_geometry_max) - pose_ok = pose_ok and pose_geometry_ok - pose_correspondence_ok = correspondence_stage_ok( - pose_correspondence_rate, - baseline_correspondence_rate, - ) - pose_ok = pose_ok and pose_correspondence_ok + stage_cals = [_clone_calibration(cal) for cal in base_cals] + stage_points = np.asarray(base_points, dtype=np.float64).copy() + stage_rms = baseline_rms + stage_ray_convergence = baseline_ray_convergence + stage_geometry = baseline_geometry + stage_geometry_max = baseline_geometry_max + stage_correspondence = baseline_correspondence + stage_correspondence_rate = baseline_correspondence_rate + pose_result = {"staged": bool(normalized_pose_stage_configs), "stages": []} + pose_ok = False + pose_geometry_ok = True + pose_correspondence_ok = True + + for micro_stage_index, variant in enumerate(pose_stage_variants_list, start=1): + candidate_cals, candidate_points, candidate_result = multi_camera_bundle_adjustment( + observed_pixels, + stage_cals, + cpar, + pose_orient_par, + point_init=stage_points, + fixed_camera_indices=fixed_camera_indices, + loss=cast(str, variant["loss"]), + method=cast(str, variant["method"]), + prior_sigmas=cast(Optional[Dict[str, float]], variant["prior_sigmas"]), + parameter_bounds=cast( + Optional[Dict[str, Tuple[float, float]]], + variant["parameter_bounds"], + ), + max_nfev=cast(Optional[int], variant["max_nfev"]), + optimize_extrinsics=True, + optimize_points=cast(bool, variant["optimize_points"]), + known_points=known_points, + known_point_sigmas=known_point_sigmas, + x_scale=cast( + Optional[float | Sequence[float] | Dict[str, float]], + variant["x_scale"], + ), + ) + candidate_rms = reprojection_rms( + observed_pixels, candidate_points, candidate_cals, cpar + ) + candidate_ray_convergence = mean_ray_convergence( + observed_pixels, candidate_cals, cpar + ) + candidate_geometry = projection_drift_summaries( + geometry_reference_cals, + candidate_cals, + geometry_reference_points, + ) + candidate_geometry_max = max_projection_drift(candidate_geometry) + candidate_correspondence = correspondence_replacement_summary( + candidate_points, + candidate_cals, + ) + candidate_correspondence_rate = ( + None + if candidate_correspondence is None + else cast(float, candidate_correspondence["replacement_rate"]) + ) + pose_geometry_ok = geometry_stage_ok( + candidate_geometry_max, + stage_geometry_max, + ) + pose_correspondence_ok = correspondence_stage_ok( + candidate_correspondence_rate, + stage_correspondence_rate, + ) + pose_ok = candidate_rms <= stage_rms and ( + not reject_on_ray_convergence + or candidate_ray_convergence <= stage_ray_convergence + pose_stage_ray_slack + ) + pose_ok = pose_ok and pose_geometry_ok and pose_correspondence_ok + pose_stage_summaries.append( + { + "stage_index": 1, + "micro_stage_index": micro_stage_index, + "released_camera_index": None, + "free_camera_indices": [ + camera_index + for camera_index in range(len(cals)) + if camera_index not in (fixed_camera_indices or []) + ], + "fixed_camera_indices": fixed_camera_indices, + "reprojection_rms": candidate_rms, + "mean_ray_convergence": candidate_ray_convergence, + "geometry": candidate_geometry, + "geometry_max": candidate_geometry_max, + "geometry_ok": pose_geometry_ok, + "correspondence": candidate_correspondence, + "correspondence_rate": candidate_correspondence_rate, + "correspondence_ok": pose_correspondence_ok, + "accepted": pose_ok or not reject_worse_solution, + "optimize_points": cast(bool, variant["optimize_points"]), + "x_scale": variant["x_scale"], + "result": candidate_result, + } + ) + cast(List[object], pose_result["stages"]).append(candidate_result) + + if reject_worse_solution and not pose_ok: + break + + stage_cals = candidate_cals + stage_points = candidate_points + stage_rms = candidate_rms + stage_ray_convergence = candidate_ray_convergence + stage_geometry = candidate_geometry + stage_geometry_max = candidate_geometry_max + stage_correspondence = candidate_correspondence + stage_correspondence_rate = candidate_correspondence_rate + + pose_cals = stage_cals + pose_points = stage_points + pose_rms = stage_rms + pose_ray_convergence = stage_ray_convergence + pose_geometry = stage_geometry + pose_geometry_max = stage_geometry_max + pose_correspondence = stage_correspondence + pose_correspondence_rate = stage_correspondence_rate else: release_order = [ int(camera_index) for camera_index in pose_release_camera_order @@ -2053,93 +2237,114 @@ def correspondence_stage_ok( for camera_index in range(len(cals)) if camera_index not in released_cameras ] - stage_cals, stage_points, stage_result = multi_camera_bundle_adjustment( - observed_pixels, - current_cals, - cpar, - pose_orient_par, - point_init=current_points, - fixed_camera_indices=stage_fixed, - loss=pose_loss, - method=pose_method, - prior_sigmas=pose_prior_sigmas, - parameter_bounds=pose_parameter_bounds, - max_nfev=pose_max_nfev, - optimize_extrinsics=True, - optimize_points=pose_optimize_points, - known_points=known_points, - known_point_sigmas=known_point_sigmas, - ) - stage_rms = reprojection_rms( - observed_pixels, stage_points, stage_cals, cpar - ) - stage_ray_convergence = mean_ray_convergence( - observed_pixels, stage_cals, cpar - ) - stage_geometry = projection_drift_summaries( - geometry_reference_cals, - stage_cals, - geometry_reference_points, - ) - stage_geometry_max = max_projection_drift(stage_geometry) - stage_correspondence = correspondence_replacement_summary( - stage_points, - stage_cals, - ) - stage_correspondence_rate = ( - None - if stage_correspondence is None - else cast(float, stage_correspondence["replacement_rate"]) - ) - stage_geometry_ok = geometry_stage_ok( - stage_geometry_max, - current_geometry_max, - ) - stage_correspondence_ok = correspondence_stage_ok( - stage_correspondence_rate, - current_correspondence_rate, - ) - stage_ok = stage_rms <= current_rms and ( - not reject_on_ray_convergence - or stage_ray_convergence - <= current_ray_convergence + pose_stage_ray_slack - ) - stage_ok = stage_ok and stage_geometry_ok and stage_correspondence_ok - pose_stage_summaries.append( - { - "stage_index": stage_index, - "released_camera_index": released_camera, - "free_camera_indices": released_cameras.copy(), - "fixed_camera_indices": stage_fixed, - "reprojection_rms": stage_rms, - "mean_ray_convergence": stage_ray_convergence, - "geometry": stage_geometry, - "geometry_max": stage_geometry_max, - "geometry_ok": stage_geometry_ok, - "correspondence": stage_correspondence, - "correspondence_rate": stage_correspondence_rate, - "correspondence_ok": stage_correspondence_ok, - "accepted": stage_ok or not reject_worse_solution, - "result": stage_result, - } - ) - cast(List[object], pose_result["stages"]).append(stage_result) + stage_ok = False + for micro_stage_index, variant in enumerate( + pose_stage_variants_list, + start=1, + ): + stage_cals, stage_points, stage_result = multi_camera_bundle_adjustment( + observed_pixels, + current_cals, + cpar, + pose_orient_par, + point_init=current_points, + fixed_camera_indices=stage_fixed, + loss=cast(str, variant["loss"]), + method=cast(str, variant["method"]), + prior_sigmas=cast( + Optional[Dict[str, float]], + variant["prior_sigmas"], + ), + parameter_bounds=cast( + Optional[Dict[str, Tuple[float, float]]], + variant["parameter_bounds"], + ), + max_nfev=cast(Optional[int], variant["max_nfev"]), + optimize_extrinsics=True, + optimize_points=cast(bool, variant["optimize_points"]), + known_points=known_points, + known_point_sigmas=known_point_sigmas, + x_scale=cast( + Optional[float | Sequence[float] | Dict[str, float]], + variant["x_scale"], + ), + ) + stage_rms = reprojection_rms( + observed_pixels, stage_points, stage_cals, cpar + ) + stage_ray_convergence = mean_ray_convergence( + observed_pixels, stage_cals, cpar + ) + stage_geometry = projection_drift_summaries( + geometry_reference_cals, + stage_cals, + geometry_reference_points, + ) + stage_geometry_max = max_projection_drift(stage_geometry) + stage_correspondence = correspondence_replacement_summary( + stage_points, + stage_cals, + ) + stage_correspondence_rate = ( + None + if stage_correspondence is None + else cast(float, stage_correspondence["replacement_rate"]) + ) + stage_geometry_ok = geometry_stage_ok( + stage_geometry_max, + current_geometry_max, + ) + stage_correspondence_ok = correspondence_stage_ok( + stage_correspondence_rate, + current_correspondence_rate, + ) + stage_ok = stage_rms <= current_rms and ( + not reject_on_ray_convergence + or stage_ray_convergence + <= current_ray_convergence + pose_stage_ray_slack + ) + stage_ok = stage_ok and stage_geometry_ok and stage_correspondence_ok + pose_stage_summaries.append( + { + "stage_index": stage_index, + "micro_stage_index": micro_stage_index, + "released_camera_index": released_camera, + "free_camera_indices": released_cameras.copy(), + "fixed_camera_indices": stage_fixed, + "reprojection_rms": stage_rms, + "mean_ray_convergence": stage_ray_convergence, + "geometry": stage_geometry, + "geometry_max": stage_geometry_max, + "geometry_ok": stage_geometry_ok, + "correspondence": stage_correspondence, + "correspondence_rate": stage_correspondence_rate, + "correspondence_ok": stage_correspondence_ok, + "accepted": stage_ok or not reject_worse_solution, + "optimize_points": cast(bool, variant["optimize_points"]), + "x_scale": variant["x_scale"], + "result": stage_result, + } + ) + cast(List[object], pose_result["stages"]).append(stage_result) + + if reject_worse_solution and not stage_ok: + break + + current_cals = stage_cals + current_points = stage_points + current_rms = stage_rms + current_ray_convergence = stage_ray_convergence + current_geometry = stage_geometry + current_geometry_max = stage_geometry_max + current_correspondence = stage_correspondence + current_correspondence_rate = stage_correspondence_rate + pose_ok = True + pose_geometry_ok = stage_geometry_ok + pose_correspondence_ok = stage_correspondence_ok if reject_worse_solution and not stage_ok: break - current_cals = stage_cals - current_points = stage_points - current_rms = stage_rms - current_ray_convergence = stage_ray_convergence - current_geometry = stage_geometry - current_geometry_max = stage_geometry_max - current_correspondence = stage_correspondence - current_correspondence_rate = stage_correspondence_rate - pose_ok = True - pose_geometry_ok = stage_geometry_ok - pose_correspondence_ok = stage_correspondence_ok - pose_cals = current_cals pose_points = current_points pose_rms = current_rms @@ -2166,6 +2371,7 @@ def correspondence_stage_ok( optimize_points=intrinsic_optimize_points, known_points=known_points, known_point_sigmas=known_point_sigmas, + x_scale=intrinsic_x_scale, ftol=intrinsic_ftol, xtol=intrinsic_xtol, gtol=intrinsic_gtol, @@ -2244,6 +2450,7 @@ def correspondence_stage_ok( "pose_geometry_ok": pose_geometry_ok, "pose_release_camera_order": pose_release_camera_order, "pose_stage_ray_slack": pose_stage_ray_slack, + "pose_stage_configs": normalized_pose_stage_configs, "pose_stage_summaries": pose_stage_summaries, "accepted_pose_stage_count": len( [summary for summary in pose_stage_summaries if summary["accepted"]] diff --git a/tests/test_bundle_adjustment.py b/tests/test_bundle_adjustment.py index 4a72c78..fd2470f 100644 --- a/tests/test_bundle_adjustment.py +++ b/tests/test_bundle_adjustment.py @@ -3,6 +3,7 @@ from unittest.mock import patch import numpy as np +import scipy.optimize from openptv_python.calibration import Calibration, read_calibration from openptv_python.imgcoord import image_coordinates @@ -144,6 +145,63 @@ def test_multi_camera_bundle_adjustment_improves_synthetic_reprojection(self): self.assertLess(after_rms, before_rms * 0.4) self.assertLess(after_rms, 3e-2) + def test_multi_camera_bundle_adjustment_passes_x_scale_to_least_squares(self): + points = np.array( + [ + [-10.0, -10.0, 0.0], + [10.0, -10.0, 1.0], + [-10.0, 10.0, -1.0], + [10.0, 10.0, 0.5], + ], + dtype=float, + ) + observed_pixels = np.empty((len(points), 4, 2), dtype=float) + for cam_num, cal in enumerate(self.true_cals): + observed_pixels[:, cam_num, :] = arr_metric_to_pixel( + image_coordinates(points, cal, self.control.mm), self.control + ) + + start_cals = self.lightly_perturb_calibrations(self.true_cals) + + def fake_least_squares(_fun, x0, **kwargs): + self.assertIn("x_scale", kwargs) + np.testing.assert_allclose( + kwargs["x_scale"], + np.array([0.02, 0.02, 0.02, 2e-4, 2e-4, 2e-4]), + ) + return scipy.optimize.OptimizeResult( + x=x0, + success=True, + message="ok", + ) + + with patch( + "openptv_python.orientation.scipy.optimize.least_squares", + side_effect=fake_least_squares, + ): + _, refined_points, result = multi_camera_bundle_adjustment( + observed_pixels, + start_cals, + self.control, + OrientPar(), + point_init=points.copy(), + fixed_camera_indices=[0, 1, 2], + optimize_points=False, + loss="linear", + method="trf", + x_scale={ + "x0": 0.02, + "y0": 0.02, + "z0": 0.02, + "omega": 2e-4, + "phi": 2e-4, + "kappa": 2e-4, + }, + ) + + self.assertTrue(result.success) + np.testing.assert_allclose(refined_points, points) + def test_cavity_reprojection_improves(self): cavity_dir = Path("tests/testing_fodder/test_cavity") control = ControlPar(4).from_file(cavity_dir / "parameters/ptv.par") @@ -735,6 +793,123 @@ def test_guarded_two_step_bundle_adjustment_supports_staged_camera_release(self) ) self.assertEqual(final_points.shape, points.shape) + def test_guarded_two_step_bundle_adjustment_supports_pose_micro_stages(self): + points = np.array( + [ + [-10.0, -10.0, 0.0], + [10.0, -10.0, 1.0], + [-10.0, 10.0, -1.0], + [10.0, 10.0, 0.5], + ], + dtype=float, + ) + observed_pixels = np.empty((len(points), 4, 2), dtype=float) + for cam_num, cal in enumerate(self.true_cals): + observed_pixels[:, cam_num, :] = arr_metric_to_pixel( + image_coordinates(points, cal, self.control.mm), self.control + ) + + start_cals = self.lightly_perturb_calibrations(self.true_cals) + intrinsics = OrientPar() + intrinsics.k1flag = 1 + intrinsics.p1flag = 1 + intrinsics.p2flag = 1 + + staged_returns = [ + (self.true_cals, points.copy(), {"success": True, "stage": f"pose_{idx}"}) + for idx in range(4) + ] + [(self.true_cals, points.copy(), {"success": True, "stage": "intrinsics"})] + + with ( + patch( + "openptv_python.orientation.multi_camera_bundle_adjustment", + side_effect=staged_returns, + ) as mocked_adjustment, + patch( + "openptv_python.orientation.reprojection_rms", + side_effect=[10.0, 9.5, 9.0, 8.5, 8.0, 7.5], + ), + patch( + "openptv_python.orientation.mean_ray_convergence", + side_effect=[6.0, 5.5, 5.0, 4.5, 4.0, 3.5], + ), + ): + _, _, summary = guarded_two_step_bundle_adjustment( + observed_pixels, + start_cals, + self.control, + OrientPar(), + intrinsics, + point_init=points, + pose_release_camera_order=[0, 1], + pose_stage_configs=[ + { + "optimize_points": False, + "max_nfev": 3, + "x_scale": { + "x0": 0.02, + "y0": 0.02, + "z0": 0.02, + "omega": 2e-4, + "phi": 2e-4, + "kappa": 2e-4, + }, + }, + { + "optimize_points": True, + "max_nfev": 4, + "x_scale": { + "x0": 0.05, + "y0": 0.05, + "z0": 0.05, + "omega": 5e-4, + "phi": 5e-4, + "kappa": 5e-4, + "points": 0.1, + }, + }, + ], + intrinsic_max_nfev=5, + ) + + self.assertEqual(mocked_adjustment.call_count, 5) + fixed_sequences = [ + call.kwargs.get("fixed_camera_indices") + for call in mocked_adjustment.call_args_list + ] + self.assertEqual( + fixed_sequences, + [[1, 2, 3], [1, 2, 3], [2, 3], [2, 3], [0, 1, 2, 3]], + ) + self.assertEqual( + [ + call.kwargs.get("optimize_points") + for call in mocked_adjustment.call_args_list[:-1] + ], + [False, True, False, True], + ) + self.assertEqual( + mocked_adjustment.call_args_list[0].kwargs.get("x_scale"), + { + "x0": 0.02, + "y0": 0.02, + "z0": 0.02, + "omega": 2e-4, + "phi": 2e-4, + "kappa": 2e-4, + }, + ) + self.assertEqual(summary["accepted_pose_stage_count"], 4) + self.assertEqual(len(summary["pose_stage_summaries"]), 4) + self.assertEqual( + [stage["micro_stage_index"] for stage in summary["pose_stage_summaries"]], + [1, 2, 1, 2], + ) + self.assertEqual( + [stage["optimize_points"] for stage in summary["pose_stage_summaries"]], + [False, True, False, True], + ) + def test_guarded_two_step_bundle_adjustment_stagewise_ray_slack_allows_near_miss( self, ): diff --git a/tests/test_demo_bundle_adjustment.py b/tests/test_demo_bundle_adjustment.py index c68978f..63637af 100644 --- a/tests/test_demo_bundle_adjustment.py +++ b/tests/test_demo_bundle_adjustment.py @@ -1,10 +1,12 @@ import unittest from pathlib import Path +from unittest.mock import patch import numpy as np from openptv_python.calibration import read_calibration from openptv_python.demo_bundle_adjustment import ( + ExperimentSpec, ExperimentResult, all_fixed_camera_pairs, build_experiment_start_calibrations, @@ -16,12 +18,15 @@ load_reference_geometry_points, normalize_staged_release_order, perturb_calibrations, + run_experiment, summarize_epipolar_consistency, summarize_fixed_camera_diagnostics, summarize_quadruplet_sensitivity, ) +from openptv_python.imgcoord import image_coordinates from openptv_python.parameters import ControlPar, read_volume_par from openptv_python.tracking_frame_buf import read_path_frame, read_targets +from openptv_python.trafo import arr_metric_to_pixel class TestBundleAdjustmentDemo(unittest.TestCase): @@ -47,6 +52,7 @@ def test_default_experiments_adds_known_point_presets(self): names = [spec.name for spec in experiments] self.assertIn("intrinsics_only", names) + self.assertIn("intrinsics_first_guarded_stagewise_release", names) self.assertIn("pose_trf_known_points", names) self.assertIn("guarded_two_step_known_points", names) self.assertIn("guarded_stagewise_release", names) @@ -71,6 +77,11 @@ def test_default_experiments_adds_known_point_presets(self): guarded_spec = next( spec for spec in experiments if spec.name == "guarded_two_step_known_points" ) + intrinsics_first_spec = next( + spec + for spec in experiments + if spec.name == "intrinsics_first_guarded_stagewise_release" + ) staged_spec = next( spec for spec in experiments if spec.name == "guarded_stagewise_release" ) @@ -88,14 +99,115 @@ def test_default_experiments_adds_known_point_presets(self): staged_spec.ba_kwargs["pose_release_camera_order"], [0, 1, 2, 3] ) self.assertEqual(staged_spec.ba_kwargs["pose_stage_ray_slack"], 0.0) + self.assertEqual(len(staged_spec.ba_kwargs["pose_stage_configs"]), 3) + self.assertFalse(staged_spec.ba_kwargs["pose_stage_configs"][0]["optimize_points"]) + self.assertTrue(staged_spec.ba_kwargs["pose_stage_configs"][1]["optimize_points"]) + self.assertTrue(staged_spec.ba_kwargs["pose_stage_configs"][2]["optimize_points"]) + self.assertEqual(intrinsics_first_spec.mode, "intrinsics_then_guarded") + self.assertFalse(intrinsics_first_spec.ba_kwargs["warmstart_optimize_extrinsics"]) + self.assertFalse(intrinsics_first_spec.ba_kwargs["warmstart_optimize_points"]) + self.assertEqual( + intrinsics_first_spec.ba_kwargs["pose_release_camera_order"], + [0, 1, 2, 3], + ) + self.assertEqual( + len(intrinsics_first_spec.ba_kwargs["pose_stage_configs"]), + 2, + ) + self.assertFalse( + intrinsics_first_spec.ba_kwargs["pose_stage_configs"][0]["optimize_points"] + ) + self.assertTrue( + intrinsics_first_spec.ba_kwargs["pose_stage_configs"][1]["optimize_points"] + ) self.assertIs(staged_known_spec.ba_kwargs["known_points"], known_points) self.assertEqual(staged_known_spec.ba_kwargs["known_point_sigmas"], 0.25) + self.assertTrue( + all( + stage_config["optimize_points"] + for stage_config in guarded_spec.ba_kwargs["pose_stage_configs"] + ) + ) + self.assertTrue( + all( + stage_config["optimize_points"] + for stage_config in staged_known_spec.ba_kwargs["pose_stage_configs"] + ) + ) self.assertEqual(guarded_spec.ba_kwargs["geometry_guard_mode"], "off") self.assertIsNone(guarded_spec.ba_kwargs["geometry_guard_threshold"]) self.assertEqual(guarded_spec.ba_kwargs["correspondence_guard_mode"], "off") self.assertIsNone(guarded_spec.ba_kwargs["correspondence_guard_threshold"]) self.assertIsNone(guarded_spec.ba_kwargs["correspondence_guard_reference_rate"]) + def test_run_experiment_intrinsics_then_guarded_executes_warmstart_first(self): + control = ControlPar(4).from_file( + Path("tests/testing_folder/control_parameters/control.par") + ) + add_file = Path("tests/testing_folder/calibration/cam1.tif.addpar") + reference_cals = [ + read_calibration( + Path(f"tests/testing_folder/calibration/sym_cam{cam_num}.tif.ori"), + add_file, + ) + for cam_num in range(1, 5) + ] + start_cals = perturb_calibrations(reference_cals, 1.0) + points = np.array( + [ + [-10.0, -10.0, 0.0], + [10.0, -10.0, 1.0], + [-10.0, 10.0, -1.0], + [10.0, 10.0, 0.5], + ], + dtype=float, + ) + observed_pixels = np.empty((len(points), 4, 2), dtype=float) + for cam_num, cal in enumerate(reference_cals): + observed_pixels[:, cam_num, :] = arr_metric_to_pixel( + image_coordinates(points, cal, control.mm), + control, + ) + + spec = next( + experiment + for experiment in default_experiments(perturbation_scale=1.0) + if experiment.name == "intrinsics_first_guarded_stagewise_release" + ) + + with ( + patch( + "openptv_python.demo_bundle_adjustment.multi_camera_bundle_adjustment", + return_value=(reference_cals, points.copy(), {"success": True, "final_reprojection_rms": 3.8, "message": "warm"}), + ) as warmstart, + patch( + "openptv_python.demo_bundle_adjustment.guarded_two_step_bundle_adjustment", + return_value=(reference_cals, points.copy(), {"accepted_stage": "intrinsics", "final_reprojection_rms": 3.7, "final_mean_ray_convergence": 0.2}), + ) as guarded, + ): + result = run_experiment( + spec, + observed_pixels=observed_pixels, + point_init=points, + control=control, + start_cals=start_cals, + reference_cals=reference_cals, + reference_geometry_points=None, + tracking_data=None, + geometry_export_threshold=None, + correspondence_export_threshold=None, + source_case_dir=Path("tests/testing_folder"), + output_dir=None, + ) + + self.assertEqual(warmstart.call_count, 1) + self.assertEqual(guarded.call_count, 1) + self.assertFalse(warmstart.call_args.kwargs["optimize_extrinsics"]) + self.assertFalse(warmstart.call_args.kwargs["optimize_points"]) + np.testing.assert_allclose(guarded.call_args.kwargs["point_init"], points) + self.assertIn("warmstart_rms=3.800000", result.notes) + self.assertIn("accepted_stage=intrinsics", result.notes) + def test_default_experiments_accepts_geometry_guard_configuration(self): experiments = default_experiments( num_cams=4, From 5bdcdb4703c987a6df6f9115bf5a0b24eda76b39 Mon Sep 17 00:00:00 2001 From: Alex Liberzon Date: Sat, 7 Mar 2026 13:49:31 +0200 Subject: [PATCH 12/27] additonal block wise alternative bundle adjustment --- openptv_python/demo_bundle_adjustment.py | 158 ++++++ openptv_python/orientation.py | 675 ++++++++++++++++++++++- tests/test_bundle_adjustment.py | 90 +++ tests/test_demo_bundle_adjustment.py | 84 +++ 4 files changed, 1006 insertions(+), 1 deletion(-) diff --git a/openptv_python/demo_bundle_adjustment.py b/openptv_python/demo_bundle_adjustment.py index 5eac602..778be24 100644 --- a/openptv_python/demo_bundle_adjustment.py +++ b/openptv_python/demo_bundle_adjustment.py @@ -19,6 +19,7 @@ from .epi import epipolar_curve from .imgcoord import image_coordinates, img_coord from .orientation import ( + alternating_bundle_adjustment, guarded_two_step_bundle_adjustment, initialize_bundle_adjustment_points, mean_ray_convergence, @@ -1361,6 +1362,42 @@ def default_experiments( "correspondence_guard_reference_rate": correspondence_guard_reference_rate, }, ), + ExperimentSpec( + name="intrinsics_first_alternating_stagewise_release", + description=( + "Intrinsics-only warm start followed by alternating " + "point/rotation/translation guarded updates" + ), + mode="alternating", + ba_kwargs={ + "pose_orient_par": OrientPar(), + "intrinsic_orient_par": intrinsics, + "fixed_camera_indices": staged_fixed, + "pose_release_camera_order": staged_order, + "pose_stage_ray_slack": pose_stage_ray_slack, + "pose_prior_sigmas": pose_priors, + "pose_parameter_bounds": pose_bounds, + "pose_max_nfev": 4, + "pose_x_scale": { + "x0": 0.02, + "y0": 0.02, + "z0": 0.02, + "omega": 2e-4, + "phi": 2e-4, + "kappa": 2e-4, + "points": 0.1, + }, + "pose_block_configs": intrinsics_first_pose_stage_configs, + "intrinsic_prior_sigmas": tight_intrinsic_priors, + "intrinsic_parameter_bounds": tight_intrinsic_bounds, + "intrinsic_max_nfev": 4, + "geometry_guard_mode": geometry_guard_mode, + "geometry_guard_threshold": geometry_guard_threshold, + "correspondence_guard_mode": correspondence_guard_mode, + "correspondence_guard_threshold": correspondence_guard_threshold, + "correspondence_guard_reference_rate": correspondence_guard_reference_rate, + }, + ), ] if known_points: @@ -1869,6 +1906,127 @@ def run_experiment( f"warmstart_rms={warmstart_rms:.6f}; " f"accepted_stage={summary['accepted_stage']}" ) + elif spec.mode == "alternating": + pose_orient_par = cast(OrientPar, spec.ba_kwargs["pose_orient_par"]) + intrinsic_orient_par = cast(OrientPar, spec.ba_kwargs["intrinsic_orient_par"]) + refined_cals, refined_points, summary = alternating_bundle_adjustment( + observed_pixels, + working_cals, + control, + pose_orient_par, + intrinsic_orient_par, + point_init=point_init, + fixed_camera_indices=cast( + List[int] | None, + spec.ba_kwargs.get("fixed_camera_indices"), + ), + pose_release_camera_order=cast( + List[int] | None, + spec.ba_kwargs.get("pose_release_camera_order"), + ), + pose_stage_ray_slack=cast( + float, + spec.ba_kwargs.get("pose_stage_ray_slack", 0.0), + ), + pose_prior_sigmas=cast( + Dict[str, float] | None, + spec.ba_kwargs.get("pose_prior_sigmas"), + ), + pose_parameter_bounds=cast( + Dict[str, tuple[float, float]] | None, + spec.ba_kwargs.get("pose_parameter_bounds"), + ), + pose_loss=cast(str, spec.ba_kwargs.get("pose_loss", "linear")), + pose_method=cast(str, spec.ba_kwargs.get("pose_method", "trf")), + pose_max_nfev=cast(int | None, spec.ba_kwargs.get("pose_max_nfev")), + pose_x_scale=cast( + float | Sequence[float] | Dict[str, float] | None, + spec.ba_kwargs.get("pose_x_scale"), + ), + pose_block_configs=cast( + Sequence[Dict[str, object]] | None, + spec.ba_kwargs.get("pose_block_configs"), + ), + intrinsic_prior_sigmas=cast( + Dict[str, float] | None, + spec.ba_kwargs.get("intrinsic_prior_sigmas"), + ), + intrinsic_parameter_bounds=cast( + Dict[str, tuple[float, float]] | None, + spec.ba_kwargs.get("intrinsic_parameter_bounds"), + ), + intrinsic_loss=cast( + str, + spec.ba_kwargs.get("intrinsic_loss", "linear"), + ), + intrinsic_method=cast( + str, + spec.ba_kwargs.get("intrinsic_method", "trf"), + ), + intrinsic_max_nfev=cast( + int | None, + spec.ba_kwargs.get("intrinsic_max_nfev"), + ), + intrinsic_x_scale=cast( + float | Sequence[float] | Dict[str, float] | None, + spec.ba_kwargs.get("intrinsic_x_scale"), + ), + intrinsic_ftol=cast( + float | None, spec.ba_kwargs.get("intrinsic_ftol", 1e-12) + ), + intrinsic_xtol=cast( + float | None, spec.ba_kwargs.get("intrinsic_xtol", 1e-12) + ), + intrinsic_gtol=cast( + float | None, spec.ba_kwargs.get("intrinsic_gtol", 1e-12) + ), + known_points=cast( + Dict[int, np.ndarray] | None, + spec.ba_kwargs.get("known_points"), + ), + known_point_sigmas=cast( + float | np.ndarray | None, + spec.ba_kwargs.get("known_point_sigmas"), + ), + geometry_reference_points=reference_geometry_points, + geometry_reference_cals=reference_cals, + geometry_guard_mode=cast( + str, + spec.ba_kwargs.get("geometry_guard_mode", "off"), + ), + geometry_guard_threshold=cast( + float | None, + spec.ba_kwargs.get("geometry_guard_threshold"), + ), + correspondence_original_ids=( + None if tracking_data is None else tracking_data.original_target_ids + ), + correspondence_point_frame_indices=( + None if tracking_data is None else tracking_data.point_frame_indices + ), + correspondence_frame_target_pixels=( + None if tracking_data is None else tracking_data.frame_target_pixels + ), + correspondence_guard_mode=cast( + str, + spec.ba_kwargs.get("correspondence_guard_mode", "off"), + ), + correspondence_guard_threshold=cast( + float | None, + spec.ba_kwargs.get("correspondence_guard_threshold"), + ), + correspondence_guard_reference_rate=cast( + float | None, + spec.ba_kwargs.get("correspondence_guard_reference_rate"), + ), + ) + success = True + final_rms = cast(float, summary["final_reprojection_rms"]) + final_ray = cast(float, summary["final_mean_ray_convergence"]) + notes = ( + f"warmstart_ok={summary['warmstart_ok']}; " + f"accepted_stage={summary['accepted_stage']}" + ) else: raise ValueError(f"Unknown experiment mode: {spec.mode}") diff --git a/openptv_python/orientation.py b/openptv_python/orientation.py index 1074deb..b273ce6 100644 --- a/openptv_python/orientation.py +++ b/openptv_python/orientation.py @@ -1568,7 +1568,12 @@ def multi_camera_bundle_adjustment( else: optimized_cam_indices = list(range(num_cams)) - if not optimize_extrinsics and not optional_names and not include_interface: + if ( + not optimize_extrinsics + and not optional_names + and not include_interface + and not optimize_points + ): raise ValueError("No camera parameters are enabled for optimization") if not optimize_points and not optimized_cam_indices: @@ -2481,3 +2486,671 @@ def correspondence_stage_ok( } return final_cals, final_points, summary + + +def alternating_bundle_adjustment( + observed_pixels: np.ndarray, + cals: List[Calibration], + cpar: ControlPar, + pose_orient_par: OrientPar, + intrinsic_orient_par: OrientPar, + *, + point_init: Optional[np.ndarray] = None, + fixed_camera_indices: Optional[List[int]] = None, + pose_release_camera_order: Optional[List[int]] = None, + pose_stage_ray_slack: float = 0.0, + pose_prior_sigmas: Optional[Dict[str, float]] = None, + pose_parameter_bounds: Optional[Dict[str, Tuple[float, float]]] = None, + pose_loss: str = "linear", + pose_method: str = "trf", + pose_max_nfev: Optional[int] = None, + pose_x_scale: Optional[float | Sequence[float] | Dict[str, float]] = None, + pose_block_configs: Optional[Sequence[Dict[str, object]]] = None, + intrinsic_prior_sigmas: Optional[Dict[str, float]] = None, + intrinsic_parameter_bounds: Optional[Dict[str, Tuple[float, float]]] = None, + intrinsic_loss: str = "linear", + intrinsic_method: str = "trf", + intrinsic_max_nfev: Optional[int] = None, + intrinsic_x_scale: Optional[float | Sequence[float] | Dict[str, float]] = None, + intrinsic_ftol: Optional[float] = 1e-12, + intrinsic_xtol: Optional[float] = 1e-12, + intrinsic_gtol: Optional[float] = 1e-12, + known_points: Optional[Dict[int, np.ndarray]] = None, + known_point_sigmas: Optional[float | np.ndarray] = None, + geometry_reference_points: Optional[np.ndarray] = None, + geometry_reference_cals: Optional[List[Calibration]] = None, + geometry_guard_mode: str = "off", + geometry_guard_threshold: Optional[float] = None, + correspondence_original_ids: Optional[np.ndarray] = None, + correspondence_point_frame_indices: Optional[np.ndarray] = None, + correspondence_frame_target_pixels: Optional[Sequence[Sequence[np.ndarray]]] = None, + correspondence_guard_mode: str = "off", + correspondence_guard_threshold: Optional[float] = None, + correspondence_guard_reference_rate: Optional[float] = None, + reject_worse_solution: bool = True, + reject_on_ray_convergence: bool = True, +) -> Tuple[List[Calibration], np.ndarray, Dict[str, object]]: + """Run intrinsics-first alternating BA with point/rotation/translation block updates.""" + if pose_stage_ray_slack < 0: + raise ValueError("pose_stage_ray_slack must be non-negative") + + def merge_bounds( + base: Optional[Dict[str, Tuple[float, float]]], + updates: Optional[Dict[str, Tuple[float, float]]], + ) -> Optional[Dict[str, Tuple[float, float]]]: + merged = dict(base or {}) + if updates is not None: + merged.update(updates) + return merged or None + + def default_pose_blocks() -> List[Dict[str, object]]: + return [ + { + "name": "points_only", + "optimize_extrinsics": False, + "optimize_points": True, + "loss": pose_loss, + "method": pose_method, + "max_nfev": 4 if pose_max_nfev is None else min(4, pose_max_nfev), + "x_scale": {"points": 0.1}, + }, + { + "name": "rotation_only", + "optimize_extrinsics": True, + "optimize_points": False, + "freeze_translation": True, + "loss": pose_loss, + "method": pose_method, + "max_nfev": 4 if pose_max_nfev is None else min(4, pose_max_nfev), + "x_scale": { + "omega": 2e-4, + "phi": 2e-4, + "kappa": 2e-4, + }, + }, + { + "name": "translation_only", + "optimize_extrinsics": True, + "optimize_points": False, + "freeze_rotation": True, + "loss": pose_loss, + "method": pose_method, + "max_nfev": 4 if pose_max_nfev is None else min(4, pose_max_nfev), + "x_scale": { + "x0": 0.02, + "y0": 0.02, + "z0": 0.02, + }, + }, + { + "name": "joint_pose_points", + "optimize_extrinsics": True, + "optimize_points": True, + "loss": pose_loss, + "method": pose_method, + "max_nfev": pose_max_nfev, + "x_scale": pose_x_scale, + }, + ] + + normalized_pose_block_configs = list( + default_pose_blocks() if pose_block_configs is None else pose_block_configs + ) + + def projection_drift_summaries( + reference_cals: List[Calibration], + candidate_cals: List[Calibration], + reference_points: Optional[np.ndarray], + ) -> Optional[List[Dict[str, float]]]: + if reference_points is None: + return None + + summaries: List[Dict[str, float]] = [] + for camera_index, (reference_cal, candidate_cal) in enumerate( + zip(reference_cals, candidate_cals), + start=1, + ): + reference_pixels = [] + candidate_pixels = [] + for point in reference_points: + ref_x, ref_y = img_coord(point, reference_cal, cpar.mm) + cand_x, cand_y = img_coord(point, candidate_cal, cpar.mm) + reference_pixels.append(metric_to_pixel(ref_x, ref_y, cpar)) + candidate_pixels.append(metric_to_pixel(cand_x, cand_y, cpar)) + + displacement = np.linalg.norm( + np.asarray(candidate_pixels) - np.asarray(reference_pixels), + axis=1, + ) + summaries.append( + { + "camera_index": float(camera_index), + "mean_distance": float(displacement.mean()), + "p95_distance": float(np.percentile(displacement, 95.0)), + "max_distance": float(displacement.max()), + } + ) + + return summaries + + def max_projection_drift( + summaries: Optional[List[Dict[str, float]]], + ) -> Optional[float]: + if not summaries: + return None + return max(item["max_distance"] for item in summaries) + + def geometry_stage_ok( + candidate_metric: Optional[float], + baseline_metric: Optional[float], + ) -> bool: + if geometry_guard_mode == "off" or candidate_metric is None: + return True + if geometry_guard_mode == "soft": + if baseline_metric is None: + return True + return candidate_metric <= baseline_metric + 1e-12 + if geometry_guard_mode == "hard": + if geometry_guard_threshold is None or geometry_guard_threshold <= 0: + raise ValueError( + "geometry_guard_threshold must be positive when geometry_guard_mode='hard'" + ) + return candidate_metric <= geometry_guard_threshold + raise ValueError("geometry_guard_mode must be one of 'off', 'soft', or 'hard'") + + def correspondence_replacement_summary( + candidate_points: np.ndarray, + candidate_cals: List[Calibration], + ) -> Optional[Dict[str, object]]: + if ( + correspondence_original_ids is None + or correspondence_point_frame_indices is None + or correspondence_frame_target_pixels is None + ): + return None + + projected_pixels = np.empty( + (candidate_points.shape[0], len(candidate_cals), 2), + dtype=np.float64, + ) + for camera_index, cal in enumerate(candidate_cals): + projected_pixels[:, camera_index, :] = arr_metric_to_pixel( + image_coordinates(candidate_points, cal, cpar.mm), + cpar, + ) + + replacement_ids = np.empty_like(correspondence_original_ids) + nearest_distances = np.empty_like( + correspondence_original_ids, + dtype=np.float64, + ) + for point_index in range(candidate_points.shape[0]): + frame_targets = correspondence_frame_target_pixels[ + int(correspondence_point_frame_indices[point_index]) + ] + for camera_index in range(len(candidate_cals)): + deltas = ( + frame_targets[camera_index] + - projected_pixels[point_index, camera_index] + ) + squared_distances = np.sum(deltas * deltas, axis=1) + nearest_index = int(np.argmin(squared_distances)) + replacement_ids[point_index, camera_index] = nearest_index + nearest_distances[point_index, camera_index] = float( + np.sqrt(squared_distances[nearest_index]) + ) + + changed_mask = np.any( + replacement_ids != correspondence_original_ids, + axis=1, + ) + camera_change_rates = [ + float( + np.mean( + replacement_ids[:, camera_index] + != correspondence_original_ids[:, camera_index] + ) + ) + for camera_index in range(len(candidate_cals)) + ] + return { + "replacement_rate": float(np.mean(changed_mask)), + "camera_change_rates": camera_change_rates, + "mean_nearest_distance": float(np.mean(nearest_distances)), + "p95_nearest_distance": float(np.percentile(nearest_distances, 95.0)), + "max_nearest_distance": float(np.max(nearest_distances)), + } + + def correspondence_stage_ok( + candidate_rate: Optional[float], + prior_rate: Optional[float], + ) -> bool: + if correspondence_guard_mode == "off" or candidate_rate is None: + return True + if correspondence_guard_mode == "soft": + if correspondence_guard_reference_rate is not None: + return candidate_rate <= correspondence_guard_reference_rate + 1e-12 + if prior_rate is None: + return True + return candidate_rate <= prior_rate + 1e-12 + if correspondence_guard_mode == "hard": + if ( + correspondence_guard_threshold is None + or correspondence_guard_threshold <= 0 + ): + raise ValueError( + "correspondence_guard_threshold must be positive when correspondence_guard_mode='hard'" + ) + return candidate_rate <= correspondence_guard_threshold + raise ValueError( + "correspondence_guard_mode must be one of 'off', 'soft', or 'hard'" + ) + + base_cals = [_clone_calibration(cal) for cal in cals] + if point_init is None: + base_points, _ = initialize_bundle_adjustment_points( + observed_pixels, base_cals, cpar + ) + else: + base_points = np.asarray(point_init, dtype=np.float64) + if base_points.shape != (observed_pixels.shape[0], 3): + raise ValueError("point_init must have shape (num_points, 3)") + + baseline_rms = reprojection_rms(observed_pixels, base_points, base_cals, cpar) + baseline_ray_convergence = mean_ray_convergence(observed_pixels, base_cals, cpar) + if geometry_reference_cals is None: + geometry_reference_cals = [_clone_calibration(cal) for cal in base_cals] + + baseline_geometry = projection_drift_summaries( + geometry_reference_cals, + base_cals, + geometry_reference_points, + ) + baseline_geometry_max = max_projection_drift(baseline_geometry) + baseline_correspondence = correspondence_replacement_summary( + base_points, + base_cals, + ) + baseline_correspondence_rate = ( + None + if baseline_correspondence is None + else cast(float, baseline_correspondence["replacement_rate"]) + ) + + warmstart_fixed = list(range(len(cals))) + warmstart_cals, warmstart_points, warmstart_result = multi_camera_bundle_adjustment( + observed_pixels, + base_cals, + cpar, + intrinsic_orient_par, + point_init=base_points, + fixed_camera_indices=warmstart_fixed, + loss=intrinsic_loss, + method=intrinsic_method, + prior_sigmas=intrinsic_prior_sigmas, + parameter_bounds=intrinsic_parameter_bounds, + max_nfev=intrinsic_max_nfev, + optimize_extrinsics=False, + optimize_points=False, + x_scale=intrinsic_x_scale, + ftol=intrinsic_ftol, + xtol=intrinsic_xtol, + gtol=intrinsic_gtol, + ) + warmstart_rms = reprojection_rms(observed_pixels, warmstart_points, warmstart_cals, cpar) + warmstart_ray_convergence = mean_ray_convergence(observed_pixels, warmstart_cals, cpar) + warmstart_geometry = projection_drift_summaries( + geometry_reference_cals, + warmstart_cals, + geometry_reference_points, + ) + warmstart_geometry_max = max_projection_drift(warmstart_geometry) + warmstart_correspondence = correspondence_replacement_summary( + warmstart_points, + warmstart_cals, + ) + warmstart_correspondence_rate = ( + None + if warmstart_correspondence is None + else cast(float, warmstart_correspondence["replacement_rate"]) + ) + warmstart_ok = warmstart_rms <= baseline_rms and ( + not reject_on_ray_convergence + or warmstart_ray_convergence <= baseline_ray_convergence + ) + warmstart_ok = warmstart_ok and geometry_stage_ok( + warmstart_geometry_max, + baseline_geometry_max, + ) + warmstart_ok = warmstart_ok and correspondence_stage_ok( + warmstart_correspondence_rate, + baseline_correspondence_rate, + ) + + if reject_worse_solution and not warmstart_ok: + current_cals = [_clone_calibration(cal) for cal in base_cals] + current_points = np.asarray(base_points, dtype=np.float64).copy() + current_rms = baseline_rms + current_ray_convergence = baseline_ray_convergence + current_geometry = baseline_geometry + current_geometry_max = baseline_geometry_max + current_correspondence = baseline_correspondence + current_correspondence_rate = baseline_correspondence_rate + else: + current_cals = warmstart_cals + current_points = warmstart_points + current_rms = warmstart_rms + current_ray_convergence = warmstart_ray_convergence + current_geometry = warmstart_geometry + current_geometry_max = warmstart_geometry_max + current_correspondence = warmstart_correspondence + current_correspondence_rate = warmstart_correspondence_rate + + if pose_release_camera_order is None: + release_order = [ + camera_index + for camera_index in range(len(cals)) + if camera_index not in (fixed_camera_indices or []) + ] + else: + release_order = [int(camera_index) for camera_index in pose_release_camera_order] + if not release_order: + raise ValueError("pose_release_camera_order must not be empty") + + alternating_result: Dict[str, object] = { + "warmstart_result": warmstart_result, + "stages": [], + } + block_summaries: List[Dict[str, object]] = [] + pose_ok = False + pose_geometry_ok = True + pose_correspondence_ok = True + released_cameras: List[int] = [] + + for stage_index, released_camera in enumerate(release_order, start=1): + released_cameras.append(released_camera) + stage_fixed = [ + camera_index + for camera_index in range(len(cals)) + if camera_index not in released_cameras + ] + stage_completed = True + for block_index, block in enumerate(normalized_pose_block_configs, start=1): + block_config = dict(block) + optimize_extrinsics = cast( + bool, + block_config.get("optimize_extrinsics", True), + ) + optimize_points = cast(bool, block_config.get("optimize_points", True)) + block_bounds = merge_bounds( + pose_parameter_bounds, + cast( + Optional[Dict[str, Tuple[float, float]]], + block_config.get("parameter_bounds"), + ), + ) + if cast(bool, block_config.get("freeze_translation", False)): + block_bounds = merge_bounds( + block_bounds, + {"x0": (0.0, 0.0), "y0": (0.0, 0.0), "z0": (0.0, 0.0)}, + ) + if cast(bool, block_config.get("freeze_rotation", False)): + block_bounds = merge_bounds( + block_bounds, + { + "omega": (0.0, 0.0), + "phi": (0.0, 0.0), + "kappa": (0.0, 0.0), + }, + ) + + block_known_points = known_points if optimize_points else None + block_known_point_sigmas = known_point_sigmas if optimize_points else None + block_cals, block_points, block_result = multi_camera_bundle_adjustment( + observed_pixels, + current_cals, + cpar, + pose_orient_par, + point_init=current_points, + fixed_camera_indices=stage_fixed, + loss=cast(str, block_config.get("loss", pose_loss)), + method=cast(str, block_config.get("method", pose_method)), + prior_sigmas=cast( + Optional[Dict[str, float]], + block_config.get("prior_sigmas", pose_prior_sigmas), + ), + parameter_bounds=block_bounds, + max_nfev=cast(Optional[int], block_config.get("max_nfev", pose_max_nfev)), + optimize_extrinsics=optimize_extrinsics, + optimize_points=optimize_points, + known_points=block_known_points, + known_point_sigmas=block_known_point_sigmas, + x_scale=cast( + Optional[float | Sequence[float] | Dict[str, float]], + block_config.get("x_scale", pose_x_scale), + ), + ) + block_rms = reprojection_rms(observed_pixels, block_points, block_cals, cpar) + block_ray_convergence = mean_ray_convergence( + observed_pixels, + block_cals, + cpar, + ) + block_geometry = projection_drift_summaries( + geometry_reference_cals, + block_cals, + geometry_reference_points, + ) + block_geometry_max = max_projection_drift(block_geometry) + block_correspondence = correspondence_replacement_summary( + block_points, + block_cals, + ) + block_correspondence_rate = ( + None + if block_correspondence is None + else cast(float, block_correspondence["replacement_rate"]) + ) + block_geometry_ok = geometry_stage_ok( + block_geometry_max, + current_geometry_max, + ) + block_correspondence_ok = correspondence_stage_ok( + block_correspondence_rate, + current_correspondence_rate, + ) + block_ok = block_rms <= current_rms and ( + not reject_on_ray_convergence + or block_ray_convergence <= current_ray_convergence + pose_stage_ray_slack + ) + block_ok = block_ok and block_geometry_ok and block_correspondence_ok + block_summaries.append( + { + "stage_index": stage_index, + "block_index": block_index, + "block_name": block_config.get("name", f"block_{block_index}"), + "released_camera_index": released_camera, + "free_camera_indices": released_cameras.copy(), + "fixed_camera_indices": stage_fixed, + "reprojection_rms": block_rms, + "mean_ray_convergence": block_ray_convergence, + "geometry": block_geometry, + "geometry_max": block_geometry_max, + "geometry_ok": block_geometry_ok, + "correspondence": block_correspondence, + "correspondence_rate": block_correspondence_rate, + "correspondence_ok": block_correspondence_ok, + "accepted": block_ok or not reject_worse_solution, + "optimize_extrinsics": optimize_extrinsics, + "optimize_points": optimize_points, + "result": block_result, + } + ) + cast(List[object], alternating_result["stages"]).append(block_result) + + if reject_worse_solution and not block_ok: + stage_completed = False + break + + current_cals = block_cals + current_points = block_points + current_rms = block_rms + current_ray_convergence = block_ray_convergence + current_geometry = block_geometry + current_geometry_max = block_geometry_max + current_correspondence = block_correspondence + current_correspondence_rate = block_correspondence_rate + pose_ok = True + pose_geometry_ok = block_geometry_ok + pose_correspondence_ok = block_correspondence_ok + + if reject_worse_solution and not stage_completed: + break + + pose_cals = current_cals + pose_points = current_points + pose_rms = current_rms + pose_ray_convergence = current_ray_convergence + pose_geometry = current_geometry + pose_geometry_max = current_geometry_max + pose_correspondence = current_correspondence + pose_correspondence_rate = current_correspondence_rate + + intrinsic_fixed = list(range(len(cals))) + intrinsic_cals, intrinsic_points, intrinsic_result = multi_camera_bundle_adjustment( + observed_pixels, + pose_cals, + cpar, + intrinsic_orient_par, + point_init=pose_points, + fixed_camera_indices=intrinsic_fixed, + loss=intrinsic_loss, + method=intrinsic_method, + prior_sigmas=intrinsic_prior_sigmas, + parameter_bounds=intrinsic_parameter_bounds, + max_nfev=intrinsic_max_nfev, + optimize_extrinsics=False, + optimize_points=False, + x_scale=intrinsic_x_scale, + ftol=intrinsic_ftol, + xtol=intrinsic_xtol, + gtol=intrinsic_gtol, + ) + intrinsic_rms = reprojection_rms( + observed_pixels, intrinsic_points, intrinsic_cals, cpar + ) + intrinsic_ray_convergence = mean_ray_convergence( + observed_pixels, intrinsic_cals, cpar + ) + intrinsic_geometry = projection_drift_summaries( + geometry_reference_cals, + intrinsic_cals, + geometry_reference_points, + ) + intrinsic_geometry_max = max_projection_drift(intrinsic_geometry) + intrinsic_correspondence = correspondence_replacement_summary( + intrinsic_points, + intrinsic_cals, + ) + intrinsic_correspondence_rate = ( + None + if intrinsic_correspondence is None + else cast(float, intrinsic_correspondence["replacement_rate"]) + ) + + accepted_stage = "intrinsics" + final_cals = intrinsic_cals + final_points = intrinsic_points + final_rms = intrinsic_rms + final_ray_convergence = intrinsic_ray_convergence + intrinsic_ok = intrinsic_rms <= pose_rms and ( + not reject_on_ray_convergence + or intrinsic_ray_convergence <= pose_ray_convergence + ) + intrinsic_ok = intrinsic_ok and geometry_stage_ok( + intrinsic_geometry_max, + pose_geometry_max, + ) + intrinsic_ok = intrinsic_ok and correspondence_stage_ok( + intrinsic_correspondence_rate, + pose_correspondence_rate, + ) + + if reject_worse_solution: + if not pose_ok: + if warmstart_ok: + accepted_stage = "warmstart" + final_cals = warmstart_cals + final_points = warmstart_points + final_rms = warmstart_rms + final_ray_convergence = warmstart_ray_convergence + else: + accepted_stage = "baseline" + final_cals = base_cals + final_points = base_points + final_rms = baseline_rms + final_ray_convergence = baseline_ray_convergence + elif not intrinsic_ok: + accepted_stage = "pose_blocks" + final_cals = pose_cals + final_points = pose_points + final_rms = pose_rms + final_ray_convergence = pose_ray_convergence + + summary = { + "baseline_reprojection_rms": baseline_rms, + "baseline_mean_ray_convergence": baseline_ray_convergence, + "baseline_cals": base_cals, + "baseline_points": base_points, + "baseline_geometry": baseline_geometry, + "baseline_geometry_max": baseline_geometry_max, + "baseline_correspondence": baseline_correspondence, + "baseline_correspondence_rate": baseline_correspondence_rate, + "warmstart_reprojection_rms": warmstart_rms, + "warmstart_mean_ray_convergence": warmstart_ray_convergence, + "warmstart_cals": warmstart_cals, + "warmstart_points": warmstart_points, + "warmstart_geometry": warmstart_geometry, + "warmstart_geometry_max": warmstart_geometry_max, + "warmstart_correspondence": warmstart_correspondence, + "warmstart_correspondence_rate": warmstart_correspondence_rate, + "warmstart_ok": warmstart_ok, + "pose_reprojection_rms": pose_rms, + "pose_mean_ray_convergence": pose_ray_convergence, + "pose_cals": pose_cals, + "pose_points": pose_points, + "pose_geometry": pose_geometry, + "pose_geometry_max": pose_geometry_max, + "pose_geometry_ok": pose_geometry_ok, + "pose_release_camera_order": release_order, + "pose_stage_ray_slack": pose_stage_ray_slack, + "pose_block_configs": normalized_pose_block_configs, + "pose_block_summaries": block_summaries, + "accepted_pose_block_count": len( + [block for block in block_summaries if block["accepted"]] + ), + "pose_correspondence": pose_correspondence, + "pose_correspondence_rate": pose_correspondence_rate, + "pose_correspondence_ok": pose_correspondence_ok, + "intrinsic_reprojection_rms": intrinsic_rms, + "intrinsic_mean_ray_convergence": intrinsic_ray_convergence, + "intrinsic_cals": intrinsic_cals, + "intrinsic_points": intrinsic_points, + "intrinsic_geometry": intrinsic_geometry, + "intrinsic_geometry_max": intrinsic_geometry_max, + "intrinsic_correspondence": intrinsic_correspondence, + "intrinsic_correspondence_rate": intrinsic_correspondence_rate, + "geometry_guard_mode": geometry_guard_mode, + "geometry_guard_threshold": geometry_guard_threshold, + "correspondence_guard_mode": correspondence_guard_mode, + "correspondence_guard_threshold": correspondence_guard_threshold, + "correspondence_guard_reference_rate": correspondence_guard_reference_rate, + "accepted_stage": accepted_stage, + "final_reprojection_rms": final_rms, + "final_mean_ray_convergence": final_ray_convergence, + "warmstart_result": warmstart_result, + "pose_result": alternating_result, + "intrinsic_result": intrinsic_result, + } + + return final_cals, final_points, summary diff --git a/tests/test_bundle_adjustment.py b/tests/test_bundle_adjustment.py index fd2470f..21cf302 100644 --- a/tests/test_bundle_adjustment.py +++ b/tests/test_bundle_adjustment.py @@ -8,6 +8,7 @@ from openptv_python.calibration import Calibration, read_calibration from openptv_python.imgcoord import image_coordinates from openptv_python.orientation import ( + alternating_bundle_adjustment, guarded_two_step_bundle_adjustment, mean_ray_convergence, multi_camera_bundle_adjustment, @@ -910,6 +911,95 @@ def test_guarded_two_step_bundle_adjustment_supports_pose_micro_stages(self): [False, True, False, True], ) + def test_alternating_bundle_adjustment_supports_block_schedule(self): + points = np.array( + [ + [-10.0, -10.0, 0.0], + [10.0, -10.0, 1.0], + [-10.0, 10.0, -1.0], + [10.0, 10.0, 0.5], + ], + dtype=float, + ) + observed_pixels = np.empty((len(points), 4, 2), dtype=float) + for cam_num, cal in enumerate(self.true_cals): + observed_pixels[:, cam_num, :] = arr_metric_to_pixel( + image_coordinates(points, cal, self.control.mm), self.control + ) + + staged_returns = [ + (self.true_cals, points.copy(), {"success": True, "final_reprojection_rms": 9.5}), + (self.true_cals, points.copy(), {"success": True, "final_reprojection_rms": 9.0}), + (self.true_cals, points.copy(), {"success": True, "final_reprojection_rms": 8.5}), + (self.true_cals, points.copy(), {"success": True, "final_reprojection_rms": 8.0}), + (self.true_cals, points.copy(), {"success": True, "final_reprojection_rms": 7.5}), + (self.true_cals, points.copy(), {"success": True, "final_reprojection_rms": 7.0}), + ] + + with ( + patch( + "openptv_python.orientation.multi_camera_bundle_adjustment", + side_effect=staged_returns, + ) as mocked_adjustment, + patch( + "openptv_python.orientation.reprojection_rms", + side_effect=[10.0, 9.5, 9.0, 8.5, 8.0, 7.5, 7.0], + ), + patch( + "openptv_python.orientation.mean_ray_convergence", + side_effect=[6.0, 5.5, 5.0, 4.5, 4.0, 3.5, 3.0], + ), + ): + _, _, summary = alternating_bundle_adjustment( + observed_pixels, + self.lightly_perturb_calibrations(self.true_cals), + self.control, + OrientPar(), + OrientPar(), + point_init=points, + pose_release_camera_order=[0, 1], + pose_block_configs=[ + { + "name": "points_only", + "optimize_extrinsics": False, + "optimize_points": True, + "max_nfev": 3, + }, + { + "name": "rotation_only", + "optimize_extrinsics": True, + "optimize_points": False, + "freeze_translation": True, + "max_nfev": 3, + }, + ], + intrinsic_max_nfev=4, + ) + + self.assertEqual(mocked_adjustment.call_count, 6) + fixed_sequences = [ + call.kwargs.get("fixed_camera_indices") + for call in mocked_adjustment.call_args_list + ] + self.assertEqual( + fixed_sequences, + [[0, 1, 2, 3], [1, 2, 3], [1, 2, 3], [2, 3], [2, 3], [0, 1, 2, 3]], + ) + self.assertFalse(mocked_adjustment.call_args_list[0].kwargs["optimize_extrinsics"]) + self.assertFalse(mocked_adjustment.call_args_list[0].kwargs["optimize_points"]) + self.assertFalse(mocked_adjustment.call_args_list[1].kwargs["optimize_extrinsics"]) + self.assertTrue(mocked_adjustment.call_args_list[1].kwargs["optimize_points"]) + self.assertEqual( + mocked_adjustment.call_args_list[2].kwargs["parameter_bounds"]["x0"], + (0.0, 0.0), + ) + self.assertEqual(summary["accepted_stage"], "intrinsics") + self.assertEqual(summary["accepted_pose_block_count"], 4) + self.assertEqual( + [block["block_name"] for block in summary["pose_block_summaries"]], + ["points_only", "rotation_only", "points_only", "rotation_only"], + ) + def test_guarded_two_step_bundle_adjustment_stagewise_ray_slack_allows_near_miss( self, ): diff --git a/tests/test_demo_bundle_adjustment.py b/tests/test_demo_bundle_adjustment.py index 63637af..8d2a683 100644 --- a/tests/test_demo_bundle_adjustment.py +++ b/tests/test_demo_bundle_adjustment.py @@ -53,6 +53,7 @@ def test_default_experiments_adds_known_point_presets(self): names = [spec.name for spec in experiments] self.assertIn("intrinsics_only", names) self.assertIn("intrinsics_first_guarded_stagewise_release", names) + self.assertIn("intrinsics_first_alternating_stagewise_release", names) self.assertIn("pose_trf_known_points", names) self.assertIn("guarded_two_step_known_points", names) self.assertIn("guarded_stagewise_release", names) @@ -82,6 +83,11 @@ def test_default_experiments_adds_known_point_presets(self): for spec in experiments if spec.name == "intrinsics_first_guarded_stagewise_release" ) + alternating_spec = next( + spec + for spec in experiments + if spec.name == "intrinsics_first_alternating_stagewise_release" + ) staged_spec = next( spec for spec in experiments if spec.name == "guarded_stagewise_release" ) @@ -120,6 +126,17 @@ def test_default_experiments_adds_known_point_presets(self): self.assertTrue( intrinsics_first_spec.ba_kwargs["pose_stage_configs"][1]["optimize_points"] ) + self.assertEqual(alternating_spec.mode, "alternating") + self.assertEqual( + len(alternating_spec.ba_kwargs["pose_block_configs"]), + 2, + ) + self.assertFalse( + alternating_spec.ba_kwargs["pose_block_configs"][0]["optimize_points"] + ) + self.assertTrue( + alternating_spec.ba_kwargs["pose_block_configs"][1]["optimize_points"] + ) self.assertIs(staged_known_spec.ba_kwargs["known_points"], known_points) self.assertEqual(staged_known_spec.ba_kwargs["known_point_sigmas"], 0.25) self.assertTrue( @@ -208,6 +225,73 @@ def test_run_experiment_intrinsics_then_guarded_executes_warmstart_first(self): self.assertIn("warmstart_rms=3.800000", result.notes) self.assertIn("accepted_stage=intrinsics", result.notes) + def test_run_experiment_alternating_mode_uses_alternating_solver(self): + control = ControlPar(4).from_file( + Path("tests/testing_folder/control_parameters/control.par") + ) + add_file = Path("tests/testing_folder/calibration/cam1.tif.addpar") + reference_cals = [ + read_calibration( + Path(f"tests/testing_folder/calibration/sym_cam{cam_num}.tif.ori"), + add_file, + ) + for cam_num in range(1, 5) + ] + start_cals = perturb_calibrations(reference_cals, 1.0) + points = np.array( + [ + [-10.0, -10.0, 0.0], + [10.0, -10.0, 1.0], + [-10.0, 10.0, -1.0], + [10.0, 10.0, 0.5], + ], + dtype=float, + ) + observed_pixels = np.empty((len(points), 4, 2), dtype=float) + for cam_num, cal in enumerate(reference_cals): + observed_pixels[:, cam_num, :] = arr_metric_to_pixel( + image_coordinates(points, cal, control.mm), + control, + ) + + spec = next( + experiment + for experiment in default_experiments(perturbation_scale=1.0) + if experiment.name == "intrinsics_first_alternating_stagewise_release" + ) + + with patch( + "openptv_python.demo_bundle_adjustment.alternating_bundle_adjustment", + return_value=( + reference_cals, + points.copy(), + { + "warmstart_ok": True, + "accepted_stage": "warmstart", + "final_reprojection_rms": 3.9, + "final_mean_ray_convergence": 0.2, + }, + ), + ) as alternating: + result = run_experiment( + spec, + observed_pixels=observed_pixels, + point_init=points, + control=control, + start_cals=start_cals, + reference_cals=reference_cals, + reference_geometry_points=None, + tracking_data=None, + geometry_export_threshold=None, + correspondence_export_threshold=None, + source_case_dir=Path("tests/testing_folder"), + output_dir=None, + ) + + self.assertEqual(alternating.call_count, 1) + self.assertIn("warmstart_ok=True", result.notes) + self.assertIn("accepted_stage=warmstart", result.notes) + def test_default_experiments_accepts_geometry_guard_configuration(self): experiments = default_experiments( num_cams=4, From badefdb2a77bc1dcaaf4cc610ecbaa10a9b5f31a Mon Sep 17 00:00:00 2001 From: Alex Liberzon Date: Sat, 7 Mar 2026 16:26:26 +0200 Subject: [PATCH 13/27] updated after new demo --- mypy.ini | 2 +- openptv_python/demo_bundle_adjustment.py | 197 +++++++++++--- openptv_python/orientation.py | 222 +++++++++++---- tests/test_bundle_adjustment.py | 331 ++++++++++++++++++++++- tests/test_demo_bundle_adjustment.py | 135 ++++++++- uv.lock | 266 ------------------ 6 files changed, 774 insertions(+), 379 deletions(-) diff --git a/mypy.ini b/mypy.ini index 1f68091..cc3fa74 100644 --- a/mypy.ini +++ b/mypy.ini @@ -1,6 +1,6 @@ [mypy] ignore_missing_imports = True -exclude = (^build/|^docs/_build/) +exclude = (^build/|^docs/_build/|^tmp/) ; disallow_untyped_defs = True ; disallow_incomplete_defs = True ; check_untyped_defs = True diff --git a/openptv_python/demo_bundle_adjustment.py b/openptv_python/demo_bundle_adjustment.py index 778be24..9f0668f 100644 --- a/openptv_python/demo_bundle_adjustment.py +++ b/openptv_python/demo_bundle_adjustment.py @@ -1128,6 +1128,121 @@ def default_experiments( }, }, ] + intrinsics_first_alternating_pose_block_configs = [ + { + "name": "points_only", + "optimize_extrinsics": False, + "optimize_points": True, + "max_nfev": 4, + "x_scale": { + "points": 0.1, + }, + }, + { + "name": "omega_only", + "optimize_extrinsics": True, + "optimize_points": False, + "freeze_translation": True, + "frozen_parameters": ["phi", "kappa"], + "prior_sigmas": { + "omega": 2e-4, + }, + "parameter_bounds": { + "omega": (-0.001, 0.001), + }, + "max_nfev": 3, + "x_scale": { + "omega": 1e-4, + }, + }, + { + "name": "phi_only", + "optimize_extrinsics": True, + "optimize_points": False, + "freeze_translation": True, + "frozen_parameters": ["omega", "kappa"], + "prior_sigmas": { + "phi": 2e-4, + }, + "parameter_bounds": { + "phi": (-0.001, 0.001), + }, + "max_nfev": 3, + "x_scale": { + "phi": 1e-4, + }, + }, + { + "name": "kappa_only", + "optimize_extrinsics": True, + "optimize_points": False, + "freeze_translation": True, + "frozen_parameters": ["omega", "phi"], + "prior_sigmas": { + "kappa": 2e-4, + }, + "parameter_bounds": { + "kappa": (-0.001, 0.001), + }, + "max_nfev": 3, + "x_scale": { + "kappa": 1e-4, + }, + }, + { + "name": "translation_only", + "optimize_extrinsics": True, + "optimize_points": False, + "freeze_rotation": True, + "prior_sigmas": { + "x0": 0.02, + "y0": 0.02, + "z0": 0.02, + }, + "parameter_bounds": { + "x0": (-0.05, 0.05), + "y0": (-0.05, 0.05), + "z0": (-0.05, 0.05), + }, + "max_nfev": 4, + "x_scale": { + "x0": 0.01, + "y0": 0.01, + "z0": 0.01, + }, + }, + { + "name": "joint_pose_points", + "optimize_extrinsics": True, + "optimize_points": True, + "prior_sigmas": { + "x0": 0.05, + "y0": 0.05, + "z0": 0.05, + "omega": 5e-4, + "phi": 5e-4, + "kappa": 5e-4, + }, + "parameter_bounds": { + "x0": (-0.1, 0.1), + "y0": (-0.1, 0.1), + "z0": (-0.1, 0.1), + "omega": (-0.002, 0.002), + "phi": (-0.002, 0.002), + "kappa": (-0.002, 0.002), + }, + "max_nfev": 4, + "x_scale": { + "x0": 0.02, + "y0": 0.02, + "z0": 0.02, + "omega": 2e-4, + "phi": 2e-4, + "kappa": 2e-4, + "points": 0.1, + }, + }, + ] known_point_pose_stage_configs = [ { **stage_config, @@ -1387,15 +1502,17 @@ def default_experiments( "kappa": 2e-4, "points": 0.1, }, - "pose_block_configs": intrinsics_first_pose_stage_configs, + "pose_block_configs": intrinsics_first_alternating_pose_block_configs, "intrinsic_prior_sigmas": tight_intrinsic_priors, "intrinsic_parameter_bounds": tight_intrinsic_bounds, "intrinsic_max_nfev": 4, "geometry_guard_mode": geometry_guard_mode, "geometry_guard_threshold": geometry_guard_threshold, + "first_release_geometry_slack": 0.35, "correspondence_guard_mode": correspondence_guard_mode, "correspondence_guard_threshold": correspondence_guard_threshold, "correspondence_guard_reference_rate": correspondence_guard_reference_rate, + "first_release_correspondence_slack": 0.02, }, ), ] @@ -1437,7 +1554,6 @@ def default_experiments( "omega": 5e-4, "phi": 5e-4, "kappa": 5e-4, - "points": 0.1, }, "pose_stage_configs": known_point_pose_stage_configs, "intrinsic_prior_sigmas": tight_intrinsic_priors, @@ -1724,40 +1840,44 @@ def run_experiment( notes = f"accepted_stage={summary['accepted_stage']}" elif spec.mode == "intrinsics_then_guarded": warmstart_orient_par = cast(OrientPar, spec.ba_kwargs["warmstart_orient_par"]) - warmstart_cals, warmstart_points, warmstart_result = multi_camera_bundle_adjustment( - observed_pixels, - working_cals, - control, - warmstart_orient_par, - point_init=point_init, - fix_first_camera=cast(bool, spec.ba_kwargs.get("fix_first_camera", True)), - fixed_camera_indices=cast( - List[int] | None, - spec.ba_kwargs.get("warmstart_fixed_camera_indices"), - ), - loss=cast(str, spec.ba_kwargs.get("warmstart_loss", "linear")), - method=cast(str, spec.ba_kwargs.get("warmstart_method", "trf")), - prior_sigmas=cast( - Dict[str, float] | None, - spec.ba_kwargs.get("warmstart_prior_sigmas"), - ), - parameter_bounds=cast( - Dict[str, tuple[float, float]] | None, - spec.ba_kwargs.get("warmstart_parameter_bounds"), - ), - max_nfev=cast(int | None, spec.ba_kwargs.get("warmstart_max_nfev")), - optimize_extrinsics=cast( - bool, - spec.ba_kwargs.get("warmstart_optimize_extrinsics", False), - ), - optimize_points=cast( - bool, - spec.ba_kwargs.get("warmstart_optimize_points", False), - ), - x_scale=cast( - float | Sequence[float] | Dict[str, float] | None, - spec.ba_kwargs.get("warmstart_x_scale"), - ), + warmstart_cals, warmstart_points, warmstart_result = ( + multi_camera_bundle_adjustment( + observed_pixels, + working_cals, + control, + warmstart_orient_par, + point_init=point_init, + fix_first_camera=cast( + bool, spec.ba_kwargs.get("fix_first_camera", True) + ), + fixed_camera_indices=cast( + List[int] | None, + spec.ba_kwargs.get("warmstart_fixed_camera_indices"), + ), + loss=cast(str, spec.ba_kwargs.get("warmstart_loss", "linear")), + method=cast(str, spec.ba_kwargs.get("warmstart_method", "trf")), + prior_sigmas=cast( + Dict[str, float] | None, + spec.ba_kwargs.get("warmstart_prior_sigmas"), + ), + parameter_bounds=cast( + Dict[str, tuple[float, float]] | None, + spec.ba_kwargs.get("warmstart_parameter_bounds"), + ), + max_nfev=cast(int | None, spec.ba_kwargs.get("warmstart_max_nfev")), + optimize_extrinsics=cast( + bool, + spec.ba_kwargs.get("warmstart_optimize_extrinsics", False), + ), + optimize_points=cast( + bool, + spec.ba_kwargs.get("warmstart_optimize_points", False), + ), + x_scale=cast( + float | Sequence[float] | Dict[str, float] | None, + spec.ba_kwargs.get("warmstart_x_scale"), + ), + ) ) pose_orient_par = cast(OrientPar, spec.ba_kwargs["pose_orient_par"]) intrinsic_orient_par = cast(OrientPar, spec.ba_kwargs["intrinsic_orient_par"]) @@ -1897,8 +2017,9 @@ def run_experiment( ) final_rms = cast(float, summary["final_reprojection_rms"]) final_ray = cast(float, summary["final_mean_ray_convergence"]) - warmstart_rms = float( - cast(Dict[str, object], warmstart_result)["final_reprojection_rms"] + warmstart_rms = cast( + float, + cast(Dict[str, object], warmstart_result)["final_reprojection_rms"], ) success = warmstart_success notes = ( diff --git a/openptv_python/orientation.py b/openptv_python/orientation.py index b273ce6..e996505 100644 --- a/openptv_python/orientation.py +++ b/openptv_python/orientation.py @@ -1355,8 +1355,18 @@ def _expand_parameter_limits( continue low_delta, high_delta = limits[name] base_value = base_blocks[block_index][param_index] - lower.append(base_value + low_delta) - upper.append(base_value + high_delta) + lower_bound = base_value + low_delta + upper_bound = base_value + high_delta + if ( + np.isfinite(lower_bound) + and np.isfinite(upper_bound) + and lower_bound == upper_bound + ): + epsilon = 32.0 * np.finfo(np.float64).eps * max(1.0, abs(lower_bound)) + lower_bound -= epsilon + upper_bound += epsilon + lower.append(lower_bound) + upper.append(upper_bound) return np.asarray(lower, dtype=np.float64), np.asarray(upper, dtype=np.float64) @@ -1396,16 +1406,16 @@ def _bundle_adjustment_x_scale( return np.asarray(values, dtype=np.float64) - values = np.asarray(x_scale, dtype=np.float64) - if values.ndim != 1: + sequence_values = np.asarray(x_scale, dtype=np.float64) + if sequence_values.ndim != 1: raise ValueError("x_scale sequence must be one-dimensional") - if values.size != total_parameters: + if sequence_values.size != total_parameters: raise ValueError( - f"x_scale sequence must have length {total_parameters}, got {values.size}" + f"x_scale sequence must have length {total_parameters}, got {sequence_values.size}" ) - if np.any(values <= 0): + if np.any(sequence_values <= 0): raise ValueError("x_scale entries must be strictly positive") - return values.copy() + return sequence_values.copy() def _bundle_adjustment_jacobian_sparsity( @@ -2100,29 +2110,33 @@ def correspondence_stage_ok( pose_correspondence_ok = True for micro_stage_index, variant in enumerate(pose_stage_variants_list, start=1): - candidate_cals, candidate_points, candidate_result = multi_camera_bundle_adjustment( - observed_pixels, - stage_cals, - cpar, - pose_orient_par, - point_init=stage_points, - fixed_camera_indices=fixed_camera_indices, - loss=cast(str, variant["loss"]), - method=cast(str, variant["method"]), - prior_sigmas=cast(Optional[Dict[str, float]], variant["prior_sigmas"]), - parameter_bounds=cast( - Optional[Dict[str, Tuple[float, float]]], - variant["parameter_bounds"], - ), - max_nfev=cast(Optional[int], variant["max_nfev"]), - optimize_extrinsics=True, - optimize_points=cast(bool, variant["optimize_points"]), - known_points=known_points, - known_point_sigmas=known_point_sigmas, - x_scale=cast( - Optional[float | Sequence[float] | Dict[str, float]], - variant["x_scale"], - ), + candidate_cals, candidate_points, candidate_result = ( + multi_camera_bundle_adjustment( + observed_pixels, + stage_cals, + cpar, + pose_orient_par, + point_init=stage_points, + fixed_camera_indices=fixed_camera_indices, + loss=cast(str, variant["loss"]), + method=cast(str, variant["method"]), + prior_sigmas=cast( + Optional[Dict[str, float]], variant["prior_sigmas"] + ), + parameter_bounds=cast( + Optional[Dict[str, Tuple[float, float]]], + variant["parameter_bounds"], + ), + max_nfev=cast(Optional[int], variant["max_nfev"]), + optimize_extrinsics=True, + optimize_points=cast(bool, variant["optimize_points"]), + known_points=known_points, + known_point_sigmas=known_point_sigmas, + x_scale=cast( + Optional[float | Sequence[float] | Dict[str, float]], + variant["x_scale"], + ), + ) ) candidate_rms = reprojection_rms( observed_pixels, candidate_points, candidate_cals, cpar @@ -2155,7 +2169,8 @@ def correspondence_stage_ok( ) pose_ok = candidate_rms <= stage_rms and ( not reject_on_ray_convergence - or candidate_ray_convergence <= stage_ray_convergence + pose_stage_ray_slack + or candidate_ray_convergence + <= stage_ray_convergence + pose_stage_ray_slack ) pose_ok = pose_ok and pose_geometry_ok and pose_correspondence_ok pose_stage_summaries.append( @@ -2521,18 +2536,24 @@ def alternating_bundle_adjustment( geometry_reference_cals: Optional[List[Calibration]] = None, geometry_guard_mode: str = "off", geometry_guard_threshold: Optional[float] = None, + first_release_geometry_slack: float = 0.0, correspondence_original_ids: Optional[np.ndarray] = None, correspondence_point_frame_indices: Optional[np.ndarray] = None, correspondence_frame_target_pixels: Optional[Sequence[Sequence[np.ndarray]]] = None, correspondence_guard_mode: str = "off", correspondence_guard_threshold: Optional[float] = None, correspondence_guard_reference_rate: Optional[float] = None, + first_release_correspondence_slack: float = 0.0, reject_worse_solution: bool = True, reject_on_ray_convergence: bool = True, ) -> Tuple[List[Calibration], np.ndarray, Dict[str, object]]: - """Run intrinsics-first alternating BA with point/rotation/translation block updates.""" + """Run intrinsics-first alternating BA with point and pose sub-block updates.""" if pose_stage_ray_slack < 0: raise ValueError("pose_stage_ray_slack must be non-negative") + if first_release_geometry_slack < 0: + raise ValueError("first_release_geometry_slack must be non-negative") + if first_release_correspondence_slack < 0: + raise ValueError("first_release_correspondence_slack must be non-negative") def merge_bounds( base: Optional[Dict[str, Tuple[float, float]]], @@ -2555,18 +2576,43 @@ def default_pose_blocks() -> List[Dict[str, object]]: "x_scale": {"points": 0.1}, }, { - "name": "rotation_only", + "name": "omega_only", "optimize_extrinsics": True, "optimize_points": False, "freeze_translation": True, "loss": pose_loss, "method": pose_method, - "max_nfev": 4 if pose_max_nfev is None else min(4, pose_max_nfev), + "max_nfev": 3 if pose_max_nfev is None else min(3, pose_max_nfev), "x_scale": { "omega": 2e-4, + }, + "frozen_parameters": ["phi", "kappa"], + }, + { + "name": "phi_only", + "optimize_extrinsics": True, + "optimize_points": False, + "freeze_translation": True, + "loss": pose_loss, + "method": pose_method, + "max_nfev": 3 if pose_max_nfev is None else min(3, pose_max_nfev), + "x_scale": { "phi": 2e-4, + }, + "frozen_parameters": ["omega", "kappa"], + }, + { + "name": "kappa_only", + "optimize_extrinsics": True, + "optimize_points": False, + "freeze_translation": True, + "loss": pose_loss, + "method": pose_method, + "max_nfev": 3 if pose_max_nfev is None else min(3, pose_max_nfev), + "x_scale": { "kappa": 2e-4, }, + "frozen_parameters": ["omega", "phi"], }, { "name": "translation_only", @@ -2643,6 +2689,8 @@ def max_projection_drift( def geometry_stage_ok( candidate_metric: Optional[float], baseline_metric: Optional[float], + *, + threshold_override: Optional[float] = None, ) -> bool: if geometry_guard_mode == "off" or candidate_metric is None: return True @@ -2651,11 +2699,16 @@ def geometry_stage_ok( return True return candidate_metric <= baseline_metric + 1e-12 if geometry_guard_mode == "hard": - if geometry_guard_threshold is None or geometry_guard_threshold <= 0: + threshold = ( + geometry_guard_threshold + if threshold_override is None + else threshold_override + ) + if threshold is None or threshold <= 0: raise ValueError( "geometry_guard_threshold must be positive when geometry_guard_mode='hard'" ) - return candidate_metric <= geometry_guard_threshold + return candidate_metric <= threshold raise ValueError("geometry_guard_mode must be one of 'off', 'soft', or 'hard'") def correspondence_replacement_summary( @@ -2724,6 +2777,8 @@ def correspondence_replacement_summary( def correspondence_stage_ok( candidate_rate: Optional[float], prior_rate: Optional[float], + *, + threshold_override: Optional[float] = None, ) -> bool: if correspondence_guard_mode == "off" or candidate_rate is None: return True @@ -2734,14 +2789,19 @@ def correspondence_stage_ok( return True return candidate_rate <= prior_rate + 1e-12 if correspondence_guard_mode == "hard": - if ( - correspondence_guard_threshold is None - or correspondence_guard_threshold <= 0 - ): + if threshold_override is None and correspondence_guard_threshold is None: + threshold = None + else: + threshold = ( + correspondence_guard_threshold + if threshold_override is None + else threshold_override + ) + if threshold is None or threshold <= 0: raise ValueError( "correspondence_guard_threshold must be positive when correspondence_guard_mode='hard'" ) - return candidate_rate <= correspondence_guard_threshold + return candidate_rate <= threshold raise ValueError( "correspondence_guard_mode must be one of 'off', 'soft', or 'hard'" ) @@ -2797,8 +2857,12 @@ def correspondence_stage_ok( xtol=intrinsic_xtol, gtol=intrinsic_gtol, ) - warmstart_rms = reprojection_rms(observed_pixels, warmstart_points, warmstart_cals, cpar) - warmstart_ray_convergence = mean_ray_convergence(observed_pixels, warmstart_cals, cpar) + warmstart_rms = reprojection_rms( + observed_pixels, warmstart_points, warmstart_cals, cpar + ) + warmstart_ray_convergence = mean_ray_convergence( + observed_pixels, warmstart_cals, cpar + ) warmstart_geometry = projection_drift_summaries( geometry_reference_cals, warmstart_cals, @@ -2853,7 +2917,9 @@ def correspondence_stage_ok( if camera_index not in (fixed_camera_indices or []) ] else: - release_order = [int(camera_index) for camera_index in pose_release_camera_order] + release_order = [ + int(camera_index) for camera_index in pose_release_camera_order + ] if not release_order: raise ValueError("pose_release_camera_order must not be empty") @@ -2889,18 +2955,27 @@ def correspondence_stage_ok( block_config.get("parameter_bounds"), ), ) + frozen_parameters = set( + cast(Sequence[str], block_config.get("frozen_parameters", [])) + ) if cast(bool, block_config.get("freeze_translation", False)): - block_bounds = merge_bounds( - block_bounds, - {"x0": (0.0, 0.0), "y0": (0.0, 0.0), "z0": (0.0, 0.0)}, - ) + frozen_parameters.update({"x0", "y0", "z0"}) if cast(bool, block_config.get("freeze_rotation", False)): + frozen_parameters.update({"omega", "phi", "kappa"}) + unknown_frozen = frozen_parameters.difference( + {"x0", "y0", "z0", "omega", "phi", "kappa"} + ) + if unknown_frozen: + raise ValueError( + "Unsupported frozen_parameters entries: " + + ", ".join(sorted(unknown_frozen)) + ) + if frozen_parameters: block_bounds = merge_bounds( block_bounds, { - "omega": (0.0, 0.0), - "phi": (0.0, 0.0), - "kappa": (0.0, 0.0), + parameter_name: (0.0, 0.0) + for parameter_name in sorted(frozen_parameters) }, ) @@ -2920,7 +2995,9 @@ def correspondence_stage_ok( block_config.get("prior_sigmas", pose_prior_sigmas), ), parameter_bounds=block_bounds, - max_nfev=cast(Optional[int], block_config.get("max_nfev", pose_max_nfev)), + max_nfev=cast( + Optional[int], block_config.get("max_nfev", pose_max_nfev) + ), optimize_extrinsics=optimize_extrinsics, optimize_points=optimize_points, known_points=block_known_points, @@ -2930,7 +3007,9 @@ def correspondence_stage_ok( block_config.get("x_scale", pose_x_scale), ), ) - block_rms = reprojection_rms(observed_pixels, block_points, block_cals, cpar) + block_rms = reprojection_rms( + observed_pixels, block_points, block_cals, cpar + ) block_ray_convergence = mean_ray_convergence( observed_pixels, block_cals, @@ -2951,17 +3030,46 @@ def correspondence_stage_ok( if block_correspondence is None else cast(float, block_correspondence["replacement_rate"]) ) + first_release_soft_geometry = ( + stage_index == 1 + and optimize_extrinsics + and geometry_guard_mode == "hard" + and first_release_geometry_slack > 0 + and geometry_guard_threshold is not None + ) + first_release_soft_correspondence = ( + stage_index == 1 + and optimize_extrinsics + and correspondence_guard_mode == "hard" + and first_release_correspondence_slack > 0 + and correspondence_guard_threshold is not None + ) + geometry_threshold_override = None + if first_release_soft_geometry: + geometry_guard_limit = cast(float, geometry_guard_threshold) + geometry_threshold_override = ( + geometry_guard_limit + first_release_geometry_slack + ) + correspondence_threshold_override = None + if first_release_soft_correspondence: + correspondence_guard_limit = cast(float, correspondence_guard_threshold) + correspondence_threshold_override = ( + correspondence_guard_limit + first_release_correspondence_slack + ) block_geometry_ok = geometry_stage_ok( block_geometry_max, current_geometry_max, + threshold_override=geometry_threshold_override, ) block_correspondence_ok = correspondence_stage_ok( block_correspondence_rate, current_correspondence_rate, + threshold_override=correspondence_threshold_override, ) block_ok = block_rms <= current_rms and ( not reject_on_ray_convergence - or block_ray_convergence <= current_ray_convergence + pose_stage_ray_slack + or block_ray_convergence + <= current_ray_convergence + pose_stage_ray_slack ) block_ok = block_ok and block_geometry_ok and block_correspondence_ok block_summaries.append( @@ -2977,9 +3085,11 @@ def correspondence_stage_ok( "geometry": block_geometry, "geometry_max": block_geometry_max, "geometry_ok": block_geometry_ok, + "first_release_soft_geometry": first_release_soft_geometry, "correspondence": block_correspondence, "correspondence_rate": block_correspondence_rate, "correspondence_ok": block_correspondence_ok, + "first_release_soft_correspondence": first_release_soft_correspondence, "accepted": block_ok or not reject_worse_solution, "optimize_extrinsics": optimize_extrinsics, "optimize_points": optimize_points, @@ -3142,9 +3252,11 @@ def correspondence_stage_ok( "intrinsic_correspondence_rate": intrinsic_correspondence_rate, "geometry_guard_mode": geometry_guard_mode, "geometry_guard_threshold": geometry_guard_threshold, + "first_release_geometry_slack": first_release_geometry_slack, "correspondence_guard_mode": correspondence_guard_mode, "correspondence_guard_threshold": correspondence_guard_threshold, "correspondence_guard_reference_rate": correspondence_guard_reference_rate, + "first_release_correspondence_slack": first_release_correspondence_slack, "accepted_stage": accepted_stage, "final_reprojection_rms": final_rms, "final_mean_ray_convergence": final_ray_convergence, diff --git a/tests/test_bundle_adjustment.py b/tests/test_bundle_adjustment.py index 21cf302..421e03b 100644 --- a/tests/test_bundle_adjustment.py +++ b/tests/test_bundle_adjustment.py @@ -203,6 +203,51 @@ def fake_least_squares(_fun, x0, **kwargs): self.assertTrue(result.success) np.testing.assert_allclose(refined_points, points) + def test_multi_camera_bundle_adjustment_accepts_zero_width_parameter_bounds(self): + points = np.array( + [ + [-10.0, -10.0, 0.0], + [10.0, -10.0, 1.0], + [-10.0, 10.0, -1.0], + [10.0, 10.0, 0.5], + ], + dtype=float, + ) + observed_pixels = np.empty((len(points), 4, 2), dtype=float) + for cam_num, cal in enumerate(self.true_cals): + observed_pixels[:, cam_num, :] = arr_metric_to_pixel( + image_coordinates(points, cal, self.control.mm), self.control + ) + + start_cals = self.lightly_perturb_calibrations(self.true_cals) + refined_cals, refined_points, result = multi_camera_bundle_adjustment( + observed_pixels, + start_cals, + self.control, + OrientPar(), + point_init=points.copy(), + fixed_camera_indices=[0, 1, 2], + optimize_points=False, + loss="linear", + method="trf", + max_nfev=5, + parameter_bounds={ + "phi": (0.0, 0.0), + }, + prior_sigmas={ + "x0": 1.0, + "y0": 1.0, + "z0": 1.0, + "omega": 0.01, + "phi": 0.01, + "kappa": 0.01, + }, + ) + + self.assertTrue(result.success, msg=result.message) + self.assertEqual(refined_points.shape, points.shape) + self.assertEqual(len(refined_cals), 4) + def test_cavity_reprojection_improves(self): cavity_dir = Path("tests/testing_fodder/test_cavity") control = ControlPar(4).from_file(cavity_dir / "parameters/ptv.par") @@ -928,12 +973,36 @@ def test_alternating_bundle_adjustment_supports_block_schedule(self): ) staged_returns = [ - (self.true_cals, points.copy(), {"success": True, "final_reprojection_rms": 9.5}), - (self.true_cals, points.copy(), {"success": True, "final_reprojection_rms": 9.0}), - (self.true_cals, points.copy(), {"success": True, "final_reprojection_rms": 8.5}), - (self.true_cals, points.copy(), {"success": True, "final_reprojection_rms": 8.0}), - (self.true_cals, points.copy(), {"success": True, "final_reprojection_rms": 7.5}), - (self.true_cals, points.copy(), {"success": True, "final_reprojection_rms": 7.0}), + ( + self.true_cals, + points.copy(), + {"success": True, "final_reprojection_rms": 9.5}, + ), + ( + self.true_cals, + points.copy(), + {"success": True, "final_reprojection_rms": 9.0}, + ), + ( + self.true_cals, + points.copy(), + {"success": True, "final_reprojection_rms": 8.5}, + ), + ( + self.true_cals, + points.copy(), + {"success": True, "final_reprojection_rms": 8.0}, + ), + ( + self.true_cals, + points.copy(), + {"success": True, "final_reprojection_rms": 7.5}, + ), + ( + self.true_cals, + points.copy(), + {"success": True, "final_reprojection_rms": 7.0}, + ), ] with ( @@ -985,9 +1054,13 @@ def test_alternating_bundle_adjustment_supports_block_schedule(self): fixed_sequences, [[0, 1, 2, 3], [1, 2, 3], [1, 2, 3], [2, 3], [2, 3], [0, 1, 2, 3]], ) - self.assertFalse(mocked_adjustment.call_args_list[0].kwargs["optimize_extrinsics"]) + self.assertFalse( + mocked_adjustment.call_args_list[0].kwargs["optimize_extrinsics"] + ) self.assertFalse(mocked_adjustment.call_args_list[0].kwargs["optimize_points"]) - self.assertFalse(mocked_adjustment.call_args_list[1].kwargs["optimize_extrinsics"]) + self.assertFalse( + mocked_adjustment.call_args_list[1].kwargs["optimize_extrinsics"] + ) self.assertTrue(mocked_adjustment.call_args_list[1].kwargs["optimize_points"]) self.assertEqual( mocked_adjustment.call_args_list[2].kwargs["parameter_bounds"]["x0"], @@ -1000,6 +1073,248 @@ def test_alternating_bundle_adjustment_supports_block_schedule(self): ["points_only", "rotation_only", "points_only", "rotation_only"], ) + def test_alternating_bundle_adjustment_freezes_requested_rotation_axes(self): + points = np.array( + [ + [-10.0, -10.0, 0.0], + [10.0, -10.0, 1.0], + [-10.0, 10.0, -1.0], + [10.0, 10.0, 0.5], + ], + dtype=float, + ) + observed_pixels = np.empty((len(points), 4, 2), dtype=float) + for cam_num, cal in enumerate(self.true_cals): + observed_pixels[:, cam_num, :] = arr_metric_to_pixel( + image_coordinates(points, cal, self.control.mm), self.control + ) + + staged_returns = [ + ( + self.true_cals, + points.copy(), + {"success": True, "final_reprojection_rms": 9.5}, + ), + ( + self.true_cals, + points.copy(), + {"success": True, "final_reprojection_rms": 9.0}, + ), + ( + self.true_cals, + points.copy(), + {"success": True, "final_reprojection_rms": 8.5}, + ), + ] + + with ( + patch( + "openptv_python.orientation.multi_camera_bundle_adjustment", + side_effect=staged_returns, + ) as mocked_adjustment, + patch( + "openptv_python.orientation.reprojection_rms", + side_effect=[10.0, 9.5, 9.0, 8.5], + ), + patch( + "openptv_python.orientation.mean_ray_convergence", + side_effect=[6.0, 5.5, 5.0, 4.5], + ), + ): + _, _, summary = alternating_bundle_adjustment( + observed_pixels, + self.lightly_perturb_calibrations(self.true_cals), + self.control, + OrientPar(), + OrientPar(), + point_init=points, + pose_release_camera_order=[0], + pose_block_configs=[ + { + "name": "omega_only", + "optimize_extrinsics": True, + "optimize_points": False, + "freeze_translation": True, + "frozen_parameters": ["phi", "kappa"], + "max_nfev": 3, + }, + ], + intrinsic_max_nfev=4, + ) + + self.assertEqual(summary["accepted_pose_block_count"], 1) + self.assertEqual( + mocked_adjustment.call_args_list[1].kwargs["parameter_bounds"]["x0"], + (0.0, 0.0), + ) + self.assertEqual( + mocked_adjustment.call_args_list[1].kwargs["parameter_bounds"]["phi"], + (0.0, 0.0), + ) + self.assertEqual( + mocked_adjustment.call_args_list[1].kwargs["parameter_bounds"]["kappa"], + (0.0, 0.0), + ) + + def test_alternating_bundle_adjustment_softens_first_release_geometry_guard(self): + points = np.array( + [ + [-10.0, -10.0, 0.0], + [10.0, -10.0, 1.0], + [-10.0, 10.0, -1.0], + [10.0, 10.0, 0.5], + ], + dtype=float, + ) + observed_pixels = np.empty((len(points), 4, 2), dtype=float) + for cam_num, cal in enumerate(self.true_cals): + observed_pixels[:, cam_num, :] = arr_metric_to_pixel( + image_coordinates(points, cal, self.control.mm), self.control + ) + + shifted_cals = [self.clone_calibration(cal) for cal in self.true_cals] + shifted_cals[0].set_angles( + shifted_cals[0].get_angles() + np.array([0.0, 0.0015, 0.0]) + ) + geometry_reference_points = np.array( + [ + [-15.0, -10.0, -2.0], + [12.0, -8.0, 1.0], + [-9.0, 14.0, 3.0], + [8.0, 6.0, -1.0], + ], + dtype=float, + ) + + def run_with_threshold( + threshold: float, + slack: float, + ) -> dict[str, object]: + with ( + patch( + "openptv_python.orientation.multi_camera_bundle_adjustment", + side_effect=[ + ( + self.true_cals, + points.copy(), + {"success": True, "stage": "warmstart"}, + ), + ( + shifted_cals, + points.copy(), + {"success": True, "stage": "omega"}, + ), + ( + self.true_cals, + points.copy(), + {"success": True, "stage": "intrinsics"}, + ), + ], + ), + patch( + "openptv_python.orientation.reprojection_rms", + side_effect=[10.0, 9.8, 9.6, 9.5], + ), + patch( + "openptv_python.orientation.mean_ray_convergence", + side_effect=[6.0, 5.9, 5.8, 5.7], + ), + ): + _, _, summary = alternating_bundle_adjustment( + observed_pixels, + [self.clone_calibration(cal) for cal in self.true_cals], + self.control, + OrientPar(), + OrientPar(), + point_init=points, + pose_release_camera_order=[0], + pose_block_configs=[ + { + "name": "omega_only", + "optimize_extrinsics": True, + "optimize_points": False, + "freeze_translation": True, + "frozen_parameters": ["phi", "kappa"], + "max_nfev": 3, + }, + ], + geometry_reference_points=geometry_reference_points, + geometry_reference_cals=[ + self.clone_calibration(cal) for cal in self.true_cals + ], + geometry_guard_mode="hard", + geometry_guard_threshold=threshold, + first_release_geometry_slack=slack, + intrinsic_max_nfev=4, + ) + return summary + + measured = run_with_threshold(1e6, 0.0) + geometry_max = float(measured["pose_block_summaries"][0]["geometry_max"]) + + rejected = run_with_threshold(geometry_max - 1e-6, 0.0) + rejected_block = rejected["pose_block_summaries"][0] + self.assertFalse(rejected_block["accepted"]) + + with ( + patch( + "openptv_python.orientation.multi_camera_bundle_adjustment", + side_effect=[ + ( + self.true_cals, + points.copy(), + {"success": True, "stage": "warmstart"}, + ), + (shifted_cals, points.copy(), {"success": True, "stage": "omega"}), + ( + self.true_cals, + points.copy(), + {"success": True, "stage": "intrinsics"}, + ), + ], + ), + patch( + "openptv_python.orientation.reprojection_rms", + side_effect=[10.0, 9.8, 9.6, 9.5], + ), + patch( + "openptv_python.orientation.mean_ray_convergence", + side_effect=[6.0, 5.9, 5.8, 5.7], + ), + ): + _, _, softened = alternating_bundle_adjustment( + observed_pixels, + [self.clone_calibration(cal) for cal in self.true_cals], + self.control, + OrientPar(), + OrientPar(), + point_init=points, + pose_release_camera_order=[0], + pose_block_configs=[ + { + "name": "omega_only", + "optimize_extrinsics": True, + "optimize_points": False, + "freeze_translation": True, + "frozen_parameters": ["phi", "kappa"], + "max_nfev": 3, + }, + ], + geometry_reference_points=geometry_reference_points, + geometry_reference_cals=[ + self.clone_calibration(cal) for cal in self.true_cals + ], + geometry_guard_mode="hard", + geometry_guard_threshold=geometry_max - 1e-6, + first_release_geometry_slack=2e-6, + intrinsic_max_nfev=4, + ) + + softened_block = softened["pose_block_summaries"][0] + self.assertTrue(softened_block["accepted"]) + self.assertTrue(softened_block["first_release_soft_geometry"]) + self.assertEqual(softened["accepted_pose_block_count"], 1) + def test_guarded_two_step_bundle_adjustment_stagewise_ray_slack_allows_near_miss( self, ): diff --git a/tests/test_demo_bundle_adjustment.py b/tests/test_demo_bundle_adjustment.py index 8d2a683..26c1b5d 100644 --- a/tests/test_demo_bundle_adjustment.py +++ b/tests/test_demo_bundle_adjustment.py @@ -6,7 +6,6 @@ from openptv_python.calibration import read_calibration from openptv_python.demo_bundle_adjustment import ( - ExperimentSpec, ExperimentResult, all_fixed_camera_pairs, build_experiment_start_calibrations, @@ -106,12 +105,27 @@ def test_default_experiments_adds_known_point_presets(self): ) self.assertEqual(staged_spec.ba_kwargs["pose_stage_ray_slack"], 0.0) self.assertEqual(len(staged_spec.ba_kwargs["pose_stage_configs"]), 3) - self.assertFalse(staged_spec.ba_kwargs["pose_stage_configs"][0]["optimize_points"]) - self.assertTrue(staged_spec.ba_kwargs["pose_stage_configs"][1]["optimize_points"]) - self.assertTrue(staged_spec.ba_kwargs["pose_stage_configs"][2]["optimize_points"]) + self.assertFalse( + staged_spec.ba_kwargs["pose_stage_configs"][0]["optimize_points"] + ) + self.assertTrue( + staged_spec.ba_kwargs["pose_stage_configs"][1]["optimize_points"] + ) + self.assertTrue( + staged_spec.ba_kwargs["pose_stage_configs"][2]["optimize_points"] + ) self.assertEqual(intrinsics_first_spec.mode, "intrinsics_then_guarded") - self.assertFalse(intrinsics_first_spec.ba_kwargs["warmstart_optimize_extrinsics"]) + self.assertFalse( + intrinsics_first_spec.ba_kwargs["warmstart_optimize_extrinsics"] + ) self.assertFalse(intrinsics_first_spec.ba_kwargs["warmstart_optimize_points"]) + self.assertNotIn( + "first_release_geometry_slack", intrinsics_first_spec.ba_kwargs + ) + self.assertNotIn( + "first_release_correspondence_slack", + intrinsics_first_spec.ba_kwargs, + ) self.assertEqual( intrinsics_first_spec.ba_kwargs["pose_release_camera_order"], [0, 1, 2, 3], @@ -129,13 +143,31 @@ def test_default_experiments_adds_known_point_presets(self): self.assertEqual(alternating_spec.mode, "alternating") self.assertEqual( len(alternating_spec.ba_kwargs["pose_block_configs"]), - 2, + 6, ) - self.assertFalse( + self.assertTrue( alternating_spec.ba_kwargs["pose_block_configs"][0]["optimize_points"] ) - self.assertTrue( - alternating_spec.ba_kwargs["pose_block_configs"][1]["optimize_points"] + self.assertEqual( + [ + block["name"] + for block in alternating_spec.ba_kwargs["pose_block_configs"] + ], + [ + "points_only", + "omega_only", + "phi_only", + "kappa_only", + "translation_only", + "joint_pose_points", + ], + ) + self.assertEqual( + alternating_spec.ba_kwargs["first_release_geometry_slack"], 0.35 + ) + self.assertEqual( + alternating_spec.ba_kwargs["first_release_correspondence_slack"], + 0.02, ) self.assertIs(staged_known_spec.ba_kwargs["known_points"], known_points) self.assertEqual(staged_known_spec.ba_kwargs["known_point_sigmas"], 0.25) @@ -195,11 +227,23 @@ def test_run_experiment_intrinsics_then_guarded_executes_warmstart_first(self): with ( patch( "openptv_python.demo_bundle_adjustment.multi_camera_bundle_adjustment", - return_value=(reference_cals, points.copy(), {"success": True, "final_reprojection_rms": 3.8, "message": "warm"}), + return_value=( + reference_cals, + points.copy(), + {"success": True, "final_reprojection_rms": 3.8, "message": "warm"}, + ), ) as warmstart, patch( "openptv_python.demo_bundle_adjustment.guarded_two_step_bundle_adjustment", - return_value=(reference_cals, points.copy(), {"accepted_stage": "intrinsics", "final_reprojection_rms": 3.7, "final_mean_ray_convergence": 0.2}), + return_value=( + reference_cals, + points.copy(), + { + "accepted_stage": "intrinsics", + "final_reprojection_rms": 3.7, + "final_mean_ray_convergence": 0.2, + }, + ), ) as guarded, ): result = run_experiment( @@ -225,6 +269,75 @@ def test_run_experiment_intrinsics_then_guarded_executes_warmstart_first(self): self.assertIn("warmstart_rms=3.800000", result.notes) self.assertIn("accepted_stage=intrinsics", result.notes) + def test_run_experiment_guarded_mode_does_not_forward_alternating_slack(self): + control = ControlPar(4).from_file( + Path("tests/testing_folder/control_parameters/control.par") + ) + add_file = Path("tests/testing_folder/calibration/cam1.tif.addpar") + reference_cals = [ + read_calibration( + Path(f"tests/testing_folder/calibration/sym_cam{cam_num}.tif.ori"), + add_file, + ) + for cam_num in range(1, 5) + ] + start_cals = perturb_calibrations(reference_cals, 1.0) + points = np.array( + [ + [-10.0, -10.0, 0.0], + [10.0, -10.0, 1.0], + [-10.0, 10.0, -1.0], + [10.0, 10.0, 0.5], + ], + dtype=float, + ) + observed_pixels = np.empty((len(points), 4, 2), dtype=float) + for cam_num, cal in enumerate(reference_cals): + observed_pixels[:, cam_num, :] = arr_metric_to_pixel( + image_coordinates(points, cal, control.mm), + control, + ) + + spec = next( + experiment + for experiment in default_experiments(perturbation_scale=1.0) + if experiment.name == "guarded_stagewise_release" + ) + + with patch( + "openptv_python.demo_bundle_adjustment.guarded_two_step_bundle_adjustment", + return_value=( + reference_cals, + points.copy(), + { + "accepted_stage": "intrinsics", + "final_reprojection_rms": 3.7, + "final_mean_ray_convergence": 0.2, + }, + ), + ) as guarded: + run_experiment( + spec, + observed_pixels=observed_pixels, + point_init=points, + control=control, + start_cals=start_cals, + reference_cals=reference_cals, + reference_geometry_points=None, + tracking_data=None, + geometry_export_threshold=None, + correspondence_export_threshold=None, + source_case_dir=Path("tests/testing_folder"), + output_dir=None, + ) + + self.assertEqual(guarded.call_count, 1) + self.assertNotIn("first_release_geometry_slack", guarded.call_args.kwargs) + self.assertNotIn( + "first_release_correspondence_slack", + guarded.call_args.kwargs, + ) + def test_run_experiment_alternating_mode_uses_alternating_solver(self): control = ControlPar(4).from_file( Path("tests/testing_folder/control_parameters/control.par") diff --git a/uv.lock b/uv.lock index fd02693..385e159 100644 --- a/uv.lock +++ b/uv.lock @@ -23,18 +23,6 @@ wheels = [ {url = "https://files.pythonhosted.org/packages/7e/b3/6b4067be973ae96ba0d615946e314c5ae35f9f993eca561b356540bb0c2b/alabaster-1.0.0-py3-none-any.whl", hash = "sha256:fc6786402dc3fcb2de3cabd5fe455a2db534b371124f1f21de8731783dec828b", size = 13929, upload-time = "2024-07-26T18:15:02.05Z"} ] -[[package]] -dependencies = [ - {name = "idna"} -] -name = "anyio" -sdist = {url = "https://files.pythonhosted.org/packages/96/f0/5eb65b2bb0d09ac6776f2eb54adee6abe8228ea05b20a5ad0e4945de8aac/anyio-4.12.1.tar.gz", hash = "sha256:41cfcc3a4c85d3f05c932da7c26d0201ac36f72abd4435ba90d0464a3ffed703", size = 228685, upload-time = "2026-01-06T11:45:21.246Z"} -source = {registry = "https://pypi.org/simple"} -version = "4.12.1" -wheels = [ - {url = "https://files.pythonhosted.org/packages/38/0e/27be9fdef66e72d64c0cdc3cc2823101b80585f8119b5c112c2e8f5f7dab/anyio-4.12.1-py3-none-any.whl", hash = "sha256:d405828884fc140aa80a3c667b8beed277f1dfedec42ba031bd6ac3db606ab6c", size = 113592, upload-time = "2026-01-06T11:45:19.497Z"} -] - [[package]] name = "astroid" sdist = {url = "https://files.pythonhosted.org/packages/a2/4c/569eefb533ce71bc9f4f12a4a0d7f0ba27e500681dec1312d4657e849d20/astroid-4.1.1.tar.gz", hash = "sha256:445d831fe785df8c670bbb46b900b8424b82f85b4af187103f71a63a63ebed43", size = 412522, upload-time = "2026-02-23T02:36:30.315Z"} @@ -109,18 +97,6 @@ wheels = [ {url = "https://files.pythonhosted.org/packages/c5/60/3a621758945513adfd4db86827a5bafcc615f913dbd0b4c2ed64a65731be/charset_normalizer-3.4.5-py3-none-any.whl", hash = "sha256:9db5e3fcdcee89a78c04dffb3fe33c79f77bd741a624946db2591c81b2fc85b0", size = 55455, upload-time = "2026-03-06T06:03:17.827Z"} ] -[[package]] -dependencies = [ - {name = "colorama", marker = "sys_platform == 'win32'"} -] -name = "click" -sdist = {url = "https://files.pythonhosted.org/packages/3d/fa/656b739db8587d7b5dfa22e22ed02566950fbfbcdc20311993483657a5c0/click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a", size = 295065, upload-time = "2025-11-15T20:45:42.706Z"} -source = {registry = "https://pypi.org/simple"} -version = "8.3.1" -wheels = [ - {url = "https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6", size = 108274, upload-time = "2025-11-15T20:45:41.139Z"} -] - [[package]] name = "colorama" sdist = {url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z"} @@ -196,15 +172,6 @@ wheels = [ {url = "https://files.pythonhosted.org/packages/f9/0b/de6f54d4a8bedfe8645c41497f3c18d749f0bd3218170c667bf4b81d0cdd/filelock-3.25.0-py3-none-any.whl", hash = "sha256:5ccf8069f7948f494968fc0713c10e5c182a9c9d9eef3a636307a20c2490f047", size = 26427, upload-time = "2026-03-01T15:08:44.593Z"} ] -[[package]] -name = "h11" -sdist = {url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z"} -source = {registry = "https://pypi.org/simple"} -version = "0.16.0" -wheels = [ - {url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z"} -] - [[package]] name = "identify" sdist = {url = "https://files.pythonhosted.org/packages/57/84/376a3b96e5a8d33a7aa2c5b3b31a4b3c364117184bf0b17418055f6ace66/identify-2.6.17.tar.gz", hash = "sha256:f816b0b596b204c9fdf076ded172322f2723cf958d02f9c3587504834c8ff04d", size = 99579, upload-time = "2026-03-01T20:04:12.702Z"} @@ -241,27 +208,6 @@ wheels = [ {url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z"} ] -[[package]] -name = "itsdangerous" -sdist = {url = "https://files.pythonhosted.org/packages/9c/cb/8ac0172223afbccb63986cc25049b154ecfb5e85932587206f42317be31d/itsdangerous-2.2.0.tar.gz", hash = "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173", size = 54410, upload-time = "2024-04-16T21:28:15.614Z"} -source = {registry = "https://pypi.org/simple"} -version = "2.2.0" -wheels = [ - {url = "https://files.pythonhosted.org/packages/04/96/92447566d16df59b2a776c0fb82dbc4d9e07cd95062562af01e408583fc4/itsdangerous-2.2.0-py3-none-any.whl", hash = "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef", size = 16234, upload-time = "2024-04-16T21:28:14.499Z"} -] - -[[package]] -dependencies = [ - {name = "parso"} -] -name = "jedi" -sdist = {url = "https://files.pythonhosted.org/packages/72/3a/79a912fbd4d8dd6fbb02bf69afd3bb72cf0c729bb3063c6f4498603db17a/jedi-0.19.2.tar.gz", hash = "sha256:4770dc3de41bde3966b02eb84fbcf557fb33cce26ad23da12c742fb50ecb11f0", size = 1231287, upload-time = "2024-11-11T01:41:42.873Z"} -source = {registry = "https://pypi.org/simple"} -version = "0.19.2" -wheels = [ - {url = "https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl", hash = "sha256:a8ef22bde8490f57fe5c7681a3c83cb58874daf72b4784de3cce5b6ef6edb5b9", size = 1572278, upload-time = "2024-11-11T01:41:40.175Z"} -] - [[package]] dependencies = [ {name = "markupsafe"} @@ -320,73 +266,6 @@ wheels = [ {url = "https://files.pythonhosted.org/packages/30/a8/e61a8c2b3cc7a597073d9cde1fcbb567e9d827f1db30c93cf80422eac70d/llvmlite-0.46.0-cp314-cp314-win_amd64.whl", hash = "sha256:7821eda3ec1f18050f981819756631d60b6d7ab1a6cf806d9efefbe3f4082d61", size = 39153056, upload-time = "2025-12-08T18:15:33.938Z"} ] -[[package]] -name = "loro" -sdist = {url = "https://files.pythonhosted.org/packages/7d/27/ea6f3298fc87ea5f2d60ebfbca088e7d9b2ceb3993f67c83bfb81778ec01/loro-1.10.3.tar.gz", hash = "sha256:68184ab1c2ab94af6ad4aaba416d22f579cabee0b26cbb09a1f67858207bbce8", size = 68833, upload-time = "2025-12-09T10:14:06.644Z"} -source = {registry = "https://pypi.org/simple"} -version = "1.10.3" -wheels = [ - {url = "https://files.pythonhosted.org/packages/8d/4a/a5340b6fdf4cd34d758bed23bd1f64063b3b1b41ff4ecc94ee39259ee9a7/loro-1.10.3-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:623cf7df17626aa55bc6ca54e89177dbe71a5f1c293e102d6153f43991a1a041", size = 3213589, upload-time = "2025-12-09T10:11:35.377Z"}, - {url = "https://files.pythonhosted.org/packages/00/93/5164e93a77e365a92def77c1258386daef233516a29fb674a3b9d973b8b8/loro-1.10.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:d8e715d475f32a1462969aca27eeb3f998f309182978f55bc37ce5c515d92e90", size = 3029557, upload-time = "2025-12-09T10:11:20.076Z"}, - {url = "https://files.pythonhosted.org/packages/6c/30/94592d7c01f480ce99e1783b0d9203eb20ba2eab42575dabd384e3c9d1fa/loro-1.10.3-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:61e012a80e8c9fe248b9d0a76e91664c9479a72d976eaeed78f87b15b5d1d732", size = 3282335, upload-time = "2025-12-09T10:08:18.168Z"}, - {url = "https://files.pythonhosted.org/packages/e9/a8/7ae3c0b955aa638fa7dbd2d194c7759749a0d0d96a94805d5dec9b30eaea/loro-1.10.3-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:686ece56756acbaf80c986848915e9126a29a06d7a62209747e3ef1efc0bd8f6", size = 3333071, upload-time = "2025-12-09T10:08:55.314Z"}, - {url = "https://files.pythonhosted.org/packages/f7/10/151edebdb2bca626ad50911b761164ced16984b25b0b37b34b674ded8b29/loro-1.10.3-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3aa821c8871deca98f4605eb0c40fb26bcf82bd29c9e7fa33b183516c5395b11", size = 3698226, upload-time = "2025-12-09T10:09:30.474Z"}, - {url = "https://files.pythonhosted.org/packages/f4/ac/02a490e38466506b1003df4910d2a8ae582265023dae9e2217c98b56ea3f/loro-1.10.3-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:507d34137adb4148f79e1da7f89a21a4aab18565621a5dc2b389773fe98ac25b", size = 3407322, upload-time = "2025-12-09T10:10:04.199Z"}, - {url = "https://files.pythonhosted.org/packages/81/db/da51f2bcad81ca3733bc21e83f3b6752446436b565b90f5c350ad227ad01/loro-1.10.3-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:91d3b2e187ccfe2b14118a6e5617266fedcdf3435f6fa0a3db7b4afce8afa687", size = 3330268, upload-time = "2025-12-09T10:10:58.61Z"}, - {url = "https://files.pythonhosted.org/packages/4e/af/50d136c83d504a3a1f4ad33a6bf38b6933985a82741302255cf446a5f7ad/loro-1.10.3-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c0016f834fd1626710081334400aed8494380b55ef131f7133d21c3bd22d892a", size = 3673582, upload-time = "2025-12-09T10:10:35.849Z"}, - {url = "https://files.pythonhosted.org/packages/63/4d/53288aae777218e05c43af9c080652bcdbbc8d97c031607eedd3fc15617d/loro-1.10.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:71c4275dca5a8a86219d60545d4f60e081b4af44b490ac912c0481906934bfc6", size = 3463731, upload-time = "2025-12-09T10:11:54.102Z"}, - {url = "https://files.pythonhosted.org/packages/75/01/2389f26ffe8bc3ffe48a0a578f610dd49c709bbcf0d5d2642c6e2b52f490/loro-1.10.3-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:490f12571b2ed1a8eaf1edd3a7fffc55adac5010b1875fe1bb9e9af9a3907c38", size = 3602334, upload-time = "2025-12-09T10:12:30.082Z"}, - {url = "https://files.pythonhosted.org/packages/a7/16/07b64af13f5fcea025e003ca27bbd6f748217abbd4803dad88ea0900526c/loro-1.10.3-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:a374a43cadaa48528a5411496481df9ae52bf01e513f4509e37d6c986f199c0e", size = 3657896, upload-time = "2025-12-09T10:13:04.86Z"}, - {url = "https://files.pythonhosted.org/packages/c9/2f/4050770d7675ceced71651fe76971d5c27456b7098c0de03a4ecdbb0a02d/loro-1.10.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:1a93b2ee59f1fa8d98dd552211fd5693551893b34c1dd2ba0324806d6d14022f", size = 3544339, upload-time = "2025-12-09T10:13:40.396Z"}, - {url = "https://files.pythonhosted.org/packages/c9/21/67e27cb404c968fc19a841d5c6277f13a17c69a56f49e3c15ea1c92a28eb/loro-1.10.3-cp314-cp314-win32.whl", hash = "sha256:baa863e3d869422e3320e822c0b1f87f5dc44cda903d1bd3b7a16f8413ce3d92", size = 2706731, upload-time = "2025-12-09T10:14:31.604Z"}, - {url = "https://files.pythonhosted.org/packages/08/54/6770cf36aeb994489375e9ab9c01201e70ab7cc286fa97e907aa41b1bae6/loro-1.10.3-cp314-cp314-win_amd64.whl", hash = "sha256:f10ed3ca89485f942b8b2de796ed9783edb990e7e570605232de77489e9f3548", size = 2933563, upload-time = "2025-12-09T10:14:13.805Z"}, - {url = "https://files.pythonhosted.org/packages/24/f5/eb089fd25eb428709dbe79fd4d36b82a00572aa54badd1dff62511a38fe3/loro-1.10.3-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2b4d049efb1953aebfc16fa0b445ff5a37d4d08a1ab93f3b5a577a454b7a5ded", size = 3282369, upload-time = "2025-12-09T10:08:20.011Z"}, - {url = "https://files.pythonhosted.org/packages/30/d7/692cb87c908f6a8af6cbfc10ebab69e16780e3796e11454c2b481b5c3817/loro-1.10.3-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:56ecad7fbac58aa8bee52bb261a764aeef6c7b39c20f0d69e8fad908ab2ca7d8", size = 3332530, upload-time = "2025-12-09T10:08:57.07Z"}, - {url = "https://files.pythonhosted.org/packages/54/46/ed3afbf749288b6f70f3b859a6762538818bf6a557ca873b07d6b036946b/loro-1.10.3-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5d8d1be349d08b3a95592c6a17b80b1ea6aef892b1b8e2b93b540062d04e34e0", size = 3702599, upload-time = "2025-12-09T10:09:31.779Z"}, - {url = "https://files.pythonhosted.org/packages/fe/30/6cb616939c12bfe96a71a01a6e3551febf1c34bf9de114fafadbcfb65064/loro-1.10.3-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1ec0a0b9bc4e32c46f14710062ec5b536c72110318aaf85632a4f8b37e9a470a", size = 3404412, upload-time = "2025-12-09T10:10:05.448Z"}, - {url = "https://files.pythonhosted.org/packages/02/a2/3d4006d3333589f9158ac6d403979bf5c985be8b461b18e7a2ea23b05414/loro-1.10.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c5d4437987f7a4a4ff5927f39d0f43ded5b34295dfb0a3c8e150687e25c3d6b8", size = 3462948, upload-time = "2025-12-09T10:11:55.405Z"}, - {url = "https://files.pythonhosted.org/packages/41/30/c640ccd3e570b08770a9f459decc2d8e7ceefdc34ac28a745418fb9cb5ba/loro-1.10.3-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:86d4f0c631ca274ad2fa2c0bdb8e1e141882d94339b7284a8bef5bf73fa6957d", size = 3599851, upload-time = "2025-12-09T10:12:31.759Z"}, - {url = "https://files.pythonhosted.org/packages/59/8f/062ea50554c47ae30e98b1f0442a458c0edecc6d4edc7fcfc4d901734dd0/loro-1.10.3-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:15e03084ff1b472e14623183ed6e1e43e0f717c2112697beda5e69b5bd0ff236", size = 3655558, upload-time = "2025-12-09T10:13:06.529Z"}, - {url = "https://files.pythonhosted.org/packages/f3/f5/c7dd8cdbd57454b23d89799c22cd42b6d2dda283cd87d7b198dc424a462c/loro-1.10.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:42d6a5ce5bc518eaa682413e82d597299650eeb03e8bc39341752d6e0d22503e", size = 3541282, upload-time = "2025-12-09T10:13:42.189Z"} -] - -[[package]] -dependencies = [ - {name = "click"}, - {name = "docutils"}, - {name = "itsdangerous"}, - {name = "jedi"}, - {name = "loro"}, - {name = "markdown"}, - {name = "msgspec"}, - {name = "narwhals"}, - {name = "packaging"}, - {name = "psutil"}, - {name = "pygments"}, - {name = "pymdown-extensions"}, - {name = "pyyaml"}, - {name = "starlette"}, - {name = "tomlkit"}, - {name = "uvicorn"}, - {name = "websockets"} -] -name = "marimo" -sdist = {url = "https://files.pythonhosted.org/packages/9c/eb/3c19ce54be3440cece96bffbb2027c57d618ab336d53ef6a962f570ce468/marimo-0.20.4.tar.gz", hash = "sha256:7f46ce837953717504673e33e47fb42a619bf5f9d2000d1a3a3b1663a47c5498", size = 38336283, upload-time = "2026-03-04T01:15:13.641Z"} -source = {registry = "https://pypi.org/simple"} -version = "0.20.4" -wheels = [ - {url = "https://files.pythonhosted.org/packages/56/62/bf2c92cc7aab69edfb3907e9bf102c37c1e16b8f4bc6ddd4258ae4c8063c/marimo-0.20.4-py3-none-any.whl", hash = "sha256:3200edd7209f470821b58deb4a45e0577889fab1052748d77088f18bef7d9dd8", size = 38752562, upload-time = "2026-03-04T01:15:09.608Z"} -] - -[[package]] -name = "markdown" -sdist = {url = "https://files.pythonhosted.org/packages/2b/f4/69fa6ed85ae003c2378ffa8f6d2e3234662abd02c10d216c0ba96081a238/markdown-3.10.2.tar.gz", hash = "sha256:994d51325d25ad8aa7ce4ebaec003febcce822c3f8c911e3b17c52f7f589f950", size = 368805, upload-time = "2026-02-09T14:57:26.942Z"} -source = {registry = "https://pypi.org/simple"} -version = "3.10.2" -wheels = [ - {url = "https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.2-py3-none-any.whl", hash = "sha256:e91464b71ae3ee7afd3017d9f358ef0baf158fd9a298db92f1d4761133824c36", size = 108180, upload-time = "2026-02-09T14:57:25.787Z"} -] - [[package]] dependencies = [ {name = "mdurl"} @@ -450,30 +329,6 @@ wheels = [ {url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z"} ] -[[package]] -name = "msgspec" -sdist = {url = "https://files.pythonhosted.org/packages/ea/9c/bfbd12955a49180cbd234c5d29ec6f74fe641698f0cd9df154a854fc8a15/msgspec-0.20.0.tar.gz", hash = "sha256:692349e588fde322875f8d3025ac01689fead5901e7fb18d6870a44519d62a29", size = 317862, upload-time = "2025-11-24T03:56:28.934Z"} -source = {registry = "https://pypi.org/simple"} -version = "0.20.0" -wheels = [ - {url = "https://files.pythonhosted.org/packages/bb/18/62dc13ab0260c7d741dda8dc7f481495b93ac9168cd887dda5929880eef8/msgspec-0.20.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:eead16538db1b3f7ec6e3ed1f6f7c5dec67e90f76e76b610e1ffb5671815633a", size = 196407, upload-time = "2025-11-24T03:55:55.001Z"}, - {url = "https://files.pythonhosted.org/packages/dd/1d/b9949e4ad6953e9f9a142c7997b2f7390c81e03e93570c7c33caf65d27e1/msgspec-0.20.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:703c3bb47bf47801627fb1438f106adbfa2998fe586696d1324586a375fca238", size = 188889, upload-time = "2025-11-24T03:55:56.311Z"}, - {url = "https://files.pythonhosted.org/packages/1e/19/f8bb2dc0f1bfe46cc7d2b6b61c5e9b5a46c62298e8f4d03bbe499c926180/msgspec-0.20.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6cdb227dc585fb109305cee0fd304c2896f02af93ecf50a9c84ee54ee67dbb42", size = 219691, upload-time = "2025-11-24T03:55:57.908Z"}, - {url = "https://files.pythonhosted.org/packages/b8/8e/6b17e43f6eb9369d9858ee32c97959fcd515628a1df376af96c11606cf70/msgspec-0.20.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:27d35044dd8818ac1bd0fedb2feb4fbdff4e3508dd7c5d14316a12a2d96a0de0", size = 224918, upload-time = "2025-11-24T03:55:59.322Z"}, - {url = "https://files.pythonhosted.org/packages/1c/db/0e833a177db1a4484797adba7f429d4242585980b90882cc38709e1b62df/msgspec-0.20.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b4296393a29ee42dd25947981c65506fd4ad39beaf816f614146fa0c5a6c91ae", size = 223436, upload-time = "2025-11-24T03:56:00.716Z"}, - {url = "https://files.pythonhosted.org/packages/c3/30/d2ee787f4c918fd2b123441d49a7707ae9015e0e8e1ab51aa7967a97b90e/msgspec-0.20.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:205fbdadd0d8d861d71c8f3399fe1a82a2caf4467bc8ff9a626df34c12176980", size = 227190, upload-time = "2025-11-24T03:56:02.371Z"}, - {url = "https://files.pythonhosted.org/packages/ff/37/9c4b58ff11d890d788e700b827db2366f4d11b3313bf136780da7017278b/msgspec-0.20.0-cp314-cp314-win_amd64.whl", hash = "sha256:7dfebc94fe7d3feec6bc6c9df4f7e9eccc1160bb5b811fbf3e3a56899e398a6b", size = 193950, upload-time = "2025-11-24T03:56:03.668Z"}, - {url = "https://files.pythonhosted.org/packages/e9/4e/cab707bf2fa57408e2934e5197fc3560079db34a1e3cd2675ff2e47e07de/msgspec-0.20.0-cp314-cp314-win_arm64.whl", hash = "sha256:2ad6ae36e4a602b24b4bf4eaf8ab5a441fec03e1f1b5931beca8ebda68f53fc0", size = 179018, upload-time = "2025-11-24T03:56:05.038Z"}, - {url = "https://files.pythonhosted.org/packages/4c/06/3da3fc9aaa55618a8f43eb9052453cfe01f82930bca3af8cea63a89f3a11/msgspec-0.20.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:f84703e0e6ef025663dd1de828ca028774797b8155e070e795c548f76dde65d5", size = 200389, upload-time = "2025-11-24T03:56:06.375Z"}, - {url = "https://files.pythonhosted.org/packages/83/3b/cc4270a5ceab40dfe1d1745856951b0a24fd16ac8539a66ed3004a60c91e/msgspec-0.20.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7c83fc24dd09cf1275934ff300e3951b3adc5573f0657a643515cc16c7dee131", size = 193198, upload-time = "2025-11-24T03:56:07.742Z"}, - {url = "https://files.pythonhosted.org/packages/cd/ae/4c7905ac53830c8e3c06fdd60e3cdcfedc0bbc993872d1549b84ea21a1bd/msgspec-0.20.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f13ccb1c335a124e80c4562573b9b90f01ea9521a1a87f7576c2e281d547f56", size = 225973, upload-time = "2025-11-24T03:56:09.18Z"}, - {url = "https://files.pythonhosted.org/packages/d9/da/032abac1de4d0678d99eaeadb1323bd9d247f4711c012404ba77ed6f15ca/msgspec-0.20.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:17c2b5ca19f19306fc83c96d85e606d2cc107e0caeea85066b5389f664e04846", size = 229509, upload-time = "2025-11-24T03:56:10.898Z"}, - {url = "https://files.pythonhosted.org/packages/69/52/fdc7bdb7057a166f309e0b44929e584319e625aaba4771b60912a9321ccd/msgspec-0.20.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:d931709355edabf66c2dd1a756b2d658593e79882bc81aae5964969d5a291b63", size = 230434, upload-time = "2025-11-24T03:56:12.48Z"}, - {url = "https://files.pythonhosted.org/packages/cb/fe/1dfd5f512b26b53043884e4f34710c73e294e7cc54278c3fe28380e42c37/msgspec-0.20.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:565f915d2e540e8a0c93a01ff67f50aebe1f7e22798c6a25873f9fda8d1325f8", size = 231758, upload-time = "2025-11-24T03:56:13.765Z"}, - {url = "https://files.pythonhosted.org/packages/97/f6/9ba7121b8e0c4e0beee49575d1dbc804e2e72467692f0428cf39ceba1ea5/msgspec-0.20.0-cp314-cp314t-win_amd64.whl", hash = "sha256:726f3e6c3c323f283f6021ebb6c8ccf58d7cd7baa67b93d73bfbe9a15c34ab8d", size = 206540, upload-time = "2025-11-24T03:56:15.029Z"}, - {url = "https://files.pythonhosted.org/packages/c8/3e/c5187de84bb2c2ca334ab163fcacf19a23ebb1d876c837f81a1b324a15bf/msgspec-0.20.0-cp314-cp314t-win_arm64.whl", hash = "sha256:93f23528edc51d9f686808a361728e903d6f2be55c901d6f5c92e44c6d546bfc", size = 183011, upload-time = "2025-11-24T03:56:16.442Z"} -] - [[package]] dependencies = [ {name = "librt", marker = "platform_python_implementation != 'PyPy'"}, @@ -521,15 +376,6 @@ wheels = [ {url = "https://files.pythonhosted.org/packages/d3/ac/686789b9145413f1a61878c407210e41bfdb097976864e0913078b24098c/myst_parser-5.0.0-py3-none-any.whl", hash = "sha256:ab31e516024918296e169139072b81592336f2fef55b8986aa31c9f04b5f7211", size = 84533, upload-time = "2026-01-15T09:08:16.788Z"} ] -[[package]] -name = "narwhals" -sdist = {url = "https://files.pythonhosted.org/packages/75/59/81d0f4cad21484083466f278e6b392addd9f4205b48d45b5c8771670ebf8/narwhals-2.17.0.tar.gz", hash = "sha256:ebd5bc95bcfa2f8e89a8ac09e2765a63055162837208e67b42d6eeb6651d5e67", size = 620306, upload-time = "2026-02-23T09:44:34.142Z"} -source = {registry = "https://pypi.org/simple"} -version = "2.17.0" -wheels = [ - {url = "https://files.pythonhosted.org/packages/4b/27/20770bd6bf8fbe1e16f848ba21da9df061f38d2e6483952c29d2bb5d1d8b/narwhals-2.17.0-py3-none-any.whl", hash = "sha256:2ac5307b7c2b275a7d66eeda906b8605e3d7a760951e188dcfff86e8ebe083dd", size = 444897, upload-time = "2026-02-23T09:44:32.006Z"} -] - [[package]] name = "nodeenv" sdist = {url = "https://files.pythonhosted.org/packages/24/bf/d1bda4f6168e0b2e9e5958945e01910052158313224ada5ce1fb2e1113b8/nodeenv-1.10.0.tar.gz", hash = "sha256:996c191ad80897d076bdfba80a41994c2b47c68e224c542b48feba42ba00f8bb", size = 55611, upload-time = "2025-12-20T14:08:54.006Z"} @@ -604,9 +450,7 @@ name = "openptv-python" source = {editable = "."} [package.metadata] -provides-extras = ["marimo"] requires-dist = [ - {name = "marimo", marker = "extra == 'marimo'"}, {name = "mypy", specifier = ">=1.19.1"}, {name = "myst-parser", specifier = ">=5.0.0"}, {name = "numba", specifier = ">=0.64.0"}, @@ -622,11 +466,6 @@ requires-dist = [ {name = "types-pyyaml", specifier = ">=6.0.12.20250915"} ] -[package.optional-dependencies] -marimo = [ - {name = "marimo"} -] - [[package]] name = "packaging" sdist = {url = "https://files.pythonhosted.org/packages/65/ee/299d360cdc32edc7d2cf530f3accf79c4fca01e96ffc950d8a52213bd8e4/packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4", size = 143416, upload-time = "2026-01-21T20:50:39.064Z"} @@ -636,15 +475,6 @@ wheels = [ {url = "https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529", size = 74366, upload-time = "2026-01-21T20:50:37.788Z"} ] -[[package]] -name = "parso" -sdist = {url = "https://files.pythonhosted.org/packages/81/76/a1e769043c0c0c9fe391b702539d594731a4362334cdf4dc25d0c09761e7/parso-0.8.6.tar.gz", hash = "sha256:2b9a0332696df97d454fa67b81618fd69c35a7b90327cbe6ba5c92d2c68a7bfd", size = 401621, upload-time = "2026-02-09T15:45:24.425Z"} -source = {registry = "https://pypi.org/simple"} -version = "0.8.6" -wheels = [ - {url = "https://files.pythonhosted.org/packages/b6/61/fae042894f4296ec49e3f193aff5d7c18440da9e48102c3315e1bc4519a7/parso-0.8.6-py2.py3-none-any.whl", hash = "sha256:2c549f800b70a5c4952197248825584cb00f033b29c692671d3bf08bf380baff", size = 106894, upload-time = "2026-02-09T15:45:21.391Z"} -] - [[package]] name = "pathspec" sdist = {url = "https://files.pythonhosted.org/packages/fa/36/e27608899f9b8d4dff0617b2d9ab17ca5608956ca44461ac14ac48b44015/pathspec-1.0.4.tar.gz", hash = "sha256:0210e2ae8a21a9137c0d470578cb0e595af87edaa6ebf12ff176f14a02e0e645", size = 131200, upload-time = "2026-01-27T03:59:46.938Z"} @@ -688,28 +518,6 @@ wheels = [ {url = "https://files.pythonhosted.org/packages/5d/19/fd3ef348460c80af7bb4669ea7926651d1f95c23ff2df18b9d24bab4f3fa/pre_commit-4.5.1-py2.py3-none-any.whl", hash = "sha256:3b3afd891e97337708c1674210f8eba659b52a38ea5f822ff142d10786221f77", size = 226437, upload-time = "2025-12-16T21:14:32.409Z"} ] -[[package]] -name = "psutil" -sdist = {url = "https://files.pythonhosted.org/packages/aa/c6/d1ddf4abb55e93cebc4f2ed8b5d6dbad109ecb8d63748dd2b20ab5e57ebe/psutil-7.2.2.tar.gz", hash = "sha256:0746f5f8d406af344fd547f1c8daa5f5c33dbc293bb8d6a16d80b4bb88f59372", size = 493740, upload-time = "2026-01-28T18:14:54.428Z"} -source = {registry = "https://pypi.org/simple"} -version = "7.2.2" -wheels = [ - {url = "https://files.pythonhosted.org/packages/81/69/ef179ab5ca24f32acc1dac0c247fd6a13b501fd5534dbae0e05a1c48b66d/psutil-7.2.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:eed63d3b4d62449571547b60578c5b2c4bcccc5387148db46e0c2313dad0ee00", size = 130664, upload-time = "2026-01-28T18:15:09.469Z"}, - {url = "https://files.pythonhosted.org/packages/7b/64/665248b557a236d3fa9efc378d60d95ef56dd0a490c2cd37dafc7660d4a9/psutil-7.2.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7b6d09433a10592ce39b13d7be5a54fbac1d1228ed29abc880fb23df7cb694c9", size = 131087, upload-time = "2026-01-28T18:15:11.724Z"}, - {url = "https://files.pythonhosted.org/packages/d5/2e/e6782744700d6759ebce3043dcfa661fb61e2fb752b91cdeae9af12c2178/psutil-7.2.2-cp314-cp314t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1fa4ecf83bcdf6e6c8f4449aff98eefb5d0604bf88cb883d7da3d8d2d909546a", size = 182383, upload-time = "2026-01-28T18:15:13.445Z"}, - {url = "https://files.pythonhosted.org/packages/57/49/0a41cefd10cb7505cdc04dab3eacf24c0c2cb158a998b8c7b1d27ee2c1f5/psutil-7.2.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e452c464a02e7dc7822a05d25db4cde564444a67e58539a00f929c51eddda0cf", size = 185210, upload-time = "2026-01-28T18:15:16.002Z"}, - {url = "https://files.pythonhosted.org/packages/dd/2c/ff9bfb544f283ba5f83ba725a3c5fec6d6b10b8f27ac1dc641c473dc390d/psutil-7.2.2-cp314-cp314t-win_amd64.whl", hash = "sha256:c7663d4e37f13e884d13994247449e9f8f574bc4655d509c3b95e9ec9e2b9dc1", size = 141228, upload-time = "2026-01-28T18:15:18.385Z"}, - {url = "https://files.pythonhosted.org/packages/f2/fc/f8d9c31db14fcec13748d373e668bc3bed94d9077dbc17fb0eebc073233c/psutil-7.2.2-cp314-cp314t-win_arm64.whl", hash = "sha256:11fe5a4f613759764e79c65cf11ebdf26e33d6dd34336f8a337aa2996d71c841", size = 136284, upload-time = "2026-01-28T18:15:19.912Z"}, - {url = "https://files.pythonhosted.org/packages/e7/36/5ee6e05c9bd427237b11b3937ad82bb8ad2752d72c6969314590dd0c2f6e/psutil-7.2.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ed0cace939114f62738d808fdcecd4c869222507e266e574799e9c0faa17d486", size = 129090, upload-time = "2026-01-28T18:15:22.168Z"}, - {url = "https://files.pythonhosted.org/packages/80/c4/f5af4c1ca8c1eeb2e92ccca14ce8effdeec651d5ab6053c589b074eda6e1/psutil-7.2.2-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:1a7b04c10f32cc88ab39cbf606e117fd74721c831c98a27dc04578deb0c16979", size = 129859, upload-time = "2026-01-28T18:15:23.795Z"}, - {url = "https://files.pythonhosted.org/packages/b5/70/5d8df3b09e25bce090399cf48e452d25c935ab72dad19406c77f4e828045/psutil-7.2.2-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:076a2d2f923fd4821644f5ba89f059523da90dc9014e85f8e45a5774ca5bc6f9", size = 155560, upload-time = "2026-01-28T18:15:25.976Z"}, - {url = "https://files.pythonhosted.org/packages/63/65/37648c0c158dc222aba51c089eb3bdfa238e621674dc42d48706e639204f/psutil-7.2.2-cp36-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b0726cecd84f9474419d67252add4ac0cd9811b04d61123054b9fb6f57df6e9e", size = 156997, upload-time = "2026-01-28T18:15:27.794Z"}, - {url = "https://files.pythonhosted.org/packages/8e/13/125093eadae863ce03c6ffdbae9929430d116a246ef69866dad94da3bfbc/psutil-7.2.2-cp36-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:fd04ef36b4a6d599bbdb225dd1d3f51e00105f6d48a28f006da7f9822f2606d8", size = 148972, upload-time = "2026-01-28T18:15:29.342Z"}, - {url = "https://files.pythonhosted.org/packages/04/78/0acd37ca84ce3ddffaa92ef0f571e073faa6d8ff1f0559ab1272188ea2be/psutil-7.2.2-cp36-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b58fabe35e80b264a4e3bb23e6b96f9e45a3df7fb7eed419ac0e5947c61e47cc", size = 148266, upload-time = "2026-01-28T18:15:31.597Z"}, - {url = "https://files.pythonhosted.org/packages/b4/90/e2159492b5426be0c1fef7acba807a03511f97c5f86b3caeda6ad92351a7/psutil-7.2.2-cp37-abi3-win_amd64.whl", hash = "sha256:eb7e81434c8d223ec4a219b5fc1c47d0417b12be7ea866e24fb5ad6e84b3d988", size = 137737, upload-time = "2026-01-28T18:15:33.849Z"}, - {url = "https://files.pythonhosted.org/packages/8c/c7/7bb2e321574b10df20cbde462a94e2b71d05f9bbda251ef27d104668306a/psutil-7.2.2-cp37-abi3-win_arm64.whl", hash = "sha256:8c233660f575a5a89e6d4cb65d9f938126312bca76d8fe087b947b3a1aaac9ee", size = 134617, upload-time = "2026-01-28T18:15:36.514Z"} -] - [[package]] dependencies = [ {name = "accessible-pygments"}, @@ -737,19 +545,6 @@ wheels = [ {url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z"} ] -[[package]] -dependencies = [ - {name = "markdown"}, - {name = "pyyaml"} -] -name = "pymdown-extensions" -sdist = {url = "https://files.pythonhosted.org/packages/ba/63/06673d1eb6d8f83c0ea1f677d770e12565fb516928b4109c9e2055656a9e/pymdown_extensions-10.21.tar.gz", hash = "sha256:39f4a020f40773f6b2ff31d2cd2546c2c04d0a6498c31d9c688d2be07e1767d5", size = 853363, upload-time = "2026-02-15T20:44:06.748Z"} -source = {registry = "https://pypi.org/simple"} -version = "10.21" -wheels = [ - {url = "https://files.pythonhosted.org/packages/6f/2c/5b079febdc65e1c3fb2729bf958d18b45be7113828528e8a0b5850dd819a/pymdown_extensions-10.21-py3-none-any.whl", hash = "sha256:91b879f9f864d49794c2d9534372b10150e6141096c3908a455e45ca72ad9d3f", size = 268877, upload-time = "2026-02-15T20:44:05.464Z"} -] - [[package]] dependencies = [ {name = "colorama", marker = "sys_platform == 'win32'"}, @@ -989,27 +784,6 @@ wheels = [ {url = "https://files.pythonhosted.org/packages/52/a7/d2782e4e3f77c8450f727ba74a8f12756d5ba823d81b941f1b04da9d033a/sphinxcontrib_serializinghtml-2.0.0-py3-none-any.whl", hash = "sha256:6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331", size = 92072, upload-time = "2024-07-29T01:10:08.203Z"} ] -[[package]] -dependencies = [ - {name = "anyio"} -] -name = "starlette" -sdist = {url = "https://files.pythonhosted.org/packages/c4/68/79977123bb7be889ad680d79a40f339082c1978b5cfcf62c2d8d196873ac/starlette-0.52.1.tar.gz", hash = "sha256:834edd1b0a23167694292e94f597773bc3f89f362be6effee198165a35d62933", size = 2653702, upload-time = "2026-01-18T13:34:11.062Z"} -source = {registry = "https://pypi.org/simple"} -version = "0.52.1" -wheels = [ - {url = "https://files.pythonhosted.org/packages/81/0d/13d1d239a25cbfb19e740db83143e95c772a1fe10202dda4b76792b114dd/starlette-0.52.1-py3-none-any.whl", hash = "sha256:0029d43eb3d273bc4f83a08720b4912ea4b071087a3b48db01b7c839f7954d74", size = 74272, upload-time = "2026-01-18T13:34:09.188Z"} -] - -[[package]] -name = "tomlkit" -sdist = {url = "https://files.pythonhosted.org/packages/c3/af/14b24e41977adb296d6bd1fb59402cf7d60ce364f90c890bd2ec65c43b5a/tomlkit-0.14.0.tar.gz", hash = "sha256:cf00efca415dbd57575befb1f6634c4f42d2d87dbba376128adb42c121b87064", size = 187167, upload-time = "2026-01-13T01:14:53.304Z"} -source = {registry = "https://pypi.org/simple"} -version = "0.14.0" -wheels = [ - {url = "https://files.pythonhosted.org/packages/b5/11/87d6d29fb5d237229d67973a6c9e06e048f01cf4994dee194ab0ea841814/tomlkit-0.14.0-py3-none-any.whl", hash = "sha256:592064ed85b40fa213469f81ac584f67a4f2992509a7c3ea2d632208623a3680", size = 39310, upload-time = "2026-01-13T01:14:51.965Z"} -] - [[package]] name = "types-pyyaml" sdist = {url = "https://files.pythonhosted.org/packages/7e/69/3c51b36d04da19b92f9e815be12753125bd8bc247ba0470a982e6979e71c/types_pyyaml-6.0.12.20250915.tar.gz", hash = "sha256:0f8b54a528c303f0e6f7165687dd33fafa81c807fcac23f632b63aa624ced1d3", size = 17522, upload-time = "2025-09-15T03:01:00.728Z"} @@ -1037,19 +811,6 @@ wheels = [ {url = "https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4", size = 131584, upload-time = "2026-01-07T16:24:42.685Z"} ] -[[package]] -dependencies = [ - {name = "click"}, - {name = "h11"} -] -name = "uvicorn" -sdist = {url = "https://files.pythonhosted.org/packages/32/ce/eeb58ae4ac36fe09e3842eb02e0eb676bf2c53ae062b98f1b2531673efdd/uvicorn-0.41.0.tar.gz", hash = "sha256:09d11cf7008da33113824ee5a1c6422d89fbc2ff476540d69a34c87fab8b571a", size = 82633, upload-time = "2026-02-16T23:07:24.1Z"} -source = {registry = "https://pypi.org/simple"} -version = "0.41.0" -wheels = [ - {url = "https://files.pythonhosted.org/packages/83/e4/d04a086285c20886c0daad0e026f250869201013d18f81d9ff5eada73a88/uvicorn-0.41.0-py3-none-any.whl", hash = "sha256:29e35b1d2c36a04b9e180d4007ede3bcb32a85fbdfd6c6aeb3f26839de088187", size = 68783, upload-time = "2026-02-16T23:07:22.357Z"} -] - [[package]] dependencies = [ {name = "distlib"}, @@ -1064,30 +825,3 @@ version = "21.1.0" wheels = [ {url = "https://files.pythonhosted.org/packages/78/55/896b06bf93a49bec0f4ae2a6f1ed12bd05c8860744ac3a70eda041064e4d/virtualenv-21.1.0-py3-none-any.whl", hash = "sha256:164f5e14c5587d170cf98e60378eb91ea35bf037be313811905d3a24ea33cc07", size = 5825072, upload-time = "2026-02-27T08:49:27.516Z"} ] - -[[package]] -name = "websockets" -sdist = {url = "https://files.pythonhosted.org/packages/04/24/4b2031d72e840ce4c1ccb255f693b15c334757fc50023e4db9537080b8c4/websockets-16.0.tar.gz", hash = "sha256:5f6261a5e56e8d5c42a4497b364ea24d94d9563e8fbd44e78ac40879c60179b5", size = 179346, upload-time = "2026-01-10T09:23:47.181Z"} -source = {registry = "https://pypi.org/simple"} -version = "16.0" -wheels = [ - {url = "https://files.pythonhosted.org/packages/f3/1d/e88022630271f5bd349ed82417136281931e558d628dd52c4d8621b4a0b2/websockets-16.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8cc451a50f2aee53042ac52d2d053d08bf89bcb31ae799cb4487587661c038a0", size = 177406, upload-time = "2026-01-10T09:23:12.178Z"}, - {url = "https://files.pythonhosted.org/packages/f2/78/e63be1bf0724eeb4616efb1ae1c9044f7c3953b7957799abb5915bffd38e/websockets-16.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:daa3b6ff70a9241cf6c7fc9e949d41232d9d7d26fd3522b1ad2b4d62487e9904", size = 175085, upload-time = "2026-01-10T09:23:13.511Z"}, - {url = "https://files.pythonhosted.org/packages/bb/f4/d3c9220d818ee955ae390cf319a7c7a467beceb24f05ee7aaaa2414345ba/websockets-16.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:fd3cb4adb94a2a6e2b7c0d8d05cb94e6f1c81a0cf9dc2694fb65c7e8d94c42e4", size = 175328, upload-time = "2026-01-10T09:23:14.727Z"}, - {url = "https://files.pythonhosted.org/packages/63/bc/d3e208028de777087e6fb2b122051a6ff7bbcca0d6df9d9c2bf1dd869ae9/websockets-16.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:781caf5e8eee67f663126490c2f96f40906594cb86b408a703630f95550a8c3e", size = 185044, upload-time = "2026-01-10T09:23:15.939Z"}, - {url = "https://files.pythonhosted.org/packages/ad/6e/9a0927ac24bd33a0a9af834d89e0abc7cfd8e13bed17a86407a66773cc0e/websockets-16.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:caab51a72c51973ca21fa8a18bd8165e1a0183f1ac7066a182ff27107b71e1a4", size = 186279, upload-time = "2026-01-10T09:23:17.148Z"}, - {url = "https://files.pythonhosted.org/packages/b9/ca/bf1c68440d7a868180e11be653c85959502efd3a709323230314fda6e0b3/websockets-16.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:19c4dc84098e523fd63711e563077d39e90ec6702aff4b5d9e344a60cb3c0cb1", size = 185711, upload-time = "2026-01-10T09:23:18.372Z"}, - {url = "https://files.pythonhosted.org/packages/c4/f8/fdc34643a989561f217bb477cbc47a3a07212cbda91c0e4389c43c296ebf/websockets-16.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:a5e18a238a2b2249c9a9235466b90e96ae4795672598a58772dd806edc7ac6d3", size = 184982, upload-time = "2026-01-10T09:23:19.652Z"}, - {url = "https://files.pythonhosted.org/packages/dd/d1/574fa27e233764dbac9c52730d63fcf2823b16f0856b3329fc6268d6ae4f/websockets-16.0-cp314-cp314-win32.whl", hash = "sha256:a069d734c4a043182729edd3e9f247c3b2a4035415a9172fd0f1b71658a320a8", size = 177915, upload-time = "2026-01-10T09:23:21.458Z"}, - {url = "https://files.pythonhosted.org/packages/8a/f1/ae6b937bf3126b5134ce1f482365fde31a357c784ac51852978768b5eff4/websockets-16.0-cp314-cp314-win_amd64.whl", hash = "sha256:c0ee0e63f23914732c6d7e0cce24915c48f3f1512ec1d079ed01fc629dab269d", size = 178381, upload-time = "2026-01-10T09:23:22.715Z"}, - {url = "https://files.pythonhosted.org/packages/06/9b/f791d1db48403e1f0a27577a6beb37afae94254a8c6f08be4a23e4930bc0/websockets-16.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:a35539cacc3febb22b8f4d4a99cc79b104226a756aa7400adc722e83b0d03244", size = 177737, upload-time = "2026-01-10T09:23:24.523Z"}, - {url = "https://files.pythonhosted.org/packages/bd/40/53ad02341fa33b3ce489023f635367a4ac98b73570102ad2cdd770dacc9a/websockets-16.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:b784ca5de850f4ce93ec85d3269d24d4c82f22b7212023c974c401d4980ebc5e", size = 175268, upload-time = "2026-01-10T09:23:25.781Z"}, - {url = "https://files.pythonhosted.org/packages/74/9b/6158d4e459b984f949dcbbb0c5d270154c7618e11c01029b9bbd1bb4c4f9/websockets-16.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:569d01a4e7fba956c5ae4fc988f0d4e187900f5497ce46339c996dbf24f17641", size = 175486, upload-time = "2026-01-10T09:23:27.033Z"}, - {url = "https://files.pythonhosted.org/packages/e5/2d/7583b30208b639c8090206f95073646c2c9ffd66f44df967981a64f849ad/websockets-16.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:50f23cdd8343b984957e4077839841146f67a3d31ab0d00e6b824e74c5b2f6e8", size = 185331, upload-time = "2026-01-10T09:23:28.259Z"}, - {url = "https://files.pythonhosted.org/packages/45/b0/cce3784eb519b7b5ad680d14b9673a31ab8dcb7aad8b64d81709d2430aa8/websockets-16.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:152284a83a00c59b759697b7f9e9cddf4e3c7861dd0d964b472b70f78f89e80e", size = 186501, upload-time = "2026-01-10T09:23:29.449Z"}, - {url = "https://files.pythonhosted.org/packages/19/60/b8ebe4c7e89fb5f6cdf080623c9d92789a53636950f7abacfc33fe2b3135/websockets-16.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:bc59589ab64b0022385f429b94697348a6a234e8ce22544e3681b2e9331b5944", size = 186062, upload-time = "2026-01-10T09:23:31.368Z"}, - {url = "https://files.pythonhosted.org/packages/88/a8/a080593f89b0138b6cba1b28f8df5673b5506f72879322288b031337c0b8/websockets-16.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:32da954ffa2814258030e5a57bc73a3635463238e797c7375dc8091327434206", size = 185356, upload-time = "2026-01-10T09:23:32.627Z"}, - {url = "https://files.pythonhosted.org/packages/c2/b6/b9afed2afadddaf5ebb2afa801abf4b0868f42f8539bfe4b071b5266c9fe/websockets-16.0-cp314-cp314t-win32.whl", hash = "sha256:5a4b4cc550cb665dd8a47f868c8d04c8230f857363ad3c9caf7a0c3bf8c61ca6", size = 178085, upload-time = "2026-01-10T09:23:33.816Z"}, - {url = "https://files.pythonhosted.org/packages/9f/3e/28135a24e384493fa804216b79a6a6759a38cc4ff59118787b9fb693df93/websockets-16.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b14dc141ed6d2dde437cddb216004bcac6a1df0935d79656387bd41632ba0bbd", size = 178531, upload-time = "2026-01-10T09:23:35.016Z"}, - {url = "https://files.pythonhosted.org/packages/6f/28/258ebab549c2bf3e64d2b0217b973467394a9cea8c42f70418ca2c5d0d2e/websockets-16.0-py3-none-any.whl", hash = "sha256:1637db62fad1dc833276dded54215f2c7fa46912301a24bd94d45d46a011ceec", size = 171598, upload-time = "2026-01-10T09:23:45.395Z"} -] From 08419d76ba4d33cc6a90f9a3bf6cb4813151c7db Mon Sep 17 00:00:00 2001 From: Alex Liberzon Date: Sat, 7 Mar 2026 19:25:33 +0200 Subject: [PATCH 14/27] created wrapper to use openptv-python or optv interchangeably for segmentation and image processing --- openptv_python/_native_compat.py | 69 ++++ openptv_python/_native_convert.py | 135 +++++++ openptv_python/image_processing.py | 286 +++++++++------ openptv_python/segmentation.py | 29 ++ plans.md | 2 + pyproject.toml | 2 + tests/test_image_processing.py | 71 ++-- tests/test_native_stress_performance.py | 288 +++++++++++++++ tests/test_segmentation.py | 65 ++++ uv.lock | 444 +++++++++++++++--------- 10 files changed, 1090 insertions(+), 301 deletions(-) create mode 100644 openptv_python/_native_compat.py create mode 100644 openptv_python/_native_convert.py create mode 100644 plans.md create mode 100644 tests/test_native_stress_performance.py diff --git a/openptv_python/_native_compat.py b/openptv_python/_native_compat.py new file mode 100644 index 0000000..fbb01c6 --- /dev/null +++ b/openptv_python/_native_compat.py @@ -0,0 +1,69 @@ +"""Optional compatibility layer for reusing optv py_bind as a native provider.""" + +from __future__ import annotations + +from importlib import import_module +from types import ModuleType +from typing import Any + + +def _optional_import(module_name: str) -> ModuleType | None: + try: + return import_module(module_name) + except Exception: + return None + + +optv_calibration = _optional_import("optv.calibration") +optv_image_processing = _optional_import("optv.image_processing") +optv_parameters = _optional_import("optv.parameters") +optv_segmentation = _optional_import("optv.segmentation") +optv_tracking_framebuf = _optional_import("optv.tracking_framebuf") + +HAS_OPTV = any( + module is not None + for module in ( + optv_calibration, + optv_image_processing, + optv_parameters, + optv_segmentation, + optv_tracking_framebuf, + ) +) + +HAS_NATIVE_PREPROCESS = ( + optv_image_processing is not None + and hasattr(optv_image_processing, "preprocess_image") + and optv_parameters is not None + and hasattr(optv_parameters, "ControlParams") +) + +HAS_NATIVE_SEGMENTATION = ( + optv_segmentation is not None + and hasattr(optv_segmentation, "target_recognition") + and optv_parameters is not None + and hasattr(optv_parameters, "TargetParams") + and hasattr(optv_parameters, "ControlParams") + and optv_tracking_framebuf is not None + and hasattr(optv_tracking_framebuf, "Target") +) + +HAS_NATIVE_CALIBRATION = optv_calibration is not None and hasattr( + optv_calibration, "Calibration" +) + +HAS_NATIVE_TARGETS = optv_tracking_framebuf is not None and hasattr( + optv_tracking_framebuf, "Target" +) + + +def native_preprocess_image(*args: Any, **kwargs: Any) -> Any: + if not HAS_NATIVE_PREPROCESS or optv_image_processing is None: + raise RuntimeError("optv native preprocess_image is not available") + return optv_image_processing.preprocess_image(*args, **kwargs) + + +def native_target_recognition(*args: Any, **kwargs: Any) -> Any: + if not HAS_NATIVE_SEGMENTATION or optv_segmentation is None: + raise RuntimeError("optv native target_recognition is not available") + return optv_segmentation.target_recognition(*args, **kwargs) \ No newline at end of file diff --git a/openptv_python/_native_convert.py b/openptv_python/_native_convert.py new file mode 100644 index 0000000..001e7ef --- /dev/null +++ b/openptv_python/_native_convert.py @@ -0,0 +1,135 @@ +"""Conversion helpers between openptv_python objects and optv py_bind objects.""" + +from __future__ import annotations + +from typing import Iterable, List +from types import ModuleType + +import numpy as np + +from ._native_compat import ( + HAS_NATIVE_CALIBRATION, + HAS_NATIVE_TARGETS, + HAS_OPTV, + optv_calibration, + optv_parameters, + optv_tracking_framebuf, +) +from .calibration import Calibration +from .parameters import ControlPar, TargetPar +from .tracking_frame_buf import Target + + +def _require_optv_parameters() -> None: + if not HAS_OPTV or optv_parameters is None: + raise RuntimeError("optv py_bind parameters are not available") + + +def _optv_parameters_module() -> ModuleType: + _require_optv_parameters() + assert optv_parameters is not None + return optv_parameters + + +def to_native_calibration(cal: Calibration): + if not HAS_NATIVE_CALIBRATION or optv_calibration is None: + raise RuntimeError("optv Calibration is not available") + + native = optv_calibration.Calibration() + native.set_pos(np.asarray(cal.get_pos(), dtype=np.float64)) + native.set_angles(np.asarray(cal.get_angles(), dtype=np.float64)) + native.set_primary_point(np.asarray(cal.get_primary_point(), dtype=np.float64)) + native.set_radial_distortion( + np.asarray(cal.get_radial_distortion(), dtype=np.float64) + ) + native.set_decentering(np.asarray(cal.get_decentering(), dtype=np.float64)) + native.set_affine_trans(np.asarray(cal.get_affine(), dtype=np.float64)) + native.set_glass_vec(np.asarray(cal.get_glass_vec(), dtype=np.float64)) + return native + + +def to_native_control_par(cpar: ControlPar): + parameters_module = _optv_parameters_module() + + flags = [ + flag_name + for enabled, flag_name in ( + (bool(cpar.hp_flag), "hp"), + (bool(cpar.all_cam_flag), "allcam"), + (bool(cpar.tiff_flag), "headers"), + ) + if enabled + ] + + native = parameters_module.ControlParams( + cpar.num_cams, + flags=flags, + image_size=(cpar.imx, cpar.imy), + pixel_size=(cpar.pix_x, cpar.pix_y), + cam_side_n=cpar.mm.n1, + wall_ns=list(cpar.mm.n2), + wall_thicks=list(cpar.mm.d), + object_side_n=cpar.mm.n3, + ) + native.set_chfield(cpar.chfield) + + for cam_index, img_base_name in enumerate(cpar.img_base_name): + native.set_img_base_name(cam_index, img_base_name) + + for cam_index, cal_img_base_name in enumerate(cpar.cal_img_base_name): + native.set_cal_img_base_name(cam_index, cal_img_base_name) + + return native + + +def to_native_target_par(tpar: TargetPar): + parameters_module = _optv_parameters_module() + + thresholds = list(tpar.gvthresh) + if len(thresholds) < 4: + thresholds.extend([0] * (4 - len(thresholds))) + + return parameters_module.TargetParams( + discont=tpar.discont, + gvthresh=thresholds[:4], + pixel_count_bounds=(tpar.nnmin, tpar.nnmax), + xsize_bounds=(tpar.nxmin, tpar.nxmax), + ysize_bounds=(tpar.nymin, tpar.nymax), + min_sum_grey=tpar.sumg_min, + cross_size=tpar.cr_sz, + ) + + +def to_native_target(target: Target): + if not HAS_NATIVE_TARGETS or optv_tracking_framebuf is None: + raise RuntimeError("optv Target is not available") + + return optv_tracking_framebuf.Target( + pnr=target.pnr, + x=target.x, + y=target.y, + n=target.n, + nx=target.nx, + ny=target.ny, + sumg=target.sumg, + tnr=target.tnr, + ) + + +def from_native_target(native_target) -> Target: + x, y = native_target.pos() + n, nx, ny = native_target.count_pixels() + return Target( + pnr=int(native_target.pnr()), + x=float(x), + y=float(y), + n=int(n), + nx=int(nx), + ny=int(ny), + sumg=int(native_target.sum_grey_value()), + tnr=int(native_target.tnr()), + ) + + +def from_native_target_array(native_targets: Iterable[object]) -> List[Target]: + return [from_native_target(target) for target in native_targets] \ No newline at end of file diff --git a/openptv_python/image_processing.py b/openptv_python/image_processing.py index bd47fdb..082e785 100644 --- a/openptv_python/image_processing.py +++ b/openptv_python/image_processing.py @@ -3,10 +3,10 @@ import copy import numpy as np -from numba import njit, prange -from scipy import ndimage -from scipy.ndimage import uniform_filter +from numba import njit +from ._native_compat import HAS_NATIVE_PREPROCESS, native_preprocess_image +from ._native_convert import to_native_control_par from .parameters import ControlPar filter_t = np.zeros((3, 3), dtype=float) @@ -15,35 +15,169 @@ @njit def filter_3(img, kernel=None) -> np.ndarray: """Apply a 3x3 filter to an image.""" - if kernel is None: # default is a low pass - kernel = np.ones((3, 3)) / 9 - filtered_img = ndimage.convolve(img, kernel) - return filtered_img + if img.dtype != np.uint8: + raise TypeError("Image must be of type uint8") + + if kernel is None: + kernel = np.ones((3, 3), dtype=np.float64) + + kernel_sum = float(np.sum(kernel)) + if kernel_sum == 0: + raise ValueError("Filter kernel sum must not be zero") + + imx = img.shape[1] + imy = img.shape[0] + image_size = imx * imy + flat_in = img.reshape(-1) + flat_out = flat_in.copy() + + for index in range(imx + 1, image_size - imx - 1): + buf = ( + kernel[0, 0] * flat_in[index - imx - 1] + + kernel[0, 1] * flat_in[index - imx] + + kernel[0, 2] * flat_in[index - imx + 1] + + kernel[1, 0] * flat_in[index - 1] + + kernel[1, 1] * flat_in[index] + + kernel[1, 2] * flat_in[index + 1] + + kernel[2, 0] * flat_in[index + imx - 1] + + kernel[2, 1] * flat_in[index + imx] + + kernel[2, 2] * flat_in[index + imx + 1] + ) + buf = int(buf / kernel_sum) + if buf > 255: + buf = 255 + if buf < 8: + buf = 8 + flat_out[index] = np.uint8(buf) + + return flat_out.reshape(img.shape) @njit def lowpass_3(img: np.ndarray) -> np.ndarray: - """Lowpass filter of 3x3.""" - # Define the 3x3 lowpass filter kernel - kernel = np.ones((3, 3)) / 9 + """Lowpass filter matching the native 3x3 implementation.""" + if img.dtype != np.uint8: + raise TypeError("Image must be of type uint8") - # Apply the filter to the image using scipy.ndimage.convolve() - img_lp = ndimage.convolve(img, kernel, mode="constant", cval=0.0) + imx = img.shape[1] + imy = img.shape[0] + image_size = imx * imy + flat_in = img.reshape(-1) + flat_out = flat_in.copy() + + for index in range(imx + 1, image_size - imx - 1): + buf = ( + int(flat_in[index - imx - 1]) + + int(flat_in[index - imx]) + + int(flat_in[index - imx + 1]) + + int(flat_in[index - 1]) + + int(flat_in[index]) + + int(flat_in[index + 1]) + + int(flat_in[index + imx - 1]) + + int(flat_in[index + imx]) + + int(flat_in[index + imx + 1]) + ) + flat_out[index] = np.uint8(buf // 9) - return img_lp + return flat_out.reshape(img.shape) -@njit -def fast_box_blur(filt_span: int, src: np.ndarray, cpar: ControlPar) -> np.ndarray: - """Fast box blur.""" +def fast_box_blur( + filt_span: int, + src: np.ndarray, + cpar: ControlPar | None = None, +) -> np.ndarray: + """Fast box blur matching liboptv border handling.""" + if src.dtype != np.uint8: + raise TypeError("Image must be of type uint8") + + if src.ndim != 2: + raise TypeError("Input array must be two-dimensional") + + if cpar is not None: + imx = int(cpar.imx) + imy = int(cpar.imy) + if src.shape != (imy, imx): + raise ValueError("Image shape does not match control parameters") + else: + imy, imx = src.shape + + image_size = imx * imy n = 2 * filt_span + 1 - row_accum = uniform_filter( - src.reshape((cpar.imy, cpar.imx)), - size=n, - mode="constant", - cval=0, - ).reshape(-1) - return row_accum + nq = n * n + src_flat = src.reshape(-1) + dest_flat = np.zeros(image_size, dtype=np.uint8) + row_accum = np.zeros(image_size, dtype=np.int64) + col_accum = np.zeros(imx, dtype=np.int64) + + for row in range(imy): + row_start = row * imx + accum = int(src_flat[row_start]) + row_accum[row_start] = accum * n + + for col in range(1, min(filt_span + 1, imx)): + right = row_start + 2 * col + left = right - 1 + if right >= row_start + imx: + break + accum += int(src_flat[left]) + int(src_flat[right]) + row_accum[row_start + col] = accum * n // (2 * col + 1) + + for col in range(filt_span + 1, max(imx - filt_span, filt_span + 1)): + accum += int(src_flat[row_start + col + filt_span]) + accum -= int(src_flat[row_start + col - filt_span - 1]) + row_accum[row_start + col] = accum + + m = n - 2 + col = imx - filt_span + left = row_start + imx - n + right = left + 1 + while col < row_start + imx - row_start and m > 0 and right < row_start + imx: + accum -= int(src_flat[left]) + int(src_flat[right]) + row_accum[row_start + col] = accum * n // m + left += 2 + right += 2 + col += 1 + m -= 2 + + for col in range(imx): + col_accum[col] = row_accum[col] + dest_flat[col] = np.uint8(col_accum[col] // n) + + max_top = min(filt_span, (imy - 1) // 2) + for row in range(1, max_top + 1): + base1 = (2 * row - 1) * imx + base2 = base1 + imx + out_base = row * imx + for col in range(imx): + col_accum[col] += row_accum[base1 + col] + row_accum[base2 + col] + dest_flat[out_base + col] = np.uint8( + n * col_accum[col] // nq // (2 * row + 1) + ) + + for row in range(filt_span + 1, max(imy - filt_span, filt_span + 1)): + remove_base = (row - filt_span - 1) * imx + add_base = (row + filt_span) * imx + out_base = row * imx + for col in range(imx): + col_accum[col] += row_accum[add_base + col] - row_accum[remove_base + col] + dest_flat[out_base + col] = np.uint8(col_accum[col] // nq) + + for remaining in range(min(filt_span, imy - 1), 0, -1): + remove_base = (imy - 2 * remaining - 1) * imx + remove_base2 = remove_base + imx + out_base = (imy - remaining) * imx + if remove_base < 0: + continue + for col in range(imx): + col_accum[col] -= ( + row_accum[remove_base + col] + row_accum[remove_base2 + col] + ) + dest_flat[out_base + col] = np.uint8( + n * col_accum[col] // nq // (2 * remaining + 1) + ) + + return dest_flat.reshape(src.shape) # def split(img: np.ndarray, half_selector: int, cpar: ControlPar) -> np.ndarray: @@ -72,7 +206,9 @@ def subtract_img(img1: np.ndarray, img2: np.ndarray, img_new: np.ndarray) -> Non img1, img2: numpy arrays containing the original images. img_new: numpy array to store the result. """ - img_new[:] = ndimage.maximum(img1 - img2, 0) + img_new[:] = np.maximum(img1.astype(np.int16) - img2.astype(np.int16), 0).astype( + np.uint8 + ) @njit @@ -98,33 +234,19 @@ def prepare_image( filter on an image, optionally followed by additional user-defined filter. """ - # image_size = cpar.imx * cpar.imy - # img_lp = np.zeros_like(img, dtype=np.uint8) - - # Apply low-pass filter - # img = img.reshape((cpar.imy, cpar.imx)) # Reshape to 2D image if img.dtype != np.uint8: raise TypeError("Image must be of type uint8") - img_lp = ndimage.uniform_filter( - img, - size=dim_lp * 2 + 1, - mode="constant", - cval=0, - ) + if img.ndim != 2: + raise TypeError("Input array must be two-dimensional") - # Subtract low-pass filtered image from original image - img_hp = img | img_lp + img_lp = fast_box_blur(dim_lp, img) + img_hp = np.empty_like(img) + subtract_img(img, img_lp, img_hp) # Filter highpass image, if wanted, if filter_hp == 0, no highpass filtering if filter_hp == 1: - img_hp = ndimage.uniform_filter( - # img_hp.reshape((cpar.imy, cpar.imx)), - img_hp, - size=3, - mode="constant", - cval=0.0, - ) + img_hp = lowpass_3(img_hp) elif filter_hp == 2 and filter_file != "": try: with open(filter_file, "r", encoding="utf-8") as fp: @@ -132,19 +254,23 @@ def prepare_image( except Exception as exc: raise IOError(f"Could not open filter file: {filter_file}") from exc - img_hp = ndimage.convolve( - # img_hp.reshape((cpar.imy, cpar.imx)), - img_hp, - weights=filt, - mode="constant", - cval=0.0, - ) + img_hp = filter_3(img_hp, filt) return img_hp def preprocess_image(img, filter_hp, cpar, dim_lp) -> np.ndarray: """Decorate prepare_image with default parameters.""" + if HAS_NATIVE_PREPROCESS: + native_cpar = to_native_control_par(cpar) + return native_preprocess_image( + img, + filter_hp, + native_cpar, + lowpass_dim=dim_lp, + filter_file=None, + output_img=None, + ) return prepare_image(img=img, dim_lp=dim_lp, filter_hp=filter_hp, filter_file="") @@ -235,62 +361,4 @@ def preprocess_image(img, filter_hp, cpar, dim_lp) -> np.ndarray: # pass # replace this with the actual implementation of the function -@njit(parallel=True) -def fast_box_blur_numba(filt_span, src, cpar): - imx, imy = cpar["imx"], cpar["imy"] - n = 2 * filt_span + 1 - nq = n * n - - row_accum = np.zeros(imx * imy, dtype=np.int32) - col_accum = np.zeros(imx, dtype=np.int32) - dest = np.zeros_like(src, dtype=np.int32) - - # Sum over lines first [1]: - for i in prange(imy): - row_start = i * imx - accum = src[row_start] - row_accum[row_start] = accum * n - - for j in range(1, filt_span + 1): - accum += src[row_start + j - 1] + src[row_start + j + filt_span] - row_accum[row_start + j] = accum * n // (2 * j + 1) - - for j in range(filt_span + 1, imx - filt_span): - accum += src[row_start + j + filt_span] - src[row_start + j - filt_span - 1] - row_accum[row_start + j] = accum - - for j in range(imx - filt_span, imx): - accum -= src[row_start + j - filt_span - 1] + src[row_start + j + filt_span] - row_accum[row_start + j] = accum * n // (2 * (imx - j - 1) + 1) - - # Sum over columns: - col_accum[:imx] = row_accum[:imx] - dest[:imx] = col_accum[:imx] // n - - for i in range(1, filt_span + 1): - ptr1 = row_accum[(2 * i - 1) * imx : (2 * i + 1) * imx] - ptr2 = ptr1[imx:] - col_accum += ptr1 + ptr2 - dest[i * imx : (i + 1) * imx] = n * col_accum // nq // (2 * i + 1) - - for i in range(filt_span + 1, imy - filt_span): - ptr1 = row_accum[(i - filt_span - 1) * imx : i * imx] - ptr2 = row_accum[(i + filt_span) * imx : (i + filt_span + 1) * imx] - col_accum += ptr2 - ptr1 - dest[i * imx : (i + 1) * imx] = col_accum // nq - - for i in range(filt_span, 0, -1): - ptr1 = row_accum[(imy - 2 * i - 1) * imx : (imy - 2 * i + 1) * imx] - ptr2 = ptr1[imx:] - col_accum -= ptr1 + ptr2 - dest[(imy - i) * imx :] = n * col_accum // nq // (2 * i + 1) - - return dest - - -# # Example usage: -# filt_span = 3 -# src = np.random.randint(0, 256, size=(1000, 1000), dtype=np.uint8) -# cpar = {'imx': src.shape[1], 'imy': src.shape[0]} - # result = fast_box_blur_numba(filt_span, src, cpar) diff --git a/openptv_python/segmentation.py b/openptv_python/segmentation.py index 9ae39d7..85c4090 100644 --- a/openptv_python/segmentation.py +++ b/openptv_python/segmentation.py @@ -5,6 +5,12 @@ from numba import njit from scipy.ndimage import center_of_mass, gaussian_filter, label, maximum_filter +from ._native_compat import HAS_NATIVE_SEGMENTATION, native_target_recognition +from ._native_convert import ( + from_native_target_array, + to_native_control_par, + to_native_target_par, +) from .constants import CORRES_NONE, MAX_TARGETS from .parameters import ControlPar, TargetPar from .tracking_frame_buf import Target @@ -40,6 +46,29 @@ def targ_rec( num_cam, ) -> List[Target]: """Target recognition function.""" + if ( + HAS_NATIVE_SEGMENTATION + and xmin <= 0 + and ymin <= 0 + and xmax >= cpar.imx + and ymax >= cpar.imy + ): + native_tpar = to_native_target_par(targ_par) + native_cpar = to_native_control_par(cpar) + native_targets = native_target_recognition( + img, + native_tpar, + int(num_cam), + native_cpar, + subrange_x=None, + subrange_y=None, + ) + targets = from_native_target_array(native_targets) + threshold = targ_par.gvthresh[int(num_cam)] + for target in targets: + target.sumg -= target.n * threshold + return targets + thres = targ_par.gvthresh[num_cam] disco = targ_par.discont diff --git a/plans.md b/plans.md new file mode 100644 index 0000000..c8885ab --- /dev/null +++ b/plans.md @@ -0,0 +1,2 @@ +openptv-python-2026-03-07-backend-design.md +openptv-python-2026-03-07-pybind-delegation-map.md \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 7ddceb8..561046f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,6 +19,7 @@ dependencies = [ "myst-parser>=5.0.0", "numba>=0.64.0", "numpy>=2.4.2", + "optv>=0.3.2", "pre-commit>=4.5.1", "pydata-sphinx-theme>=0.16.1", "pytest>=9.0.2", @@ -34,6 +35,7 @@ dynamic = ["version"] license = {file = "LICENSE"} name = "openptv-python" readme = "README.md" +requires-python = ">=3.12,<3.14" [tool.coverage.run] branch = true diff --git a/tests/test_image_processing.py b/tests/test_image_processing.py index c432473..7d6351b 100644 --- a/tests/test_image_processing.py +++ b/tests/test_image_processing.py @@ -4,6 +4,12 @@ import numpy as np +import openptv_python.image_processing as image_processing +from openptv_python._native_compat import ( + HAS_NATIVE_PREPROCESS, + native_preprocess_image, +) +from openptv_python._native_convert import to_native_control_par from openptv_python.image_processing import prepare_image from openptv_python.parameters import ControlPar @@ -38,24 +44,13 @@ def test_arguments(self): def test_preprocess_image(self): """Test that the function returns the correct result.""" - # correct_res = np.array( - # [ - # [0, 0, 0, 0, 0], - # [0, 142, 85, 142, 0], - # [0, 85, 0, 85, 0], - # [0, 142, 85, 142, 0], - # [0, 0, 0, 0, 0], - # ], - # dtype=np.uint8, - # ) - correct_res = np.array( [ - [28, 56, 85, 56, 28], - [56, 255, 255, 255, 56], - [85, 255, 255, 255, 85], - [56, 255, 255, 255, 56], - [28, 56, 85, 56, 28], + [0, 0, 0, 0, 0], + [0, 142, 85, 142, 0], + [0, 85, 0, 85, 0], + [0, 142, 85, 142, 0], + [0, 0, 0, 0, 0], ], dtype=np.uint8, ) @@ -67,12 +62,46 @@ def test_preprocess_image(self): # filter_file='', ) - # print(res) + np.testing.assert_array_equal(res, correct_res) + + @unittest.skipUnless( + HAS_NATIVE_PREPROCESS, + "optv native preprocess_image is not available", + ) + def test_preprocess_image_matches_optv_output(self): + """The Python and optv preprocessing implementations should agree.""" + input_img = np.array( + [ + [0, 15, 30, 45, 60, 45, 30], + [10, 80, 130, 180, 130, 80, 10], + [20, 120, 255, 255, 255, 120, 20], + [30, 150, 255, 255, 255, 150, 30], + [20, 120, 255, 255, 255, 120, 20], + [10, 80, 130, 180, 130, 80, 10], + [0, 15, 30, 45, 60, 45, 30], + ], + dtype=np.uint8, + ) + + control = ControlPar(1) + control.set_image_size((7, 7)) + + python_result = image_processing.prepare_image( + input_img, + dim_lp=1, + filter_hp=0, + filter_file="", + ) + native_result = native_preprocess_image( + input_img, + 0, + to_native_control_par(control), + lowpass_dim=1, + filter_file=None, + output_img=None, + ) - # this test fails as we changed the image processing - # to use Numpy approach - # np.testing.assert_array_equal(res, correct_res) - assert np.allclose(res[1:4, 1:4], correct_res[1:4, 1:4]) + np.testing.assert_array_equal(native_result, python_result) if __name__ == "__main__": diff --git a/tests/test_native_stress_performance.py b/tests/test_native_stress_performance.py new file mode 100644 index 0000000..7978afc --- /dev/null +++ b/tests/test_native_stress_performance.py @@ -0,0 +1,288 @@ +"""Stress benchmarks comparing native and non-native backends.""" + +from __future__ import annotations + +import os +import unittest +from pathlib import Path +from statistics import median +from time import perf_counter +from unittest.mock import patch + +import numpy as np + +import openptv_python.image_processing as image_processing +import openptv_python.orientation as orientation +import openptv_python.segmentation as segmentation +from openptv_python._native_compat import ( + HAS_OPTV, + HAS_NATIVE_PREPROCESS, + HAS_NATIVE_SEGMENTATION, +) +from openptv_python._native_convert import to_native_calibration, to_native_control_par +from openptv_python.calibration import Calibration +from openptv_python.imgcoord import image_coordinates +from openptv_python.parameters import ControlPar, TargetPar, VolumePar + +try: + from optv.orientation import point_positions as native_point_positions + from optv.parameters import VolumeParams as NativeVolumeParams + + HAS_NATIVE_RECONSTRUCTION = True +except ImportError: + native_point_positions = None + NativeVolumeParams = None + HAS_NATIVE_RECONSTRUCTION = False + + +def _env_flag_enabled(name: str) -> bool: + """Interpret common truthy environment variable values.""" + return os.getenv(name, "").strip().lower() in {"1", "true", "yes", "on"} + + +RUN_STRESS_BENCHMARKS = _env_flag_enabled("OPENPTV_RUN_STRESS_BENCHMARKS") + + +def _benchmark(function, *, warmups: int = 1, runs: int = 3): + """Run a callable repeatedly and return the final result and timings.""" + result = None + for _ in range(warmups): + result = function() + + timings = [] + for _ in range(runs): + start = perf_counter() + result = function() + timings.append(perf_counter() - start) + + return result, timings + + +def _timing_summary(label: str, timings: list[float]) -> str: + """Return a compact timing summary for benchmark output.""" + return ( + f"{label}: median={median(timings):.6f}s " + f"min={min(timings):.6f}s max={max(timings):.6f}s" + ) + + +def _serialize_targets(targets) -> list[tuple[int, float, float, int, int, int, int, int]]: + """Convert target objects into stable tuples for comparisons.""" + return [ + ( + int(target.pnr), + round(float(target.x), 9), + round(float(target.y), 9), + int(target.n), + int(target.nx), + int(target.ny), + int(target.sumg), + int(target.tnr), + ) + for target in targets + ] + + +def _build_segmentation_stress_image( + width: int = 512, + height: int = 512, + spacing: int = 24, +) -> np.ndarray: + """Create a deterministic image with many isolated synthetic targets.""" + image = np.zeros((height, width), dtype=np.uint8) + patch = np.array( + [ + [0, 220, 230, 220, 0], + [220, 240, 248, 240, 220], + [230, 248, 255, 248, 230], + [220, 240, 248, 240, 220], + [0, 220, 230, 220, 0], + ], + dtype=np.uint8, + ) + + for center_y in range(12, height - 12, spacing): + for center_x in range(12, width - 12, spacing): + image[center_y - 2 : center_y + 3, center_x - 2 : center_x + 3] = patch + + return image + + +def _build_reconstruction_stress_case( + num_points: int = 4096, +) -> tuple[np.ndarray, np.ndarray, ControlPar, list[Calibration]]: + """Build a deterministic multi-camera reconstruction workload.""" + rng = np.random.default_rng(20260307) + points = np.empty((num_points, 3), dtype=np.float64) + points[:, 0] = rng.uniform(-25.0, 25.0, size=num_points) + points[:, 1] = rng.uniform(20.0, 65.0, size=num_points) + points[:, 2] = rng.uniform(-15.0, 15.0, size=num_points) + + cpar = ControlPar(4).from_file( + Path("tests/testing_folder/control_parameters/control.par") + ) + cpar.mm.set_n1(1.0) + cpar.mm.set_layers([1.0], [1.0]) + cpar.mm.set_n3(1.0) + + add_file = Path("tests/testing_folder/calibration/cam1.tif.addpar") + calibs = [ + Calibration().from_file( + ori_file=Path( + f"tests/testing_folder/calibration/sym_cam{cam_num}.tif.ori" + ), + add_file=add_file, + ) + for cam_num in range(1, 5) + ] + + projections = [image_coordinates(points, cal, cpar.mm) for cal in calibs] + targets = np.asarray(projections, dtype=np.float64).transpose(1, 0, 2) + return points, targets, cpar, calibs + + +@unittest.skipUnless( + RUN_STRESS_BENCHMARKS, + "set OPENPTV_RUN_STRESS_BENCHMARKS=1 to run stress benchmarks", +) +class TestNativeStressPerformance(unittest.TestCase): + """Stress tests comparing native and non-native runtime paths.""" + + @unittest.skipUnless( + HAS_NATIVE_PREPROCESS, + "optv native preprocess_image is not available", + ) + def test_preprocess_image_stress_timing(self): + """Compare native preprocessing timing against the Python path.""" + rng = np.random.default_rng(20260307) + image = rng.integers(0, 256, size=(1024, 1024), dtype=np.uint8) + + cpar = ControlPar(1) + cpar.set_image_size((image.shape[1], image.shape[0])) + + def python_path(): + return image_processing.prepare_image( + image, + dim_lp=1, + filter_hp=0, + filter_file="", + ) + + def native_path(): + return image_processing.preprocess_image( + image, + filter_hp=0, + cpar=cpar, + dim_lp=1, + ) + + python_result, python_timings = _benchmark(python_path) + with patch.object( + image_processing, + "native_preprocess_image", + wraps=image_processing.native_preprocess_image, + ) as native_call: + native_result, native_timings = _benchmark(native_path) + + np.testing.assert_array_equal(native_result, python_result) + self.assertGreaterEqual(native_call.call_count, 3) + + speedup = median(python_timings) / median(native_timings) + print( + "preprocess stress benchmark: " + f"{_timing_summary('python', python_timings)}; " + f"{_timing_summary('native', native_timings)}; " + f"speedup={speedup:.2f}x" + ) + + @unittest.skipUnless( + HAS_NATIVE_SEGMENTATION, + "optv native target recognition is not available", + ) + def test_target_recognition_stress_timing(self): + """Compare native target recognition timing against Python+Numba.""" + image = _build_segmentation_stress_image() + + cpar = ControlPar(1) + cpar.set_image_size((image.shape[1], image.shape[0])) + tpar = TargetPar( + gvthresh=[200], + discont=20, + nnmin=1, + nnmax=100, + sumg_min=1, + nxmin=1, + nxmax=10, + nymin=1, + nymax=10, + ) + + def python_numba_path(): + with patch.object(segmentation, "HAS_NATIVE_SEGMENTATION", False): + return segmentation.target_recognition(image, tpar, 0, cpar) + + def native_path(): + return segmentation.target_recognition(image, tpar, 0, cpar) + + python_targets, python_timings = _benchmark(python_numba_path) + with patch.object( + segmentation, + "native_target_recognition", + wraps=segmentation.native_target_recognition, + ) as native_call: + native_targets, native_timings = _benchmark(native_path) + + self.assertEqual( + _serialize_targets(native_targets), + _serialize_targets(python_targets), + ) + self.assertGreaterEqual(native_call.call_count, 3) + + speedup = median(python_timings) / median(native_timings) + print( + "segmentation stress benchmark: " + f"{_timing_summary('python+numba', python_timings)}; " + f"{_timing_summary('native', native_timings)}; " + f"speedup={speedup:.2f}x" + ) + + @unittest.skipUnless( + HAS_OPTV and HAS_NATIVE_RECONSTRUCTION, + "optv native point_positions is not available", + ) + def test_point_reconstruction_stress_timing(self): + """Compare native multi-camera reconstruction timing against Python.""" + expected_points, targets, cpar, calibs = _build_reconstruction_stress_case() + native_cpar = to_native_control_par(cpar) + native_cals = [to_native_calibration(cal) for cal in calibs] + native_vpar = NativeVolumeParams() + python_vpar = VolumePar() + + def python_path(): + return orientation.point_positions(targets, cpar.mm, calibs, python_vpar) + + def native_path(): + assert native_point_positions is not None + return native_point_positions(targets, native_cpar, native_cals, native_vpar) + + python_result, python_timings = _benchmark(python_path) + native_result, native_timings = _benchmark(native_path) + python_points, python_rcm = python_result + native_points, native_rcm = native_result + + np.testing.assert_allclose(python_points, expected_points, atol=1e-6) + np.testing.assert_allclose(native_points, expected_points, atol=1e-6) + np.testing.assert_allclose(native_points, python_points, atol=1e-9) + np.testing.assert_allclose(native_rcm, python_rcm, atol=1e-9) + + speedup = median(python_timings) / median(native_timings) + print( + "reconstruction stress benchmark: " + f"{_timing_summary('python', python_timings)}; " + f"{_timing_summary('native', native_timings)}; " + f"speedup={speedup:.2f}x" + ) + + +if __name__ == "__main__": + unittest.main() \ No newline at end of file diff --git a/tests/test_segmentation.py b/tests/test_segmentation.py index 922d45c..99cfd49 100644 --- a/tests/test_segmentation.py +++ b/tests/test_segmentation.py @@ -8,9 +8,12 @@ """ import unittest +from unittest.mock import patch import numpy as np +import openptv_python.segmentation as segmentation +from openptv_python._native_compat import HAS_NATIVE_SEGMENTATION from openptv_python.parameters import ControlPar, TargetPar from openptv_python.segmentation import target_recognition @@ -18,6 +21,20 @@ class TestTargRec(unittest.TestCase): """Test the target recognition algorithm.""" + def assert_targets_match(self, native_targets, pure_targets): + """Compare target lists field by field to avoid backend coupling.""" + self.assertEqual(len(native_targets), len(pure_targets)) + + for native_target, pure_target in zip(native_targets, pure_targets): + self.assertEqual(native_target.pnr, pure_target.pnr) + self.assertAlmostEqual(native_target.x, pure_target.x, places=9) + self.assertAlmostEqual(native_target.y, pure_target.y, places=9) + self.assertEqual(native_target.n, pure_target.n) + self.assertEqual(native_target.nx, pure_target.nx) + self.assertEqual(native_target.ny, pure_target.ny) + self.assertEqual(native_target.sumg, pure_target.sumg) + self.assertEqual(native_target.tnr, pure_target.tnr) + def test_single_target(self): """Test a single target.""" img = np.array( @@ -122,6 +139,54 @@ def test_one_targets2(self): self.assertEqual(len(target_array), 1) self.assertEqual(target_array[0].count_pixels(), (4, 3, 2)) + @unittest.skipUnless( + HAS_NATIVE_SEGMENTATION, "optv native target recognition is not available" + ) + def test_target_recognition_matches_native_backend(self): + """Whole-image target detection should call native code and match Python.""" + img = np.array( + [ + [0, 0, 0, 0, 0, 0, 0], + [0, 240, 250, 245, 0, 0, 0], + [0, 242, 255, 244, 0, 0, 0], + [0, 0, 0, 0, 0, 253, 0], + [0, 0, 0, 0, 251, 255, 252], + [0, 0, 0, 0, 0, 250, 0], + [0, 0, 0, 0, 0, 0, 0], + ], + dtype=np.uint8, + ) + + cpar = ControlPar(1) + cpar.set_image_size((7, 7)) + tpar = TargetPar( + gvthresh=[239], + discont=5, + nnmin=1, + nnmax=20, + sumg_min=1, + nxmin=1, + nxmax=10, + nymin=1, + nymax=10, + ) + + with patch.object(segmentation, "HAS_NATIVE_SEGMENTATION", False): + pure_targets = target_recognition(img, tpar, 0, cpar) + + with patch.object( + segmentation, + "native_target_recognition", + wraps=segmentation.native_target_recognition, + ) as native_call: + native_targets = target_recognition(img, tpar, 0, cpar) + + self.assertTrue( + native_call.called, "expected the native implementation to be called" + ) + + self.assert_targets_match(native_targets, pure_targets) + # def test_peak_fit_new(): # """ Test the peak_fit function.""" # import matplotlib.pyplot as plt diff --git a/uv.lock b/uv.lock index 385e159..0b3e19c 100644 --- a/uv.lock +++ b/uv.lock @@ -1,4 +1,4 @@ -requires-python = ">=3.14" +requires-python = ">=3.12, <3.14" revision = 3 version = 1 @@ -78,22 +78,38 @@ sdist = {url = "https://files.pythonhosted.org/packages/1d/35/02daf95b9cd686320b source = {registry = "https://pypi.org/simple"} version = "3.4.5" wheels = [ - {url = "https://files.pythonhosted.org/packages/43/be/0f0fd9bb4a7fa4fb5067fb7d9ac693d4e928d306f80a0d02bde43a7c4aee/charset_normalizer-3.4.5-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8197abe5ca1ffb7d91e78360f915eef5addff270f8a71c1fc5be24a56f3e4873", size = 280232, upload-time = "2026-03-06T06:02:01.508Z"}, - {url = "https://files.pythonhosted.org/packages/28/02/983b5445e4bef49cd8c9da73a8e029f0825f39b74a06d201bfaa2e55142a/charset_normalizer-3.4.5-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a2aecdb364b8a1802afdc7f9327d55dad5366bc97d8502d0f5854e50712dbc5f", size = 189688, upload-time = "2026-03-06T06:02:02.857Z"}, - {url = "https://files.pythonhosted.org/packages/d0/88/152745c5166437687028027dc080e2daed6fe11cfa95a22f4602591c42db/charset_normalizer-3.4.5-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a66aa5022bf81ab4b1bebfb009db4fd68e0c6d4307a1ce5ef6a26e5878dfc9e4", size = 206833, upload-time = "2026-03-06T06:02:05.127Z"}, - {url = "https://files.pythonhosted.org/packages/cb/0f/ebc15c8b02af2f19be9678d6eed115feeeccc45ce1f4b098d986c13e8769/charset_normalizer-3.4.5-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d77f97e515688bd615c1d1f795d540f32542d514242067adcb8ef532504cb9ee", size = 202879, upload-time = "2026-03-06T06:02:06.446Z"}, - {url = "https://files.pythonhosted.org/packages/38/9c/71336bff6934418dc8d1e8a1644176ac9088068bc571da612767619c97b3/charset_normalizer-3.4.5-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01a1ed54b953303ca7e310fafe0fe347aab348bd81834a0bcd602eb538f89d66", size = 195764, upload-time = "2026-03-06T06:02:08.763Z"}, - {url = "https://files.pythonhosted.org/packages/b7/95/ce92fde4f98615661871bc282a856cf9b8a15f686ba0af012984660d480b/charset_normalizer-3.4.5-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:b2d37d78297b39a9eb9eb92c0f6df98c706467282055419df141389b23f93362", size = 183728, upload-time = "2026-03-06T06:02:10.137Z"}, - {url = "https://files.pythonhosted.org/packages/1c/e7/f5b4588d94e747ce45ae680f0f242bc2d98dbd4eccfab73e6160b6893893/charset_normalizer-3.4.5-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e71bbb595973622b817c042bd943c3f3667e9c9983ce3d205f973f486fec98a7", size = 192937, upload-time = "2026-03-06T06:02:11.663Z"}, - {url = "https://files.pythonhosted.org/packages/f9/29/9d94ed6b929bf9f48bf6ede6e7474576499f07c4c5e878fb186083622716/charset_normalizer-3.4.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:4cd966c2559f501c6fd69294d082c2934c8dd4719deb32c22961a5ac6db0df1d", size = 192040, upload-time = "2026-03-06T06:02:13.489Z"}, - {url = "https://files.pythonhosted.org/packages/15/d2/1a093a1cf827957f9445f2fe7298bcc16f8fc5e05c1ed2ad1af0b239035e/charset_normalizer-3.4.5-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:d5e52d127045d6ae01a1e821acfad2f3a1866c54d0e837828538fabe8d9d1bd6", size = 184107, upload-time = "2026-03-06T06:02:14.83Z"}, - {url = "https://files.pythonhosted.org/packages/0f/7d/82068ce16bd36135df7b97f6333c5d808b94e01d4599a682e2337ed5fd14/charset_normalizer-3.4.5-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:30a2b1a48478c3428d047ed9690d57c23038dac838a87ad624c85c0a78ebeb39", size = 208310, upload-time = "2026-03-06T06:02:16.165Z"}, - {url = "https://files.pythonhosted.org/packages/84/4e/4dfb52307bb6af4a5c9e73e482d171b81d36f522b21ccd28a49656baa680/charset_normalizer-3.4.5-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:d8ed79b8f6372ca4254955005830fd61c1ccdd8c0fac6603e2c145c61dd95db6", size = 192918, upload-time = "2026-03-06T06:02:18.144Z"}, - {url = "https://files.pythonhosted.org/packages/08/a4/159ff7da662cf7201502ca89980b8f06acf3e887b278956646a8aeb178ab/charset_normalizer-3.4.5-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:c5af897b45fa606b12464ccbe0014bbf8c09191e0a66aab6aa9d5cf6e77e0c94", size = 204615, upload-time = "2026-03-06T06:02:19.821Z"}, - {url = "https://files.pythonhosted.org/packages/d6/62/0dd6172203cb6b429ffffc9935001fde42e5250d57f07b0c28c6046deb6b/charset_normalizer-3.4.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:1088345bcc93c58d8d8f3d783eca4a6e7a7752bbff26c3eee7e73c597c191c2e", size = 197784, upload-time = "2026-03-06T06:02:21.86Z"}, - {url = "https://files.pythonhosted.org/packages/c7/5e/1aab5cb737039b9c59e63627dc8bbc0d02562a14f831cc450e5f91d84ce1/charset_normalizer-3.4.5-cp314-cp314-win32.whl", hash = "sha256:ee57b926940ba00bca7ba7041e665cc956e55ef482f851b9b65acb20d867e7a2", size = 133009, upload-time = "2026-03-06T06:02:23.289Z"}, - {url = "https://files.pythonhosted.org/packages/40/65/e7c6c77d7aaa4c0d7974f2e403e17f0ed2cb0fc135f77d686b916bf1eead/charset_normalizer-3.4.5-cp314-cp314-win_amd64.whl", hash = "sha256:4481e6da1830c8a1cc0b746b47f603b653dadb690bcd851d039ffaefe70533aa", size = 143511, upload-time = "2026-03-06T06:02:26.195Z"}, - {url = "https://files.pythonhosted.org/packages/ba/91/52b0841c71f152f563b8e072896c14e3d83b195c188b338d3cc2e582d1d4/charset_normalizer-3.4.5-cp314-cp314-win_arm64.whl", hash = "sha256:97ab7787092eb9b50fb47fa04f24c75b768a606af1bcba1957f07f128a7219e4", size = 133775, upload-time = "2026-03-06T06:02:27.473Z"}, + {url = "https://files.pythonhosted.org/packages/9c/b6/9ee9c1a608916ca5feae81a344dffbaa53b26b90be58cc2159e3332d44ec/charset_normalizer-3.4.5-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ed97c282ee4f994ef814042423a529df9497e3c666dca19be1d4cd1129dc7ade", size = 280976, upload-time = "2026-03-06T06:01:15.276Z"}, + {url = "https://files.pythonhosted.org/packages/f8/d8/a54f7c0b96f1df3563e9190f04daf981e365a9b397eedfdfb5dbef7e5c6c/charset_normalizer-3.4.5-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0294916d6ccf2d069727d65973c3a1ca477d68708db25fd758dd28b0827cff54", size = 189356, upload-time = "2026-03-06T06:01:16.511Z"}, + {url = "https://files.pythonhosted.org/packages/42/69/2bf7f76ce1446759a5787cb87d38f6a61eb47dbbdf035cfebf6347292a65/charset_normalizer-3.4.5-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:dc57a0baa3eeedd99fafaef7511b5a6ef4581494e8168ee086031744e2679467", size = 206369, upload-time = "2026-03-06T06:01:17.853Z"}, + {url = "https://files.pythonhosted.org/packages/10/9c/949d1a46dab56b959d9a87272482195f1840b515a3380e39986989a893ae/charset_normalizer-3.4.5-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ed1a9a204f317ef879b32f9af507d47e49cd5e7f8e8d5d96358c98373314fc60", size = 203285, upload-time = "2026-03-06T06:01:19.473Z"}, + {url = "https://files.pythonhosted.org/packages/67/5c/ae30362a88b4da237d71ea214a8c7eb915db3eec941adda511729ac25fa2/charset_normalizer-3.4.5-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7ad83b8f9379176c841f8865884f3514d905bcd2a9a3b210eaa446e7d2223e4d", size = 196274, upload-time = "2026-03-06T06:01:20.728Z"}, + {url = "https://files.pythonhosted.org/packages/b2/07/c9f2cb0e46cb6d64fdcc4f95953747b843bb2181bda678dc4e699b8f0f9a/charset_normalizer-3.4.5-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:a118e2e0b5ae6b0120d5efa5f866e58f2bb826067a646431da4d6a2bdae7950e", size = 184715, upload-time = "2026-03-06T06:01:22.194Z"}, + {url = "https://files.pythonhosted.org/packages/36/64/6b0ca95c44fddf692cd06d642b28f63009d0ce325fad6e9b2b4d0ef86a52/charset_normalizer-3.4.5-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:754f96058e61a5e22e91483f823e07df16416ce76afa4ebf306f8e1d1296d43f", size = 193426, upload-time = "2026-03-06T06:01:23.795Z"}, + {url = "https://files.pythonhosted.org/packages/50/bc/a730690d726403743795ca3f5bb2baf67838c5fea78236098f324b965e40/charset_normalizer-3.4.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0c300cefd9b0970381a46394902cd18eaf2aa00163f999590ace991989dcd0fc", size = 191780, upload-time = "2026-03-06T06:01:25.053Z"}, + {url = "https://files.pythonhosted.org/packages/97/4f/6c0bc9af68222b22951552d73df4532b5be6447cee32d58e7e8c74ecbb7b/charset_normalizer-3.4.5-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:c108f8619e504140569ee7de3f97d234f0fbae338a7f9f360455071ef9855a95", size = 185805, upload-time = "2026-03-06T06:01:26.294Z"}, + {url = "https://files.pythonhosted.org/packages/dd/b9/a523fb9b0ee90814b503452b2600e4cbc118cd68714d57041564886e7325/charset_normalizer-3.4.5-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:d1028de43596a315e2720a9849ee79007ab742c06ad8b45a50db8cdb7ed4a82a", size = 208342, upload-time = "2026-03-06T06:01:27.55Z"}, + {url = "https://files.pythonhosted.org/packages/4d/61/c59e761dee4464050713e50e27b58266cc8e209e518c0b378c1580c959ba/charset_normalizer-3.4.5-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:19092dde50335accf365cce21998a1c6dd8eafd42c7b226eb54b2747cdce2fac", size = 193661, upload-time = "2026-03-06T06:01:29.051Z"}, + {url = "https://files.pythonhosted.org/packages/1c/43/729fa30aad69783f755c5ad8649da17ee095311ca42024742701e202dc59/charset_normalizer-3.4.5-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:4354e401eb6dab9aed3c7b4030514328a6c748d05e1c3e19175008ca7de84fb1", size = 204819, upload-time = "2026-03-06T06:01:30.298Z"}, + {url = "https://files.pythonhosted.org/packages/87/33/d9b442ce5a91b96fc0840455a9e49a611bbadae6122778d0a6a79683dd31/charset_normalizer-3.4.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a68766a3c58fde7f9aaa22b3786276f62ab2f594efb02d0a1421b6282e852e98", size = 198080, upload-time = "2026-03-06T06:01:31.478Z"}, + {url = "https://files.pythonhosted.org/packages/56/5a/b8b5a23134978ee9885cee2d6995f4c27cc41f9baded0a9685eabc5338f0/charset_normalizer-3.4.5-cp312-cp312-win32.whl", hash = "sha256:1827734a5b308b65ac54e86a618de66f935a4f63a8a462ff1e19a6788d6c2262", size = 132630, upload-time = "2026-03-06T06:01:33.056Z"}, + {url = "https://files.pythonhosted.org/packages/70/53/e44a4c07e8904500aec95865dc3f6464dc3586a039ef0df606eb3ac38e35/charset_normalizer-3.4.5-cp312-cp312-win_amd64.whl", hash = "sha256:728c6a963dfab66ef865f49286e45239384249672cd598576765acc2a640a636", size = 142856, upload-time = "2026-03-06T06:01:34.489Z"}, + {url = "https://files.pythonhosted.org/packages/ea/aa/c5628f7cad591b1cf45790b7a61483c3e36cf41349c98af7813c483fd6e8/charset_normalizer-3.4.5-cp312-cp312-win_arm64.whl", hash = "sha256:75dfd1afe0b1647449e852f4fb428195a7ed0588947218f7ba929f6538487f02", size = 132982, upload-time = "2026-03-06T06:01:35.641Z"}, + {url = "https://files.pythonhosted.org/packages/f5/48/9f34ec4bb24aa3fdba1890c1bddb97c8a4be1bd84ef5c42ac2352563ad05/charset_normalizer-3.4.5-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ac59c15e3f1465f722607800c68713f9fbc2f672b9eb649fe831da4019ae9b23", size = 280788, upload-time = "2026-03-06T06:01:37.126Z"}, + {url = "https://files.pythonhosted.org/packages/0e/09/6003e7ffeb90cc0560da893e3208396a44c210c5ee42efff539639def59b/charset_normalizer-3.4.5-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:165c7b21d19365464e8f70e5ce5e12524c58b48c78c1f5a57524603c1ab003f8", size = 188890, upload-time = "2026-03-06T06:01:38.73Z"}, + {url = "https://files.pythonhosted.org/packages/42/1e/02706edf19e390680daa694d17e2b8eab4b5f7ac285e2a51168b4b22ee6b/charset_normalizer-3.4.5-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:28269983f25a4da0425743d0d257a2d6921ea7d9b83599d4039486ec5b9f911d", size = 206136, upload-time = "2026-03-06T06:01:40.016Z"}, + {url = "https://files.pythonhosted.org/packages/c7/87/942c3def1b37baf3cf786bad01249190f3ca3d5e63a84f831e704977de1f/charset_normalizer-3.4.5-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d27ce22ec453564770d29d03a9506d449efbb9fa13c00842262b2f6801c48cce", size = 202551, upload-time = "2026-03-06T06:01:41.522Z"}, + {url = "https://files.pythonhosted.org/packages/94/0a/af49691938dfe175d71b8a929bd7e4ace2809c0c5134e28bc535660d5262/charset_normalizer-3.4.5-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0625665e4ebdddb553ab185de5db7054393af8879fb0c87bd5690d14379d6819", size = 195572, upload-time = "2026-03-06T06:01:43.208Z"}, + {url = "https://files.pythonhosted.org/packages/20/ea/dfb1792a8050a8e694cfbde1570ff97ff74e48afd874152d38163d1df9ae/charset_normalizer-3.4.5-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:c23eb3263356d94858655b3e63f85ac5d50970c6e8febcdde7830209139cc37d", size = 184438, upload-time = "2026-03-06T06:01:44.755Z"}, + {url = "https://files.pythonhosted.org/packages/72/12/c281e2067466e3ddd0595bfaea58a6946765ace5c72dfa3edc2f5f118026/charset_normalizer-3.4.5-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e6302ca4ae283deb0af68d2fbf467474b8b6aedcd3dab4db187e07f94c109763", size = 193035, upload-time = "2026-03-06T06:01:46.051Z"}, + {url = "https://files.pythonhosted.org/packages/ba/4f/3792c056e7708e10464bad0438a44708886fb8f92e3c3d29ec5e2d964d42/charset_normalizer-3.4.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e51ae7d81c825761d941962450f50d041db028b7278e7b08930b4541b3e45cb9", size = 191340, upload-time = "2026-03-06T06:01:47.547Z"}, + {url = "https://files.pythonhosted.org/packages/e7/86/80ddba897127b5c7a9bccc481b0cd36c8fefa485d113262f0fe4332f0bf4/charset_normalizer-3.4.5-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:597d10dec876923e5c59e48dbd366e852eacb2b806029491d307daea6b917d7c", size = 185464, upload-time = "2026-03-06T06:01:48.764Z"}, + {url = "https://files.pythonhosted.org/packages/4d/00/b5eff85ba198faacab83e0e4b6f0648155f072278e3b392a82478f8b988b/charset_normalizer-3.4.5-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:5cffde4032a197bd3b42fd0b9509ec60fb70918d6970e4cc773f20fc9180ca67", size = 208014, upload-time = "2026-03-06T06:01:50.371Z"}, + {url = "https://files.pythonhosted.org/packages/c8/11/d36f70be01597fd30850dde8a1269ebc8efadd23ba5785808454f2389bde/charset_normalizer-3.4.5-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:2da4eedcb6338e2321e831a0165759c0c620e37f8cd044a263ff67493be8ffb3", size = 193297, upload-time = "2026-03-06T06:01:51.933Z"}, + {url = "https://files.pythonhosted.org/packages/1a/1d/259eb0a53d4910536c7c2abb9cb25f4153548efb42800c6a9456764649c0/charset_normalizer-3.4.5-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:65a126fb4b070d05340a84fc709dd9e7c75d9b063b610ece8a60197a291d0adf", size = 204321, upload-time = "2026-03-06T06:01:53.887Z"}, + {url = "https://files.pythonhosted.org/packages/84/31/faa6c5b9d3688715e1ed1bb9d124c384fe2fc1633a409e503ffe1c6398c1/charset_normalizer-3.4.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c7a80a9242963416bd81f99349d5f3fce1843c303bd404f204918b6d75a75fd6", size = 197509, upload-time = "2026-03-06T06:01:56.439Z"}, + {url = "https://files.pythonhosted.org/packages/fd/a5/c7d9dd1503ffc08950b3260f5d39ec2366dd08254f0900ecbcf3a6197c7c/charset_normalizer-3.4.5-cp313-cp313-win32.whl", hash = "sha256:f1d725b754e967e648046f00c4facc42d414840f5ccc670c5670f59f83693e4f", size = 132284, upload-time = "2026-03-06T06:01:57.812Z"}, + {url = "https://files.pythonhosted.org/packages/b9/0f/57072b253af40c8aa6636e6de7d75985624c1eb392815b2f934199340a89/charset_normalizer-3.4.5-cp313-cp313-win_amd64.whl", hash = "sha256:e37bd100d2c5d3ba35db9c7c5ba5a9228cbcffe5c4778dc824b164e5257813d7", size = 142630, upload-time = "2026-03-06T06:01:59.062Z"}, + {url = "https://files.pythonhosted.org/packages/31/41/1c4b7cc9f13bd9d369ce3bc993e13d374ce25fa38a2663644283ecf422c1/charset_normalizer-3.4.5-cp313-cp313-win_arm64.whl", hash = "sha256:93b3b2cc5cf1b8743660ce77a4f45f3f6d1172068207c1defc779a36eea6bb36", size = 133254, upload-time = "2026-03-06T06:02:00.281Z"}, {url = "https://files.pythonhosted.org/packages/c5/60/3a621758945513adfd4db86827a5bafcc615f913dbd0b4c2ed64a65731be/charset_normalizer-3.4.5-py3-none-any.whl", hash = "sha256:9db5e3fcdcee89a78c04dffb3fe33c79f77bd741a624946db2591c81b2fc85b0", size = 55455, upload-time = "2026-03-06T06:03:17.827Z"} ] @@ -112,36 +128,51 @@ sdist = {url = "https://files.pythonhosted.org/packages/24/56/95b7e30fa389756cb5 source = {registry = "https://pypi.org/simple"} version = "7.13.4" wheels = [ - {url = "https://files.pythonhosted.org/packages/92/11/a9cf762bb83386467737d32187756a42094927150c3e107df4cb078e8590/coverage-7.13.4-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:300deaee342f90696ed186e3a00c71b5b3d27bffe9e827677954f4ee56969601", size = 219522, upload-time = "2026-02-09T12:58:08.623Z"}, - {url = "https://files.pythonhosted.org/packages/d3/28/56e6d892b7b052236d67c95f1936b6a7cf7c3e2634bf27610b8cbd7f9c60/coverage-7.13.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:29e3220258d682b6226a9b0925bc563ed9a1ebcff3cad30f043eceea7eaf2689", size = 219855, upload-time = "2026-02-09T12:58:10.176Z"}, - {url = "https://files.pythonhosted.org/packages/e5/69/233459ee9eb0c0d10fcc2fe425a029b3fa5ce0f040c966ebce851d030c70/coverage-7.13.4-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:391ee8f19bef69210978363ca930f7328081c6a0152f1166c91f0b5fdd2a773c", size = 250887, upload-time = "2026-02-09T12:58:12.503Z"}, - {url = "https://files.pythonhosted.org/packages/06/90/2cdab0974b9b5bbc1623f7876b73603aecac11b8d95b85b5b86b32de5eab/coverage-7.13.4-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0dd7ab8278f0d58a0128ba2fca25824321f05d059c1441800e934ff2efa52129", size = 253396, upload-time = "2026-02-09T12:58:14.615Z"}, - {url = "https://files.pythonhosted.org/packages/ac/15/ea4da0f85bf7d7b27635039e649e99deb8173fe551096ea15017f7053537/coverage-7.13.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:78cdf0d578b15148b009ccf18c686aa4f719d887e76e6b40c38ffb61d264a552", size = 254745, upload-time = "2026-02-09T12:58:16.162Z"}, - {url = "https://files.pythonhosted.org/packages/99/11/bb356e86920c655ca4d61daee4e2bbc7258f0a37de0be32d233b561134ff/coverage-7.13.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:48685fee12c2eb3b27c62f2658e7ea21e9c3239cba5a8a242801a0a3f6a8c62a", size = 257055, upload-time = "2026-02-09T12:58:17.892Z"}, - {url = "https://files.pythonhosted.org/packages/c9/0f/9ae1f8cb17029e09da06ca4e28c9e1d5c1c0a511c7074592e37e0836c915/coverage-7.13.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4e83efc079eb39480e6346a15a1bcb3e9b04759c5202d157e1dd4303cd619356", size = 250911, upload-time = "2026-02-09T12:58:19.495Z"}, - {url = "https://files.pythonhosted.org/packages/89/3a/adfb68558fa815cbc29747b553bc833d2150228f251b127f1ce97e48547c/coverage-7.13.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ecae9737b72408d6a950f7e525f30aca12d4bd8dd95e37342e5beb3a2a8c4f71", size = 252754, upload-time = "2026-02-09T12:58:21.064Z"}, - {url = "https://files.pythonhosted.org/packages/32/b1/540d0c27c4e748bd3cd0bd001076ee416eda993c2bae47a73b7cc9357931/coverage-7.13.4-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ae4578f8528569d3cf303fef2ea569c7f4c4059a38c8667ccef15c6e1f118aa5", size = 250720, upload-time = "2026-02-09T12:58:22.622Z"}, - {url = "https://files.pythonhosted.org/packages/c7/95/383609462b3ffb1fe133014a7c84fc0dd01ed55ac6140fa1093b5af7ebb1/coverage-7.13.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:6fdef321fdfbb30a197efa02d48fcd9981f0d8ad2ae8903ac318adc653f5df98", size = 254994, upload-time = "2026-02-09T12:58:24.548Z"}, - {url = "https://files.pythonhosted.org/packages/f7/ba/1761138e86c81680bfc3c49579d66312865457f9fe405b033184e5793cb3/coverage-7.13.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b0f6ccf3dbe577170bebfce1318707d0e8c3650003cb4b3a9dd744575daa8b5", size = 250531, upload-time = "2026-02-09T12:58:26.271Z"}, - {url = "https://files.pythonhosted.org/packages/f8/8e/05900df797a9c11837ab59c4d6fe94094e029582aab75c3309a93e6fb4e3/coverage-7.13.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:75fcd519f2a5765db3f0e391eb3b7d150cce1a771bf4c9f861aeab86c767a3c0", size = 252189, upload-time = "2026-02-09T12:58:27.807Z"}, - {url = "https://files.pythonhosted.org/packages/00/bd/29c9f2db9ea4ed2738b8a9508c35626eb205d51af4ab7bf56a21a2e49926/coverage-7.13.4-cp314-cp314-win32.whl", hash = "sha256:8e798c266c378da2bd819b0677df41ab46d78065fb2a399558f3f6cae78b2fbb", size = 222258, upload-time = "2026-02-09T12:58:29.441Z"}, - {url = "https://files.pythonhosted.org/packages/a7/4d/1f8e723f6829977410efeb88f73673d794075091c8c7c18848d273dc9d73/coverage-7.13.4-cp314-cp314-win_amd64.whl", hash = "sha256:245e37f664d89861cf2329c9afa2c1fe9e6d4e1a09d872c947e70718aeeac505", size = 223073, upload-time = "2026-02-09T12:58:31.026Z"}, - {url = "https://files.pythonhosted.org/packages/51/5b/84100025be913b44e082ea32abcf1afbf4e872f5120b7a1cab1d331b1e13/coverage-7.13.4-cp314-cp314-win_arm64.whl", hash = "sha256:ad27098a189e5838900ce4c2a99f2fe42a0bf0c2093c17c69b45a71579e8d4a2", size = 221638, upload-time = "2026-02-09T12:58:32.599Z"}, - {url = "https://files.pythonhosted.org/packages/a7/e4/c884a405d6ead1370433dad1e3720216b4f9fd8ef5b64bfd984a2a60a11a/coverage-7.13.4-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:85480adfb35ffc32d40918aad81b89c69c9cc5661a9b8a81476d3e645321a056", size = 220246, upload-time = "2026-02-09T12:58:34.181Z"}, - {url = "https://files.pythonhosted.org/packages/81/5c/4d7ed8b23b233b0fffbc9dfec53c232be2e695468523242ea9fd30f97ad2/coverage-7.13.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:79be69cf7f3bf9b0deeeb062eab7ac7f36cd4cc4c4dd694bd28921ba4d8596cc", size = 220514, upload-time = "2026-02-09T12:58:35.704Z"}, - {url = "https://files.pythonhosted.org/packages/2f/6f/3284d4203fd2f28edd73034968398cd2d4cb04ab192abc8cff007ea35679/coverage-7.13.4-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:caa421e2684e382c5d8973ac55e4f36bed6821a9bad5c953494de960c74595c9", size = 261877, upload-time = "2026-02-09T12:58:37.864Z"}, - {url = "https://files.pythonhosted.org/packages/09/aa/b672a647bbe1556a85337dc95bfd40d146e9965ead9cc2fe81bde1e5cbce/coverage-7.13.4-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:14375934243ee05f56c45393fe2ce81fe5cc503c07cee2bdf1725fb8bef3ffaf", size = 264004, upload-time = "2026-02-09T12:58:39.492Z"}, - {url = "https://files.pythonhosted.org/packages/79/a1/aa384dbe9181f98bba87dd23dda436f0c6cf2e148aecbb4e50fc51c1a656/coverage-7.13.4-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:25a41c3104d08edb094d9db0d905ca54d0cd41c928bb6be3c4c799a54753af55", size = 266408, upload-time = "2026-02-09T12:58:41.852Z"}, - {url = "https://files.pythonhosted.org/packages/53/5e/5150bf17b4019bc600799f376bb9606941e55bd5a775dc1e096b6ffea952/coverage-7.13.4-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6f01afcff62bf9a08fb32b2c1d6e924236c0383c02c790732b6537269e466a72", size = 267544, upload-time = "2026-02-09T12:58:44.093Z"}, - {url = "https://files.pythonhosted.org/packages/e0/ed/f1de5c675987a4a7a672250d2c5c9d73d289dbf13410f00ed7181d8017dd/coverage-7.13.4-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:eb9078108fbf0bcdde37c3f4779303673c2fa1fe8f7956e68d447d0dd426d38a", size = 260980, upload-time = "2026-02-09T12:58:45.721Z"}, - {url = "https://files.pythonhosted.org/packages/b3/e3/fe758d01850aa172419a6743fe76ba8b92c29d181d4f676ffe2dae2ba631/coverage-7.13.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0e086334e8537ddd17e5f16a344777c1ab8194986ec533711cbe6c41cde841b6", size = 263871, upload-time = "2026-02-09T12:58:47.334Z"}, - {url = "https://files.pythonhosted.org/packages/b6/76/b829869d464115e22499541def9796b25312b8cf235d3bb00b39f1675395/coverage-7.13.4-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:725d985c5ab621268b2edb8e50dfe57633dc69bda071abc470fed55a14935fd3", size = 261472, upload-time = "2026-02-09T12:58:48.995Z"}, - {url = "https://files.pythonhosted.org/packages/14/9e/caedb1679e73e2f6ad240173f55218488bfe043e38da577c4ec977489915/coverage-7.13.4-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:3c06f0f1337c667b971ca2f975523347e63ec5e500b9aa5882d91931cd3ef750", size = 265210, upload-time = "2026-02-09T12:58:51.178Z"}, - {url = "https://files.pythonhosted.org/packages/3a/10/0dd02cb009b16ede425b49ec344aba13a6ae1dc39600840ea6abcb085ac4/coverage-7.13.4-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:590c0ed4bf8e85f745e6b805b2e1c457b2e33d5255dd9729743165253bc9ad39", size = 260319, upload-time = "2026-02-09T12:58:53.081Z"}, - {url = "https://files.pythonhosted.org/packages/92/8e/234d2c927af27c6d7a5ffad5bd2cf31634c46a477b4c7adfbfa66baf7ebb/coverage-7.13.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:eb30bf180de3f632cd043322dad5751390e5385108b2807368997d1a92a509d0", size = 262638, upload-time = "2026-02-09T12:58:55.258Z"}, - {url = "https://files.pythonhosted.org/packages/2f/64/e5547c8ff6964e5965c35a480855911b61509cce544f4d442caa759a0702/coverage-7.13.4-cp314-cp314t-win32.whl", hash = "sha256:c4240e7eded42d131a2d2c4dec70374b781b043ddc79a9de4d55ca71f8e98aea", size = 223040, upload-time = "2026-02-09T12:58:56.936Z"}, - {url = "https://files.pythonhosted.org/packages/c7/96/38086d58a181aac86d503dfa9c47eb20715a79c3e3acbdf786e92e5c09a8/coverage-7.13.4-cp314-cp314t-win_amd64.whl", hash = "sha256:4c7d3cc01e7350f2f0f6f7036caaf5673fb56b6998889ccfe9e1c1fe75a9c932", size = 224148, upload-time = "2026-02-09T12:58:58.645Z"}, - {url = "https://files.pythonhosted.org/packages/ce/72/8d10abd3740a0beb98c305e0c3faf454366221c0f37a8bcf8f60020bb65a/coverage-7.13.4-cp314-cp314t-win_arm64.whl", hash = "sha256:23e3f687cf945070d1c90f85db66d11e3025665d8dafa831301a0e0038f3db9b", size = 222172, upload-time = "2026-02-09T12:59:00.396Z"}, + {url = "https://files.pythonhosted.org/packages/d1/81/4ce2fdd909c5a0ed1f6dedb88aa57ab79b6d1fbd9b588c1ac7ef45659566/coverage-7.13.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:02231499b08dabbe2b96612993e5fc34217cdae907a51b906ac7fca8027a4459", size = 219449, upload-time = "2026-02-09T12:56:54.889Z"}, + {url = "https://files.pythonhosted.org/packages/5d/96/5238b1efc5922ddbdc9b0db9243152c09777804fb7c02ad1741eb18a11c0/coverage-7.13.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40aa8808140e55dc022b15d8aa7f651b6b3d68b365ea0398f1441e0b04d859c3", size = 219810, upload-time = "2026-02-09T12:56:56.33Z"}, + {url = "https://files.pythonhosted.org/packages/78/72/2f372b726d433c9c35e56377cf1d513b4c16fe51841060d826b95caacec1/coverage-7.13.4-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5b856a8ccf749480024ff3bd7310adaef57bf31fd17e1bfc404b7940b6986634", size = 251308, upload-time = "2026-02-09T12:56:57.858Z"}, + {url = "https://files.pythonhosted.org/packages/5d/a0/2ea570925524ef4e00bb6c82649f5682a77fac5ab910a65c9284de422600/coverage-7.13.4-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2c048ea43875fbf8b45d476ad79f179809c590ec7b79e2035c662e7afa3192e3", size = 254052, upload-time = "2026-02-09T12:56:59.754Z"}, + {url = "https://files.pythonhosted.org/packages/e8/ac/45dc2e19a1939098d783c846e130b8f862fbb50d09e0af663988f2f21973/coverage-7.13.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b7b38448866e83176e28086674fe7368ab8590e4610fb662b44e345b86d63ffa", size = 255165, upload-time = "2026-02-09T12:57:01.287Z"}, + {url = "https://files.pythonhosted.org/packages/2d/4d/26d236ff35abc3b5e63540d3386e4c3b192168c1d96da5cb2f43c640970f/coverage-7.13.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:de6defc1c9badbf8b9e67ae90fd00519186d6ab64e5cc5f3d21359c2a9b2c1d3", size = 257432, upload-time = "2026-02-09T12:57:02.637Z"}, + {url = "https://files.pythonhosted.org/packages/ec/55/14a966c757d1348b2e19caf699415a2a4c4f7feaa4bbc6326a51f5c7dd1b/coverage-7.13.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7eda778067ad7ffccd23ecffce537dface96212576a07924cbf0d8799d2ded5a", size = 251716, upload-time = "2026-02-09T12:57:04.056Z"}, + {url = "https://files.pythonhosted.org/packages/77/33/50116647905837c66d28b2af1321b845d5f5d19be9655cb84d4a0ea806b4/coverage-7.13.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e87f6c587c3f34356c3759f0420693e35e7eb0e2e41e4c011cb6ec6ecbbf1db7", size = 253089, upload-time = "2026-02-09T12:57:05.503Z"}, + {url = "https://files.pythonhosted.org/packages/c2/b4/8efb11a46e3665d92635a56e4f2d4529de6d33f2cb38afd47d779d15fc99/coverage-7.13.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:8248977c2e33aecb2ced42fef99f2d319e9904a36e55a8a68b69207fb7e43edc", size = 251232, upload-time = "2026-02-09T12:57:06.879Z"}, + {url = "https://files.pythonhosted.org/packages/51/24/8cd73dd399b812cc76bb0ac260e671c4163093441847ffe058ac9fda1e32/coverage-7.13.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:25381386e80ae727608e662474db537d4df1ecd42379b5ba33c84633a2b36d47", size = 255299, upload-time = "2026-02-09T12:57:08.245Z"}, + {url = "https://files.pythonhosted.org/packages/03/94/0a4b12f1d0e029ce1ccc1c800944a9984cbe7d678e470bb6d3c6bc38a0da/coverage-7.13.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:ee756f00726693e5ba94d6df2bdfd64d4852d23b09bb0bc700e3b30e6f333985", size = 250796, upload-time = "2026-02-09T12:57:10.142Z"}, + {url = "https://files.pythonhosted.org/packages/73/44/6002fbf88f6698ca034360ce474c406be6d5a985b3fdb3401128031eef6b/coverage-7.13.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fdfc1e28e7c7cdce44985b3043bc13bbd9c747520f94a4d7164af8260b3d91f0", size = 252673, upload-time = "2026-02-09T12:57:12.197Z"}, + {url = "https://files.pythonhosted.org/packages/de/c6/a0279f7c00e786be75a749a5674e6fa267bcbd8209cd10c9a450c655dfa7/coverage-7.13.4-cp312-cp312-win32.whl", hash = "sha256:01d4cbc3c283a17fc1e42d614a119f7f438eabb593391283adca8dc86eff1246", size = 221990, upload-time = "2026-02-09T12:57:14.085Z"}, + {url = "https://files.pythonhosted.org/packages/77/4e/c0a25a425fcf5557d9abd18419c95b63922e897bc86c1f327f155ef234a9/coverage-7.13.4-cp312-cp312-win_amd64.whl", hash = "sha256:9401ebc7ef522f01d01d45532c68c5ac40fb27113019b6b7d8b208f6e9baa126", size = 222800, upload-time = "2026-02-09T12:57:15.944Z"}, + {url = "https://files.pythonhosted.org/packages/47/ac/92da44ad9a6f4e3a7debd178949d6f3769bedca33830ce9b1dcdab589a37/coverage-7.13.4-cp312-cp312-win_arm64.whl", hash = "sha256:b1ec7b6b6e93255f952e27ab58fbc68dcc468844b16ecbee881aeb29b6ab4d8d", size = 221415, upload-time = "2026-02-09T12:57:17.497Z"}, + {url = "https://files.pythonhosted.org/packages/db/23/aad45061a31677d68e47499197a131eea55da4875d16c1f42021ab963503/coverage-7.13.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b66a2da594b6068b48b2692f043f35d4d3693fb639d5ea8b39533c2ad9ac3ab9", size = 219474, upload-time = "2026-02-09T12:57:19.332Z"}, + {url = "https://files.pythonhosted.org/packages/a5/70/9b8b67a0945f3dfec1fd896c5cefb7c19d5a3a6d74630b99a895170999ae/coverage-7.13.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3599eb3992d814d23b35c536c28df1a882caa950f8f507cef23d1cbf334995ac", size = 219844, upload-time = "2026-02-09T12:57:20.66Z"}, + {url = "https://files.pythonhosted.org/packages/97/fd/7e859f8fab324cef6c4ad7cff156ca7c489fef9179d5749b0c8d321281c2/coverage-7.13.4-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:93550784d9281e374fb5a12bf1324cc8a963fd63b2d2f223503ef0fd4aa339ea", size = 250832, upload-time = "2026-02-09T12:57:22.007Z"}, + {url = "https://files.pythonhosted.org/packages/e4/dc/b2442d10020c2f52617828862d8b6ee337859cd8f3a1f13d607dddda9cf7/coverage-7.13.4-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b720ce6a88a2755f7c697c23268ddc47a571b88052e6b155224347389fdf6a3b", size = 253434, upload-time = "2026-02-09T12:57:23.339Z"}, + {url = "https://files.pythonhosted.org/packages/5a/88/6728a7ad17428b18d836540630487231f5470fb82454871149502f5e5aa2/coverage-7.13.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7b322db1284a2ed3aa28ffd8ebe3db91c929b7a333c0820abec3d838ef5b3525", size = 254676, upload-time = "2026-02-09T12:57:24.774Z"}, + {url = "https://files.pythonhosted.org/packages/7c/bc/21244b1b8cedf0dff0a2b53b208015fe798d5f2a8d5348dbfece04224fff/coverage-7.13.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f4594c67d8a7c89cf922d9df0438c7c7bb022ad506eddb0fdb2863359ff78242", size = 256807, upload-time = "2026-02-09T12:57:26.125Z"}, + {url = "https://files.pythonhosted.org/packages/97/a0/ddba7ed3251cff51006737a727d84e05b61517d1784a9988a846ba508877/coverage-7.13.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:53d133df809c743eb8bce33b24bcababb371f4441340578cd406e084d94a6148", size = 251058, upload-time = "2026-02-09T12:57:27.614Z"}, + {url = "https://files.pythonhosted.org/packages/9b/55/e289addf7ff54d3a540526f33751951bf0878f3809b47f6dfb3def69c6f7/coverage-7.13.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:76451d1978b95ba6507a039090ba076105c87cc76fc3efd5d35d72093964d49a", size = 252805, upload-time = "2026-02-09T12:57:29.066Z"}, + {url = "https://files.pythonhosted.org/packages/13/4e/cc276b1fa4a59be56d96f1dabddbdc30f4ba22e3b1cd42504c37b3313255/coverage-7.13.4-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:7f57b33491e281e962021de110b451ab8a24182589be17e12a22c79047935e23", size = 250766, upload-time = "2026-02-09T12:57:30.522Z"}, + {url = "https://files.pythonhosted.org/packages/94/44/1093b8f93018f8b41a8cf29636c9292502f05e4a113d4d107d14a3acd044/coverage-7.13.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:1731dc33dc276dafc410a885cbf5992f1ff171393e48a21453b78727d090de80", size = 254923, upload-time = "2026-02-09T12:57:31.946Z"}, + {url = "https://files.pythonhosted.org/packages/8b/55/ea2796da2d42257f37dbea1aab239ba9263b31bd91d5527cdd6db5efe174/coverage-7.13.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:bd60d4fe2f6fa7dff9223ca1bbc9f05d2b6697bc5961072e5d3b952d46e1b1ea", size = 250591, upload-time = "2026-02-09T12:57:33.842Z"}, + {url = "https://files.pythonhosted.org/packages/d4/fa/7c4bb72aacf8af5020675aa633e59c1fbe296d22aed191b6a5b711eb2bc7/coverage-7.13.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9181a3ccead280b828fae232df12b16652702b49d41e99d657f46cc7b1f6ec7a", size = 252364, upload-time = "2026-02-09T12:57:35.743Z"}, + {url = "https://files.pythonhosted.org/packages/5c/38/a8d2ec0146479c20bbaa7181b5b455a0c41101eed57f10dd19a78ab44c80/coverage-7.13.4-cp313-cp313-win32.whl", hash = "sha256:f53d492307962561ac7de4cd1de3e363589b000ab69617c6156a16ba7237998d", size = 222010, upload-time = "2026-02-09T12:57:37.25Z"}, + {url = "https://files.pythonhosted.org/packages/e2/0c/dbfafbe90a185943dcfbc766fe0e1909f658811492d79b741523a414a6cc/coverage-7.13.4-cp313-cp313-win_amd64.whl", hash = "sha256:e6f70dec1cc557e52df5306d051ef56003f74d56e9c4dd7ddb07e07ef32a84dd", size = 222818, upload-time = "2026-02-09T12:57:38.734Z"}, + {url = "https://files.pythonhosted.org/packages/04/d1/934918a138c932c90d78301f45f677fb05c39a3112b96fd2c8e60503cdc7/coverage-7.13.4-cp313-cp313-win_arm64.whl", hash = "sha256:fb07dc5da7e849e2ad31a5d74e9bece81f30ecf5a42909d0a695f8bd1874d6af", size = 221438, upload-time = "2026-02-09T12:57:40.223Z"}, + {url = "https://files.pythonhosted.org/packages/52/57/ee93ced533bcb3e6df961c0c6e42da2fc6addae53fb95b94a89b1e33ebd7/coverage-7.13.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:40d74da8e6c4b9ac18b15331c4b5ebc35a17069410cad462ad4f40dcd2d50c0d", size = 220165, upload-time = "2026-02-09T12:57:41.639Z"}, + {url = "https://files.pythonhosted.org/packages/c5/e0/969fc285a6fbdda49d91af278488d904dcd7651b2693872f0ff94e40e84a/coverage-7.13.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4223b4230a376138939a9173f1bdd6521994f2aff8047fae100d6d94d50c5a12", size = 220516, upload-time = "2026-02-09T12:57:44.215Z"}, + {url = "https://files.pythonhosted.org/packages/b1/b8/9531944e16267e2735a30a9641ff49671f07e8138ecf1ca13db9fd2560c7/coverage-7.13.4-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1d4be36a5114c499f9f1f9195e95ebf979460dbe2d88e6816ea202010ba1c34b", size = 261804, upload-time = "2026-02-09T12:57:45.989Z"}, + {url = "https://files.pythonhosted.org/packages/8a/f3/e63df6d500314a2a60390d1989240d5f27318a7a68fa30ad3806e2a9323e/coverage-7.13.4-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:200dea7d1e8095cc6e98cdabe3fd1d21ab17d3cee6dab00cadbb2fe35d9c15b9", size = 263885, upload-time = "2026-02-09T12:57:47.42Z"}, + {url = "https://files.pythonhosted.org/packages/f3/67/7654810de580e14b37670b60a09c599fa348e48312db5b216d730857ffe6/coverage-7.13.4-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b8eb931ee8e6d8243e253e5ed7336deea6904369d2fd8ae6e43f68abbf167092", size = 266308, upload-time = "2026-02-09T12:57:49.345Z"}, + {url = "https://files.pythonhosted.org/packages/37/6f/39d41eca0eab3cc82115953ad41c4e77935286c930e8fad15eaed1389d83/coverage-7.13.4-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:75eab1ebe4f2f64d9509b984f9314d4aa788540368218b858dad56dc8f3e5eb9", size = 267452, upload-time = "2026-02-09T12:57:50.811Z"}, + {url = "https://files.pythonhosted.org/packages/50/6d/39c0fbb8fc5cd4d2090811e553c2108cf5112e882f82505ee7495349a6bf/coverage-7.13.4-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c35eb28c1d085eb7d8c9b3296567a1bebe03ce72962e932431b9a61f28facf26", size = 261057, upload-time = "2026-02-09T12:57:52.447Z"}, + {url = "https://files.pythonhosted.org/packages/a4/a2/60010c669df5fa603bb5a97fb75407e191a846510da70ac657eb696b7fce/coverage-7.13.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:eb88b316ec33760714a4720feb2816a3a59180fd58c1985012054fa7aebee4c2", size = 263875, upload-time = "2026-02-09T12:57:53.938Z"}, + {url = "https://files.pythonhosted.org/packages/3e/d9/63b22a6bdbd17f1f96e9ed58604c2a6b0e72a9133e37d663bef185877cf6/coverage-7.13.4-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:7d41eead3cc673cbd38a4417deb7fd0b4ca26954ff7dc6078e33f6ff97bed940", size = 261500, upload-time = "2026-02-09T12:57:56.012Z"}, + {url = "https://files.pythonhosted.org/packages/70/bf/69f86ba1ad85bc3ad240e4c0e57a2e620fbc0e1645a47b5c62f0e941ad7f/coverage-7.13.4-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:fb26a934946a6afe0e326aebe0730cdff393a8bc0bbb65a2f41e30feddca399c", size = 265212, upload-time = "2026-02-09T12:57:57.5Z"}, + {url = "https://files.pythonhosted.org/packages/ae/f2/5f65a278a8c2148731831574c73e42f57204243d33bedaaf18fa79c5958f/coverage-7.13.4-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:dae88bc0fc77edaa65c14be099bd57ee140cf507e6bfdeea7938457ab387efb0", size = 260398, upload-time = "2026-02-09T12:57:59.027Z"}, + {url = "https://files.pythonhosted.org/packages/ef/80/6e8280a350ee9fea92f14b8357448a242dcaa243cb2c72ab0ca591f66c8c/coverage-7.13.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:845f352911777a8e722bfce168958214951e07e47e5d5d9744109fa5fe77f79b", size = 262584, upload-time = "2026-02-09T12:58:01.129Z"}, + {url = "https://files.pythonhosted.org/packages/22/63/01ff182fc95f260b539590fb12c11ad3e21332c15f9799cb5e2386f71d9f/coverage-7.13.4-cp313-cp313t-win32.whl", hash = "sha256:2fa8d5f8de70688a28240de9e139fa16b153cc3cbb01c5f16d88d6505ebdadf9", size = 222688, upload-time = "2026-02-09T12:58:02.736Z"}, + {url = "https://files.pythonhosted.org/packages/a9/43/89de4ef5d3cd53b886afa114065f7e9d3707bdb3e5efae13535b46ae483d/coverage-7.13.4-cp313-cp313t-win_amd64.whl", hash = "sha256:9351229c8c8407645840edcc277f4a2d44814d1bc34a2128c11c2a031d45a5dd", size = 223746, upload-time = "2026-02-09T12:58:05.362Z"}, + {url = "https://files.pythonhosted.org/packages/35/39/7cf0aa9a10d470a5309b38b289b9bb07ddeac5d61af9b664fe9775a4cb3e/coverage-7.13.4-cp313-cp313t-win_arm64.whl", hash = "sha256:30b8d0512f2dc8c8747557e8fb459d6176a2c9e5731e2b74d311c03b78451997", size = 222003, upload-time = "2026-02-09T12:58:06.952Z"}, {url = "https://files.pythonhosted.org/packages/0d/4a/331fe2caf6799d591109bb9c08083080f6de90a823695d412a935622abb2/coverage-7.13.4-py3-none-any.whl", hash = "sha256:1af1641e57cf7ba1bd67d677c9abdbcd6cc2ab7da3bca7fa1e2b7e50e65f2ad0", size = 211242, upload-time = "2026-02-09T12:59:02.032Z"} ] @@ -226,32 +257,32 @@ sdist = {url = "https://files.pythonhosted.org/packages/56/9c/b4b0c54d84da4a94b3 source = {registry = "https://pypi.org/simple"} version = "0.8.1" wheels = [ - {url = "https://files.pythonhosted.org/packages/c9/6a/907ef6800f7bca71b525a05f1839b21f708c09043b1c6aa77b6b827b3996/librt-0.8.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:6cfa7fe54fd4d1f47130017351a959fe5804bda7a0bc7e07a2cdbc3fdd28d34f", size = 66081, upload-time = "2026-02-17T16:12:12.766Z"}, - {url = "https://files.pythonhosted.org/packages/1b/18/25e991cd5640c9fb0f8d91b18797b29066b792f17bf8493da183bf5caabe/librt-0.8.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:228c2409c079f8c11fb2e5d7b277077f694cb93443eb760e00b3b83cb8b3176c", size = 68309, upload-time = "2026-02-17T16:12:13.756Z"}, - {url = "https://files.pythonhosted.org/packages/a4/36/46820d03f058cfb5a9de5940640ba03165ed8aded69e0733c417bb04df34/librt-0.8.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7aae78ab5e3206181780e56912d1b9bb9f90a7249ce12f0e8bf531d0462dd0fc", size = 196804, upload-time = "2026-02-17T16:12:14.818Z"}, - {url = "https://files.pythonhosted.org/packages/59/18/5dd0d3b87b8ff9c061849fbdb347758d1f724b9a82241aa908e0ec54ccd0/librt-0.8.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:172d57ec04346b047ca6af181e1ea4858086c80bdf455f61994c4aa6fc3f866c", size = 206907, upload-time = "2026-02-17T16:12:16.513Z"}, - {url = "https://files.pythonhosted.org/packages/d1/96/ef04902aad1424fd7299b62d1890e803e6ab4018c3044dca5922319c4b97/librt-0.8.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6b1977c4ea97ce5eb7755a78fae68d87e4102e4aaf54985e8b56806849cc06a3", size = 221217, upload-time = "2026-02-17T16:12:17.906Z"}, - {url = "https://files.pythonhosted.org/packages/6d/ff/7e01f2dda84a8f5d280637a2e5827210a8acca9a567a54507ef1c75b342d/librt-0.8.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:10c42e1f6fd06733ef65ae7bebce2872bcafd8d6e6b0a08fe0a05a23b044fb14", size = 214622, upload-time = "2026-02-17T16:12:19.108Z"}, - {url = "https://files.pythonhosted.org/packages/1e/8c/5b093d08a13946034fed57619742f790faf77058558b14ca36a6e331161e/librt-0.8.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:4c8dfa264b9193c4ee19113c985c95f876fae5e51f731494fc4e0cf594990ba7", size = 221987, upload-time = "2026-02-17T16:12:20.331Z"}, - {url = "https://files.pythonhosted.org/packages/d3/cc/86b0b3b151d40920ad45a94ce0171dec1aebba8a9d72bb3fa00c73ab25dd/librt-0.8.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:01170b6729a438f0dedc4a26ed342e3dc4f02d1000b4b19f980e1877f0c297e6", size = 215132, upload-time = "2026-02-17T16:12:21.54Z"}, - {url = "https://files.pythonhosted.org/packages/fc/be/8588164a46edf1e69858d952654e216a9a91174688eeefb9efbb38a9c799/librt-0.8.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:7b02679a0d783bdae30d443025b94465d8c3dc512f32f5b5031f93f57ac32071", size = 215195, upload-time = "2026-02-17T16:12:23.073Z"}, - {url = "https://files.pythonhosted.org/packages/f5/f2/0b9279bea735c734d69344ecfe056c1ba211694a72df10f568745c899c76/librt-0.8.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:190b109bb69592a3401fe1ffdea41a2e73370ace2ffdc4a0e8e2b39cdea81b78", size = 237946, upload-time = "2026-02-17T16:12:24.275Z"}, - {url = "https://files.pythonhosted.org/packages/e9/cc/5f2a34fbc8aeb35314a3641f9956fa9051a947424652fad9882be7a97949/librt-0.8.1-cp314-cp314-win32.whl", hash = "sha256:e70a57ecf89a0f64c24e37f38d3fe217a58169d2fe6ed6d70554964042474023", size = 50689, upload-time = "2026-02-17T16:12:25.766Z"}, - {url = "https://files.pythonhosted.org/packages/a0/76/cd4d010ab2147339ca2b93e959c3686e964edc6de66ddacc935c325883d7/librt-0.8.1-cp314-cp314-win_amd64.whl", hash = "sha256:7e2f3edca35664499fbb36e4770650c4bd4a08abc1f4458eab9df4ec56389730", size = 57875, upload-time = "2026-02-17T16:12:27.465Z"}, - {url = "https://files.pythonhosted.org/packages/84/0f/2143cb3c3ca48bd3379dcd11817163ca50781927c4537345d608b5045998/librt-0.8.1-cp314-cp314-win_arm64.whl", hash = "sha256:0d2f82168e55ddefd27c01c654ce52379c0750ddc31ee86b4b266bcf4d65f2a3", size = 48058, upload-time = "2026-02-17T16:12:28.556Z"}, - {url = "https://files.pythonhosted.org/packages/d2/0e/9b23a87e37baf00311c3efe6b48d6b6c168c29902dfc3f04c338372fd7db/librt-0.8.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2c74a2da57a094bd48d03fa5d196da83d2815678385d2978657499063709abe1", size = 68313, upload-time = "2026-02-17T16:12:29.659Z"}, - {url = "https://files.pythonhosted.org/packages/db/9a/859c41e5a4f1c84200a7d2b92f586aa27133c8243b6cac9926f6e54d01b9/librt-0.8.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a355d99c4c0d8e5b770313b8b247411ed40949ca44e33e46a4789b9293a907ee", size = 70994, upload-time = "2026-02-17T16:12:31.516Z"}, - {url = "https://files.pythonhosted.org/packages/4c/28/10605366ee599ed34223ac2bf66404c6fb59399f47108215d16d5ad751a8/librt-0.8.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:2eb345e8b33fb748227409c9f1233d4df354d6e54091f0e8fc53acdb2ffedeb7", size = 220770, upload-time = "2026-02-17T16:12:33.294Z"}, - {url = "https://files.pythonhosted.org/packages/af/8d/16ed8fd452dafae9c48d17a6bc1ee3e818fd40ef718d149a8eff2c9f4ea2/librt-0.8.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9be2f15e53ce4e83cc08adc29b26fb5978db62ef2a366fbdf716c8a6c8901040", size = 235409, upload-time = "2026-02-17T16:12:35.443Z"}, - {url = "https://files.pythonhosted.org/packages/89/1b/7bdf3e49349c134b25db816e4a3db6b94a47ac69d7d46b1e682c2c4949be/librt-0.8.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:785ae29c1f5c6e7c2cde2c7c0e148147f4503da3abc5d44d482068da5322fd9e", size = 246473, upload-time = "2026-02-17T16:12:36.656Z"}, - {url = "https://files.pythonhosted.org/packages/4e/8a/91fab8e4fd2a24930a17188c7af5380eb27b203d72101c9cc000dbdfd95a/librt-0.8.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1d3a7da44baf692f0c6aeb5b2a09c5e6fc7a703bca9ffa337ddd2e2da53f7732", size = 238866, upload-time = "2026-02-17T16:12:37.849Z"}, - {url = "https://files.pythonhosted.org/packages/b9/e0/c45a098843fc7c07e18a7f8a24ca8496aecbf7bdcd54980c6ca1aaa79a8e/librt-0.8.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5fc48998000cbc39ec0d5311312dda93ecf92b39aaf184c5e817d5d440b29624", size = 250248, upload-time = "2026-02-17T16:12:39.445Z"}, - {url = "https://files.pythonhosted.org/packages/82/30/07627de23036640c952cce0c1fe78972e77d7d2f8fd54fa5ef4554ff4a56/librt-0.8.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:e96baa6820280077a78244b2e06e416480ed859bbd8e5d641cf5742919d8beb4", size = 240629, upload-time = "2026-02-17T16:12:40.889Z"}, - {url = "https://files.pythonhosted.org/packages/fb/c1/55bfe1ee3542eba055616f9098eaf6eddb966efb0ca0f44eaa4aba327307/librt-0.8.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:31362dbfe297b23590530007062c32c6f6176f6099646bb2c95ab1b00a57c382", size = 239615, upload-time = "2026-02-17T16:12:42.446Z"}, - {url = "https://files.pythonhosted.org/packages/2b/39/191d3d28abc26c9099b19852e6c99f7f6d400b82fa5a4e80291bd3803e19/librt-0.8.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cc3656283d11540ab0ea01978378e73e10002145117055e03722417aeab30994", size = 263001, upload-time = "2026-02-17T16:12:43.627Z"}, - {url = "https://files.pythonhosted.org/packages/b9/eb/7697f60fbe7042ab4e88f4ee6af496b7f222fffb0a4e3593ef1f29f81652/librt-0.8.1-cp314-cp314t-win32.whl", hash = "sha256:738f08021b3142c2918c03692608baed43bc51144c29e35807682f8070ee2a3a", size = 51328, upload-time = "2026-02-17T16:12:45.148Z"}, - {url = "https://files.pythonhosted.org/packages/7c/72/34bf2eb7a15414a23e5e70ecb9440c1d3179f393d9349338a91e2781c0fb/librt-0.8.1-cp314-cp314t-win_amd64.whl", hash = "sha256:89815a22daf9c51884fb5dbe4f1ef65ee6a146e0b6a8df05f753e2e4a9359bf4", size = 58722, upload-time = "2026-02-17T16:12:46.85Z"}, - {url = "https://files.pythonhosted.org/packages/b2/c8/d148e041732d631fc76036f8b30fae4e77b027a1e95b7a84bb522481a940/librt-0.8.1-cp314-cp314t-win_arm64.whl", hash = "sha256:bf512a71a23504ed08103a13c941f763db13fb11177beb3d9244c98c29fb4a61", size = 48755, upload-time = "2026-02-17T16:12:47.943Z"} + {url = "https://files.pythonhosted.org/packages/95/21/d39b0a87ac52fc98f621fb6f8060efb017a767ebbbac2f99fbcbc9ddc0d7/librt-0.8.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a28f2612ab566b17f3698b0da021ff9960610301607c9a5e8eaca62f5e1c350a", size = 66516, upload-time = "2026-02-17T16:11:41.604Z"}, + {url = "https://files.pythonhosted.org/packages/69/f1/46375e71441c43e8ae335905e069f1c54febee63a146278bcee8782c84fd/librt-0.8.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:60a78b694c9aee2a0f1aaeaa7d101cf713e92e8423a941d2897f4fa37908dab9", size = 68634, upload-time = "2026-02-17T16:11:43.268Z"}, + {url = "https://files.pythonhosted.org/packages/0a/33/c510de7f93bf1fa19e13423a606d8189a02624a800710f6e6a0a0f0784b3/librt-0.8.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:758509ea3f1eba2a57558e7e98f4659d0ea7670bff49673b0dde18a3c7e6c0eb", size = 198941, upload-time = "2026-02-17T16:11:44.28Z"}, + {url = "https://files.pythonhosted.org/packages/dd/36/e725903416409a533d92398e88ce665476f275081d0d7d42f9c4951999e5/librt-0.8.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:039b9f2c506bd0ab0f8725aa5ba339c6f0cd19d3b514b50d134789809c24285d", size = 209991, upload-time = "2026-02-17T16:11:45.462Z"}, + {url = "https://files.pythonhosted.org/packages/30/7a/8d908a152e1875c9f8eac96c97a480df425e657cdb47854b9efaa4998889/librt-0.8.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5bb54f1205a3a6ab41a6fd71dfcdcbd278670d3a90ca502a30d9da583105b6f7", size = 224476, upload-time = "2026-02-17T16:11:46.542Z"}, + {url = "https://files.pythonhosted.org/packages/a8/b8/a22c34f2c485b8903a06f3fe3315341fe6876ef3599792344669db98fcff/librt-0.8.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:05bd41cdee35b0c59c259f870f6da532a2c5ca57db95b5f23689fcb5c9e42440", size = 217518, upload-time = "2026-02-17T16:11:47.746Z"}, + {url = "https://files.pythonhosted.org/packages/79/6f/5c6fea00357e4f82ba44f81dbfb027921f1ab10e320d4a64e1c408d035d9/librt-0.8.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:adfab487facf03f0d0857b8710cf82d0704a309d8ffc33b03d9302b4c64e91a9", size = 225116, upload-time = "2026-02-17T16:11:49.298Z"}, + {url = "https://files.pythonhosted.org/packages/f2/a0/95ced4e7b1267fe1e2720a111685bcddf0e781f7e9e0ce59d751c44dcfe5/librt-0.8.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:153188fe98a72f206042be10a2c6026139852805215ed9539186312d50a8e972", size = 217751, upload-time = "2026-02-17T16:11:50.49Z"}, + {url = "https://files.pythonhosted.org/packages/93/c2/0517281cb4d4101c27ab59472924e67f55e375bc46bedae94ac6dc6e1902/librt-0.8.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:dd3c41254ee98604b08bd5b3af5bf0a89740d4ee0711de95b65166bf44091921", size = 218378, upload-time = "2026-02-17T16:11:51.783Z"}, + {url = "https://files.pythonhosted.org/packages/43/e8/37b3ac108e8976888e559a7b227d0ceac03c384cfd3e7a1c2ee248dbae79/librt-0.8.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e0d138c7ae532908cbb342162b2611dbd4d90c941cd25ab82084aaf71d2c0bd0", size = 241199, upload-time = "2026-02-17T16:11:53.561Z"}, + {url = "https://files.pythonhosted.org/packages/4b/5b/35812d041c53967fedf551a39399271bbe4257e681236a2cf1a69c8e7fa1/librt-0.8.1-cp312-cp312-win32.whl", hash = "sha256:43353b943613c5d9c49a25aaffdba46f888ec354e71e3529a00cca3f04d66a7a", size = 54917, upload-time = "2026-02-17T16:11:54.758Z"}, + {url = "https://files.pythonhosted.org/packages/de/d1/fa5d5331b862b9775aaf2a100f5ef86854e5d4407f71bddf102f4421e034/librt-0.8.1-cp312-cp312-win_amd64.whl", hash = "sha256:ff8baf1f8d3f4b6b7257fcb75a501f2a5499d0dda57645baa09d4d0d34b19444", size = 62017, upload-time = "2026-02-17T16:11:55.748Z"}, + {url = "https://files.pythonhosted.org/packages/c7/7c/c614252f9acda59b01a66e2ddfd243ed1c7e1deab0293332dfbccf862808/librt-0.8.1-cp312-cp312-win_arm64.whl", hash = "sha256:0f2ae3725904f7377e11cc37722d5d401e8b3d5851fb9273d7f4fe04f6b3d37d", size = 52441, upload-time = "2026-02-17T16:11:56.801Z"}, + {url = "https://files.pythonhosted.org/packages/c5/3c/f614c8e4eaac7cbf2bbdf9528790b21d89e277ee20d57dc6e559c626105f/librt-0.8.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7e6bad1cd94f6764e1e21950542f818a09316645337fd5ab9a7acc45d99a8f35", size = 66529, upload-time = "2026-02-17T16:11:57.809Z"}, + {url = "https://files.pythonhosted.org/packages/ab/96/5836544a45100ae411eda07d29e3d99448e5258b6e9c8059deb92945f5c2/librt-0.8.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cf450f498c30af55551ba4f66b9123b7185362ec8b625a773b3d39aa1a717583", size = 68669, upload-time = "2026-02-17T16:11:58.843Z"}, + {url = "https://files.pythonhosted.org/packages/06/53/f0b992b57af6d5531bf4677d75c44f095f2366a1741fb695ee462ae04b05/librt-0.8.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:eca45e982fa074090057132e30585a7e8674e9e885d402eae85633e9f449ce6c", size = 199279, upload-time = "2026-02-17T16:11:59.862Z"}, + {url = "https://files.pythonhosted.org/packages/f3/ad/4848cc16e268d14280d8168aee4f31cea92bbd2b79ce33d3e166f2b4e4fc/librt-0.8.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0c3811485fccfda840861905b8c70bba5ec094e02825598bb9d4ca3936857a04", size = 210288, upload-time = "2026-02-17T16:12:00.954Z"}, + {url = "https://files.pythonhosted.org/packages/52/05/27fdc2e95de26273d83b96742d8d3b7345f2ea2bdbd2405cc504644f2096/librt-0.8.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5e4af413908f77294605e28cfd98063f54b2c790561383971d2f52d113d9c363", size = 224809, upload-time = "2026-02-17T16:12:02.108Z"}, + {url = "https://files.pythonhosted.org/packages/7a/d0/78200a45ba3240cb042bc597d6f2accba9193a2c57d0356268cbbe2d0925/librt-0.8.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5212a5bd7fae98dae95710032902edcd2ec4dc994e883294f75c857b83f9aba0", size = 218075, upload-time = "2026-02-17T16:12:03.631Z"}, + {url = "https://files.pythonhosted.org/packages/af/72/a210839fa74c90474897124c064ffca07f8d4b347b6574d309686aae7ca6/librt-0.8.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e692aa2d1d604e6ca12d35e51fdc36f4cda6345e28e36374579f7ef3611b3012", size = 225486, upload-time = "2026-02-17T16:12:04.725Z"}, + {url = "https://files.pythonhosted.org/packages/a3/c1/a03cc63722339ddbf087485f253493e2b013039f5b707e8e6016141130fa/librt-0.8.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4be2a5c926b9770c9e08e717f05737a269b9d0ebc5d2f0060f0fe3fe9ce47acb", size = 218219, upload-time = "2026-02-17T16:12:05.828Z"}, + {url = "https://files.pythonhosted.org/packages/58/f5/fff6108af0acf941c6f274a946aea0e484bd10cd2dc37610287ce49388c5/librt-0.8.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:fd1a720332ea335ceb544cf0a03f81df92abd4bb887679fd1e460976b0e6214b", size = 218750, upload-time = "2026-02-17T16:12:07.09Z"}, + {url = "https://files.pythonhosted.org/packages/71/67/5a387bfef30ec1e4b4f30562c8586566faf87e47d696768c19feb49e3646/librt-0.8.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:93c2af9e01e0ef80d95ae3c720be101227edae5f2fe7e3dc63d8857fadfc5a1d", size = 241624, upload-time = "2026-02-17T16:12:08.43Z"}, + {url = "https://files.pythonhosted.org/packages/d4/be/24f8502db11d405232ac1162eb98069ca49c3306c1d75c6ccc61d9af8789/librt-0.8.1-cp313-cp313-win32.whl", hash = "sha256:086a32dbb71336627e78cc1d6ee305a68d038ef7d4c39aaff41ae8c9aa46e91a", size = 54969, upload-time = "2026-02-17T16:12:09.633Z"}, + {url = "https://files.pythonhosted.org/packages/5c/73/c9fdf6cb2a529c1a092ce769a12d88c8cca991194dfe641b6af12fa964d2/librt-0.8.1-cp313-cp313-win_amd64.whl", hash = "sha256:e11769a1dbda4da7b00a76cfffa67aa47cfa66921d2724539eee4b9ede780b79", size = 62000, upload-time = "2026-02-17T16:12:10.632Z"}, + {url = "https://files.pythonhosted.org/packages/d3/97/68f80ca3ac4924f250cdfa6e20142a803e5e50fca96ef5148c52ee8c10ea/librt-0.8.1-cp313-cp313-win_arm64.whl", hash = "sha256:924817ab3141aca17893386ee13261f1d100d1ef410d70afe4389f2359fea4f0", size = 52495, upload-time = "2026-02-17T16:12:11.633Z"} ] [[package]] @@ -260,10 +291,14 @@ sdist = {url = "https://files.pythonhosted.org/packages/74/cd/08ae687ba099c7e3d2 source = {registry = "https://pypi.org/simple"} version = "0.46.0" wheels = [ - {url = "https://files.pythonhosted.org/packages/95/ae/af0ffb724814cc2ea64445acad05f71cff5f799bb7efb22e47ee99340dbc/llvmlite-0.46.0-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:d252edfb9f4ac1fcf20652258e3f102b26b03eef738dc8a6ffdab7d7d341d547", size = 37232768, upload-time = "2025-12-08T18:15:25.055Z"}, - {url = "https://files.pythonhosted.org/packages/c9/19/5018e5352019be753b7b07f7759cdabb69ca5779fea2494be8839270df4c/llvmlite-0.46.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:379fdd1c59badeff8982cb47e4694a6143bec3bb49aa10a466e095410522064d", size = 56275173, upload-time = "2025-12-08T18:15:28.109Z"}, - {url = "https://files.pythonhosted.org/packages/9f/c9/d57877759d707e84c082163c543853245f91b70c804115a5010532890f18/llvmlite-0.46.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2e8cbfff7f6db0fa2c771ad24154e2a7e457c2444d7673e6de06b8b698c3b269", size = 55128628, upload-time = "2025-12-08T18:15:31.098Z"}, - {url = "https://files.pythonhosted.org/packages/30/a8/e61a8c2b3cc7a597073d9cde1fcbb567e9d827f1db30c93cf80422eac70d/llvmlite-0.46.0-cp314-cp314-win_amd64.whl", hash = "sha256:7821eda3ec1f18050f981819756631d60b6d7ab1a6cf806d9efefbe3f4082d61", size = 39153056, upload-time = "2025-12-08T18:15:33.938Z"} + {url = "https://files.pythonhosted.org/packages/2b/f8/4db016a5e547d4e054ff2f3b99203d63a497465f81ab78ec8eb2ff7b2304/llvmlite-0.46.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6b9588ad4c63b4f0175a3984b85494f0c927c6b001e3a246a3a7fb3920d9a137", size = 37232767, upload-time = "2025-12-08T18:15:00.737Z"}, + {url = "https://files.pythonhosted.org/packages/aa/85/4890a7c14b4fa54400945cb52ac3cd88545bbdb973c440f98ca41591cdc5/llvmlite-0.46.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3535bd2bb6a2d7ae4012681ac228e5132cdb75fefb1bcb24e33f2f3e0c865ed4", size = 56275176, upload-time = "2025-12-08T18:15:03.936Z"}, + {url = "https://files.pythonhosted.org/packages/6a/07/3d31d39c1a1a08cd5337e78299fca77e6aebc07c059fbd0033e3edfab45c/llvmlite-0.46.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4cbfd366e60ff87ea6cc62f50bc4cd800ebb13ed4c149466f50cf2163a473d1e", size = 55128630, upload-time = "2025-12-08T18:15:07.196Z"}, + {url = "https://files.pythonhosted.org/packages/2a/6b/d139535d7590a1bba1ceb68751bef22fadaa5b815bbdf0e858e3875726b2/llvmlite-0.46.0-cp312-cp312-win_amd64.whl", hash = "sha256:398b39db462c39563a97b912d4f2866cd37cba60537975a09679b28fbbc0fb38", size = 38138940, upload-time = "2025-12-08T18:15:10.162Z"}, + {url = "https://files.pythonhosted.org/packages/e6/ff/3eba7eb0aed4b6fca37125387cd417e8c458e750621fce56d2c541f67fa8/llvmlite-0.46.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:30b60892d034bc560e0ec6654737aaa74e5ca327bd8114d82136aa071d611172", size = 37232767, upload-time = "2025-12-08T18:15:13.22Z"}, + {url = "https://files.pythonhosted.org/packages/0e/54/737755c0a91558364b9200702c3c9c15d70ed63f9b98a2c32f1c2aa1f3ba/llvmlite-0.46.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6cc19b051753368a9c9f31dc041299059ee91aceec81bd57b0e385e5d5bf1a54", size = 56275176, upload-time = "2025-12-08T18:15:16.339Z"}, + {url = "https://files.pythonhosted.org/packages/e6/91/14f32e1d70905c1c0aa4e6609ab5d705c3183116ca02ac6df2091868413a/llvmlite-0.46.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bca185892908f9ede48c0acd547fe4dc1bafefb8a4967d47db6cf664f9332d12", size = 55128629, upload-time = "2025-12-08T18:15:19.493Z"}, + {url = "https://files.pythonhosted.org/packages/4a/a7/d526ae86708cea531935ae777b6dbcabe7db52718e6401e0fb9c5edea80e/llvmlite-0.46.0-cp313-cp313-win_amd64.whl", hash = "sha256:67438fd30e12349ebb054d86a5a1a57fd5e87d264d2451bcfafbbbaa25b82a35", size = 38138941, upload-time = "2025-12-08T18:15:22.536Z"} ] [[package]] @@ -284,28 +319,39 @@ sdist = {url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd959 source = {registry = "https://pypi.org/simple"} version = "3.0.3" wheels = [ - {url = "https://files.pythonhosted.org/packages/33/8a/8e42d4838cd89b7dde187011e97fe6c3af66d8c044997d2183fbd6d31352/markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe", size = 11619, upload-time = "2025-09-27T18:37:06.342Z"}, - {url = "https://files.pythonhosted.org/packages/b5/64/7660f8a4a8e53c924d0fa05dc3a55c9cee10bbd82b11c5afb27d44b096ce/markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026", size = 12029, upload-time = "2025-09-27T18:37:07.213Z"}, - {url = "https://files.pythonhosted.org/packages/da/ef/e648bfd021127bef5fa12e1720ffed0c6cbb8310c8d9bea7266337ff06de/markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737", size = 24408, upload-time = "2025-09-27T18:37:09.572Z"}, - {url = "https://files.pythonhosted.org/packages/41/3c/a36c2450754618e62008bf7435ccb0f88053e07592e6028a34776213d877/markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97", size = 23005, upload-time = "2025-09-27T18:37:10.58Z"}, - {url = "https://files.pythonhosted.org/packages/bc/20/b7fdf89a8456b099837cd1dc21974632a02a999ec9bf7ca3e490aacd98e7/markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d", size = 22048, upload-time = "2025-09-27T18:37:11.547Z"}, - {url = "https://files.pythonhosted.org/packages/9a/a7/591f592afdc734f47db08a75793a55d7fbcc6902a723ae4cfbab61010cc5/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda", size = 23821, upload-time = "2025-09-27T18:37:12.48Z"}, - {url = "https://files.pythonhosted.org/packages/7d/33/45b24e4f44195b26521bc6f1a82197118f74df348556594bd2262bda1038/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf", size = 21606, upload-time = "2025-09-27T18:37:13.485Z"}, - {url = "https://files.pythonhosted.org/packages/ff/0e/53dfaca23a69fbfbbf17a4b64072090e70717344c52eaaaa9c5ddff1e5f0/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe", size = 23043, upload-time = "2025-09-27T18:37:14.408Z"}, - {url = "https://files.pythonhosted.org/packages/46/11/f333a06fc16236d5238bfe74daccbca41459dcd8d1fa952e8fbd5dccfb70/markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9", size = 14747, upload-time = "2025-09-27T18:37:15.36Z"}, - {url = "https://files.pythonhosted.org/packages/28/52/182836104b33b444e400b14f797212f720cbc9ed6ba34c800639d154e821/markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581", size = 15341, upload-time = "2025-09-27T18:37:16.496Z"}, - {url = "https://files.pythonhosted.org/packages/6f/18/acf23e91bd94fd7b3031558b1f013adfa21a8e407a3fdb32745538730382/markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4", size = 14073, upload-time = "2025-09-27T18:37:17.476Z"}, - {url = "https://files.pythonhosted.org/packages/3c/f0/57689aa4076e1b43b15fdfa646b04653969d50cf30c32a102762be2485da/markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab", size = 11661, upload-time = "2025-09-27T18:37:18.453Z"}, - {url = "https://files.pythonhosted.org/packages/89/c3/2e67a7ca217c6912985ec766c6393b636fb0c2344443ff9d91404dc4c79f/markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175", size = 12069, upload-time = "2025-09-27T18:37:19.332Z"}, - {url = "https://files.pythonhosted.org/packages/f0/00/be561dce4e6ca66b15276e184ce4b8aec61fe83662cce2f7d72bd3249d28/markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634", size = 25670, upload-time = "2025-09-27T18:37:20.245Z"}, - {url = "https://files.pythonhosted.org/packages/50/09/c419f6f5a92e5fadde27efd190eca90f05e1261b10dbd8cbcb39cd8ea1dc/markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50", size = 23598, upload-time = "2025-09-27T18:37:21.177Z"}, - {url = "https://files.pythonhosted.org/packages/22/44/a0681611106e0b2921b3033fc19bc53323e0b50bc70cffdd19f7d679bb66/markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e", size = 23261, upload-time = "2025-09-27T18:37:22.167Z"}, - {url = "https://files.pythonhosted.org/packages/5f/57/1b0b3f100259dc9fffe780cfb60d4be71375510e435efec3d116b6436d43/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5", size = 24835, upload-time = "2025-09-27T18:37:23.296Z"}, - {url = "https://files.pythonhosted.org/packages/26/6a/4bf6d0c97c4920f1597cc14dd720705eca0bf7c787aebc6bb4d1bead5388/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523", size = 22733, upload-time = "2025-09-27T18:37:24.237Z"}, - {url = "https://files.pythonhosted.org/packages/14/c7/ca723101509b518797fedc2fdf79ba57f886b4aca8a7d31857ba3ee8281f/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc", size = 23672, upload-time = "2025-09-27T18:37:25.271Z"}, - {url = "https://files.pythonhosted.org/packages/fb/df/5bd7a48c256faecd1d36edc13133e51397e41b73bb77e1a69deab746ebac/markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d", size = 14819, upload-time = "2025-09-27T18:37:26.285Z"}, - {url = "https://files.pythonhosted.org/packages/1a/8a/0402ba61a2f16038b48b39bccca271134be00c5c9f0f623208399333c448/markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9", size = 15426, upload-time = "2025-09-27T18:37:27.316Z"}, - {url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z"} + {url = "https://files.pythonhosted.org/packages/5a/72/147da192e38635ada20e0a2e1a51cf8823d2119ce8883f7053879c2199b5/markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e", size = 11615, upload-time = "2025-09-27T18:36:30.854Z"}, + {url = "https://files.pythonhosted.org/packages/9a/81/7e4e08678a1f98521201c3079f77db69fb552acd56067661f8c2f534a718/markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce", size = 12020, upload-time = "2025-09-27T18:36:31.971Z"}, + {url = "https://files.pythonhosted.org/packages/1e/2c/799f4742efc39633a1b54a92eec4082e4f815314869865d876824c257c1e/markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d", size = 24332, upload-time = "2025-09-27T18:36:32.813Z"}, + {url = "https://files.pythonhosted.org/packages/3c/2e/8d0c2ab90a8c1d9a24f0399058ab8519a3279d1bd4289511d74e909f060e/markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d", size = 22947, upload-time = "2025-09-27T18:36:33.86Z"}, + {url = "https://files.pythonhosted.org/packages/2c/54/887f3092a85238093a0b2154bd629c89444f395618842e8b0c41783898ea/markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a", size = 21962, upload-time = "2025-09-27T18:36:35.099Z"}, + {url = "https://files.pythonhosted.org/packages/c9/2f/336b8c7b6f4a4d95e91119dc8521402461b74a485558d8f238a68312f11c/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b", size = 23760, upload-time = "2025-09-27T18:36:36.001Z"}, + {url = "https://files.pythonhosted.org/packages/32/43/67935f2b7e4982ffb50a4d169b724d74b62a3964bc1a9a527f5ac4f1ee2b/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f", size = 21529, upload-time = "2025-09-27T18:36:36.906Z"}, + {url = "https://files.pythonhosted.org/packages/89/e0/4486f11e51bbba8b0c041098859e869e304d1c261e59244baa3d295d47b7/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b", size = 23015, upload-time = "2025-09-27T18:36:37.868Z"}, + {url = "https://files.pythonhosted.org/packages/2f/e1/78ee7a023dac597a5825441ebd17170785a9dab23de95d2c7508ade94e0e/markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d", size = 14540, upload-time = "2025-09-27T18:36:38.761Z"}, + {url = "https://files.pythonhosted.org/packages/aa/5b/bec5aa9bbbb2c946ca2733ef9c4ca91c91b6a24580193e891b5f7dbe8e1e/markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c", size = 15105, upload-time = "2025-09-27T18:36:39.701Z"}, + {url = "https://files.pythonhosted.org/packages/e5/f1/216fc1bbfd74011693a4fd837e7026152e89c4bcf3e77b6692fba9923123/markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f", size = 13906, upload-time = "2025-09-27T18:36:40.689Z"}, + {url = "https://files.pythonhosted.org/packages/38/2f/907b9c7bbba283e68f20259574b13d005c121a0fa4c175f9bed27c4597ff/markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795", size = 11622, upload-time = "2025-09-27T18:36:41.777Z"}, + {url = "https://files.pythonhosted.org/packages/9c/d9/5f7756922cdd676869eca1c4e3c0cd0df60ed30199ffd775e319089cb3ed/markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219", size = 12029, upload-time = "2025-09-27T18:36:43.257Z"}, + {url = "https://files.pythonhosted.org/packages/00/07/575a68c754943058c78f30db02ee03a64b3c638586fba6a6dd56830b30a3/markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6", size = 24374, upload-time = "2025-09-27T18:36:44.508Z"}, + {url = "https://files.pythonhosted.org/packages/a9/21/9b05698b46f218fc0e118e1f8168395c65c8a2c750ae2bab54fc4bd4e0e8/markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676", size = 22980, upload-time = "2025-09-27T18:36:45.385Z"}, + {url = "https://files.pythonhosted.org/packages/7f/71/544260864f893f18b6827315b988c146b559391e6e7e8f7252839b1b846a/markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9", size = 21990, upload-time = "2025-09-27T18:36:46.916Z"}, + {url = "https://files.pythonhosted.org/packages/c2/28/b50fc2f74d1ad761af2f5dcce7492648b983d00a65b8c0e0cb457c82ebbe/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1", size = 23784, upload-time = "2025-09-27T18:36:47.884Z"}, + {url = "https://files.pythonhosted.org/packages/ed/76/104b2aa106a208da8b17a2fb72e033a5a9d7073c68f7e508b94916ed47a9/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc", size = 21588, upload-time = "2025-09-27T18:36:48.82Z"}, + {url = "https://files.pythonhosted.org/packages/b5/99/16a5eb2d140087ebd97180d95249b00a03aa87e29cc224056274f2e45fd6/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12", size = 23041, upload-time = "2025-09-27T18:36:49.797Z"}, + {url = "https://files.pythonhosted.org/packages/19/bc/e7140ed90c5d61d77cea142eed9f9c303f4c4806f60a1044c13e3f1471d0/markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed", size = 14543, upload-time = "2025-09-27T18:36:51.584Z"}, + {url = "https://files.pythonhosted.org/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5", size = 15113, upload-time = "2025-09-27T18:36:52.537Z"}, + {url = "https://files.pythonhosted.org/packages/f0/3a/fa34a0f7cfef23cf9500d68cb7c32dd64ffd58a12b09225fb03dd37d5b80/markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485", size = 13911, upload-time = "2025-09-27T18:36:53.513Z"}, + {url = "https://files.pythonhosted.org/packages/e4/d7/e05cd7efe43a88a17a37b3ae96e79a19e846f3f456fe79c57ca61356ef01/markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73", size = 11658, upload-time = "2025-09-27T18:36:54.819Z"}, + {url = "https://files.pythonhosted.org/packages/99/9e/e412117548182ce2148bdeacdda3bb494260c0b0184360fe0d56389b523b/markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37", size = 12066, upload-time = "2025-09-27T18:36:55.714Z"}, + {url = "https://files.pythonhosted.org/packages/bc/e6/fa0ffcda717ef64a5108eaa7b4f5ed28d56122c9a6d70ab8b72f9f715c80/markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19", size = 25639, upload-time = "2025-09-27T18:36:56.908Z"}, + {url = "https://files.pythonhosted.org/packages/96/ec/2102e881fe9d25fc16cb4b25d5f5cde50970967ffa5dddafdb771237062d/markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025", size = 23569, upload-time = "2025-09-27T18:36:57.913Z"}, + {url = "https://files.pythonhosted.org/packages/4b/30/6f2fce1f1f205fc9323255b216ca8a235b15860c34b6798f810f05828e32/markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6", size = 23284, upload-time = "2025-09-27T18:36:58.833Z"}, + {url = "https://files.pythonhosted.org/packages/58/47/4a0ccea4ab9f5dcb6f79c0236d954acb382202721e704223a8aafa38b5c8/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f", size = 24801, upload-time = "2025-09-27T18:36:59.739Z"}, + {url = "https://files.pythonhosted.org/packages/6a/70/3780e9b72180b6fecb83a4814d84c3bf4b4ae4bf0b19c27196104149734c/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb", size = 22769, upload-time = "2025-09-27T18:37:00.719Z"}, + {url = "https://files.pythonhosted.org/packages/98/c5/c03c7f4125180fc215220c035beac6b9cb684bc7a067c84fc69414d315f5/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009", size = 23642, upload-time = "2025-09-27T18:37:01.673Z"}, + {url = "https://files.pythonhosted.org/packages/80/d6/2d1b89f6ca4bff1036499b1e29a1d02d282259f3681540e16563f27ebc23/markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354", size = 14612, upload-time = "2025-09-27T18:37:02.639Z"}, + {url = "https://files.pythonhosted.org/packages/2b/98/e48a4bfba0a0ffcf9925fe2d69240bfaa19c6f7507b8cd09c70684a53c1e/markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218", size = 15200, upload-time = "2025-09-27T18:37:03.582Z"}, + {url = "https://files.pythonhosted.org/packages/0e/72/e3cc540f351f316e9ed0f092757459afbc595824ca724cbc5a5d4263713f/markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287", size = 13973, upload-time = "2025-09-27T18:37:04.929Z"} ] [[package]] @@ -341,12 +387,18 @@ sdist = {url = "https://files.pythonhosted.org/packages/f5/db/4efed9504bc01309ab source = {registry = "https://pypi.org/simple"} version = "1.19.1" wheels = [ - {url = "https://files.pythonhosted.org/packages/de/eb/b83e75f4c820c4247a58580ef86fcd35165028f191e7e1ba57128c52782d/mypy-1.19.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:06e6170bd5836770e8104c8fdd58e5e725cfeb309f0a6c681a811f557e97eac1", size = 13199744, upload-time = "2025-12-15T05:03:30.823Z"}, - {url = "https://files.pythonhosted.org/packages/94/28/52785ab7bfa165f87fcbb61547a93f98bb20e7f82f90f165a1f69bce7b3d/mypy-1.19.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:804bd67b8054a85447c8954215a906d6eff9cabeabe493fb6334b24f4bfff718", size = 12215815, upload-time = "2025-12-15T05:02:42.323Z"}, - {url = "https://files.pythonhosted.org/packages/0a/c6/bdd60774a0dbfb05122e3e925f2e9e846c009e479dcec4821dad881f5b52/mypy-1.19.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:21761006a7f497cb0d4de3d8ef4ca70532256688b0523eee02baf9eec895e27b", size = 12740047, upload-time = "2025-12-15T05:03:33.168Z"}, - {url = "https://files.pythonhosted.org/packages/32/2a/66ba933fe6c76bd40d1fe916a83f04fed253152f451a877520b3c4a5e41e/mypy-1.19.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:28902ee51f12e0f19e1e16fbe2f8f06b6637f482c459dd393efddd0ec7f82045", size = 13601998, upload-time = "2025-12-15T05:03:13.056Z"}, - {url = "https://files.pythonhosted.org/packages/e3/da/5055c63e377c5c2418760411fd6a63ee2b96cf95397259038756c042574f/mypy-1.19.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:481daf36a4c443332e2ae9c137dfee878fcea781a2e3f895d54bd3002a900957", size = 13807476, upload-time = "2025-12-15T05:03:17.977Z"}, - {url = "https://files.pythonhosted.org/packages/cd/09/4ebd873390a063176f06b0dbf1f7783dd87bd120eae7727fa4ae4179b685/mypy-1.19.1-cp314-cp314-win_amd64.whl", hash = "sha256:8bb5c6f6d043655e055be9b542aa5f3bdd30e4f3589163e85f93f3640060509f", size = 10281872, upload-time = "2025-12-15T05:03:05.549Z"}, + {url = "https://files.pythonhosted.org/packages/06/8a/19bfae96f6615aa8a0604915512e0289b1fad33d5909bf7244f02935d33a/mypy-1.19.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a8174a03289288c1f6c46d55cef02379b478bfbc8e358e02047487cad44c6ca1", size = 13206053, upload-time = "2025-12-15T05:03:46.622Z"}, + {url = "https://files.pythonhosted.org/packages/a5/34/3e63879ab041602154ba2a9f99817bb0c85c4df19a23a1443c8986e4d565/mypy-1.19.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ffcebe56eb09ff0c0885e750036a095e23793ba6c2e894e7e63f6d89ad51f22e", size = 12219134, upload-time = "2025-12-15T05:03:24.367Z"}, + {url = "https://files.pythonhosted.org/packages/89/cc/2db6f0e95366b630364e09845672dbee0cbf0bbe753a204b29a944967cd9/mypy-1.19.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b64d987153888790bcdb03a6473d321820597ab8dd9243b27a92153c4fa50fd2", size = 12731616, upload-time = "2025-12-15T05:02:44.725Z"}, + {url = "https://files.pythonhosted.org/packages/00/be/dd56c1fd4807bc1eba1cf18b2a850d0de7bacb55e158755eb79f77c41f8e/mypy-1.19.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c35d298c2c4bba75feb2195655dfea8124d855dfd7343bf8b8c055421eaf0cf8", size = 13620847, upload-time = "2025-12-15T05:03:39.633Z"}, + {url = "https://files.pythonhosted.org/packages/6d/42/332951aae42b79329f743bf1da088cd75d8d4d9acc18fbcbd84f26c1af4e/mypy-1.19.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:34c81968774648ab5ac09c29a375fdede03ba253f8f8287847bd480782f73a6a", size = 13834976, upload-time = "2025-12-15T05:03:08.786Z"}, + {url = "https://files.pythonhosted.org/packages/6f/63/e7493e5f90e1e085c562bb06e2eb32cae27c5057b9653348d38b47daaecc/mypy-1.19.1-cp312-cp312-win_amd64.whl", hash = "sha256:b10e7c2cd7870ba4ad9b2d8a6102eb5ffc1f16ca35e3de6bfa390c1113029d13", size = 10118104, upload-time = "2025-12-15T05:03:10.834Z"}, + {url = "https://files.pythonhosted.org/packages/de/9f/a6abae693f7a0c697dbb435aac52e958dc8da44e92e08ba88d2e42326176/mypy-1.19.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e3157c7594ff2ef1634ee058aafc56a82db665c9438fd41b390f3bde1ab12250", size = 13201927, upload-time = "2025-12-15T05:02:29.138Z"}, + {url = "https://files.pythonhosted.org/packages/9a/a4/45c35ccf6e1c65afc23a069f50e2c66f46bd3798cbe0d680c12d12935caa/mypy-1.19.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bdb12f69bcc02700c2b47e070238f42cb87f18c0bc1fc4cdb4fb2bc5fd7a3b8b", size = 12206730, upload-time = "2025-12-15T05:03:01.325Z"}, + {url = "https://files.pythonhosted.org/packages/05/bb/cdcf89678e26b187650512620eec8368fded4cfd99cfcb431e4cdfd19dec/mypy-1.19.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f859fb09d9583a985be9a493d5cfc5515b56b08f7447759a0c5deaf68d80506e", size = 12724581, upload-time = "2025-12-15T05:03:20.087Z"}, + {url = "https://files.pythonhosted.org/packages/d1/32/dd260d52babf67bad8e6770f8e1102021877ce0edea106e72df5626bb0ec/mypy-1.19.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c9a6538e0415310aad77cb94004ca6482330fece18036b5f360b62c45814c4ef", size = 13616252, upload-time = "2025-12-15T05:02:49.036Z"}, + {url = "https://files.pythonhosted.org/packages/71/d0/5e60a9d2e3bd48432ae2b454b7ef2b62a960ab51292b1eda2a95edd78198/mypy-1.19.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:da4869fc5e7f62a88f3fe0b5c919d1d9f7ea3cef92d3689de2823fd27e40aa75", size = 13840848, upload-time = "2025-12-15T05:02:55.95Z"}, + {url = "https://files.pythonhosted.org/packages/98/76/d32051fa65ecf6cc8c6610956473abdc9b4c43301107476ac03559507843/mypy-1.19.1-cp313-cp313-win_amd64.whl", hash = "sha256:016f2246209095e8eda7538944daa1d60e1e8134d98983b9fc1e92c1fc0cb8dd", size = 10135510, upload-time = "2025-12-15T05:02:58.438Z"}, {url = "https://files.pythonhosted.org/packages/8d/f4/4ce9a05ce5ded1de3ec1c1d96cf9f9504a04e54ce0ed55cfa38619a32b8d/mypy-1.19.1-py3-none-any.whl", hash = "sha256:f1235f5ea01b7db5468d53ece6aaddf1ad0b88d9e7462b86ef96fe04995d7247", size = 2471239, upload-time = "2025-12-15T05:03:07.248Z"} ] @@ -395,10 +447,14 @@ sdist = {url = "https://files.pythonhosted.org/packages/23/c9/a0fb41787d01d62104 source = {registry = "https://pypi.org/simple"} version = "0.64.0" wheels = [ - {url = "https://files.pythonhosted.org/packages/3d/8a/77d26afe0988c592dd97cb8d4e80bfb3dfc7dbdacfca7d74a7c5c81dd8c2/numba-0.64.0-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:f565d55eaeff382cbc86c63c8c610347453af3d1e7afb2b6569aac1c9b5c93ce", size = 2683590, upload-time = "2026-02-18T18:41:12.897Z"}, - {url = "https://files.pythonhosted.org/packages/8e/4b/600b8b7cdbc7f9cebee9ea3d13bb70052a79baf28944024ffcb59f0712e3/numba-0.64.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:9b55169b18892c783f85e9ad9e6f5297a6d12967e4414e6b71361086025ff0bb", size = 3781163, upload-time = "2026-02-18T18:41:15.377Z"}, - {url = "https://files.pythonhosted.org/packages/ff/73/53f2d32bfa45b7175e9944f6b816d8c32840178c3eee9325033db5bf838e/numba-0.64.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:196bcafa02c9dd1707e068434f6d5cedde0feb787e3432f7f1f0e993cc336c4c", size = 3481172, upload-time = "2026-02-18T18:41:17.281Z"}, - {url = "https://files.pythonhosted.org/packages/b5/00/aebd2f7f1e11e38814bb96e95a27580817a7b340608d3ac085fdbab83174/numba-0.64.0-cp314-cp314-win_amd64.whl", hash = "sha256:213e9acbe7f1c05090592e79020315c1749dd52517b90e94c517dca3f014d4a1", size = 2754700, upload-time = "2026-02-18T18:41:19.277Z"} + {url = "https://files.pythonhosted.org/packages/70/a6/9fc52cb4f0d5e6d8b5f4d81615bc01012e3cf24e1052a60f17a68deb8092/numba-0.64.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:69440a8e8bc1a81028446f06b363e28635aa67bd51b1e498023f03b812e0ce68", size = 2683418, upload-time = "2026-02-18T18:40:59.886Z"}, + {url = "https://files.pythonhosted.org/packages/9b/89/1a74ea99b180b7a5587b0301ed1b183a2937c4b4b67f7994689b5d36fc34/numba-0.64.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f13721011f693ba558b8dd4e4db7f2640462bba1b855bdc804be45bbeb55031a", size = 3804087, upload-time = "2026-02-18T18:41:01.699Z"}, + {url = "https://files.pythonhosted.org/packages/91/e1/583c647404b15f807410510fec1eb9b80cb8474165940b7749f026f21cbc/numba-0.64.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e0b180b1133f2b5d8b3f09d96b6d7a9e51a7da5dda3c09e998b5bcfac85d222c", size = 3504309, upload-time = "2026-02-18T18:41:03.252Z"}, + {url = "https://files.pythonhosted.org/packages/85/23/0fce5789b8a5035e7ace21216a468143f3144e02013252116616c58339aa/numba-0.64.0-cp312-cp312-win_amd64.whl", hash = "sha256:e63dc94023b47894849b8b106db28ccb98b49d5498b98878fac1a38f83ac007a", size = 2752740, upload-time = "2026-02-18T18:41:05.097Z"}, + {url = "https://files.pythonhosted.org/packages/52/80/2734de90f9300a6e2503b35ee50d9599926b90cbb7ac54f9e40074cd07f1/numba-0.64.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:3bab2c872194dcd985f1153b70782ec0fbbe348fffef340264eacd3a76d59fd6", size = 2683392, upload-time = "2026-02-18T18:41:06.563Z"}, + {url = "https://files.pythonhosted.org/packages/42/e8/14b5853ebefd5b37723ef365c5318a30ce0702d39057eaa8d7d76392859d/numba-0.64.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:703a246c60832cad231d2e73c1182f25bf3cc8b699759ec8fe58a2dbc689a70c", size = 3812245, upload-time = "2026-02-18T18:41:07.963Z"}, + {url = "https://files.pythonhosted.org/packages/8a/a2/f60dc6c96d19b7185144265a5fbf01c14993d37ff4cd324b09d0212aa7ce/numba-0.64.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7e2e49a7900ee971d32af7609adc0cfe6aa7477c6f6cccdf6d8138538cf7756f", size = 3511328, upload-time = "2026-02-18T18:41:09.504Z"}, + {url = "https://files.pythonhosted.org/packages/9c/2a/fe7003ea7e7237ee7014f8eaeeb7b0d228a2db22572ca85bab2648cf52cb/numba-0.64.0-cp313-cp313-win_amd64.whl", hash = "sha256:396f43c3f77e78d7ec84cdfc6b04969c78f8f169351b3c4db814b97e7acf4245", size = 2752668, upload-time = "2026-02-18T18:41:11.455Z"} ] [[package]] @@ -407,27 +463,38 @@ sdist = {url = "https://files.pythonhosted.org/packages/57/fd/0005efbd0af48e55eb source = {registry = "https://pypi.org/simple"} version = "2.4.2" wheels = [ - {url = "https://files.pythonhosted.org/packages/18/88/b7df6050bf18fdcfb7046286c6535cabbdd2064a3440fca3f069d319c16e/numpy-2.4.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:444be170853f1f9d528428eceb55f12918e4fda5d8805480f36a002f1415e09b", size = 16663092, upload-time = "2026-01-31T23:12:04.521Z"}, - {url = "https://files.pythonhosted.org/packages/25/7a/1fee4329abc705a469a4afe6e69b1ef7e915117747886327104a8493a955/numpy-2.4.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:d1240d50adff70c2a88217698ca844723068533f3f5c5fa6ee2e3220e3bdb000", size = 14698770, upload-time = "2026-01-31T23:12:06.96Z"}, - {url = "https://files.pythonhosted.org/packages/fb/0b/f9e49ba6c923678ad5bc38181c08ac5e53b7a5754dbca8e581aa1a56b1ff/numpy-2.4.2-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:7cdde6de52fb6664b00b056341265441192d1291c130e99183ec0d4b110ff8b1", size = 5208562, upload-time = "2026-01-31T23:12:09.632Z"}, - {url = "https://files.pythonhosted.org/packages/7d/12/d7de8f6f53f9bb76997e5e4c069eda2051e3fe134e9181671c4391677bb2/numpy-2.4.2-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:cda077c2e5b780200b6b3e09d0b42205a3d1c68f30c6dceb90401c13bff8fe74", size = 6543710, upload-time = "2026-01-31T23:12:11.969Z"}, - {url = "https://files.pythonhosted.org/packages/09/63/c66418c2e0268a31a4cf8a8b512685748200f8e8e8ec6c507ce14e773529/numpy-2.4.2-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d30291931c915b2ab5717c2974bb95ee891a1cf22ebc16a8006bd59cd210d40a", size = 15677205, upload-time = "2026-01-31T23:12:14.33Z"}, - {url = "https://files.pythonhosted.org/packages/5d/6c/7f237821c9642fb2a04d2f1e88b4295677144ca93285fd76eff3bcba858d/numpy-2.4.2-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bba37bc29d4d85761deed3954a1bc62be7cf462b9510b51d367b769a8c8df325", size = 16611738, upload-time = "2026-01-31T23:12:16.525Z"}, - {url = "https://files.pythonhosted.org/packages/c2/a7/39c4cdda9f019b609b5c473899d87abff092fc908cfe4d1ecb2fcff453b0/numpy-2.4.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b2f0073ed0868db1dcd86e052d37279eef185b9c8db5bf61f30f46adac63c909", size = 17028888, upload-time = "2026-01-31T23:12:19.306Z"}, - {url = "https://files.pythonhosted.org/packages/da/b3/e84bb64bdfea967cc10950d71090ec2d84b49bc691df0025dddb7c26e8e3/numpy-2.4.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7f54844851cdb630ceb623dcec4db3240d1ac13d4990532446761baede94996a", size = 18339556, upload-time = "2026-01-31T23:12:21.816Z"}, - {url = "https://files.pythonhosted.org/packages/88/f5/954a291bc1192a27081706862ac62bb5920fbecfbaa302f64682aa90beed/numpy-2.4.2-cp314-cp314-win32.whl", hash = "sha256:12e26134a0331d8dbd9351620f037ec470b7c75929cb8a1537f6bfe411152a1a", size = 6006899, upload-time = "2026-01-31T23:12:24.14Z"}, - {url = "https://files.pythonhosted.org/packages/05/cb/eff72a91b2efdd1bc98b3b8759f6a1654aa87612fc86e3d87d6fe4f948c4/numpy-2.4.2-cp314-cp314-win_amd64.whl", hash = "sha256:068cdb2d0d644cdb45670810894f6a0600797a69c05f1ac478e8d31670b8ee75", size = 12443072, upload-time = "2026-01-31T23:12:26.33Z"}, - {url = "https://files.pythonhosted.org/packages/37/75/62726948db36a56428fce4ba80a115716dc4fad6a3a4352487f8bb950966/numpy-2.4.2-cp314-cp314-win_arm64.whl", hash = "sha256:6ed0be1ee58eef41231a5c943d7d1375f093142702d5723ca2eb07db9b934b05", size = 10494886, upload-time = "2026-01-31T23:12:28.488Z"}, - {url = "https://files.pythonhosted.org/packages/36/2f/ee93744f1e0661dc267e4b21940870cabfae187c092e1433b77b09b50ac4/numpy-2.4.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:98f16a80e917003a12c0580f97b5f875853ebc33e2eaa4bccfc8201ac6869308", size = 14818567, upload-time = "2026-01-31T23:12:30.709Z"}, - {url = "https://files.pythonhosted.org/packages/a7/24/6535212add7d76ff938d8bdc654f53f88d35cddedf807a599e180dcb8e66/numpy-2.4.2-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:20abd069b9cda45874498b245c8015b18ace6de8546bf50dfa8cea1696ed06ef", size = 5328372, upload-time = "2026-01-31T23:12:32.962Z"}, - {url = "https://files.pythonhosted.org/packages/5e/9d/c48f0a035725f925634bf6b8994253b43f2047f6778a54147d7e213bc5a7/numpy-2.4.2-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:e98c97502435b53741540a5717a6749ac2ada901056c7db951d33e11c885cc7d", size = 6649306, upload-time = "2026-01-31T23:12:34.797Z"}, - {url = "https://files.pythonhosted.org/packages/81/05/7c73a9574cd4a53a25907bad38b59ac83919c0ddc8234ec157f344d57d9a/numpy-2.4.2-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:da6cad4e82cb893db4b69105c604d805e0c3ce11501a55b5e9f9083b47d2ffe8", size = 15722394, upload-time = "2026-01-31T23:12:36.565Z"}, - {url = "https://files.pythonhosted.org/packages/35/fa/4de10089f21fc7d18442c4a767ab156b25c2a6eaf187c0db6d9ecdaeb43f/numpy-2.4.2-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e4424677ce4b47fe73c8b5556d876571f7c6945d264201180db2dc34f676ab5", size = 16653343, upload-time = "2026-01-31T23:12:39.188Z"}, - {url = "https://files.pythonhosted.org/packages/b8/f9/d33e4ffc857f3763a57aa85650f2e82486832d7492280ac21ba9efda80da/numpy-2.4.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:2b8f157c8a6f20eb657e240f8985cc135598b2b46985c5bccbde7616dc9c6b1e", size = 17078045, upload-time = "2026-01-31T23:12:42.041Z"}, - {url = "https://files.pythonhosted.org/packages/c8/b8/54bdb43b6225badbea6389fa038c4ef868c44f5890f95dd530a218706da3/numpy-2.4.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5daf6f3914a733336dab21a05cdec343144600e964d2fcdabaac0c0269874b2a", size = 18380024, upload-time = "2026-01-31T23:12:44.331Z"}, - {url = "https://files.pythonhosted.org/packages/a5/55/6e1a61ded7af8df04016d81b5b02daa59f2ea9252ee0397cb9f631efe9e5/numpy-2.4.2-cp314-cp314t-win32.whl", hash = "sha256:8c50dd1fc8826f5b26a5ee4d77ca55d88a895f4e4819c7ecc2a9f5905047a443", size = 6153937, upload-time = "2026-01-31T23:12:47.229Z"}, - {url = "https://files.pythonhosted.org/packages/45/aa/fa6118d1ed6d776b0983f3ceac9b1a5558e80df9365b1c3aa6d42bf9eee4/numpy-2.4.2-cp314-cp314t-win_amd64.whl", hash = "sha256:fcf92bee92742edd401ba41135185866f7026c502617f422eb432cfeca4fe236", size = 12631844, upload-time = "2026-01-31T23:12:48.997Z"}, - {url = "https://files.pythonhosted.org/packages/32/0a/2ec5deea6dcd158f254a7b372fb09cfba5719419c8d66343bab35237b3fb/numpy-2.4.2-cp314-cp314t-win_arm64.whl", hash = "sha256:1f92f53998a17265194018d1cc321b2e96e900ca52d54c7c77837b71b9465181", size = 10565379, upload-time = "2026-01-31T23:12:51.345Z"} + {url = "https://files.pythonhosted.org/packages/51/6e/6f394c9c77668153e14d4da83bcc247beb5952f6ead7699a1a2992613bea/numpy-2.4.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:21982668592194c609de53ba4933a7471880ccbaadcc52352694a59ecc860b3a", size = 16667963, upload-time = "2026-01-31T23:10:52.147Z"}, + {url = "https://files.pythonhosted.org/packages/1f/f8/55483431f2b2fd015ae6ed4fe62288823ce908437ed49db5a03d15151678/numpy-2.4.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40397bda92382fcec844066efb11f13e1c9a3e2a8e8f318fb72ed8b6db9f60f1", size = 14693571, upload-time = "2026-01-31T23:10:54.789Z"}, + {url = "https://files.pythonhosted.org/packages/2f/20/18026832b1845cdc82248208dd929ca14c9d8f2bac391f67440707fff27c/numpy-2.4.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:b3a24467af63c67829bfaa61eecf18d5432d4f11992688537be59ecd6ad32f5e", size = 5203469, upload-time = "2026-01-31T23:10:57.343Z"}, + {url = "https://files.pythonhosted.org/packages/7d/33/2eb97c8a77daaba34eaa3fa7241a14ac5f51c46a6bd5911361b644c4a1e2/numpy-2.4.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:805cc8de9fd6e7a22da5aed858e0ab16be5a4db6c873dde1d7451c541553aa27", size = 6550820, upload-time = "2026-01-31T23:10:59.429Z"}, + {url = "https://files.pythonhosted.org/packages/b1/91/b97fdfd12dc75b02c44e26c6638241cc004d4079a0321a69c62f51470c4c/numpy-2.4.2-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6d82351358ffbcdcd7b686b90742a9b86632d6c1c051016484fa0b326a0a1548", size = 15663067, upload-time = "2026-01-31T23:11:01.291Z"}, + {url = "https://files.pythonhosted.org/packages/f5/c6/a18e59f3f0b8071cc85cbc8d80cd02d68aa9710170b2553a117203d46936/numpy-2.4.2-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e35d3e0144137d9fdae62912e869136164534d64a169f86438bc9561b6ad49f", size = 16619782, upload-time = "2026-01-31T23:11:03.669Z"}, + {url = "https://files.pythonhosted.org/packages/b7/83/9751502164601a79e18847309f5ceec0b1446d7b6aa12305759b72cf98b2/numpy-2.4.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:adb6ed2ad29b9e15321d167d152ee909ec73395901b70936f029c3bc6d7f4460", size = 17013128, upload-time = "2026-01-31T23:11:05.913Z"}, + {url = "https://files.pythonhosted.org/packages/61/c4/c4066322256ec740acc1c8923a10047818691d2f8aec254798f3dd90f5f2/numpy-2.4.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8906e71fd8afcb76580404e2a950caef2685df3d2a57fe82a86ac8d33cc007ba", size = 18345324, upload-time = "2026-01-31T23:11:08.248Z"}, + {url = "https://files.pythonhosted.org/packages/ab/af/6157aa6da728fa4525a755bfad486ae7e3f76d4c1864138003eb84328497/numpy-2.4.2-cp312-cp312-win32.whl", hash = "sha256:ec055f6dae239a6299cace477b479cca2fc125c5675482daf1dd886933a1076f", size = 5960282, upload-time = "2026-01-31T23:11:10.497Z"}, + {url = "https://files.pythonhosted.org/packages/92/0f/7ceaaeaacb40567071e94dbf2c9480c0ae453d5bb4f52bea3892c39dc83c/numpy-2.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:209fae046e62d0ce6435fcfe3b1a10537e858249b3d9b05829e2a05218296a85", size = 12314210, upload-time = "2026-01-31T23:11:12.176Z"}, + {url = "https://files.pythonhosted.org/packages/2f/a3/56c5c604fae6dd40fa2ed3040d005fca97e91bd320d232ac9931d77ba13c/numpy-2.4.2-cp312-cp312-win_arm64.whl", hash = "sha256:fbde1b0c6e81d56f5dccd95dd4a711d9b95df1ae4009a60887e56b27e8d903fa", size = 10220171, upload-time = "2026-01-31T23:11:14.684Z"}, + {url = "https://files.pythonhosted.org/packages/a1/22/815b9fe25d1d7ae7d492152adbc7226d3eff731dffc38fe970589fcaaa38/numpy-2.4.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:25f2059807faea4b077a2b6837391b5d830864b3543627f381821c646f31a63c", size = 16663696, upload-time = "2026-01-31T23:11:17.516Z"}, + {url = "https://files.pythonhosted.org/packages/09/f0/817d03a03f93ba9c6c8993de509277d84e69f9453601915e4a69554102a1/numpy-2.4.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bd3a7a9f5847d2fb8c2c6d1c862fa109c31a9abeca1a3c2bd5a64572955b2979", size = 14688322, upload-time = "2026-01-31T23:11:19.883Z"}, + {url = "https://files.pythonhosted.org/packages/da/b4/f805ab79293c728b9a99438775ce51885fd4f31b76178767cfc718701a39/numpy-2.4.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:8e4549f8a3c6d13d55041925e912bfd834285ef1dd64d6bc7d542583355e2e98", size = 5198157, upload-time = "2026-01-31T23:11:22.375Z"}, + {url = "https://files.pythonhosted.org/packages/74/09/826e4289844eccdcd64aac27d13b0fd3f32039915dd5b9ba01baae1f436c/numpy-2.4.2-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:aea4f66ff44dfddf8c2cffd66ba6538c5ec67d389285292fe428cb2c738c8aef", size = 6546330, upload-time = "2026-01-31T23:11:23.958Z"}, + {url = "https://files.pythonhosted.org/packages/19/fb/cbfdbfa3057a10aea5422c558ac57538e6acc87ec1669e666d32ac198da7/numpy-2.4.2-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c3cd545784805de05aafe1dde61752ea49a359ccba9760c1e5d1c88a93bbf2b7", size = 15660968, upload-time = "2026-01-31T23:11:25.713Z"}, + {url = "https://files.pythonhosted.org/packages/04/dc/46066ce18d01645541f0186877377b9371b8fa8017fa8262002b4ef22612/numpy-2.4.2-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d0d9b7c93578baafcbc5f0b83eaf17b79d345c6f36917ba0c67f45226911d499", size = 16607311, upload-time = "2026-01-31T23:11:28.117Z"}, + {url = "https://files.pythonhosted.org/packages/14/d9/4b5adfc39a43fa6bf918c6d544bc60c05236cc2f6339847fc5b35e6cb5b0/numpy-2.4.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f74f0f7779cc7ae07d1810aab8ac6b1464c3eafb9e283a40da7309d5e6e48fbb", size = 17012850, upload-time = "2026-01-31T23:11:30.888Z"}, + {url = "https://files.pythonhosted.org/packages/b7/20/adb6e6adde6d0130046e6fdfb7675cc62bc2f6b7b02239a09eb58435753d/numpy-2.4.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c7ac672d699bf36275c035e16b65539931347d68b70667d28984c9fb34e07fa7", size = 18334210, upload-time = "2026-01-31T23:11:33.214Z"}, + {url = "https://files.pythonhosted.org/packages/78/0e/0a73b3dff26803a8c02baa76398015ea2a5434d9b8265a7898a6028c1591/numpy-2.4.2-cp313-cp313-win32.whl", hash = "sha256:8e9afaeb0beff068b4d9cd20d322ba0ee1cecfb0b08db145e4ab4dd44a6b5110", size = 5958199, upload-time = "2026-01-31T23:11:35.385Z"}, + {url = "https://files.pythonhosted.org/packages/43/bc/6352f343522fcb2c04dbaf94cb30cca6fd32c1a750c06ad6231b4293708c/numpy-2.4.2-cp313-cp313-win_amd64.whl", hash = "sha256:7df2de1e4fba69a51c06c28f5a3de36731eb9639feb8e1cf7e4a7b0daf4cf622", size = 12310848, upload-time = "2026-01-31T23:11:38.001Z"}, + {url = "https://files.pythonhosted.org/packages/6e/8d/6da186483e308da5da1cc6918ce913dcfe14ffde98e710bfeff2a6158d4e/numpy-2.4.2-cp313-cp313-win_arm64.whl", hash = "sha256:0fece1d1f0a89c16b03442eae5c56dc0be0c7883b5d388e0c03f53019a4bfd71", size = 10221082, upload-time = "2026-01-31T23:11:40.392Z"}, + {url = "https://files.pythonhosted.org/packages/25/a1/9510aa43555b44781968935c7548a8926274f815de42ad3997e9e83680dd/numpy-2.4.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5633c0da313330fd20c484c78cdd3f9b175b55e1a766c4a174230c6b70ad8262", size = 14815866, upload-time = "2026-01-31T23:11:42.495Z"}, + {url = "https://files.pythonhosted.org/packages/36/30/6bbb5e76631a5ae46e7923dd16ca9d3f1c93cfa8d4ed79a129814a9d8db3/numpy-2.4.2-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:d9f64d786b3b1dd742c946c42d15b07497ed14af1a1f3ce840cce27daa0ce913", size = 5325631, upload-time = "2026-01-31T23:11:44.7Z"}, + {url = "https://files.pythonhosted.org/packages/46/00/3a490938800c1923b567b3a15cd17896e68052e2145d8662aaf3e1ffc58f/numpy-2.4.2-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:b21041e8cb6a1eb5312dd1d2f80a94d91efffb7a06b70597d44f1bd2dfc315ab", size = 6646254, upload-time = "2026-01-31T23:11:46.341Z"}, + {url = "https://files.pythonhosted.org/packages/d3/e9/fac0890149898a9b609caa5af7455a948b544746e4b8fe7c212c8edd71f8/numpy-2.4.2-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:00ab83c56211a1d7c07c25e3217ea6695e50a3e2f255053686b081dc0b091a82", size = 15720138, upload-time = "2026-01-31T23:11:48.082Z"}, + {url = "https://files.pythonhosted.org/packages/ea/5c/08887c54e68e1e28df53709f1893ce92932cc6f01f7c3d4dc952f61ffd4e/numpy-2.4.2-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2fb882da679409066b4603579619341c6d6898fc83a8995199d5249f986e8e8f", size = 16655398, upload-time = "2026-01-31T23:11:50.293Z"}, + {url = "https://files.pythonhosted.org/packages/4d/89/253db0fa0e66e9129c745e4ef25631dc37d5f1314dad2b53e907b8538e6d/numpy-2.4.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:66cb9422236317f9d44b67b4d18f44efe6e9c7f8794ac0462978513359461554", size = 17079064, upload-time = "2026-01-31T23:11:52.927Z"}, + {url = "https://files.pythonhosted.org/packages/2a/d5/cbade46ce97c59c6c3da525e8d95b7abe8a42974a1dc5c1d489c10433e88/numpy-2.4.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:0f01dcf33e73d80bd8dc0f20a71303abbafa26a19e23f6b68d1aa9990af90257", size = 18379680, upload-time = "2026-01-31T23:11:55.22Z"}, + {url = "https://files.pythonhosted.org/packages/40/62/48f99ae172a4b63d981babe683685030e8a3df4f246c893ea5c6ef99f018/numpy-2.4.2-cp313-cp313t-win32.whl", hash = "sha256:52b913ec40ff7ae845687b0b34d8d93b60cb66dcee06996dd5c99f2fc9328657", size = 6082433, upload-time = "2026-01-31T23:11:58.096Z"}, + {url = "https://files.pythonhosted.org/packages/07/38/e054a61cfe48ad9f1ed0d188e78b7e26859d0b60ef21cd9de4897cdb5326/numpy-2.4.2-cp313-cp313t-win_amd64.whl", hash = "sha256:5eea80d908b2c1f91486eb95b3fb6fab187e569ec9752ab7d9333d2e66bf2d6b", size = 12451181, upload-time = "2026-01-31T23:11:59.782Z"}, + {url = "https://files.pythonhosted.org/packages/6e/a4/a05c3a6418575e185dd84d0b9680b6bb2e2dc3e4202f036b7b4e22d6e9dc/numpy-2.4.2-cp313-cp313t-win_arm64.whl", hash = "sha256:fd49860271d52127d61197bb50b64f58454e9f578cb4b2c001a6de8b1f50b0b1", size = 10290756, upload-time = "2026-01-31T23:12:02.438Z"} ] [[package]] @@ -436,6 +503,7 @@ dependencies = [ {name = "myst-parser"}, {name = "numba"}, {name = "numpy"}, + {name = "optv"}, {name = "pre-commit"}, {name = "pydata-sphinx-theme"}, {name = "pytest"}, @@ -455,6 +523,7 @@ requires-dist = [ {name = "myst-parser", specifier = ">=5.0.0"}, {name = "numba", specifier = ">=0.64.0"}, {name = "numpy", specifier = ">=2.4.2"}, + {name = "optv", specifier = ">=0.3.2"}, {name = "pre-commit", specifier = ">=4.5.1"}, {name = "pydata-sphinx-theme", specifier = ">=0.16.1"}, {name = "pytest", specifier = ">=9.0.2"}, @@ -466,6 +535,27 @@ requires-dist = [ {name = "types-pyyaml", specifier = ">=6.0.12.20250915"} ] +[[package]] +dependencies = [ + {name = "numpy"}, + {name = "pyyaml"} +] +name = "optv" +source = {registry = "https://pypi.org/simple"} +version = "0.3.2" +wheels = [ + {url = "https://files.pythonhosted.org/packages/93/29/ac2ab83e885a53ae2d4b6537b26c9029ce663618a56eb8e9ab10a0c53330/optv-0.3.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:9cf47f3756a95ce4aad6895ccd7a8ca9fdc51d513900fb68761a16450289f84e", size = 2347739, upload-time = "2026-03-01T23:03:35.73Z"}, + {url = "https://files.pythonhosted.org/packages/07/0a/cca02bf1a478450f1ba2a3e44db4a917b56e15c6eb6274d195ed217c0950/optv-0.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4404e00d0bf0d21dfeaa1595ac453fe66ffed26d889e70f6727630280895916b", size = 2262784, upload-time = "2026-03-01T23:03:39.472Z"}, + {url = "https://files.pythonhosted.org/packages/90/f6/47953d940fc82f78a629e59afe0251f936c49452065715349faf2803cb14/optv-0.3.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9df069358f9283560ba707e23c87f2071ad2c1815c201f3891ed95e3216eb457", size = 6179852, upload-time = "2026-03-01T23:03:47.464Z"}, + {url = "https://files.pythonhosted.org/packages/28/3b/1c38b9b6b9d19e432cd1dbba8c2bd658fd0b852692cd1c9574a67b3bc58d/optv-0.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:555c6a06ab28e1ae93d9c3d21ec89a9a2d6bb573ebf09efd1606553ccf13eeb1", size = 12215450, upload-time = "2026-03-01T23:03:59.265Z"}, + {url = "https://files.pythonhosted.org/packages/c0/6b/6afeed9e26263fd3a6f1cded81efc031c43985aee42bea7686dbc554f7cc/optv-0.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:e36e3093abdee39a35680e4a2304e5fdcddbb392096741326b7632a13c65b991", size = 1731896, upload-time = "2026-03-01T23:04:03.206Z"}, + {url = "https://files.pythonhosted.org/packages/c6/40/9b8e9edda07f9f5f9d2469cf26edc8ee674a02069145f5d21b8a9b789ad0/optv-0.3.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4c05949ea6c15dcc8fcdb36ca4e935aff273ac81d80405930e7a2b64ff2a710e", size = 2342514, upload-time = "2026-03-01T23:04:06.56Z"}, + {url = "https://files.pythonhosted.org/packages/b4/76/30921ef229074276aebfdd18f0603d7a14461c3cf74a9964678bbf82d80a/optv-0.3.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a549e36df4d3f1c3f30b7a667e6595f52da50bcb3727f57de045a652ab9a1194", size = 2257415, upload-time = "2026-03-01T23:04:10.218Z"}, + {url = "https://files.pythonhosted.org/packages/ae/e1/bbeb1dd50759ec6c88d074570a1790741fb373fde4c66915980e192eb51c/optv-0.3.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3fdc240569f2804c6d74951a27e0b7f168e85de2a9e4ab5b09a290efc8d70417", size = 6070665, upload-time = "2026-03-01T23:04:17.879Z"}, + {url = "https://files.pythonhosted.org/packages/f3/3a/76b420f2c7937fb4b5231a05dacc45eb66c46d88515b56b96701e89b2708/optv-0.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:80529429fed3f984cd400e77fcef986b7409322b0b14fdd6b8aad47fde69cef4", size = 5968060, upload-time = "2026-03-01T23:04:25.215Z"}, + {url = "https://files.pythonhosted.org/packages/31/d3/475e6f830eca6f886f5d0d74e542d059978d6dabfc16ddd61f004ea727d8/optv-0.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:c863518e8c91e018887f24d0cf99ca1720f8f2155fe92b9b46fa794e97e43259", size = 1728112, upload-time = "2026-03-01T23:04:28.062Z"} +] + [[package]] name = "packaging" sdist = {url = "https://files.pythonhosted.org/packages/65/ee/299d360cdc32edc7d2cf530f3accf79c4fca01e96ffc950d8a52213bd8e4/packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4", size = 143416, upload-time = "2026-01-21T20:50:39.064Z"} @@ -581,11 +671,11 @@ dependencies = [ {name = "platformdirs"} ] name = "python-discovery" -sdist = {url = "https://files.pythonhosted.org/packages/82/bb/93a3e83bdf9322c7e21cafd092e56a4a17c4d8ef4277b6eb01af1a540a6f/python_discovery-1.1.0.tar.gz", hash = "sha256:447941ba1aed8cc2ab7ee3cb91be5fc137c5bdbb05b7e6ea62fbdcb66e50b268", size = 55674, upload-time = "2026-02-26T09:42:49.668Z"} +sdist = {url = "https://files.pythonhosted.org/packages/ec/67/09765eacf4e44413c4f8943ba5a317fcb9c7b447c3b8b0b7fce7e3090b0b/python_discovery-1.1.1.tar.gz", hash = "sha256:584c08b141c5b7029f206b4e8b78b1a1764b22121e21519b89dec56936e95b0a", size = 56016, upload-time = "2026-03-07T00:00:56.354Z"} source = {registry = "https://pypi.org/simple"} -version = "1.1.0" +version = "1.1.1" wheels = [ - {url = "https://files.pythonhosted.org/packages/06/54/82a6e2ef37f0f23dccac604b9585bdcbd0698604feb64807dcb72853693e/python_discovery-1.1.0-py3-none-any.whl", hash = "sha256:a162893b8809727f54594a99ad2179d2ede4bf953e12d4c7abc3cc9cdbd1437b", size = 30687, upload-time = "2026-02-26T09:42:48.548Z"} + {url = "https://files.pythonhosted.org/packages/75/0f/2bf7e3b5a4a65f623cb820feb5793e243fad58ae561015ee15a6152f67a2/python_discovery-1.1.1-py3-none-any.whl", hash = "sha256:69f11073fa2392251e405d4e847d60ffffd25fd762a0dc4d1a7d6b9c3f79f1a3", size = 30732, upload-time = "2026-03-07T00:00:55.143Z"} ] [[package]] @@ -594,24 +684,26 @@ sdist = {url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd772 source = {registry = "https://pypi.org/simple"} version = "6.0.3" wheels = [ - {url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z"}, - {url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z"}, - {url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z"}, - {url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z"}, - {url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z"}, - {url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z"}, - {url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z"}, - {url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z"}, - {url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z"}, - {url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z"}, - {url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z"}, - {url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z"}, - {url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z"}, - {url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z"}, - {url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z"}, - {url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z"}, - {url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z"}, - {url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z"} + {url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z"}, + {url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z"}, + {url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z"}, + {url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z"}, + {url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z"}, + {url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z"}, + {url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z"}, + {url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z"}, + {url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z"}, + {url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z"}, + {url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z"}, + {url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z"}, + {url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z"}, + {url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z"}, + {url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z"}, + {url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z"}, + {url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z"}, + {url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z"}, + {url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z"}, + {url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z"} ] [[package]] @@ -647,26 +739,36 @@ sdist = {url = "https://files.pythonhosted.org/packages/7a/97/5a3609c4f8d58b0391 source = {registry = "https://pypi.org/simple"} version = "1.17.1" wheels = [ - {url = "https://files.pythonhosted.org/packages/cf/83/333afb452af6f0fd70414dc04f898647ee1423979ce02efa75c3b0f2c28e/scipy-1.17.1-cp314-cp314-macosx_10_14_x86_64.whl", hash = "sha256:a48a72c77a310327f6a3a920092fa2b8fd03d7deaa60f093038f22d98e096717", size = 31584510, upload-time = "2026-02-23T00:21:01.015Z"}, - {url = "https://files.pythonhosted.org/packages/ed/a6/d05a85fd51daeb2e4ea71d102f15b34fedca8e931af02594193ae4fd25f7/scipy-1.17.1-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:45abad819184f07240d8a696117a7aacd39787af9e0b719d00285549ed19a1e9", size = 28170131, upload-time = "2026-02-23T00:21:05.888Z"}, - {url = "https://files.pythonhosted.org/packages/db/7b/8624a203326675d7746a254083a187398090a179335b2e4a20e2ddc46e83/scipy-1.17.1-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:3fd1fcdab3ea951b610dc4cef356d416d5802991e7e32b5254828d342f7b7e0b", size = 20342032, upload-time = "2026-02-23T00:21:09.904Z"}, - {url = "https://files.pythonhosted.org/packages/c9/35/2c342897c00775d688d8ff3987aced3426858fd89d5a0e26e020b660b301/scipy-1.17.1-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:7bdf2da170b67fdf10bca777614b1c7d96ae3ca5794fd9587dce41eb2966e866", size = 22678766, upload-time = "2026-02-23T00:21:14.313Z"}, - {url = "https://files.pythonhosted.org/packages/ef/f2/7cdb8eb308a1a6ae1e19f945913c82c23c0c442a462a46480ce487fdc0ac/scipy-1.17.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:adb2642e060a6549c343603a3851ba76ef0b74cc8c079a9a58121c7ec9fe2350", size = 32957007, upload-time = "2026-02-23T00:21:19.663Z"}, - {url = "https://files.pythonhosted.org/packages/0b/2e/7eea398450457ecb54e18e9d10110993fa65561c4f3add5e8eccd2b9cd41/scipy-1.17.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:eee2cfda04c00a857206a4330f0c5e3e56535494e30ca445eb19ec624ae75118", size = 35221333, upload-time = "2026-02-23T00:21:25.278Z"}, - {url = "https://files.pythonhosted.org/packages/d9/77/5b8509d03b77f093a0d52e606d3c4f79e8b06d1d38c441dacb1e26cacf46/scipy-1.17.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d2650c1fb97e184d12d8ba010493ee7b322864f7d3d00d3f9bb97d9c21de4068", size = 35042066, upload-time = "2026-02-23T00:21:31.358Z"}, - {url = "https://files.pythonhosted.org/packages/f9/df/18f80fb99df40b4070328d5ae5c596f2f00fffb50167e31439e932f29e7d/scipy-1.17.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:08b900519463543aa604a06bec02461558a6e1cef8fdbb8098f77a48a83c8118", size = 37612763, upload-time = "2026-02-23T00:21:37.247Z"}, - {url = "https://files.pythonhosted.org/packages/4b/39/f0e8ea762a764a9dc52aa7dabcfad51a354819de1f0d4652b6a1122424d6/scipy-1.17.1-cp314-cp314-win_amd64.whl", hash = "sha256:3877ac408e14da24a6196de0ddcace62092bfc12a83823e92e49e40747e52c19", size = 37290984, upload-time = "2026-02-23T00:22:35.023Z"}, - {url = "https://files.pythonhosted.org/packages/7c/56/fe201e3b0f93d1a8bcf75d3379affd228a63d7e2d80ab45467a74b494947/scipy-1.17.1-cp314-cp314-win_arm64.whl", hash = "sha256:f8885db0bc2bffa59d5c1b72fad7a6a92d3e80e7257f967dd81abb553a90d293", size = 25192877, upload-time = "2026-02-23T00:22:39.798Z"}, - {url = "https://files.pythonhosted.org/packages/96/ad/f8c414e121f82e02d76f310f16db9899c4fcde36710329502a6b2a3c0392/scipy-1.17.1-cp314-cp314t-macosx_10_14_x86_64.whl", hash = "sha256:1cc682cea2ae55524432f3cdff9e9a3be743d52a7443d0cba9017c23c87ae2f6", size = 31949750, upload-time = "2026-02-23T00:21:42.289Z"}, - {url = "https://files.pythonhosted.org/packages/7c/b0/c741e8865d61b67c81e255f4f0a832846c064e426636cd7de84e74d209be/scipy-1.17.1-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:2040ad4d1795a0ae89bfc7e8429677f365d45aa9fd5e4587cf1ea737f927b4a1", size = 28585858, upload-time = "2026-02-23T00:21:47.706Z"}, - {url = "https://files.pythonhosted.org/packages/ed/1b/3985219c6177866628fa7c2595bfd23f193ceebbe472c98a08824b9466ff/scipy-1.17.1-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:131f5aaea57602008f9822e2115029b55d4b5f7c070287699fe45c661d051e39", size = 20757723, upload-time = "2026-02-23T00:21:52.039Z"}, - {url = "https://files.pythonhosted.org/packages/c0/19/2a04aa25050d656d6f7b9e7b685cc83d6957fb101665bfd9369ca6534563/scipy-1.17.1-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:9cdc1a2fcfd5c52cfb3045feb399f7b3ce822abdde3a193a6b9a60b3cb5854ca", size = 23043098, upload-time = "2026-02-23T00:21:56.185Z"}, - {url = "https://files.pythonhosted.org/packages/86/f1/3383beb9b5d0dbddd030335bf8a8b32d4317185efe495374f134d8be6cce/scipy-1.17.1-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e3dcd57ab780c741fde8dc68619de988b966db759a3c3152e8e9142c26295ad", size = 33030397, upload-time = "2026-02-23T00:22:01.404Z"}, - {url = "https://files.pythonhosted.org/packages/41/68/8f21e8a65a5a03f25a79165ec9d2b28c00e66dc80546cf5eb803aeeff35b/scipy-1.17.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a9956e4d4f4a301ebf6cde39850333a6b6110799d470dbbb1e25326ac447f52a", size = 35281163, upload-time = "2026-02-23T00:22:07.024Z"}, - {url = "https://files.pythonhosted.org/packages/84/8d/c8a5e19479554007a5632ed7529e665c315ae7492b4f946b0deb39870e39/scipy-1.17.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:a4328d245944d09fd639771de275701ccadf5f781ba0ff092ad141e017eccda4", size = 35116291, upload-time = "2026-02-23T00:22:12.585Z"}, - {url = "https://files.pythonhosted.org/packages/52/52/e57eceff0e342a1f50e274264ed47497b59e6a4e3118808ee58ddda7b74a/scipy-1.17.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a77cbd07b940d326d39a1d1b37817e2ee4d79cb30e7338f3d0cddffae70fcaa2", size = 37682317, upload-time = "2026-02-23T00:22:18.513Z"}, - {url = "https://files.pythonhosted.org/packages/11/2f/b29eafe4a3fbc3d6de9662b36e028d5f039e72d345e05c250e121a230dd4/scipy-1.17.1-cp314-cp314t-win_amd64.whl", hash = "sha256:eb092099205ef62cd1782b006658db09e2fed75bffcae7cc0d44052d8aa0f484", size = 37345327, upload-time = "2026-02-23T00:22:24.442Z"}, - {url = "https://files.pythonhosted.org/packages/07/39/338d9219c4e87f3e708f18857ecd24d22a0c3094752393319553096b98af/scipy-1.17.1-cp314-cp314t-win_arm64.whl", hash = "sha256:200e1050faffacc162be6a486a984a0497866ec54149a01270adc8a59b7c7d21", size = 25489165, upload-time = "2026-02-23T00:22:29.563Z"} + {url = "https://files.pythonhosted.org/packages/35/48/b992b488d6f299dbe3f11a20b24d3dda3d46f1a635ede1c46b5b17a7b163/scipy-1.17.1-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:35c3a56d2ef83efc372eaec584314bd0ef2e2f0d2adb21c55e6ad5b344c0dcb8", size = 31610954, upload-time = "2026-02-23T00:17:49.855Z"}, + {url = "https://files.pythonhosted.org/packages/b2/02/cf107b01494c19dc100f1d0b7ac3cc08666e96ba2d64db7626066cee895e/scipy-1.17.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:fcb310ddb270a06114bb64bbe53c94926b943f5b7f0842194d585c65eb4edd76", size = 28172662, upload-time = "2026-02-23T00:18:01.64Z"}, + {url = "https://files.pythonhosted.org/packages/cf/a9/599c28631bad314d219cf9ffd40e985b24d603fc8a2f4ccc5ae8419a535b/scipy-1.17.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:cc90d2e9c7e5c7f1a482c9875007c095c3194b1cfedca3c2f3291cdc2bc7c086", size = 20344366, upload-time = "2026-02-23T00:18:12.015Z"}, + {url = "https://files.pythonhosted.org/packages/35/f5/906eda513271c8deb5af284e5ef0206d17a96239af79f9fa0aebfe0e36b4/scipy-1.17.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:c80be5ede8f3f8eded4eff73cc99a25c388ce98e555b17d31da05287015ffa5b", size = 22704017, upload-time = "2026-02-23T00:18:21.502Z"}, + {url = "https://files.pythonhosted.org/packages/da/34/16f10e3042d2f1d6b66e0428308ab52224b6a23049cb2f5c1756f713815f/scipy-1.17.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e19ebea31758fac5893a2ac360fedd00116cbb7628e650842a6691ba7ca28a21", size = 32927842, upload-time = "2026-02-23T00:18:35.367Z"}, + {url = "https://files.pythonhosted.org/packages/01/8e/1e35281b8ab6d5d72ebe9911edcdffa3f36b04ed9d51dec6dd140396e220/scipy-1.17.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:02ae3b274fde71c5e92ac4d54bc06c42d80e399fec704383dcd99b301df37458", size = 35235890, upload-time = "2026-02-23T00:18:49.188Z"}, + {url = "https://files.pythonhosted.org/packages/c5/5c/9d7f4c88bea6e0d5a4f1bc0506a53a00e9fcb198de372bfe4d3652cef482/scipy-1.17.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8a604bae87c6195d8b1045eddece0514d041604b14f2727bbc2b3020172045eb", size = 35003557, upload-time = "2026-02-23T00:18:54.74Z"}, + {url = "https://files.pythonhosted.org/packages/65/94/7698add8f276dbab7a9de9fb6b0e02fc13ee61d51c7c3f85ac28b65e1239/scipy-1.17.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f590cd684941912d10becc07325a3eeb77886fe981415660d9265c4c418d0bea", size = 37625856, upload-time = "2026-02-23T00:19:00.307Z"}, + {url = "https://files.pythonhosted.org/packages/a2/84/dc08d77fbf3d87d3ee27f6a0c6dcce1de5829a64f2eae85a0ecc1f0daa73/scipy-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:41b71f4a3a4cab9d366cd9065b288efc4d4f3c0b37a91a8e0947fb5bd7f31d87", size = 36549682, upload-time = "2026-02-23T00:19:07.67Z"}, + {url = "https://files.pythonhosted.org/packages/bc/98/fe9ae9ffb3b54b62559f52dedaebe204b408db8109a8c66fdd04869e6424/scipy-1.17.1-cp312-cp312-win_arm64.whl", hash = "sha256:f4115102802df98b2b0db3cce5cb9b92572633a1197c77b7553e5203f284a5b3", size = 24547340, upload-time = "2026-02-23T00:19:12.024Z"}, + {url = "https://files.pythonhosted.org/packages/76/27/07ee1b57b65e92645f219b37148a7e7928b82e2b5dbeccecb4dff7c64f0b/scipy-1.17.1-cp313-cp313-macosx_10_14_x86_64.whl", hash = "sha256:5e3c5c011904115f88a39308379c17f91546f77c1667cea98739fe0fccea804c", size = 31590199, upload-time = "2026-02-23T00:19:17.192Z"}, + {url = "https://files.pythonhosted.org/packages/ec/ae/db19f8ab842e9b724bf5dbb7db29302a91f1e55bc4d04b1025d6d605a2c5/scipy-1.17.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:6fac755ca3d2c3edcb22f479fceaa241704111414831ddd3bc6056e18516892f", size = 28154001, upload-time = "2026-02-23T00:19:22.241Z"}, + {url = "https://files.pythonhosted.org/packages/5b/58/3ce96251560107b381cbd6e8413c483bbb1228a6b919fa8652b0d4090e7f/scipy-1.17.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:7ff200bf9d24f2e4d5dc6ee8c3ac64d739d3a89e2326ba68aaf6c4a2b838fd7d", size = 20325719, upload-time = "2026-02-23T00:19:26.329Z"}, + {url = "https://files.pythonhosted.org/packages/b2/83/15087d945e0e4d48ce2377498abf5ad171ae013232ae31d06f336e64c999/scipy-1.17.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:4b400bdc6f79fa02a4d86640310dde87a21fba0c979efff5248908c6f15fad1b", size = 22683595, upload-time = "2026-02-23T00:19:30.304Z"}, + {url = "https://files.pythonhosted.org/packages/b4/e0/e58fbde4a1a594c8be8114eb4aac1a55bcd6587047efc18a61eb1f5c0d30/scipy-1.17.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2b64ca7d4aee0102a97f3ba22124052b4bd2152522355073580bf4845e2550b6", size = 32896429, upload-time = "2026-02-23T00:19:35.536Z"}, + {url = "https://files.pythonhosted.org/packages/f5/5f/f17563f28ff03c7b6799c50d01d5d856a1d55f2676f537ca8d28c7f627cd/scipy-1.17.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:581b2264fc0aa555f3f435a5944da7504ea3a065d7029ad60e7c3d1ae09c5464", size = 35203952, upload-time = "2026-02-23T00:19:42.259Z"}, + {url = "https://files.pythonhosted.org/packages/8d/a5/9afd17de24f657fdfe4df9a3f1ea049b39aef7c06000c13db1530d81ccca/scipy-1.17.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:beeda3d4ae615106d7094f7e7cef6218392e4465cc95d25f900bebabfded0950", size = 34979063, upload-time = "2026-02-23T00:19:47.547Z"}, + {url = "https://files.pythonhosted.org/packages/8b/13/88b1d2384b424bf7c924f2038c1c409f8d88bb2a8d49d097861dd64a57b2/scipy-1.17.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6609bc224e9568f65064cfa72edc0f24ee6655b47575954ec6339534b2798369", size = 37598449, upload-time = "2026-02-23T00:19:53.238Z"}, + {url = "https://files.pythonhosted.org/packages/35/e5/d6d0e51fc888f692a35134336866341c08655d92614f492c6860dc45bb2c/scipy-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:37425bc9175607b0268f493d79a292c39f9d001a357bebb6b88fdfaff13f6448", size = 36510943, upload-time = "2026-02-23T00:20:50.89Z"}, + {url = "https://files.pythonhosted.org/packages/2a/fd/3be73c564e2a01e690e19cc618811540ba5354c67c8680dce3281123fb79/scipy-1.17.1-cp313-cp313-win_arm64.whl", hash = "sha256:5cf36e801231b6a2059bf354720274b7558746f3b1a4efb43fcf557ccd484a87", size = 24545621, upload-time = "2026-02-23T00:20:55.871Z"}, + {url = "https://files.pythonhosted.org/packages/6f/6b/17787db8b8114933a66f9dcc479a8272e4b4da75fe03b0c282f7b0ade8cd/scipy-1.17.1-cp313-cp313t-macosx_10_14_x86_64.whl", hash = "sha256:d59c30000a16d8edc7e64152e30220bfbd724c9bbb08368c054e24c651314f0a", size = 31936708, upload-time = "2026-02-23T00:19:58.694Z"}, + {url = "https://files.pythonhosted.org/packages/38/2e/524405c2b6392765ab1e2b722a41d5da33dc5c7b7278184a8ad29b6cb206/scipy-1.17.1-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:010f4333c96c9bb1a4516269e33cb5917b08ef2166d5556ca2fd9f082a9e6ea0", size = 28570135, upload-time = "2026-02-23T00:20:03.934Z"}, + {url = "https://files.pythonhosted.org/packages/fd/c3/5bd7199f4ea8556c0c8e39f04ccb014ac37d1468e6cfa6a95c6b3562b76e/scipy-1.17.1-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:2ceb2d3e01c5f1d83c4189737a42d9cb2fc38a6eeed225e7515eef71ad301dce", size = 20741977, upload-time = "2026-02-23T00:20:07.935Z"}, + {url = "https://files.pythonhosted.org/packages/d9/b8/8ccd9b766ad14c78386599708eb745f6b44f08400a5fd0ade7cf89b6fc93/scipy-1.17.1-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:844e165636711ef41f80b4103ed234181646b98a53c8f05da12ca5ca289134f6", size = 23029601, upload-time = "2026-02-23T00:20:12.161Z"}, + {url = "https://files.pythonhosted.org/packages/6d/a0/3cb6f4d2fb3e17428ad2880333cac878909ad1a89f678527b5328b93c1d4/scipy-1.17.1-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:158dd96d2207e21c966063e1635b1063cd7787b627b6f07305315dd73d9c679e", size = 33019667, upload-time = "2026-02-23T00:20:17.208Z"}, + {url = "https://files.pythonhosted.org/packages/f3/c3/2d834a5ac7bf3a0c806ad1508efc02dda3c8c61472a56132d7894c312dea/scipy-1.17.1-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:74cbb80d93260fe2ffa334efa24cb8f2f0f622a9b9febf8b483c0b865bfb3475", size = 35264159, upload-time = "2026-02-23T00:20:23.087Z"}, + {url = "https://files.pythonhosted.org/packages/4d/77/d3ed4becfdbd217c52062fafe35a72388d1bd82c2d0ba5ca19d6fcc93e11/scipy-1.17.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:dbc12c9f3d185f5c737d801da555fb74b3dcfa1a50b66a1a93e09190f41fab50", size = 35102771, upload-time = "2026-02-23T00:20:28.636Z"}, + {url = "https://files.pythonhosted.org/packages/bd/12/d19da97efde68ca1ee5538bb261d5d2c062f0c055575128f11a2730e3ac1/scipy-1.17.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:94055a11dfebe37c656e70317e1996dc197e1a15bbcc351bcdd4610e128fe1ca", size = 37665910, upload-time = "2026-02-23T00:20:34.743Z"}, + {url = "https://files.pythonhosted.org/packages/06/1c/1172a88d507a4baaf72c5a09bb6c018fe2ae0ab622e5830b703a46cc9e44/scipy-1.17.1-cp313-cp313t-win_amd64.whl", hash = "sha256:e30bdeaa5deed6bc27b4cc490823cd0347d7dae09119b8803ae576ea0ce52e4c", size = 36562980, upload-time = "2026-02-23T00:20:40.575Z"}, + {url = "https://files.pythonhosted.org/packages/70/b0/eb757336e5a76dfa7911f63252e3b7d1de00935d7705cf772db5b45ec238/scipy-1.17.1-cp313-cp313t-win_arm64.whl", hash = "sha256:a720477885a9d2411f94a93d16f9d89bad0f28ca23c3f8daa521e2dcc3f44d49", size = 24856543, upload-time = "2026-02-23T00:20:45.313Z"} ] [[package]] From d2ee77ef1bf76a2958655a817764b8f87bdddc71 Mon Sep 17 00:00:00 2001 From: Alex Liberzon Date: Sat, 7 Mar 2026 20:00:47 +0200 Subject: [PATCH 15/27] added also tracking --- openptv_python/_native_compat.py | 2 +- openptv_python/_native_convert.py | 4 +- openptv_python/tracking_frame_buf.py | 18 + plans.md | 2 +- tests/test_native_stress_performance.py | 441 +++++++++++++++++++++++- tests/test_read_frame.py | 27 ++ 6 files changed, 481 insertions(+), 13 deletions(-) diff --git a/openptv_python/_native_compat.py b/openptv_python/_native_compat.py index fbb01c6..cea39e1 100644 --- a/openptv_python/_native_compat.py +++ b/openptv_python/_native_compat.py @@ -66,4 +66,4 @@ def native_preprocess_image(*args: Any, **kwargs: Any) -> Any: def native_target_recognition(*args: Any, **kwargs: Any) -> Any: if not HAS_NATIVE_SEGMENTATION or optv_segmentation is None: raise RuntimeError("optv native target_recognition is not available") - return optv_segmentation.target_recognition(*args, **kwargs) \ No newline at end of file + return optv_segmentation.target_recognition(*args, **kwargs) diff --git a/openptv_python/_native_convert.py b/openptv_python/_native_convert.py index 001e7ef..949581f 100644 --- a/openptv_python/_native_convert.py +++ b/openptv_python/_native_convert.py @@ -2,8 +2,8 @@ from __future__ import annotations -from typing import Iterable, List from types import ModuleType +from typing import Iterable, List import numpy as np @@ -132,4 +132,4 @@ def from_native_target(native_target) -> Target: def from_native_target_array(native_targets: Iterable[object]) -> List[Target]: - return [from_native_target(target) for target in native_targets] \ No newline at end of file + return [from_native_target(target) for target in native_targets] diff --git a/openptv_python/tracking_frame_buf.py b/openptv_python/tracking_frame_buf.py index 8ea507c..3539887 100644 --- a/openptv_python/tracking_frame_buf.py +++ b/openptv_python/tracking_frame_buf.py @@ -341,6 +341,24 @@ def read( frame_num: int, ) -> bool: """Read a frame from the disk.""" + required_files = [Path(f"{corres_file_base}.{frame_num}")] + + if linkage_file_base != "": + required_files.append(Path(f"{linkage_file_base}.{frame_num}")) + + if prio_file_base != "": + required_files.append(Path(f"{prio_file_base}.{frame_num}")) + + for file_base in target_file_base: + if frame_num > 0: + required_files.append(Path(file_base % frame_num + "_targets")) + else: + required_files.append(Path(f"{file_base}_targets")) + + for path in required_files: + if not path.exists(): + return False + cor_buf, path_buf = read_path_frame( # self.correspond, self.path_info = read_path_frame( corres_file_base, diff --git a/plans.md b/plans.md index c8885ab..a7521ae 100644 --- a/plans.md +++ b/plans.md @@ -1,2 +1,2 @@ openptv-python-2026-03-07-backend-design.md -openptv-python-2026-03-07-pybind-delegation-map.md \ No newline at end of file +openptv-python-2026-03-07-pybind-delegation-map.md diff --git a/tests/test_native_stress_performance.py b/tests/test_native_stress_performance.py index 7978afc..a97c24b 100644 --- a/tests/test_native_stress_performance.py +++ b/tests/test_native_stress_performance.py @@ -3,7 +3,10 @@ from __future__ import annotations import os +import shutil +import tempfile import unittest +from contextlib import contextmanager from pathlib import Path from statistics import median from time import perf_counter @@ -11,18 +14,35 @@ import numpy as np +import openptv_python.correspondences as correspondences import openptv_python.image_processing as image_processing import openptv_python.orientation as orientation import openptv_python.segmentation as segmentation from openptv_python._native_compat import ( - HAS_OPTV, HAS_NATIVE_PREPROCESS, HAS_NATIVE_SEGMENTATION, + HAS_OPTV, ) from openptv_python._native_convert import to_native_calibration, to_native_control_par from openptv_python.calibration import Calibration -from openptv_python.imgcoord import image_coordinates -from openptv_python.parameters import ControlPar, TargetPar, VolumePar +from openptv_python.epi import Coord2d_dtype +from openptv_python.imgcoord import image_coordinates, img_coord +from openptv_python.parameters import ( + ControlPar, + TargetPar, + VolumePar, + read_control_par, + read_sequence_par, + read_volume_par, +) +from openptv_python.track import ( + track_forward_start, + trackcorr_c_finish, + trackcorr_c_loop, +) +from openptv_python.tracking_frame_buf import Target +from openptv_python.tracking_run import tr_new +from openptv_python.trafo import dist_to_flat, metric_to_pixel, pixel_to_metric try: from optv.orientation import point_positions as native_point_positions @@ -34,6 +54,40 @@ NativeVolumeParams = None HAS_NATIVE_RECONSTRUCTION = False +try: + from optv.correspondences import ( + MatchedCoords as NativeMatchedCoords, + ) + from optv.correspondences import ( + correspondences as native_correspondences, + ) + from optv.tracking_framebuf import TargetArray as NativeTargetArray + + HAS_NATIVE_STEREOMATCHING = True +except ImportError: + NativeMatchedCoords = None + NativeTargetArray = None + native_correspondences = None + HAS_NATIVE_STEREOMATCHING = False + +try: + from optv.parameters import ( + SequenceParams as NativeSequenceParams, + ) + from optv.parameters import ( + TrackingParams as NativeTrackingParams, + ) + from optv.tracker import Tracker as NativeTracker + + HAS_NATIVE_SEQUENCE = True + HAS_NATIVE_TRACKING = NativeVolumeParams is not None +except ImportError: + NativeSequenceParams = None + NativeTrackingParams = None + NativeTracker = None + HAS_NATIVE_SEQUENCE = False + HAS_NATIVE_TRACKING = False + def _env_flag_enabled(name: str) -> bool: """Interpret common truthy environment variable values.""" @@ -66,7 +120,9 @@ def _timing_summary(label: str, timings: list[float]) -> str: ) -def _serialize_targets(targets) -> list[tuple[int, float, float, int, int, int, int, int]]: +def _serialize_targets( + targets, +) -> list[tuple[int, float, float, int, int, int, int, int]]: """Convert target objects into stable tuples for comparisons.""" return [ ( @@ -128,9 +184,7 @@ def _build_reconstruction_stress_case( add_file = Path("tests/testing_folder/calibration/cam1.tif.addpar") calibs = [ Calibration().from_file( - ori_file=Path( - f"tests/testing_folder/calibration/sym_cam{cam_num}.tif.ori" - ), + ori_file=Path(f"tests/testing_folder/calibration/sym_cam{cam_num}.tif.ori"), add_file=add_file, ) for cam_num in range(1, 5) @@ -141,6 +195,250 @@ def _build_reconstruction_stress_case( return points, targets, cpar, calibs +def _build_stereomatching_stress_case( + grid_width: int = 8, + grid_height: int = 8, + spacing: float = 5.0, +): + """Build a deterministic multi-camera correspondence workload.""" + cpar = read_control_par(Path("tests/testing_fodder/parameters/ptv.par")) + vpar = read_volume_par(Path("tests/testing_fodder/parameters/criteria.par")) + cpar.mm.n2[0] = 1.0001 + cpar.mm.n3 = 1.0001 + + calibs = [ + Calibration().from_file( + ori_file=Path(f"tests/testing_fodder/cal/sym_cam{cam_num}.tif.ori"), + add_file=Path("tests/testing_fodder/cal/cam1.tif.addpar"), + ) + for cam_num in range(1, cpar.num_cams + 1) + ] + + img_pts: list[list[Target]] = [] + for cam in range(cpar.num_cams): + cam_targets = [Target() for _ in range(grid_width * grid_height)] + for row in range(grid_height): + for col in range(grid_width): + pnr = row * grid_width + col + if cam % 2: + pnr = grid_width * grid_height - 1 - pnr + + pos3d = np.array([col * spacing, row * spacing, 0.0], dtype=np.float64) + x, y = img_coord(pos3d, calibs[cam], cpar.mm) + x, y = metric_to_pixel(x, y, cpar) + + cam_targets[pnr] = Target( + pnr=pnr, + x=float(x), + y=float(y), + n=25, + nx=5, + ny=5, + sumg=10, + tnr=-1, + ) + + img_pts.append(cam_targets) + + corrected = np.recarray( + (cpar.num_cams, len(img_pts[0])), + dtype=Coord2d_dtype, + ) + for cam in range(cpar.num_cams): + for part, targ in enumerate(img_pts[cam]): + x, y = pixel_to_metric(targ.x, targ.y, cpar) + x, y = dist_to_flat(x, y, calibs[cam], 0.0001) + corrected[cam][part].pnr = targ.pnr + corrected[cam][part].x = x + corrected[cam][part].y = y + + corrected[cam].sort(order="x") + + return img_pts, corrected, calibs, vpar, cpar + + +def _to_native_target_array(targets: list[Target]): + """Convert Python targets into a native TargetArray.""" + if NativeTargetArray is None: + raise RuntimeError("optv TargetArray is not available") + + native_targets = NativeTargetArray(len(targets)) + for index, target in enumerate(targets): + native_target = native_targets[index] + native_target.set_pnr(int(target.pnr)) + native_target.set_tnr(int(target.tnr)) + native_target.set_pos((float(target.x), float(target.y))) + native_target.set_pixel_counts(int(target.n), int(target.nx), int(target.ny)) + native_target.set_sum_grey_value(int(target.sumg)) + + return native_targets + + +def _to_native_volume_par(vpar: VolumePar): + """Convert Python volume parameters into native VolumeParams.""" + if NativeVolumeParams is None: + raise RuntimeError("optv VolumeParams is not available") + + native_vpar = NativeVolumeParams() + native_vpar.set_X_lay(list(vpar.x_lay)) + native_vpar.set_Zmin_lay(list(vpar.z_min_lay)) + native_vpar.set_Zmax_lay(list(vpar.z_max_lay)) + native_vpar.set_cn(vpar.cn) + native_vpar.set_cnx(vpar.cnx) + native_vpar.set_cny(vpar.cny) + native_vpar.set_csumg(vpar.csumg) + native_vpar.set_eps0(vpar.eps0) + native_vpar.set_corrmin(vpar.corrmin) + return native_vpar + + +def _normalize_correspondence_output(sorted_pos, sorted_corresp): + """Sort correspondence outputs into a stable order for comparisons.""" + normalized_pos = [] + normalized_corresp = [] + + for positions, identifiers in zip(sorted_pos, sorted_corresp): + if identifiers.shape[1] == 0: + normalized_pos.append(positions) + normalized_corresp.append(identifiers) + continue + + sortable_ids = np.where(identifiers < 0, np.iinfo(np.int64).max, identifiers) + order = np.lexsort(sortable_ids[::-1]) + normalized_pos.append(positions[:, order, :]) + normalized_corresp.append(identifiers[:, order]) + + return normalized_pos, normalized_corresp + + +@contextmanager +def _working_directory(path: Path): + """Temporarily change the current working directory.""" + previous = Path.cwd() + os.chdir(path) + try: + yield + finally: + os.chdir(previous) + + +def _read_track_calibrations(workdir: Path, num_cams: int) -> list[Calibration]: + """Read all tracking calibrations from a copied tracking fixture.""" + return [ + Calibration().from_file( + ori_file=workdir / f"cal/cam{cam_num}.tif.ori", + add_file=workdir / f"cal/cam{cam_num}.tif.addpar", + ) + for cam_num in range(1, num_cams + 1) + ] + + +def _snapshot_text_outputs(root: Path) -> dict[str, tuple[int, str]]: + """Summarize benchmark output files into a stable comparison dictionary.""" + return { + str(path.relative_to(root)): ( + len(path.read_text(encoding="utf-8").splitlines()), + path.read_text(encoding="utf-8").splitlines()[0] + if path.read_text(encoding="utf-8").splitlines() + else "", + ) + for path in sorted(root.rglob("*")) + if path.is_file() + } + + +def _run_python_tracking_fixture() -> dict[str, tuple[int, str]]: + """Execute the Python tracking loop in a temporary fixture directory.""" + source = Path("tests/testing_fodder/track") + with tempfile.TemporaryDirectory() as tmp_dir: + workdir = Path(tmp_dir) / "track" + shutil.copytree(source, workdir) + shutil.copytree(workdir / "img_orig", workdir / "img") + output_dir = workdir / "py_res" + shutil.copytree(workdir / "res_orig", output_dir) + + with _working_directory(workdir): + cpar = read_control_par(Path("parameters/ptv.par")) + calibs = _read_track_calibrations(workdir, cpar.num_cams) + run = tr_new( + Path("parameters/sequence.par"), + Path("parameters/track.par"), + Path("parameters/criteria.par"), + Path("parameters/ptv.par"), + 4, + 20000, + "py_res/rt_is", + "py_res/ptv_is", + "py_res/added", + calibs, + 10000.0, + ) + run.seq_par.first = 10240 + run.seq_par.last = 10250 + run.tpar = run.tpar._replace(add=1) + + track_forward_start(run) + trackcorr_c_loop(run, run.seq_par.first) + for step in range(run.seq_par.first + 1, run.seq_par.last): + trackcorr_c_loop(run, step) + trackcorr_c_finish(run, run.seq_par.last) + + return _snapshot_text_outputs(output_dir) + + +def _run_native_tracking_fixture() -> dict[str, tuple[int, str]]: + """Execute the native tracker in a temporary fixture directory.""" + if ( + NativeTracker is None + or NativeSequenceParams is None + or NativeTrackingParams is None + ): + raise RuntimeError("optv Tracker is not available") + + source = Path("tests/testing_fodder/track") + with tempfile.TemporaryDirectory() as tmp_dir: + workdir = Path(tmp_dir) / "track" + shutil.copytree(source, workdir) + shutil.copytree(workdir / "img_orig", workdir / "img") + output_dir = workdir / "native_res" + shutil.copytree(workdir / "res_orig", output_dir) + + with _working_directory(workdir): + py_cpar = read_control_par(Path("parameters/ptv.par")) + py_vpar = read_volume_par(Path("parameters/criteria.par")) + calibs = _read_track_calibrations(workdir, py_cpar.num_cams) + + native_cpar = to_native_control_par(py_cpar) + native_vpar = _to_native_volume_par(py_vpar) + native_tpar = NativeTrackingParams() + native_tpar.read_track_par("parameters/track.par") + native_tpar.set_add(1) + + native_spar = NativeSequenceParams(num_cams=py_cpar.num_cams) + native_spar.read_sequence_par("parameters/sequence.par", py_cpar.num_cams) + native_spar.set_first(10240) + native_spar.set_last(10250) + for cam, img_base_name in enumerate(py_cpar.img_base_name): + native_spar.set_img_base_name(cam, img_base_name.replace("%05d", "")) + + tracker = NativeTracker( + native_cpar, + native_vpar, + native_tpar, + native_spar, + [to_native_calibration(cal) for cal in calibs], + naming={ + "corres": "native_res/rt_is", + "linkage": "native_res/ptv_is", + "prio": "native_res/added", + }, + flatten_tol=10000.0, + ) + tracker.full_forward() + + return _snapshot_text_outputs(output_dir) + + @unittest.skipUnless( RUN_STRESS_BENCHMARKS, "set OPENPTV_RUN_STRESS_BENCHMARKS=1 to run stress benchmarks", @@ -148,6 +446,41 @@ def _build_reconstruction_stress_case( class TestNativeStressPerformance(unittest.TestCase): """Stress tests comparing native and non-native runtime paths.""" + @unittest.skipUnless( + HAS_NATIVE_SEQUENCE, + "optv native SequenceParams is not available", + ) + def test_sequence_parameter_read_stress_timing(self): + """Compare sequence parameter loading timing against native bindings.""" + sequence_file = Path("tests/testing_folder/sequence_parameters/sequence.par") + + def python_path(): + return read_sequence_par(sequence_file, 4) + + def native_path(): + assert NativeSequenceParams is not None + seq = NativeSequenceParams(num_cams=4) + seq.read_sequence_par(str(sequence_file), 4) + return seq + + python_result, python_timings = _benchmark(python_path, runs=5) + native_result, native_timings = _benchmark(native_path, runs=5) + + self.assertEqual(python_result.first, native_result.get_first()) + self.assertEqual(python_result.last, native_result.get_last()) + self.assertEqual( + python_result.img_base_name, + [native_result.get_img_base_name(cam) for cam in range(4)], + ) + + speedup = median(python_timings) / median(native_timings) + print( + "sequence stress benchmark: " + f"{_timing_summary('python', python_timings)}; " + f"{_timing_summary('native', native_timings)}; " + f"speedup={speedup:.2f}x" + ) + @unittest.skipUnless( HAS_NATIVE_PREPROCESS, "optv native preprocess_image is not available", @@ -246,6 +579,67 @@ def native_path(): f"speedup={speedup:.2f}x" ) + @unittest.skipUnless( + HAS_NATIVE_STEREOMATCHING, + "optv native correspondences is not available", + ) + def test_stereomatching_stress_timing(self): + """Compare native correspondences timing against the Python path.""" + img_pts, corrected, calibs, vpar, cpar = _build_stereomatching_stress_case() + native_cpar = to_native_control_par(cpar) + native_vpar = _to_native_volume_par(vpar) + native_cals = [to_native_calibration(cal) for cal in calibs] + native_img_pts = [_to_native_target_array(targets) for targets in img_pts] + native_flat_coords = [ + NativeMatchedCoords( + native_img_pts[cam], native_cpar, native_cals[cam], 0.0001 + ) + for cam in range(cpar.num_cams) + ] + + def python_path(): + return correspondences.py_correspondences( + img_pts, corrected, calibs, vpar, cpar + ) + + def native_path(): + assert native_correspondences is not None + return native_correspondences( + native_img_pts, + native_flat_coords, + native_cals, + native_vpar, + native_cpar, + ) + + python_result, python_timings = _benchmark(python_path) + native_result, native_timings = _benchmark(native_path) + python_pos, python_corresp, python_num_targs = python_result + native_pos, native_corresp, native_num_targs = native_result + python_pos, python_corresp = _normalize_correspondence_output( + python_pos, + python_corresp, + ) + native_pos, native_corresp = _normalize_correspondence_output( + native_pos, + native_corresp, + ) + + self.assertEqual(python_num_targs, native_num_targs) + self.assertEqual(len(python_corresp), len(native_corresp)) + for python_positions, native_positions in zip(python_pos, native_pos): + np.testing.assert_allclose(native_positions, python_positions, atol=1e-9) + for python_ids, native_ids in zip(python_corresp, native_corresp): + np.testing.assert_array_equal(native_ids, python_ids) + + speedup = median(python_timings) / median(native_timings) + print( + "stereomatching stress benchmark: " + f"{_timing_summary('python', python_timings)}; " + f"{_timing_summary('native', native_timings)}; " + f"speedup={speedup:.2f}x" + ) + @unittest.skipUnless( HAS_OPTV and HAS_NATIVE_RECONSTRUCTION, "optv native point_positions is not available", @@ -263,7 +657,9 @@ def python_path(): def native_path(): assert native_point_positions is not None - return native_point_positions(targets, native_cpar, native_cals, native_vpar) + return native_point_positions( + targets, native_cpar, native_cals, native_vpar + ) python_result, python_timings = _benchmark(python_path) native_result, native_timings = _benchmark(native_path) @@ -283,6 +679,33 @@ def native_path(): f"speedup={speedup:.2f}x" ) + @unittest.skipUnless( + HAS_NATIVE_TRACKING, + "optv native Tracker is not available", + ) + def test_tracking_sequence_stress_timing(self): + """Compare native tracking over a short sequence against Python.""" + + def python_path(): + return _run_python_tracking_fixture() + + def native_path(): + return _run_native_tracking_fixture() + + python_outputs, python_timings = _benchmark(python_path, warmups=0, runs=1) + native_outputs, native_timings = _benchmark(native_path, warmups=0, runs=1) + + self.assertTrue(python_outputs) + self.assertEqual(native_outputs, python_outputs) + + speedup = median(python_timings) / median(native_timings) + print( + "tracking stress benchmark: " + f"{_timing_summary('python', python_timings)}; " + f"{_timing_summary('native', native_timings)}; " + f"speedup={speedup:.2f}x" + ) + if __name__ == "__main__": - unittest.main() \ No newline at end of file + unittest.main() diff --git a/tests/test_read_frame.py b/tests/test_read_frame.py index 430482e..6409a38 100644 --- a/tests/test_read_frame.py +++ b/tests/test_read_frame.py @@ -1,6 +1,9 @@ """Test the Frame class.""" +import shutil +import tempfile import unittest +from pathlib import Path import numpy as np @@ -62,6 +65,30 @@ def test_read_frame(self): ) np.testing.assert_array_equal(targs, targs_correct) + def test_read_frame_returns_false_when_linkage_file_missing(self): + """Missing linkage data must fail the frame read instead of producing an empty frame.""" + source_dir = Path("tests/testing_folder/frame") + + with tempfile.TemporaryDirectory() as temp_dir: + work_dir = Path(temp_dir) + for path in source_dir.iterdir(): + shutil.copy2(path, work_dir / path.name) + + (work_dir / "ptv_is.333").unlink() + + targ_files = [str(work_dir / f"cam{c:d}.%04d") for c in range(1, 5)] + frm = Frame(num_cams=4) + + self.assertFalse( + frm.read( + corres_file_base=str(work_dir / "rt_is"), + linkage_file_base=str(work_dir / "ptv_is"), + prio_file_base=str(work_dir / "added"), + target_file_base=targ_files, + frame_num=333, + ) + ) + if __name__ == "__main__": unittest.main() From 36624bba45438e5ed600e6df2276ad0480b4051f Mon Sep 17 00:00:00 2001 From: Alex Liberzon Date: Sat, 7 Mar 2026 22:17:41 +0200 Subject: [PATCH 16/27] documentation --- README.html | 679 ++++++++++++++++++++++++ README.md | 129 ++++- docs/index.md | 9 + tests/test_native_stress_performance.py | 4 +- 4 files changed, 818 insertions(+), 3 deletions(-) create mode 100644 README.html diff --git a/README.html b/README.html new file mode 100644 index 0000000..8096b12 --- /dev/null +++ b/README.html @@ -0,0 +1,679 @@ + + + + + + + + + +readme + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+

openptv-python

+

Python version of the OpenPTV library - this is a work in progress

+
+

What This Repo Provides

+

openptv-python keeps the Python API as the main interface and combines three execution modes behind that API:

+
    +
  • Pure Python: the reference implementation and the easiest path for reading, debugging, and extending the code.
  • +
  • Python + Numba: several hot kernels are JIT-compiled automatically on first use, so the Python implementation still benefits from acceleration.
  • +
  • Native optv bindings: selected operations reuse the native OpenPTV implementation when the optv package is available.
  • +
+

At the moment, automatic native delegation is implemented for image preprocessing and full-frame target recognition. The rest of the library keeps the same Python API and remains usable even when those native paths are not in use.

+
+
+

How this is started

+

This work started from the https://github.com/OpenPTV/openptv/tree/pure_python branch. It’s a long-standing idea to convert all the C code to Python and now it’s possible with ChatGPT to save a lot of typing time.

+

This repo is created using a cookiecutter and the rest of the readme describes the way to work with this structure

+
+
+

Supported Python Versions

+

The project currently supports Python >=3.12,<3.14.

+
+
+

Installation

+ +
+

Alternative: conda + pip

+
conda create -n openptv-python -c conda-forge python=3.12
+conda activate openptv-python
+pip install -e .
+
+
+

What gets installed

+
    +
  • numba is part of the default dependency set and accelerates selected Python kernels automatically after the first call.
  • +
  • optv is part of the default dependency set and enables native interop on supported platforms.
  • +
  • The public API stays the same regardless of which backend is active.
  • +
+
+
+
+

Backend Behavior

+
+

Pure Python backend

+

This is the base implementation for the whole library. It is always the source of truth for the Python API and remains the fallback behavior for code paths that are not delegated to optv.

+
+
+

Python + Numba backend

+

Numba accelerates selected computational kernels inside the Python implementation. This is automatic; there is no separate API to enable it. Expect the first call to a JIT-compiled function to be slower due to compilation, with later calls running faster.

+
+
+

Native optv backend

+

When optv imports successfully, openptv-python automatically reuses native implementations for:

+
    +
  • image preprocessing
  • +
  • full-frame target recognition / segmentation
  • +
+

These native paths are validated against the Python implementation by parity tests, so results stay backend-independent.

+
+
+

Backend Capability Table

+ ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
OperationPure PythonPython + NumbaNative optv
Image preprocessingYesYesYes, automatic delegation
Target recognition / segmentationYesYesYes, automatic delegation
Point reconstructionYesPartial internal kernelsNot used by default
Correspondence search / stereo matchingYesPartial internal kernelsNot used by default
TrackingYesPartial internal kernelsNot used by default
Sequence parameter I/OYesNoAvailable in native bindings
+

Not used by default means the native path exists in benchmarks or conversion helpers, but the regular openptv-python runtime path still uses the Python implementation unless that operation is explicitly integrated later.

+
+
+
+

Getting Started

+
+

1. Install the project

+

Use one of the installation methods above.

+
+
+

2. Verify imports

+
uv run python - <<'PY'
+import openptv_python
+import numba
+import optv
+
+print("openptv_python ok")
+print("numba ok", numba.__version__)
+print("optv ok", optv.__version__)
+PY
+
+
+

3. Start using the Python API

+
>>> import openptv_python
+
+
+

4. Run the test suite

+
uv run make
+

Stress and performance tests are part of the default suite now. If you need a faster validation pass locally, you can skip them explicitly:

+
OPENPTV_SKIP_STRESS_BENCHMARKS=1 uv run make
+
+
+

Workflow for developers/contributors

+

For the best experience create a new conda environment (e.g. DEVELOP) with Python 3.12:

+
conda create -n openptv-python -c conda-forge python=3.12
+conda activate openptv-python
+

Before pushing to GitHub, run the following commands:

+
    +
  1. Update conda environment: make conda-env-update or uv venv and source .venv/bin/activate followed by uv sync --upgrade
  2. +
  3. Install this package: pip install -e .
  4. +
  5. Sync with the latest template (optional): make template-update
  6. +
  7. Run quality assurance checks: make qa
  8. +
  9. Run tests: make unit-tests
  10. +
  11. Run the static type checker: make type-check
  12. +
  13. Build the documentation (see Sphinx tutorial): make docs-build
  14. +
+
+
+

License

+
Copyright 2023, OpenPTV consortium.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+
+
+ +
+ + +
+ + + + + \ No newline at end of file diff --git a/README.md b/README.md index 1a873da..c2b7bbc 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,23 @@ Python version of the OpenPTV library - this is *a work in progress* +## What This Repo Provides + +`openptv-python` keeps the Python API as the main interface and combines three +execution modes behind that API: + +- Pure Python: the reference implementation and the easiest path for reading, + debugging, and extending the code. +- Python + Numba: several hot kernels are JIT-compiled automatically on first + use, so the Python implementation still benefits from acceleration. +- Native `optv` bindings: selected operations reuse the native OpenPTV + implementation when the `optv` package is available. + +At the moment, automatic native delegation is implemented for image +preprocessing and full-frame target recognition. The rest of the library keeps +the same Python API and remains usable even when those native paths are not in +use. + ## How this is started This work started from the https://github.com/OpenPTV/openptv/tree/pure_python branch. It's a long-standing idea to convert all the C code to Python and now it's possible with ChatGPT to save @@ -10,13 +27,123 @@ a lot of typing time. This repo is created using a *cookiecutter* and the rest of the readme describes the way to work with this structure -### Quick Start +## Supported Python Versions + +The project currently supports Python `>=3.12,<3.14`. + +## Installation + +### Recommended: uv + +Create the environment and install all project dependencies: + +```bash +uv venv +source .venv/bin/activate +uv sync +``` + +This is the simplest way to get the current default stack, including NumPy, +SciPy, Numba, and the `optv` native bindings when they are available for your +platform and Python version. + +### Alternative: conda + pip + +```bash +conda create -n openptv-python -c conda-forge python=3.12 +conda activate openptv-python +pip install -e . +``` + +### What gets installed + +- `numba` is part of the default dependency set and accelerates selected Python + kernels automatically after the first call. +- `optv` is part of the default dependency set and enables native interop on + supported platforms. +- The public API stays the same regardless of which backend is active. + +## Backend Behavior + +### Pure Python backend + +This is the base implementation for the whole library. It is always the source +of truth for the Python API and remains the fallback behavior for code paths +that are not delegated to `optv`. + +### Python + Numba backend + +Numba accelerates selected computational kernels inside the Python +implementation. This is automatic; there is no separate API to enable it. +Expect the first call to a JIT-compiled function to be slower due to +compilation, with later calls running faster. + +### Native `optv` backend + +When `optv` imports successfully, `openptv-python` automatically reuses native +implementations for: + +- image preprocessing +- full-frame target recognition / segmentation + +These native paths are validated against the Python implementation by parity +tests, so results stay backend-independent. + +### Backend Capability Table + +| Operation | Pure Python | Python + Numba | Native `optv` | +| --- | --- | --- | --- | +| Image preprocessing | Yes | Yes | Yes, automatic delegation | +| Target recognition / segmentation | Yes | Yes | Yes, automatic delegation | +| Point reconstruction | Yes | Partial internal kernels | Not used by default | +| Correspondence search / stereo matching | Yes | Partial internal kernels | Not used by default | +| Tracking | Yes | Partial internal kernels | Not used by default | +| Sequence parameter I/O | Yes | No | Available in native bindings | + +`Not used by default` means the native path exists in benchmarks or conversion +helpers, but the regular `openptv-python` runtime path still uses the Python +implementation unless that operation is explicitly integrated later. + +## Getting Started + +### 1. Install the project + +Use one of the installation methods above. + +### 2. Verify imports + +```bash +uv run python - <<'PY' +import openptv_python +import numba +import optv + +print("openptv_python ok") +print("numba ok", numba.__version__) +print("optv ok", optv.__version__) +PY +``` + +### 3. Start using the Python API ```python >>> import openptv_python ``` +### 4. Run the test suite + +```bash +uv run make +``` + +Stress and performance tests are part of the default suite now. If you need a +faster validation pass locally, you can skip them explicitly: + +```bash +OPENPTV_SKIP_STRESS_BENCHMARKS=1 uv run make +``` + ### Workflow for developers/contributors For the best experience create a new conda environment (e.g. DEVELOP) with Python 3.12: diff --git a/docs/index.md b/docs/index.md index 37ea85c..d414197 100644 --- a/docs/index.md +++ b/docs/index.md @@ -2,6 +2,15 @@ Python version of the OpenPTV library. +Start with the README for installation and backend selection details. The short +version is: + +- install with `uv sync` on Python 3.12 or 3.13 +- use the same Python API regardless of backend +- get pure Python behavior everywhere, Numba acceleration in selected kernels, + and automatic native `optv` delegation for preprocessing and full-frame + target recognition when available + ```{toctree} :caption: 'Contents:' :maxdepth: 2 diff --git a/tests/test_native_stress_performance.py b/tests/test_native_stress_performance.py index a97c24b..ad7be44 100644 --- a/tests/test_native_stress_performance.py +++ b/tests/test_native_stress_performance.py @@ -94,7 +94,7 @@ def _env_flag_enabled(name: str) -> bool: return os.getenv(name, "").strip().lower() in {"1", "true", "yes", "on"} -RUN_STRESS_BENCHMARKS = _env_flag_enabled("OPENPTV_RUN_STRESS_BENCHMARKS") +RUN_STRESS_BENCHMARKS = not _env_flag_enabled("OPENPTV_SKIP_STRESS_BENCHMARKS") def _benchmark(function, *, warmups: int = 1, runs: int = 3): @@ -441,7 +441,7 @@ def _run_native_tracking_fixture() -> dict[str, tuple[int, str]]: @unittest.skipUnless( RUN_STRESS_BENCHMARKS, - "set OPENPTV_RUN_STRESS_BENCHMARKS=1 to run stress benchmarks", + "set OPENPTV_SKIP_STRESS_BENCHMARKS=1 to skip stress benchmarks", ) class TestNativeStressPerformance(unittest.TestCase): """Stress tests comparing native and non-native runtime paths.""" From d63ed211bfafc9c9f034f362c60d789a49f49ce5 Mon Sep 17 00:00:00 2001 From: Alex Liberzon Date: Sat, 7 Mar 2026 22:52:43 +0200 Subject: [PATCH 17/27] added numba to stereo matching --- README.html | 22 ++-- openptv_python/orientation.py | 111 +++++++++++++++++-- tests/test_native_stress_performance.py | 48 ++++++-- tests/test_point_positions.py | 139 ++++++++++++++++++++++++ 4 files changed, 292 insertions(+), 28 deletions(-) diff --git a/README.html b/README.html index 8096b12..7dbf403 100644 --- a/README.html +++ b/README.html @@ -17,7 +17,7 @@ ul.task-list{list-style: none;} ul.task-list li input[type="checkbox"] { width: 0.8em; - margin: 0 0.8em 0.2em -1em; /* quarto-specific, see https://github.com/quarto-dev/quarto-cli/issues/4556 */ + margin: 0 0.8em 0.2em -1em; /* quarto-specific, see https://github.com/quarto-dev/quarto-cli/issues/4556 */ vertical-align: middle; } /* CSS for syntax highlighting */ @@ -283,7 +283,7 @@

License

anchorJS.add('.anchored'); const isCodeAnnotation = (el) => { for (const clz of el.classList) { - if (clz.startsWith('code-annotation-')) { + if (clz.startsWith('code-annotation-')) { return true; } } @@ -303,11 +303,11 @@

License

button.setAttribute("data-bs-toggle", "tooltip"); button.setAttribute("data-bs-placement", "left"); button.setAttribute("data-bs-title", "Copied!"); - tooltip = new bootstrap.Tooltip(button, - { trigger: "manual", + tooltip = new bootstrap.Tooltip(button, + { trigger: "manual", customClass: "code-copy-button-tooltip", offset: [0, -8]}); - tooltip.show(); + tooltip.show(); } setTimeout(function() { if (tooltip) { @@ -383,7 +383,7 @@

License

if (onUntriggerFn) { config.onUntrigger = onUntriggerFn; } - window.tippy(el, config); + window.tippy(el, config); } const noterefs = window.document.querySelectorAll('a[role="doc-noteref"]'); for (var i=0; iLicense tippyHover(xref, undefined, function(instance) { instance.disable(); let url = xref.getAttribute('href'); - let hash = undefined; + let hash = undefined; if (url.startsWith('#')) { hash = url; } else { @@ -486,7 +486,7 @@

License

if (note !== null) { const html = processXRef(id, note); instance.setContent(html); - } + } }).finally(() => { instance.enable(); instance.show(); @@ -510,7 +510,7 @@

License

} const html = processXRef(null, note); instance.setContent(html); - } + } }).finally(() => { instance.enable(); instance.show(); @@ -550,7 +550,7 @@

License

height = bottom - top; } if (top !== null && height !== null && parent !== null) { - // cook up a div (if necessary) and position it + // cook up a div (if necessary) and position it let div = window.document.getElementById("code-annotation-line-highlight"); if (div === null) { div = window.document.createElement("div"); @@ -676,4 +676,4 @@

License

- \ No newline at end of file + diff --git a/openptv_python/orientation.py b/openptv_python/orientation.py index e996505..9e8fc91 100644 --- a/openptv_python/orientation.py +++ b/openptv_python/orientation.py @@ -4,7 +4,7 @@ import numpy as np import scipy -from numba import njit +from numba import njit, prange from openptv_python.constants import COORD_UNUSED @@ -15,7 +15,7 @@ # from .lsqadj import ata, atl, matinv, matmul from .parameters import ControlPar, MultimediaPar, OrientPar, VolumePar -from .ray_tracing import ray_tracing +from .ray_tracing import fast_ray_tracing, ray_tracing from .sortgrid import sortgrid from .tracking_frame_buf import Target from .trafo import ( @@ -55,6 +55,79 @@ def skew_midpoint( return float(scale), res +@njit(cache=True, fastmath=True, nogil=True, parallel=True) +def _multi_cam_point_positions_numba( + targets: np.ndarray, + distortion_matrices: np.ndarray, + primary_points: np.ndarray, + glass_vectors: np.ndarray, + ccs: np.ndarray, + distance_param: float, + refractive_index_medium1: float, + refractive_index_medium2: float, + refractive_index_medium3: float, + coord_unused: float, +) -> Tuple[np.ndarray, np.ndarray]: + """Calculate multi-camera point positions using compiled per-point loops.""" + num_targets = targets.shape[0] + num_cams = targets.shape[1] + res = np.empty((num_targets, 3), dtype=np.float64) + rcm = np.empty(num_targets, dtype=np.float64) + + for pt in prange(num_targets): + vertices = np.zeros((num_cams, 3), dtype=np.float64) + directs = np.zeros((num_cams, 3), dtype=np.float64) + point_tot = np.zeros(3, dtype=np.float64) + num_used_pairs = 0 + dtot = 0.0 + + for cam in range(num_cams): + if targets[pt, cam, 0] == coord_unused: + continue + + camera = np.empty(3, dtype=np.float64) + camera[0] = targets[pt, cam, 0] + camera[1] = targets[pt, cam, 1] + camera[2] = -ccs[cam] + vertices[cam], directs[cam] = fast_ray_tracing( + camera, + distortion_matrices[cam], + primary_points[cam], + glass_vectors[cam], + distance_param, + refractive_index_medium1, + refractive_index_medium2, + refractive_index_medium3, + ) + + for cam in range(num_cams): + if targets[pt, cam, 0] == coord_unused: + continue + + for pair in range(cam + 1, num_cams): + if targets[pt, pair, 0] == coord_unused: + continue + + num_used_pairs += 1 + tmp, point = skew_midpoint( + vertices[cam], + directs[cam], + vertices[pair], + directs[pair], + ) + dtot += tmp + point_tot += point + + if num_used_pairs == 0: + rcm[pt] = np.nan + res[pt, :] = np.nan + else: + rcm[pt] = dtot / num_used_pairs + res[pt, :] = point_tot / num_used_pairs + + return res, rcm + + def point_position( targets: np.ndarray, num_cams: int, @@ -1060,15 +1133,33 @@ def multi_cam_point_positions( # So we can address targets.data directly instead of get_ptr stuff: - num_targets = targets.shape[0] - num_cams = targets.shape[1] - res = np.empty((num_targets, 3)) - rcm = np.empty(num_targets) - - for pt in range(num_targets): - rcm[pt], res[pt] = point_position(targets[pt], num_cams, mm_par, cals) + distortion_matrices = np.ascontiguousarray( + np.stack([cal.ext_par.dm for cal in cals]).astype(np.float64) + ) + primary_points = np.ascontiguousarray( + np.stack( + [np.array([cal.ext_par.x0, cal.ext_par.y0, cal.ext_par.z0]) for cal in cals] + ).astype(np.float64) + ) + glass_vectors = np.ascontiguousarray( + np.stack([cal.glass_par for cal in cals]).astype(np.float64) + ) + ccs = np.ascontiguousarray( + np.array([cal.int_par.cc for cal in cals], dtype=np.float64) + ) - return res, rcm + return _multi_cam_point_positions_numba( + np.ascontiguousarray(targets, dtype=np.float64), + distortion_matrices, + primary_points, + glass_vectors, + ccs, + float(mm_par.d[0]), + float(mm_par.n1), + float(mm_par.n2[0]), + float(mm_par.n3), + float(COORD_UNUSED), + ) def _clone_calibration(cal: Calibration) -> Calibration: diff --git a/tests/test_native_stress_performance.py b/tests/test_native_stress_performance.py index ad7be44..cceca9a 100644 --- a/tests/test_native_stress_performance.py +++ b/tests/test_native_stress_performance.py @@ -195,6 +195,27 @@ def _build_reconstruction_stress_case( return points, targets, cpar, calibs +def _python_point_positions_reference( + targets: np.ndarray, + mm_par, + cals: list[Calibration], +) -> tuple[np.ndarray, np.ndarray]: + """Reconstruct points with the original Python per-target loop.""" + num_targets = targets.shape[0] + points = np.empty((num_targets, 3), dtype=np.float64) + rcm = np.empty(num_targets, dtype=np.float64) + + for pt in range(num_targets): + rcm[pt], points[pt] = orientation.point_position( + targets[pt], + len(cals), + mm_par, + cals, + ) + + return points, rcm + + def _build_stereomatching_stress_case( grid_width: int = 8, grid_height: int = 8, @@ -645,38 +666,51 @@ def native_path(): "optv native point_positions is not available", ) def test_point_reconstruction_stress_timing(self): - """Compare native multi-camera reconstruction timing against Python.""" + """Compare compiled Python, legacy Python, and native reconstruction.""" expected_points, targets, cpar, calibs = _build_reconstruction_stress_case() native_cpar = to_native_control_par(cpar) native_cals = [to_native_calibration(cal) for cal in calibs] native_vpar = NativeVolumeParams() python_vpar = VolumePar() - def python_path(): + def compiled_python_path(): return orientation.point_positions(targets, cpar.mm, calibs, python_vpar) + def legacy_python_path(): + return _python_point_positions_reference(targets, cpar.mm, calibs) + def native_path(): assert native_point_positions is not None return native_point_positions( targets, native_cpar, native_cals, native_vpar ) - python_result, python_timings = _benchmark(python_path) + compiled_python_result, compiled_python_timings = _benchmark(compiled_python_path) + legacy_python_result, legacy_python_timings = _benchmark(legacy_python_path) native_result, native_timings = _benchmark(native_path) - python_points, python_rcm = python_result + python_points, python_rcm = compiled_python_result + legacy_points, legacy_rcm = legacy_python_result native_points, native_rcm = native_result np.testing.assert_allclose(python_points, expected_points, atol=1e-6) + np.testing.assert_allclose(legacy_points, expected_points, atol=1e-6) np.testing.assert_allclose(native_points, expected_points, atol=1e-6) + np.testing.assert_allclose(legacy_points, python_points, atol=1e-9) + np.testing.assert_allclose(legacy_rcm, python_rcm, atol=1e-9) np.testing.assert_allclose(native_points, python_points, atol=1e-9) np.testing.assert_allclose(native_rcm, python_rcm, atol=1e-9) - speedup = median(python_timings) / median(native_timings) + compiled_vs_legacy = median(legacy_python_timings) / median( + compiled_python_timings + ) + compiled_vs_native = median(compiled_python_timings) / median(native_timings) print( "reconstruction stress benchmark: " - f"{_timing_summary('python', python_timings)}; " + f"{_timing_summary('compiled-python', compiled_python_timings)}; " + f"{_timing_summary('legacy-python', legacy_python_timings)}; " f"{_timing_summary('native', native_timings)}; " - f"speedup={speedup:.2f}x" + f"compiled-vs-legacy={compiled_vs_legacy:.2f}x; " + f"compiled-vs-native={compiled_vs_native:.2f}x" ) @unittest.skipUnless( diff --git a/tests/test_point_positions.py b/tests/test_point_positions.py index a2cf0f6..a4eeef9 100644 --- a/tests/test_point_positions.py +++ b/tests/test_point_positions.py @@ -3,11 +3,15 @@ import numpy as np +from openptv_python._native_compat import HAS_OPTV +from openptv_python._native_convert import to_native_calibration, to_native_control_par from openptv_python.calibration import ( Calibration, ) +from openptv_python.constants import COORD_UNUSED from openptv_python.imgcoord import flat_image_coordinates, image_coordinates from openptv_python.orientation import ( + point_position, match_detection_to_ref, point_positions, weighted_dumbbell_precision, @@ -16,6 +20,28 @@ from openptv_python.tracking_frame_buf import Target from openptv_python.trafo import arr_metric_to_pixel +try: + from optv.orientation import point_positions as native_point_positions + from optv.parameters import VolumeParams as NativeVolumeParams + + HAS_NATIVE_RECONSTRUCTION = True +except ImportError: + native_point_positions = None + NativeVolumeParams = None + HAS_NATIVE_RECONSTRUCTION = False + + +def _python_multi_cam_point_positions_reference(targets, mm_par, cals): + """Reconstruct points with the original per-target Python loop.""" + num_targets = targets.shape[0] + points = np.empty((num_targets, 3), dtype=np.float64) + rcm = np.empty(num_targets, dtype=np.float64) + + for pt in range(num_targets): + rcm[pt], points[pt] = point_position(targets[pt], len(cals), mm_par, cals) + + return points, rcm + class TestOrientation(unittest.TestCase): """Test the orientation module.""" @@ -165,6 +191,119 @@ def test_point_positions(self): if np.any(np.linalg.norm(points - skew_dist_jigged[0], axis=1) > 0.1): self.fail("Rays converge on wrong position after jigging.") + def test_multi_camera_point_positions_matches_python_reference_loop(self): + """Compiled multi-camera reconstruction matches the original Python loop.""" + mult_params = MultimediaPar() + mult_params.set_n1(1.0) + mult_params.set_layers([1.0], [1.0]) + mult_params.set_n3(1.0) + + points = np.array( + [ + [17.0, 42.0, 0.0], + [12.5, 35.0, -4.0], + [-8.0, 50.0, 7.5], + ], + dtype=np.float64, + ) + + add_file = Path("tests/testing_folder/calibration/cam1.tif.addpar") + calibs = [ + Calibration().from_file( + ori_file=Path( + f"tests/testing_folder/calibration/sym_cam{cam_num}.tif.ori" + ), + add_file=add_file, + ) + for cam_num in range(1, 5) + ] + + projections = [image_coordinates(points, cal, mult_params) for cal in calibs] + targets = np.asarray(projections, dtype=np.float64).transpose(1, 0, 2) + + partial_targets = targets.copy() + partial_targets[0, 3, :] = COORD_UNUSED + partial_targets[1, 1, :] = COORD_UNUSED + partial_targets[2, 0, :] = COORD_UNUSED + partial_targets[2, 2, :] = COORD_UNUSED + + compiled_points, compiled_rcm = point_positions( + partial_targets, + mult_params, + calibs, + self.vpar, + ) + reference_points, reference_rcm = _python_multi_cam_point_positions_reference( + partial_targets, + mult_params, + calibs, + ) + + np.testing.assert_allclose(compiled_points, reference_points, atol=1e-9) + np.testing.assert_allclose(compiled_rcm, reference_rcm, atol=1e-9) + np.testing.assert_allclose(compiled_points, points, atol=1e-6) + + @unittest.skipUnless( + HAS_OPTV and HAS_NATIVE_RECONSTRUCTION, + "optv native point_positions is not available", + ) + def test_multi_camera_point_positions_matches_native_backend(self): + """Compiled multi-camera reconstruction matches optv output.""" + mult_params = MultimediaPar() + mult_params.set_n1(1.0) + mult_params.set_layers([1.0], [1.0]) + mult_params.set_n3(1.0) + + points = np.array( + [ + [17.0, 42.0, 0.0], + [12.5, 35.0, -4.0], + [-8.0, 50.0, 7.5], + ], + dtype=np.float64, + ) + + cpar = ControlPar(4).from_file(self.control_file_name) + cpar.mm.set_n1(1.0) + cpar.mm.set_layers([1.0], [1.0]) + cpar.mm.set_n3(1.0) + + add_file = Path("tests/testing_folder/calibration/cam1.tif.addpar") + calibs = [ + Calibration().from_file( + ori_file=Path( + f"tests/testing_folder/calibration/sym_cam{cam_num}.tif.ori" + ), + add_file=add_file, + ) + for cam_num in range(1, 5) + ] + + projections = [image_coordinates(points, cal, cpar.mm) for cal in calibs] + targets = np.asarray(projections, dtype=np.float64).transpose(1, 0, 2) + + python_points, python_rcm = point_positions(targets, cpar.mm, calibs, self.vpar) + reference_points, reference_rcm = _python_multi_cam_point_positions_reference( + targets, + cpar.mm, + calibs, + ) + + assert native_point_positions is not None + assert NativeVolumeParams is not None + native_points, native_rcm = native_point_positions( + targets, + to_native_control_par(cpar), + [to_native_calibration(cal) for cal in calibs], + NativeVolumeParams(), + ) + + np.testing.assert_allclose(python_points, reference_points, atol=1e-9) + np.testing.assert_allclose(python_rcm, reference_rcm, atol=1e-9) + np.testing.assert_allclose(native_points, python_points, atol=1e-9) + np.testing.assert_allclose(native_rcm, python_rcm, atol=1e-9) + np.testing.assert_allclose(python_points, points, atol=1e-6) + def test_single_camera_point_positions(self): """Point positions for a single camera case.""" num_cams = 1 From a3bfbd121b4d18e023acb193c96fa96990bb2b41 Mon Sep 17 00:00:00 2001 From: Alex Liberzon Date: Sat, 7 Mar 2026 23:46:18 +0200 Subject: [PATCH 18/27] improved mutlimedia --- openptv_python/multimed.py | 13 +- openptv_python/parameters.py | 2 +- tests/test_multimedia_n_lay.py | 242 +++++++++++++++++++++++- tests/test_native_stress_performance.py | 71 ++++++- tests/test_point_positions.py | 2 +- 5 files changed, 320 insertions(+), 10 deletions(-) diff --git a/openptv_python/multimed.py b/openptv_python/multimed.py index d2a32c2..236f534 100644 --- a/openptv_python/multimed.py +++ b/openptv_python/multimed.py @@ -80,17 +80,18 @@ def fast_multimed_r_nlay( zdiff = 1.0 it = 0 rdiff = 0.1 # Initialize to enter the loop + beta2 = np.zeros(nlay, dtype=np.float64) while abs(rdiff) > 0.001 and it < n_iter: beta1 = np.arctan(rq / zdiff) - beta2 = np.arcsin(np.sin(beta1) * n1 / n2[0]) + sin_beta1 = np.sin(beta1) + for layer in range(nlay): + beta2[layer] = np.arcsin(sin_beta1 * n1 / n2[layer]) beta3 = np.arcsin(np.sin(beta1) * n1 / n3) - rbeta = ( - (z0 - d[0]) * np.tan(beta1) - - zout * np.tan(beta3) - + np.sum(d * np.tan(beta2)) - ) + rbeta = (z0 - d[0]) * np.tan(beta1) - zout * np.tan(beta3) + for layer in range(nlay): + rbeta += d[layer] * np.tan(beta2[layer]) rdiff = r - rbeta rq += rdiff diff --git a/openptv_python/parameters.py b/openptv_python/parameters.py index a5ea3d2..d9062e6 100644 --- a/openptv_python/parameters.py +++ b/openptv_python/parameters.py @@ -105,7 +105,7 @@ def set_layers(self, refr_index: list[float], thickness: list[float]): else: self.n2 = refr_index self.d = thickness - # self.nlay = len(refr_index) + self.nlay = len(refr_index) def __str__(self) -> str: return f"nlay = {self.nlay}, n1 = {self.n1}, n2 = {self.n2}, d = {self.d}, n3 = {self.n3}" diff --git a/tests/test_multimedia_n_lay.py b/tests/test_multimedia_n_lay.py index a4f14e4..aa7be0b 100644 --- a/tests/test_multimedia_n_lay.py +++ b/tests/test_multimedia_n_lay.py @@ -3,9 +3,128 @@ import numpy as np +from openptv_python._native_compat import HAS_OPTV +from openptv_python._native_convert import to_native_calibration, to_native_control_par from openptv_python.calibration import read_calibration +from openptv_python.epi import epipolar_curve +from openptv_python.imgcoord import image_coordinates from openptv_python.multimed import init_mmlut, multimed_nlay, multimed_r_nlay -from openptv_python.parameters import read_control_par, read_volume_par +from openptv_python.orientation import point_position, point_positions +from openptv_python.parameters import ( + ControlPar, + MultimediaPar, + VolumePar, + read_control_par, + read_volume_par, +) +from openptv_python.trafo import metric_to_pixel + +try: + from optv.epipolar import epipolar_curve as native_epipolar_curve + from optv.orientation import ( + multi_cam_point_positions as native_multi_cam_point_positions, + ) + from optv.parameters import VolumeParams as NativeVolumeParams + + HAS_NATIVE_MULTIMEDIA_PARITY = True +except ImportError: + native_epipolar_curve = None + native_multi_cam_point_positions = None + NativeVolumeParams = None + HAS_NATIVE_MULTIMEDIA_PARITY = False + + +def _multimed_r_nlay_reference(cal, mm, pos): + """Match the native C multilayer loop in a Python reference implementation.""" + if mm.n1 == 1 and mm.nlay == 1 and mm.n2[0] == 1 and mm.n3 == 1: + return 1.0 + + x, y, z = pos + zout = z + sum(mm.d[1 : mm.nlay]) + r = float(np.linalg.norm(np.array([x - cal.ext_par.x0, y - cal.ext_par.y0]))) + rq = r + rdiff = 0.1 + it = 0 + + while abs(rdiff) > 0.001 and it < 40: + beta1 = np.arctan(rq / (cal.ext_par.z0 - z)) + beta2 = [np.arcsin(np.sin(beta1) * mm.n1 / mm.n2[i]) for i in range(mm.nlay)] + beta3 = np.arcsin(np.sin(beta1) * mm.n1 / mm.n3) + + rbeta = (cal.ext_par.z0 - mm.d[0]) * np.tan(beta1) - zout * np.tan(beta3) + for layer in range(mm.nlay): + rbeta += mm.d[layer] * np.tan(beta2[layer]) + + rdiff = r - rbeta + rq += rdiff + it += 1 + + return 1.0 if r == 0 else float(rq / r) + + +def _python_multi_cam_point_positions_reference(targets, mm_par, cals): + """Reconstruct points with the original Python per-point loop.""" + num_targets = targets.shape[0] + points = np.empty((num_targets, 3), dtype=np.float64) + rcm = np.empty(num_targets, dtype=np.float64) + + for pt in range(num_targets): + rcm[pt], points[pt] = point_position(targets[pt], len(cals), mm_par, cals) + + return points, rcm + + +def _to_native_volume_par(vpar: VolumePar): + """Convert Python volume parameters into native VolumeParams.""" + if NativeVolumeParams is None: + raise RuntimeError("optv VolumeParams is not available") + + native_vpar = NativeVolumeParams() + native_vpar.set_X_lay(list(vpar.x_lay)) + native_vpar.set_Zmin_lay(list(vpar.z_min_lay)) + native_vpar.set_Zmax_lay(list(vpar.z_max_lay)) + native_vpar.set_cn(vpar.cn) + native_vpar.set_cnx(vpar.cnx) + native_vpar.set_cny(vpar.cny) + native_vpar.set_csumg(vpar.csumg) + native_vpar.set_eps0(vpar.eps0) + native_vpar.set_corrmin(vpar.corrmin) + return native_vpar + + +def _build_multilayer_case(): + """Build a deterministic multi-camera multilayer reconstruction fixture.""" + cpar = ControlPar(4).from_file( + Path("tests/testing_folder/control_parameters/control.par") + ) + cpar.mm.set_n1(1.0) + cpar.mm.set_layers([1.49, 1.10], [5.0, 10.0]) + cpar.mm.set_n3(1.33) + + vpar = read_volume_par(Path("tests/testing_folder/corresp/criteria.par")) + add_file = Path("tests/testing_folder/calibration/cam1.tif.addpar") + calibs = [ + read_calibration( + Path(f"tests/testing_folder/calibration/sym_cam{cam_num}.tif.ori"), + add_file, + ) + for cam_num in range(1, 5) + ] + + points = np.array( + [ + [17.0, 42.0, 0.0], + [8.0, 36.0, -4.0], + [-6.0, 55.0, 7.0], + ], + dtype=np.float64, + ) + targets = np.asarray( + [image_coordinates(points, cal, cpar.mm) for cal in calibs], + dtype=np.float64, + ).transpose(1, 0, 2) + + return cpar, vpar, calibs, points, targets class TestMultimedRnlay(unittest.TestCase): @@ -74,6 +193,127 @@ def test_multimed_r_nlay_2(self): self.assertAlmostEqual(Xq, correct_Xq, delta=1e-6) self.assertAlmostEqual(Yq, correct_Yq, delta=1e-6) + def test_multimed_r_nlay_matches_native_formula_for_multiple_layers(self): + """Multi-layer radial shift matches the native per-layer formulation.""" + mm = MultimediaPar( + nlay=2, + n1=1.0, + n2=[1.49, 1.10], + d=[5.0, 10.0], + n3=1.33, + ) + pos = np.array([10.0, 15.0, 1.23], dtype=np.float64) + + observed = multimed_r_nlay(self.cal, mm, pos) + expected = _multimed_r_nlay_reference(self.cal, mm, pos) + + self.assertAlmostEqual(observed, expected, delta=1e-10) + + def test_multimed_r_nlay_matches_reference_across_multilayer_cases(self): + """Multi-layer radial shift stays aligned with the native loop across cases.""" + cases = [ + ( + MultimediaPar(nlay=2, n1=1.0, n2=[1.49, 1.10], d=[5.0, 10.0], n3=1.33), + np.array([10.0, 15.0, 1.23], dtype=np.float64), + ), + ( + MultimediaPar(nlay=2, n1=1.0, n2=[1.49, 1.20], d=[5.0, 3.0], n3=1.33), + np.array([1.23, 1.23, 1.23], dtype=np.float64), + ), + ( + MultimediaPar( + nlay=3, + n1=1.0, + n2=[1.49, 1.20, 1.05], + d=[5.0, 3.0, 1.5], + n3=1.33, + ), + np.array([12.0, -6.0, 2.5], dtype=np.float64), + ), + ] + + for mm, pos in cases: + with self.subTest(mm=mm, pos=pos): + observed = multimed_r_nlay(self.cal, mm, pos) + expected = _multimed_r_nlay_reference(self.cal, mm, pos) + self.assertAlmostEqual(observed, expected, delta=1e-10) + + @unittest.skipUnless( + HAS_OPTV and HAS_NATIVE_MULTIMEDIA_PARITY, + "optv native multimedia comparison hooks are not available", + ) + def test_multilayer_reconstruction_matches_python_numba_and_native(self): + """Multi-layer reconstruction agrees across Python, Numba, and optv.""" + cpar, vpar, calibs, points, targets = _build_multilayer_case() + + python_points, python_rcm = _python_multi_cam_point_positions_reference( + targets, + cpar.mm, + calibs, + ) + compiled_points, compiled_rcm = point_positions(targets, cpar.mm, calibs, vpar) + + assert native_multi_cam_point_positions is not None + native_points, native_rcm = native_multi_cam_point_positions( + targets, + to_native_control_par(cpar), + [to_native_calibration(cal) for cal in calibs], + ) + + np.testing.assert_allclose(compiled_points, python_points, atol=1e-9) + np.testing.assert_allclose(compiled_rcm, python_rcm, atol=1e-9) + np.testing.assert_allclose(native_points, compiled_points, atol=1e-9) + np.testing.assert_allclose(native_rcm, compiled_rcm, atol=1e-9) + + reprojected = np.asarray( + [image_coordinates(compiled_points, cal, cpar.mm) for cal in calibs], + dtype=np.float64, + ).transpose(1, 0, 2) + self.assertLess(np.max(np.abs(reprojected - targets)), 0.25) + self.assertTrue(np.all(np.isfinite(compiled_rcm))) + + @unittest.skipUnless( + HAS_OPTV and HAS_NATIVE_MULTIMEDIA_PARITY, + "optv native multimedia comparison hooks are not available", + ) + def test_multilayer_epipolar_curve_matches_native(self): + """Multi-layer epipolar geometry matches the native binding output.""" + cpar, vpar, calibs, points, _targets = _build_multilayer_case() + origin_projection = image_coordinates(points[:1], calibs[0], cpar.mm)[0] + image_point = np.array( + metric_to_pixel(origin_projection[0], origin_projection[1], cpar) + ) + + python_line = epipolar_curve( + image_point, + calibs[0], + calibs[2], + 9, + cpar, + vpar, + ) + + assert native_epipolar_curve is not None + native_line = native_epipolar_curve( + image_point, + to_native_calibration(calibs[0]), + to_native_calibration(calibs[2]), + 9, + to_native_control_par(cpar), + _to_native_volume_par(vpar), + ) + + np.testing.assert_allclose(native_line, python_line, atol=1e-6) + + def test_set_layers_updates_multimedia_layer_count(self): + """set_layers keeps nlay in sync with the provided layer arrays.""" + mm = MultimediaPar() + mm.set_layers([1.49, 1.10], [5.0, 10.0]) + + self.assertEqual(mm.nlay, 2) + self.assertEqual(mm.n2, [1.49, 1.10]) + self.assertEqual(mm.d, [5.0, 10.0]) + if __name__ == "__main__": unittest.main() diff --git a/tests/test_native_stress_performance.py b/tests/test_native_stress_performance.py index cceca9a..3b7a6c7 100644 --- a/tests/test_native_stress_performance.py +++ b/tests/test_native_stress_performance.py @@ -45,11 +45,15 @@ from openptv_python.trafo import dist_to_flat, metric_to_pixel, pixel_to_metric try: + from optv.orientation import ( + multi_cam_point_positions as native_multi_cam_point_positions, + ) from optv.orientation import point_positions as native_point_positions from optv.parameters import VolumeParams as NativeVolumeParams HAS_NATIVE_RECONSTRUCTION = True except ImportError: + native_multi_cam_point_positions = None native_point_positions = None NativeVolumeParams = None HAS_NATIVE_RECONSTRUCTION = False @@ -195,6 +199,20 @@ def _build_reconstruction_stress_case( return points, targets, cpar, calibs +def _build_multilayer_reconstruction_stress_case( + num_points: int = 4096, +) -> tuple[np.ndarray, np.ndarray, ControlPar, list[Calibration]]: + """Build a deterministic multi-layer multi-camera reconstruction workload.""" + points, _targets, cpar, calibs = _build_reconstruction_stress_case(num_points) + cpar.mm.set_n1(1.0) + cpar.mm.set_layers([1.49, 1.10], [5.0, 10.0]) + cpar.mm.set_n3(1.33) + + projections = [image_coordinates(points, cal, cpar.mm) for cal in calibs] + targets = np.asarray(projections, dtype=np.float64).transpose(1, 0, 2) + return points, targets, cpar, calibs + + def _python_point_positions_reference( targets: np.ndarray, mm_par, @@ -685,7 +703,9 @@ def native_path(): targets, native_cpar, native_cals, native_vpar ) - compiled_python_result, compiled_python_timings = _benchmark(compiled_python_path) + compiled_python_result, compiled_python_timings = _benchmark( + compiled_python_path + ) legacy_python_result, legacy_python_timings = _benchmark(legacy_python_path) native_result, native_timings = _benchmark(native_path) python_points, python_rcm = compiled_python_result @@ -713,6 +733,55 @@ def native_path(): f"compiled-vs-native={compiled_vs_native:.2f}x" ) + @unittest.skipUnless( + HAS_OPTV and HAS_NATIVE_RECONSTRUCTION, + "optv native point_positions is not available", + ) + def test_multilayer_point_reconstruction_stress_timing(self): + """Compare compiled Python, legacy Python, and native multilayer reconstruction.""" + _expected_points, targets, cpar, calibs = ( + _build_multilayer_reconstruction_stress_case() + ) + native_cpar = to_native_control_par(cpar) + native_cals = [to_native_calibration(cal) for cal in calibs] + + def compiled_python_path(): + return orientation.multi_cam_point_positions(targets, cpar.mm, calibs) + + def legacy_python_path(): + return _python_point_positions_reference(targets, cpar.mm, calibs) + + def native_path(): + assert native_multi_cam_point_positions is not None + return native_multi_cam_point_positions(targets, native_cpar, native_cals) + + compiled_python_result, compiled_python_timings = _benchmark( + compiled_python_path + ) + legacy_python_result, legacy_python_timings = _benchmark(legacy_python_path) + native_result, native_timings = _benchmark(native_path) + python_points, python_rcm = compiled_python_result + legacy_points, legacy_rcm = legacy_python_result + native_points, native_rcm = native_result + + np.testing.assert_allclose(legacy_points, python_points, atol=1e-9) + np.testing.assert_allclose(legacy_rcm, python_rcm, atol=1e-9) + np.testing.assert_allclose(native_points, python_points, atol=1e-9) + np.testing.assert_allclose(native_rcm, python_rcm, atol=1e-9) + + compiled_vs_legacy = median(legacy_python_timings) / median( + compiled_python_timings + ) + compiled_vs_native = median(compiled_python_timings) / median(native_timings) + print( + "multilayer reconstruction stress benchmark: " + f"{_timing_summary('compiled-python', compiled_python_timings)}; " + f"{_timing_summary('legacy-python', legacy_python_timings)}; " + f"{_timing_summary('native', native_timings)}; " + f"compiled-vs-legacy={compiled_vs_legacy:.2f}x; " + f"compiled-vs-native={compiled_vs_native:.2f}x" + ) + @unittest.skipUnless( HAS_NATIVE_TRACKING, "optv native Tracker is not available", diff --git a/tests/test_point_positions.py b/tests/test_point_positions.py index a4eeef9..17a9ecb 100644 --- a/tests/test_point_positions.py +++ b/tests/test_point_positions.py @@ -11,8 +11,8 @@ from openptv_python.constants import COORD_UNUSED from openptv_python.imgcoord import flat_image_coordinates, image_coordinates from openptv_python.orientation import ( - point_position, match_detection_to_ref, + point_position, point_positions, weighted_dumbbell_precision, ) From 80c6bc21e0f43db26d6c30f802d08f1f331f1bb8 Mon Sep 17 00:00:00 2001 From: Alex Liberzon Date: Sun, 8 Mar 2026 19:35:25 +0200 Subject: [PATCH 19/27] updated tracking test --- tests/test_tracking_ground_truth.py | 739 ++++++++++++++++++++++++++++ 1 file changed, 739 insertions(+) create mode 100644 tests/test_tracking_ground_truth.py diff --git a/tests/test_tracking_ground_truth.py b/tests/test_tracking_ground_truth.py new file mode 100644 index 0000000..afb7847 --- /dev/null +++ b/tests/test_tracking_ground_truth.py @@ -0,0 +1,739 @@ +import os +import re +import shutil +import tempfile +import unittest +from contextlib import ExitStack, redirect_stdout +from dataclasses import dataclass +import io +from pathlib import Path +from unittest.mock import patch + +import numpy as np + +import openptv_python.track as track +from openptv_python._native_compat import HAS_OPTV +from openptv_python._native_convert import to_native_calibration, to_native_control_par +from openptv_python.calibration import Calibration +from openptv_python.constants import CORRES_NONE +from openptv_python.imgcoord import image_coordinates +from openptv_python.parameters import ControlPar, read_volume_par +from openptv_python.track import track_forward_start, trackcorr_c_finish, trackcorr_c_loop +from openptv_python.tracking_frame_buf import ( + Frame, + Pathinfo, + Target, + read_path_frame, + write_path_frame, + write_targets, +) +from openptv_python.tracking_run import tr_new +from openptv_python.trafo import metric_to_pixel + +try: + from optv.parameters import SequenceParams as NativeSequenceParams + from optv.parameters import TrackingParams as NativeTrackingParams + from optv.parameters import VolumeParams as NativeVolumeParams + from optv.tracker import Tracker as NativeTracker + + HAS_NATIVE_TRACKING = True +except ImportError: + NativeSequenceParams = None + NativeTrackingParams = None + NativeVolumeParams = None + NativeTracker = None + HAS_NATIVE_TRACKING = False + + +FRAMES = tuple(range(10001, 10007)) +PERMISSIVE_BOUNDS = { + "dacc": 2.0, + "dangle": 100.0, + "dvxmin": -0.02, + "dvxmax": 0.02, + "dvymin": -0.02, + "dvymax": 0.02, + "dvzmin": -0.02, + "dvzmax": 0.02, +} +STRICT_BOUNDS = { + "dacc": 2.0, + "dangle": 100.0, + "dvxmin": -0.005, + "dvxmax": 0.005, + "dvymin": -0.005, + "dvymax": 0.005, + "dvzmin": -0.005, + "dvzmax": 0.005, +} +EXPECTED_PERMISSIVE_SNAPSHOT = { + 10001: [(-1, 0, (2, 1, 1, -1), (0.0, 0.0, 0.0)), (-1, 1, (1, 0, 0, -1), (0.02, 0.01, 0.0))], + 10002: [(0, 0, (2, 1, 1, -1), (0.01, 0.0, 0.0)), (1, 1, (1, 0, 0, -1), (0.03, 0.01, 0.0))], + 10003: [(0, 0, (2, 1, 1, -1), (0.02, 0.0, 0.0)), (1, 1, (1, 0, 0, -1), (0.04, 0.01, 0.0))], + 10004: [(0, 0, (2, 1, 1, -1), (0.03, 0.0, 0.0)), (1, 1, (1, 0, 0, -1), (0.05, 0.01, 0.0))], + 10005: [(0, 0, (2, 1, 1, -1), (0.04, 0.0, 0.0)), (1, 1, (1, 0, 0, -1), (0.06, 0.01, 0.0))], + 10006: [(0, -2, (2, 1, 1, -1), (0.05, 0.0, 0.0)), (1, -2, (1, 0, 0, -1), (0.07, 0.01, 0.0))], +} +EXPECTED_STRICT_SNAPSHOT = { + frame: [ + (-1, -2, (2, 1, 1, -1), (round(0.01 * (frame - 10001), 5), 0.0, 0.0)), + (-1, -2, (1, 0, 0, -1), (round(0.02 + 0.01 * (frame - 10001), 5), 0.01, 0.0)), + ] + for frame in FRAMES +} +EXPECTED_PERMISSIVE_STEP_STATS = {frame: (2, 0) for frame in FRAMES[:-1]} +EXPECTED_STRICT_STEP_STATS = {frame: (0, 2) for frame in FRAMES[:-1]} +BAD_FILE_LOOKUP_PATTERN = re.compile(r"Can't open ascii file|\d{10}_targets") + +CONSTANT_DIAGONAL_TRACKS = { + 0: [np.array([0.01 * index, 0.01 * index, 0.01 * index]) for index in range(len(FRAMES))], +} +TURNING_TRACKS = { + 0: [ + np.array([0.0, 0.0, 0.0]), + np.array([0.01, 0.0, 0.001]), + np.array([0.018, 0.006, 0.002]), + np.array([0.023, 0.014, 0.003]), + np.array([0.025, 0.023, 0.004]), + np.array([0.024, 0.033, 0.005]), + ], +} +JUMP_SWITCH_TRACKS = { + 0: [ + np.array([0.0, 0.0, 0.0]), + np.array([0.01, 0.01, 0.01]), + np.array([0.02, 0.02, 0.02]), + None, + None, + None, + None, + ], + 1: [ + None, + None, + np.array([0.05, 0.05, 0.05]), + np.array([0.06, 0.06, 0.06]), + np.array([0.07, 0.07, 0.07]), + np.array([0.08, 0.08, 0.08]), + ], +} +BRANCH_SWITCH_TRACKS = { + 0: [ + np.array([0.0, 0.0, 0.0]), + np.array([0.01, 0.0, 0.001]), + np.array([0.018, 0.006, 0.002]), + None, + None, + None, + ], + 1: [ + None, + None, + None, + np.array([0.024, 0.022, 0.004]), + np.array([0.030, 0.032, 0.005]), + np.array([0.036, 0.042, 0.006]), + ], +} + + +@dataclass(frozen=True) +class TrackingSnapshotResult: + snapshot: dict[int, list[tuple[int, int, tuple[int, int, int, int], tuple[float, float, float]]]] + log: str + step_stats: dict[int, tuple[int, int]] + + +def _make_bounds(*, dv_limit: float, dacc: float, dangle: float) -> dict[str, float]: + return { + "dacc": dacc, + "dangle": dangle, + "dvxmin": -dv_limit, + "dvxmax": dv_limit, + "dvymin": -dv_limit, + "dvymax": dv_limit, + "dvzmin": -dv_limit, + "dvzmax": dv_limit, + } + + +def _rounded_position(pos: np.ndarray) -> tuple[float, float, float]: + return tuple(round(float(value), 5) for value in pos) + + +def _snapshot_graph(snapshot): + return { + frame: [(prev, next_, pos) for prev, next_, _corres, pos in rows] + for frame, rows in snapshot.items() + } + + +def _candidate_passes_tracking_gate( + prev_pos: np.ndarray, + curr_pos: np.ndarray, + cand_pos: np.ndarray, + bounds: dict[str, float], +) -> bool: + diff_pos = cand_pos - curr_pos + if not ( + bounds["dvxmin"] < diff_pos[0] < bounds["dvxmax"] + and bounds["dvymin"] < diff_pos[1] < bounds["dvymax"] + and bounds["dvzmin"] < diff_pos[2] < bounds["dvzmax"] + ): + return False + + pred_pos = track.search_volume_center_moving.py_func(prev_pos, curr_pos) + angle, acc = track.angle_acc.py_func(curr_pos, pred_pos, cand_pos) + return (acc < bounds["dacc"] and angle < bounds["dangle"]) or ( + acc < bounds["dacc"] / 10 + ) + + +def _single_track_chain_graph(positions: list[np.ndarray]): + return { + frame: [ + ( + -1 if index == 0 else 0, + -2 if index == len(FRAMES) - 1 else 0, + _rounded_position(pos), + ) + ] + for index, (frame, pos) in enumerate(zip(FRAMES, positions)) + } + + +def _single_track_delayed_chain_graph(positions: list[np.ndarray]): + return { + FRAMES[0]: [(-1, -2, _rounded_position(positions[0]))], + FRAMES[1]: [(-1, 0, _rounded_position(positions[1]))], + FRAMES[2]: [(0, 0, _rounded_position(positions[2]))], + FRAMES[3]: [(0, 0, _rounded_position(positions[3]))], + FRAMES[4]: [(0, 0, _rounded_position(positions[4]))], + FRAMES[5]: [(0, -2, _rounded_position(positions[5]))], + } + + +def _single_track_unlinked_graph(positions: list[np.ndarray]): + return { + frame: [(-1, -2, _rounded_position(pos))] + for frame, pos in zip(FRAMES, positions) + } + + +def _switch_graph(tracks, *, inherited: bool): + first_positions = tracks[0] + second_positions = tracks[1] + return { + FRAMES[0]: [(-1, 0, _rounded_position(first_positions[0]))], + FRAMES[1]: [(0, 0, _rounded_position(first_positions[1]))], + FRAMES[2]: [(0, 0 if inherited else -2, _rounded_position(first_positions[2]))], + FRAMES[3]: [((-1, 0)[inherited], 0, _rounded_position(second_positions[3]))], + FRAMES[4]: [(0, 0, _rounded_position(second_positions[4]))], + FRAMES[5]: [(0, -2, _rounded_position(second_positions[5]))], + } + + +EXPECTED_CONSTANT_DIAGONAL_CHAIN_GRAPH = _single_track_chain_graph( + CONSTANT_DIAGONAL_TRACKS[0] +) +EXPECTED_CONSTANT_DIAGONAL_REJECT_GRAPH = _single_track_unlinked_graph( + CONSTANT_DIAGONAL_TRACKS[0] +) +EXPECTED_TURNING_CHAIN_GRAPH = _single_track_delayed_chain_graph(TURNING_TRACKS[0]) +EXPECTED_TURNING_REJECT_GRAPH = _single_track_unlinked_graph(TURNING_TRACKS[0]) +EXPECTED_JUMP_FRESH_START_GRAPH = _switch_graph(JUMP_SWITCH_TRACKS, inherited=False) +EXPECTED_JUMP_FALSE_LINK_GRAPH = _switch_graph(JUMP_SWITCH_TRACKS, inherited=True) +EXPECTED_BRANCH_FRESH_START_GRAPH = _switch_graph(BRANCH_SWITCH_TRACKS, inherited=False) +EXPECTED_BRANCH_FALSE_LINK_GRAPH = _switch_graph(BRANCH_SWITCH_TRACKS, inherited=True) +EXPECTED_SINGLE_TRACK_LINK_STATS = {frame: (1, 0) for frame in FRAMES[:-1]} +EXPECTED_SINGLE_TRACK_REJECT_STATS = {frame: (0, 1) for frame in FRAMES[:-1]} +EXPECTED_TURNING_DELAYED_LINK_STATS = { + FRAMES[0]: (0, 1), + FRAMES[1]: (1, 0), + FRAMES[2]: (1, 0), + FRAMES[3]: (1, 0), + FRAMES[4]: (1, 0), +} + + +def _to_native_volume_par(vpar): + """Convert Python volume parameters into native VolumeParams.""" + if NativeVolumeParams is None: + raise RuntimeError("optv VolumeParams is not available") + + native_vpar = NativeVolumeParams() + native_vpar.set_X_lay(list(vpar.x_lay)) + native_vpar.set_Zmin_lay(list(vpar.z_min_lay)) + native_vpar.set_Zmax_lay(list(vpar.z_max_lay)) + native_vpar.set_cn(vpar.cn) + native_vpar.set_cnx(vpar.cnx) + native_vpar.set_cny(vpar.cny) + native_vpar.set_csumg(vpar.csumg) + native_vpar.set_eps0(vpar.eps0) + native_vpar.set_corrmin(vpar.corrmin) + return native_vpar + + +def _synthetic_track_positions(): + """Return deterministic per-frame 3D positions for two tracks.""" + return { + 0: [np.array([0.00 + 0.01 * index, 0.0, 0.0]) for index in range(len(FRAMES))], + 1: [np.array([0.02 + 0.01 * index, 0.01, 0.0]) for index in range(len(FRAMES))], + } + + +def _write_sequence_range(sequence_path: Path) -> None: + lines = sequence_path.read_text(encoding="utf-8").splitlines() + lines[-2] = str(FRAMES[0]) + lines[-1] = str(FRAMES[-1]) + sequence_path.write_text("\n".join(lines) + "\n", encoding="utf-8") + + +def _parse_tracking_step_stats(log: str) -> dict[int, tuple[int, int]]: + return { + int(frame): (int(links), int(lost)) + for frame, links, lost in re.findall( + r"step:\s*(\d+),.*?links:\s*(-?\d+),\s*lost:\s*(-?\d+)", + log, + re.DOTALL, + ) + } + + +def _write_synthetic_tracking_fixture( + workdir: Path, + tracks: dict[int, list[np.ndarray | None]] | None = None, + *, + add_distractor: bool = True, +) -> list[Calibration]: + """Write a clean deterministic tracking fixture into a temporary workspace.""" + source = Path("/home/user/Documents/GitHub/openptv-python/tests/testing_fodder") + shutil.copytree(source, workdir) + os.chdir(workdir) + + calibrations = [ + Calibration().from_file( + Path(f"cal/sym_cam{cam + 1}.tif.ori"), + Path("cal/cam1.tif.addpar"), + ) + for cam in range(3) + ] + + os.chdir(workdir / "track") + Path("res").mkdir() + Path("newpart").mkdir(exist_ok=True) + _write_sequence_range(Path("parameters/sequence_newpart.par")) + + cpar = ControlPar(3).from_file(Path("parameters/control_newpart.par")) + tracks = tracks or _synthetic_track_positions() + + for frame_index, frame_num in enumerate(FRAMES): + frame = Frame(3, 16) + cam_targets = [[] for _ in range(3)] + correspond_indices = { + track_id: [CORRES_NONE] * 4 + for track_id, positions in tracks.items() + if positions[frame_index] is not None + } + + for track_id, positions in tracks.items(): + pos = positions[frame_index] + if pos is None: + continue + projections = [ + image_coordinates(np.array([pos]), cal, cpar.mm)[0] + for cal in calibrations + ] + for cam, xy in enumerate(projections): + px, py = metric_to_pixel(xy[0], xy[1], cpar) + cam_targets[cam].append( + Target( + pnr=len(cam_targets[cam]), + x=float(px), + y=float(py), + n=10, + nx=3, + ny=3, + sumg=100, + tnr=track_id, + ) + ) + + # Keep one unmatched distractor near the search area in a single camera. + if add_distractor: + cam_targets[0].append( + Target( + pnr=99, + x=960.0, + y=540.0 + frame_index, + n=8, + nx=2, + ny=2, + sumg=50, + tnr=CORRES_NONE, + ) + ) + + for cam in range(3): + cam_targets[cam].sort(key=lambda target: target.y) + write_targets( + cam_targets[cam], + len(cam_targets[cam]), + f"newpart/cam{cam + 1}.%05d", + frame_num, + ) + for idx, target in enumerate(cam_targets[cam]): + if target.tnr in correspond_indices: + correspond_indices[target.tnr][cam] = idx + + active_track_ids = sorted(correspond_indices) + frame.num_parts = len(active_track_ids) + frame.correspond = np.recarray((frame.num_parts,), dtype=frame.correspond.dtype) + frame.path_info = [Pathinfo() for _ in range(frame.num_parts)] + + for row_index, track_id in enumerate(active_track_ids): + frame.correspond[row_index].nr = track_id + 1 + frame.correspond[row_index].p = np.array( + correspond_indices[track_id], + dtype=np.int32, + ) + frame.path_info[row_index].x = tracks[track_id][frame_index] + + write_path_frame( + frame.correspond, + frame.path_info, + frame.num_parts, + "res/particles", + "res/linkage", + "res/whatever", + frame_num, + ) + + return calibrations + + +def _snapshot_tracking_outputs() -> dict[int, list[tuple[int, int, tuple[int, int, int, int], tuple[float, float, float]]]]: + """Read the written tracking outputs into a stable frame-by-frame snapshot.""" + snapshots = {} + for frame in FRAMES: + correspond, path_info = read_path_frame( + "res/particles", + "res/linkage", + "res/whatever", + frame, + ) + snapshots[frame] = [ + ( + int(path.prev_frame), + int(path.next_frame), + tuple(int(value) for value in correspond[idx].p), + tuple(round(float(value), 5) for value in path.x), + ) + for idx, path in enumerate(path_info) + ] + return snapshots + + +def _run_python_tracking_snapshot( + mode: str, + bounds: dict[str, float], + *, + tracks: dict[int, list[np.ndarray | None]] | None = None, + add_distractor: bool = True, +) -> TrackingSnapshotResult: + """Run the Python tracker in either compiled or patched-Python mode.""" + with tempfile.TemporaryDirectory() as tmp_dir: + workdir = Path(tmp_dir) / "testing_fodder" + original_cwd = Path.cwd() + try: + calibrations = _write_synthetic_tracking_fixture( + workdir, + tracks=tracks, + add_distractor=add_distractor, + ) + run = tr_new( + Path("parameters/sequence_newpart.par"), + Path("parameters/track.par"), + Path("parameters/criteria.par"), + Path("parameters/control_newpart.par"), + 4, + 16, + "res/particles", + "res/linkage", + "res/whatever", + calibrations, + 0.1, + ) + run.tpar = run.tpar._replace(add=0, **bounds) + + with ExitStack() as stack: + if mode == "python": + stack.enter_context( + patch.object( + track, + "search_volume_center_moving", + track.search_volume_center_moving.py_func, + ) + ) + stack.enter_context( + patch.object( + track, + "pos3d_in_bounds", + track.pos3d_in_bounds.py_func, + ) + ) + stack.enter_context( + patch.object( + track, + "angle_acc", + track.angle_acc.py_func, + ) + ) + + def run_tracker() -> None: + track_forward_start(run) + for step in range(run.seq_par.first, run.seq_par.last): + trackcorr_c_loop(run, step) + trackcorr_c_finish(run, run.seq_par.last) + + log_buffer = io.StringIO() + with redirect_stdout(log_buffer): + run_tracker() + log = log_buffer.getvalue() + return TrackingSnapshotResult( + snapshot=_snapshot_tracking_outputs(), + log=log, + step_stats=_parse_tracking_step_stats(log), + ) + finally: + os.chdir(original_cwd) + + +def _run_native_tracking_snapshot( + bounds: dict[str, float], + *, + tracks: dict[int, list[np.ndarray | None]] | None = None, + add_distractor: bool = True, +): + """Run the native optv tracker on the same synthetic fixture.""" + if not HAS_NATIVE_TRACKING or NativeTracker is None or NativeTrackingParams is None: + raise RuntimeError("optv Tracker is not available") + + with tempfile.TemporaryDirectory() as tmp_dir: + workdir = Path(tmp_dir) / "testing_fodder" + original_cwd = Path.cwd() + try: + calibrations = _write_synthetic_tracking_fixture( + workdir, + tracks=tracks, + add_distractor=add_distractor, + ) + cpar = ControlPar(3).from_file(Path("parameters/control_newpart.par")) + cpar.img_base_name = [f"newpart/cam{cam + 1}." for cam in range(3)] + vpar = read_volume_par(Path("parameters/criteria.par")) + + native_tpar = NativeTrackingParams() + native_tpar.read_track_par("parameters/track.par") + native_tpar.set_add(0) + native_tpar.set_dacc(bounds["dacc"]) + native_tpar.set_dangle(bounds["dangle"]) + native_tpar.set_dvxmin(bounds["dvxmin"]) + native_tpar.set_dvxmax(bounds["dvxmax"]) + native_tpar.set_dvymin(bounds["dvymin"]) + native_tpar.set_dvymax(bounds["dvymax"]) + native_tpar.set_dvzmin(bounds["dvzmin"]) + native_tpar.set_dvzmax(bounds["dvzmax"]) + + native_spar = NativeSequenceParams(num_cams=3) + native_spar.read_sequence_par("parameters/sequence_newpart.par", 3) + native_spar.set_first(FRAMES[0]) + native_spar.set_last(FRAMES[-1]) + for cam in range(3): + native_spar.set_img_base_name(cam, f"newpart/cam{cam + 1}.") + + tracker = NativeTracker( + to_native_control_par(cpar), + _to_native_volume_par(vpar), + native_tpar, + native_spar, + [to_native_calibration(cal) for cal in calibrations], + naming={ + "corres": "res/particles", + "linkage": "res/linkage", + "prio": "res/whatever", + }, + flatten_tol=0.1, + ) + tracker.full_forward() + return TrackingSnapshotResult( + snapshot=_snapshot_tracking_outputs(), + log="", + step_stats={}, + ) + finally: + os.chdir(original_cwd) + + +class TestTrackingGroundTruth(unittest.TestCase): + """Ground-truth tracking comparisons across Python and native backends.""" + + def assert_python_modes_match_graph( + self, + *, + tracks, + bounds, + expected_graph, + expected_step_stats=None, + add_distractor=False, + ): + compiled_snapshot = _run_python_tracking_snapshot( + "python+numba", + bounds, + tracks=tracks, + add_distractor=add_distractor, + ) + python_snapshot = _run_python_tracking_snapshot( + "python", + bounds, + tracks=tracks, + add_distractor=add_distractor, + ) + + self.assertEqual(_snapshot_graph(compiled_snapshot.snapshot), expected_graph) + self.assertEqual(_snapshot_graph(python_snapshot.snapshot), expected_graph) + self.assertEqual(compiled_snapshot.snapshot, python_snapshot.snapshot) + if expected_step_stats is not None: + self.assertEqual(compiled_snapshot.step_stats, expected_step_stats) + self.assertEqual(python_snapshot.step_stats, expected_step_stats) + self.assertNotRegex(compiled_snapshot.log, BAD_FILE_LOOKUP_PATTERN) + self.assertNotRegex(python_snapshot.log, BAD_FILE_LOOKUP_PATTERN) + + def test_tracking_exact_link_graph_matches_ground_truth_across_backends(self): + """Match the exact permissive link graph across Python, Numba, and optv.""" + expected = EXPECTED_PERMISSIVE_SNAPSHOT + compiled_snapshot = _run_python_tracking_snapshot("python+numba", PERMISSIVE_BOUNDS) + python_snapshot = _run_python_tracking_snapshot("python", PERMISSIVE_BOUNDS) + + self.assertEqual(compiled_snapshot.snapshot, expected) + self.assertEqual(python_snapshot.snapshot, expected) + self.assertEqual(compiled_snapshot.snapshot, python_snapshot.snapshot) + self.assertEqual(compiled_snapshot.step_stats, EXPECTED_PERMISSIVE_STEP_STATS) + self.assertEqual(python_snapshot.step_stats, EXPECTED_PERMISSIVE_STEP_STATS) + self.assertNotRegex(compiled_snapshot.log, BAD_FILE_LOOKUP_PATTERN) + self.assertNotRegex(python_snapshot.log, BAD_FILE_LOOKUP_PATTERN) + + if HAS_OPTV and HAS_NATIVE_TRACKING: + native_snapshot = _run_native_tracking_snapshot(PERMISSIVE_BOUNDS) + self.assertEqual(native_snapshot.snapshot, expected) + self.assertEqual(native_snapshot.snapshot, compiled_snapshot.snapshot) + + def test_tracking_strict_bounds_reject_links_by_design(self): + """Reject links consistently across backends when the velocity window excludes the motion.""" + expected = EXPECTED_STRICT_SNAPSHOT + compiled_snapshot = _run_python_tracking_snapshot("python+numba", STRICT_BOUNDS) + python_snapshot = _run_python_tracking_snapshot("python", STRICT_BOUNDS) + + self.assertEqual(compiled_snapshot.snapshot, expected) + self.assertEqual(python_snapshot.snapshot, expected) + self.assertEqual(compiled_snapshot.snapshot, python_snapshot.snapshot) + self.assertEqual(compiled_snapshot.step_stats, EXPECTED_STRICT_STEP_STATS) + self.assertEqual(python_snapshot.step_stats, EXPECTED_STRICT_STEP_STATS) + self.assertNotRegex(compiled_snapshot.log, BAD_FILE_LOOKUP_PATTERN) + self.assertNotRegex(python_snapshot.log, BAD_FILE_LOOKUP_PATTERN) + + def test_tracking_velocity_window_has_valid_range_between_reject_and_false_link(self): + """A symmetric 3D velocity window must be wide enough for truth but narrow enough to block jumps.""" + self.assert_python_modes_match_graph( + tracks=CONSTANT_DIAGONAL_TRACKS, + bounds=_make_bounds(dv_limit=0.005, dacc=0.2, dangle=100.0), + expected_graph=EXPECTED_CONSTANT_DIAGONAL_REJECT_GRAPH, + expected_step_stats=EXPECTED_SINGLE_TRACK_REJECT_STATS, + ) + self.assert_python_modes_match_graph( + tracks=CONSTANT_DIAGONAL_TRACKS, + bounds=_make_bounds(dv_limit=0.015, dacc=0.2, dangle=100.0), + expected_graph=EXPECTED_CONSTANT_DIAGONAL_CHAIN_GRAPH, + expected_step_stats=EXPECTED_SINGLE_TRACK_LINK_STATS, + ) + self.assertFalse( + _candidate_passes_tracking_gate( + np.array([0.01, 0.01, 0.01]), + np.array([0.02, 0.02, 0.02]), + np.array([0.05, 0.05, 0.04]), + _make_bounds(dv_limit=0.015, dacc=0.2, dangle=100.0), + ) + ) + self.assertTrue( + _candidate_passes_tracking_gate( + np.array([0.01, 0.01, 0.01]), + np.array([0.02, 0.02, 0.02]), + np.array([0.05, 0.05, 0.04]), + _make_bounds(dv_limit=0.05, dacc=0.2, dangle=100.0), + ) + ) + + def test_tracking_dacc_has_valid_range_between_reject_and_false_link(self): + """Acceleration bounds should admit smooth curvature but still reject identity-switch jumps.""" + self.assert_python_modes_match_graph( + tracks=TURNING_TRACKS, + bounds=_make_bounds(dv_limit=0.02, dacc=0.002, dangle=40.0), + expected_graph=EXPECTED_TURNING_REJECT_GRAPH, + expected_step_stats=EXPECTED_SINGLE_TRACK_REJECT_STATS, + ) + self.assert_python_modes_match_graph( + tracks=TURNING_TRACKS, + bounds=_make_bounds(dv_limit=0.02, dacc=0.02, dangle=40.0), + expected_graph=EXPECTED_TURNING_CHAIN_GRAPH, + expected_step_stats=EXPECTED_TURNING_DELAYED_LINK_STATS, + ) + self.assertFalse( + _candidate_passes_tracking_gate( + np.array([0.01, 0.01, 0.01]), + np.array([0.02, 0.02, 0.02]), + np.array([0.05, 0.05, 0.04]), + _make_bounds(dv_limit=0.05, dacc=0.02, dangle=100.0), + ) + ) + self.assertTrue( + _candidate_passes_tracking_gate( + np.array([0.01, 0.01, 0.01]), + np.array([0.02, 0.02, 0.02]), + np.array([0.05, 0.05, 0.04]), + _make_bounds(dv_limit=0.05, dacc=0.06, dangle=100.0), + ) + ) + + def test_tracking_dangle_has_valid_range_between_reject_and_false_link(self): + """Angular bounds should admit smooth turning but still reject a sharp branch switch.""" + self.assert_python_modes_match_graph( + tracks=TURNING_TRACKS, + bounds=_make_bounds(dv_limit=0.02, dacc=0.02, dangle=10.0), + expected_graph=EXPECTED_TURNING_REJECT_GRAPH, + expected_step_stats=EXPECTED_SINGLE_TRACK_REJECT_STATS, + ) + self.assert_python_modes_match_graph( + tracks=TURNING_TRACKS, + bounds=_make_bounds(dv_limit=0.02, dacc=0.02, dangle=30.0), + expected_graph=EXPECTED_TURNING_CHAIN_GRAPH, + expected_step_stats=EXPECTED_TURNING_DELAYED_LINK_STATS, + ) + self.assertFalse( + _candidate_passes_tracking_gate( + np.array([0.01, 0.0, 0.001]), + np.array([0.018, 0.006, 0.002]), + np.array([0.024, 0.022, 0.004]), + _make_bounds(dv_limit=0.02, dacc=0.02, dangle=30.0), + ) + ) + self.assertTrue( + _candidate_passes_tracking_gate( + np.array([0.01, 0.0, 0.001]), + np.array([0.018, 0.006, 0.002]), + np.array([0.024, 0.022, 0.004]), + _make_bounds(dv_limit=0.02, dacc=0.02, dangle=40.0), + ) + ) + + +if __name__ == "__main__": + unittest.main() \ No newline at end of file From 4f1765b86acb696dbea98856155a60cdc09ad6f0 Mon Sep 17 00:00:00 2001 From: Alex Liberzon Date: Sat, 14 Mar 2026 23:41:09 +0200 Subject: [PATCH 20/27] fixed tests --- openptv_python/track.py | 9 +++-- tests/test_tracking.py | 11 +++++ tests/test_tracking_ground_truth.py | 62 ++++++++++++++++++++++------- 3 files changed, 65 insertions(+), 17 deletions(-) diff --git a/openptv_python/track.py b/openptv_python/track.py index 8b19443..f9e2697 100644 --- a/openptv_python/track.py +++ b/openptv_python/track.py @@ -261,9 +261,12 @@ def angle_acc(start: np.ndarray, pred: np.ndarray, cand: np.ndarray) -> np.ndarr norm_start_pred = np.linalg.norm(start - pred) norm_start_cand = np.linalg.norm(start - cand) - angle = (200.0 / np.pi) * np.arccos( - dot_product / (norm_start_pred * norm_start_cand) - ) + if norm_start_pred == 0.0 or norm_start_cand == 0.0: + angle = 0.0 + else: + cosine = dot_product / (norm_start_pred * norm_start_cand) + cosine = min(1.0, max(-1.0, cosine)) + angle = (200.0 / np.pi) * np.arccos(cosine) return np.array([angle, acc]) diff --git a/tests/test_tracking.py b/tests/test_tracking.py index b4a0afe..b329788 100644 --- a/tests/test_tracking.py +++ b/tests/test_tracking.py @@ -180,6 +180,17 @@ def test_angle_acc(self): ) self.assertTrue(isclose(acc, 0.0, rel_tol=EPS), f"Expected 0.0 but found {acc}") + cand = np.array([2.0, 2.0, 2.0]) + + angle, acc = angle_acc(start, pred, cand) + self.assertTrue( + isclose(angle, 0.0, rel_tol=EPS), f"Expected 0.0 but found {angle}" + ) + self.assertTrue( + isclose(acc, np.sqrt(3.0), rel_tol=EPS), + f"Expected {np.sqrt(3.0)} but found {acc}", + ) + cand = vec_scalar_mul(pred, -1) angle, acc = angle_acc(start, pred, cand) diff --git a/tests/test_tracking_ground_truth.py b/tests/test_tracking_ground_truth.py index afb7847..026954b 100644 --- a/tests/test_tracking_ground_truth.py +++ b/tests/test_tracking_ground_truth.py @@ -1,3 +1,4 @@ +import io import os import re import shutil @@ -5,7 +6,6 @@ import unittest from contextlib import ExitStack, redirect_stdout from dataclasses import dataclass -import io from pathlib import Path from unittest.mock import patch @@ -18,7 +18,11 @@ from openptv_python.constants import CORRES_NONE from openptv_python.imgcoord import image_coordinates from openptv_python.parameters import ControlPar, read_volume_par -from openptv_python.track import track_forward_start, trackcorr_c_finish, trackcorr_c_loop +from openptv_python.track import ( + track_forward_start, + trackcorr_c_finish, + trackcorr_c_loop, +) from openptv_python.tracking_frame_buf import ( Frame, Pathinfo, @@ -67,12 +71,30 @@ "dvzmax": 0.005, } EXPECTED_PERMISSIVE_SNAPSHOT = { - 10001: [(-1, 0, (2, 1, 1, -1), (0.0, 0.0, 0.0)), (-1, 1, (1, 0, 0, -1), (0.02, 0.01, 0.0))], - 10002: [(0, 0, (2, 1, 1, -1), (0.01, 0.0, 0.0)), (1, 1, (1, 0, 0, -1), (0.03, 0.01, 0.0))], - 10003: [(0, 0, (2, 1, 1, -1), (0.02, 0.0, 0.0)), (1, 1, (1, 0, 0, -1), (0.04, 0.01, 0.0))], - 10004: [(0, 0, (2, 1, 1, -1), (0.03, 0.0, 0.0)), (1, 1, (1, 0, 0, -1), (0.05, 0.01, 0.0))], - 10005: [(0, 0, (2, 1, 1, -1), (0.04, 0.0, 0.0)), (1, 1, (1, 0, 0, -1), (0.06, 0.01, 0.0))], - 10006: [(0, -2, (2, 1, 1, -1), (0.05, 0.0, 0.0)), (1, -2, (1, 0, 0, -1), (0.07, 0.01, 0.0))], + 10001: [ + (-1, 0, (2, 1, 1, -1), (0.0, 0.0, 0.0)), + (-1, 1, (1, 0, 0, -1), (0.02, 0.01, 0.0)), + ], + 10002: [ + (0, 0, (2, 1, 1, -1), (0.01, 0.0, 0.0)), + (1, 1, (1, 0, 0, -1), (0.03, 0.01, 0.0)), + ], + 10003: [ + (0, 0, (2, 1, 1, -1), (0.02, 0.0, 0.0)), + (1, 1, (1, 0, 0, -1), (0.04, 0.01, 0.0)), + ], + 10004: [ + (0, 0, (2, 1, 1, -1), (0.03, 0.0, 0.0)), + (1, 1, (1, 0, 0, -1), (0.05, 0.01, 0.0)), + ], + 10005: [ + (0, 0, (2, 1, 1, -1), (0.04, 0.0, 0.0)), + (1, 1, (1, 0, 0, -1), (0.06, 0.01, 0.0)), + ], + 10006: [ + (0, -2, (2, 1, 1, -1), (0.05, 0.0, 0.0)), + (1, -2, (1, 0, 0, -1), (0.07, 0.01, 0.0)), + ], } EXPECTED_STRICT_SNAPSHOT = { frame: [ @@ -86,7 +108,10 @@ BAD_FILE_LOOKUP_PATTERN = re.compile(r"Can't open ascii file|\d{10}_targets") CONSTANT_DIAGONAL_TRACKS = { - 0: [np.array([0.01 * index, 0.01 * index, 0.01 * index]) for index in range(len(FRAMES))], + 0: [ + np.array([0.01 * index, 0.01 * index, 0.01 * index]) + for index in range(len(FRAMES)) + ], } TURNING_TRACKS = { 0: [ @@ -139,7 +164,10 @@ @dataclass(frozen=True) class TrackingSnapshotResult: - snapshot: dict[int, list[tuple[int, int, tuple[int, int, int, int], tuple[float, float, float]]]] + snapshot: dict[ + int, + list[tuple[int, int, tuple[int, int, int, int], tuple[float, float, float]]], + ] log: str step_stats: dict[int, tuple[int, int]] @@ -412,7 +440,9 @@ def _write_synthetic_tracking_fixture( return calibrations -def _snapshot_tracking_outputs() -> dict[int, list[tuple[int, int, tuple[int, int, int, int], tuple[float, float, float]]]]: +def _snapshot_tracking_outputs() -> dict[ + int, list[tuple[int, int, tuple[int, int, int, int], tuple[float, float, float]]] +]: """Read the written tracking outputs into a stable frame-by-frame snapshot.""" snapshots = {} for frame in FRAMES: @@ -611,7 +641,9 @@ def assert_python_modes_match_graph( def test_tracking_exact_link_graph_matches_ground_truth_across_backends(self): """Match the exact permissive link graph across Python, Numba, and optv.""" expected = EXPECTED_PERMISSIVE_SNAPSHOT - compiled_snapshot = _run_python_tracking_snapshot("python+numba", PERMISSIVE_BOUNDS) + compiled_snapshot = _run_python_tracking_snapshot( + "python+numba", PERMISSIVE_BOUNDS + ) python_snapshot = _run_python_tracking_snapshot("python", PERMISSIVE_BOUNDS) self.assertEqual(compiled_snapshot.snapshot, expected) @@ -641,7 +673,9 @@ def test_tracking_strict_bounds_reject_links_by_design(self): self.assertNotRegex(compiled_snapshot.log, BAD_FILE_LOOKUP_PATTERN) self.assertNotRegex(python_snapshot.log, BAD_FILE_LOOKUP_PATTERN) - def test_tracking_velocity_window_has_valid_range_between_reject_and_false_link(self): + def test_tracking_velocity_window_has_valid_range_between_reject_and_false_link( + self, + ): """A symmetric 3D velocity window must be wide enough for truth but narrow enough to block jumps.""" self.assert_python_modes_match_graph( tracks=CONSTANT_DIAGONAL_TRACKS, @@ -736,4 +770,4 @@ def test_tracking_dangle_has_valid_range_between_reject_and_false_link(self): if __name__ == "__main__": - unittest.main() \ No newline at end of file + unittest.main() From eeaedc8380566ae80df4d8870d857dfbea2a2fca Mon Sep 17 00:00:00 2001 From: Alex Liberzon Date: Sun, 15 Mar 2026 00:56:18 +0200 Subject: [PATCH 21/27] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 7cee62b..2a48625 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ PROJECT := openptv_python CONDA := conda CONDAFLAGS := COV_REPORT := html -PYTHON ?= .venv/bin/python +PYTHON ?= python default: qa unit-tests type-check From cf81b7ada9db356cdb9e9fc7d0a267096afab65f Mon Sep 17 00:00:00 2001 From: Alex Liberzon Date: Sun, 15 Mar 2026 01:15:43 +0200 Subject: [PATCH 22/27] uv sync vs uv sync --extra dev --- .github/workflows/on-push.yml | 6 +- .github/workflows/static.yml | 14 +- README.html | 679 ------------------- README.md | 73 +- ci/environment-ci.yml | 2 + docs/index.md | 3 +- plans.md | 2 - pyproject.toml | 39 +- uv.lock | 1175 +++++++++++++++++---------------- 9 files changed, 703 insertions(+), 1290 deletions(-) delete mode 100644 README.html delete mode 100644 plans.md diff --git a/.github/workflows/on-push.yml b/.github/workflows/on-push.yml index b04fbfb..6251794 100644 --- a/.github/workflows/on-push.yml +++ b/.github/workflows/on-push.yml @@ -25,7 +25,7 @@ jobs: - uses: actions/checkout@v6 - uses: actions/setup-python@v6 with: - python-version: 3.x + python-version: '3.12' - uses: pre-commit/action@v3.0.1 combine-environments: @@ -35,7 +35,7 @@ jobs: - uses: actions/checkout@v6 - uses: actions/setup-python@v6 with: - python-version: 3.x + python-version: '3.12' - name: Install conda-merge run: | python -m pip install conda-merge @@ -55,7 +55,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ['3.11', '3.12'] + python-version: ['3.12', '3.13'] steps: - uses: actions/checkout@v6 diff --git a/.github/workflows/static.yml b/.github/workflows/static.yml index 6d12253..2391e33 100644 --- a/.github/workflows/static.yml +++ b/.github/workflows/static.yml @@ -31,13 +31,23 @@ jobs: steps: - name: Checkout uses: actions/checkout@v4 + - name: Setup Python + uses: actions/setup-python@v6 + with: + python-version: '3.12' + - name: Install documentation dependencies + run: | + python -m pip install --upgrade pip + python -m pip install -e ".[docs]" + - name: Build documentation + run: | + make docs-build - name: Setup Pages uses: actions/configure-pages@v4 - name: Upload artifact uses: actions/upload-pages-artifact@v3 with: - # Upload entire repository - path: '.' + path: docs/_build/html - name: Deploy to GitHub Pages id: deployment uses: actions/deploy-pages@v4 diff --git a/README.html b/README.html deleted file mode 100644 index 7dbf403..0000000 --- a/README.html +++ /dev/null @@ -1,679 +0,0 @@ - - - - - - - - - -readme - - - - - - - - - - - - - - - - - - - - -
- -
- - - - -
-

openptv-python

-

Python version of the OpenPTV library - this is a work in progress

-
-

What This Repo Provides

-

openptv-python keeps the Python API as the main interface and combines three execution modes behind that API:

-
    -
  • Pure Python: the reference implementation and the easiest path for reading, debugging, and extending the code.
  • -
  • Python + Numba: several hot kernels are JIT-compiled automatically on first use, so the Python implementation still benefits from acceleration.
  • -
  • Native optv bindings: selected operations reuse the native OpenPTV implementation when the optv package is available.
  • -
-

At the moment, automatic native delegation is implemented for image preprocessing and full-frame target recognition. The rest of the library keeps the same Python API and remains usable even when those native paths are not in use.

-
-
-

How this is started

-

This work started from the https://github.com/OpenPTV/openptv/tree/pure_python branch. It’s a long-standing idea to convert all the C code to Python and now it’s possible with ChatGPT to save a lot of typing time.

-

This repo is created using a cookiecutter and the rest of the readme describes the way to work with this structure

-
-
-

Supported Python Versions

-

The project currently supports Python >=3.12,<3.14.

-
-
-

Installation

- -
-

Alternative: conda + pip

-
conda create -n openptv-python -c conda-forge python=3.12
-conda activate openptv-python
-pip install -e .
-
-
-

What gets installed

-
    -
  • numba is part of the default dependency set and accelerates selected Python kernels automatically after the first call.
  • -
  • optv is part of the default dependency set and enables native interop on supported platforms.
  • -
  • The public API stays the same regardless of which backend is active.
  • -
-
-
-
-

Backend Behavior

-
-

Pure Python backend

-

This is the base implementation for the whole library. It is always the source of truth for the Python API and remains the fallback behavior for code paths that are not delegated to optv.

-
-
-

Python + Numba backend

-

Numba accelerates selected computational kernels inside the Python implementation. This is automatic; there is no separate API to enable it. Expect the first call to a JIT-compiled function to be slower due to compilation, with later calls running faster.

-
-
-

Native optv backend

-

When optv imports successfully, openptv-python automatically reuses native implementations for:

-
    -
  • image preprocessing
  • -
  • full-frame target recognition / segmentation
  • -
-

These native paths are validated against the Python implementation by parity tests, so results stay backend-independent.

-
-
-

Backend Capability Table

- ------ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
OperationPure PythonPython + NumbaNative optv
Image preprocessingYesYesYes, automatic delegation
Target recognition / segmentationYesYesYes, automatic delegation
Point reconstructionYesPartial internal kernelsNot used by default
Correspondence search / stereo matchingYesPartial internal kernelsNot used by default
TrackingYesPartial internal kernelsNot used by default
Sequence parameter I/OYesNoAvailable in native bindings
-

Not used by default means the native path exists in benchmarks or conversion helpers, but the regular openptv-python runtime path still uses the Python implementation unless that operation is explicitly integrated later.

-
-
-
-

Getting Started

-
-

1. Install the project

-

Use one of the installation methods above.

-
-
-

2. Verify imports

-
uv run python - <<'PY'
-import openptv_python
-import numba
-import optv
-
-print("openptv_python ok")
-print("numba ok", numba.__version__)
-print("optv ok", optv.__version__)
-PY
-
-
-

3. Start using the Python API

-
>>> import openptv_python
-
-
-

4. Run the test suite

-
uv run make
-

Stress and performance tests are part of the default suite now. If you need a faster validation pass locally, you can skip them explicitly:

-
OPENPTV_SKIP_STRESS_BENCHMARKS=1 uv run make
-
-
-

Workflow for developers/contributors

-

For the best experience create a new conda environment (e.g. DEVELOP) with Python 3.12:

-
conda create -n openptv-python -c conda-forge python=3.12
-conda activate openptv-python
-

Before pushing to GitHub, run the following commands:

-
    -
  1. Update conda environment: make conda-env-update or uv venv and source .venv/bin/activate followed by uv sync --upgrade
  2. -
  3. Install this package: pip install -e .
  4. -
  5. Sync with the latest template (optional): make template-update
  6. -
  7. Run quality assurance checks: make qa
  8. -
  9. Run tests: make unit-tests
  10. -
  11. Run the static type checker: make type-check
  12. -
  13. Build the documentation (see Sphinx tutorial): make docs-build
  14. -
-
-
-

License

-
Copyright 2023, OpenPTV consortium.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-
-
-
- -
- - -
- - - - - diff --git a/README.md b/README.md index c2b7bbc..6466639 100644 --- a/README.md +++ b/README.md @@ -33,9 +33,11 @@ The project currently supports Python `>=3.12,<3.14`. ## Installation -### Recommended: uv +### Default user install -Create the environment and install all project dependencies: +#### Recommended: uv + +Create the environment and install the runtime dependencies: ```bash uv venv @@ -43,25 +45,57 @@ source .venv/bin/activate uv sync ``` -This is the simplest way to get the current default stack, including NumPy, -SciPy, Numba, and the `optv` native bindings when they are available for your -platform and Python version. +This gives you the standard runtime stack: NumPy, SciPy, Numba, and YAML +support. + +If you also want native `optv` delegation when bindings are available for your +platform and Python version, install the optional extra: + +```bash +uv sync --extra native +``` + +#### Alternative: pip + +```bash +conda create -n openptv-python -c conda-forge python=3.12 +conda activate openptv-python +pip install . +``` + +Optional native bindings: + +```bash +pip install ".[native]" +``` + +### Developer install + +#### Recommended: uv + +```bash +uv venv +source .venv/bin/activate +uv sync --extra dev +``` -### Alternative: conda + pip +#### Alternative: conda + pip ```bash conda create -n openptv-python -c conda-forge python=3.12 conda activate openptv-python -pip install -e . +pip install -e ".[dev]" ``` ### What gets installed -- `numba` is part of the default dependency set and accelerates selected Python - kernels automatically after the first call. -- `optv` is part of the default dependency set and enables native interop on - supported platforms. -- The public API stays the same regardless of which backend is active. +- The default install contains the runtime dependencies only. +- The optional `native` extra adds `optv` bindings for automatic native + delegation on supported platforms. +- The optional `dev` extra adds test, docs, typing, and pre-commit tooling for + contributors. +- The public API stays the same regardless of which backend extras are + installed. ## Backend Behavior @@ -116,11 +150,15 @@ Use one of the installation methods above. uv run python - <<'PY' import openptv_python import numba -import optv +try: + import optv +except ImportError: + print("optv not installed; native delegation disabled") +else: + print("optv ok", optv.__version__) print("openptv_python ok") print("numba ok", numba.__version__) -print("optv ok", optv.__version__) PY ``` @@ -153,10 +191,11 @@ conda create -n openptv-python -c conda-forge python=3.12 conda activate openptv-python ``` -Before pushing to GitHub, run the following commands: +Before pushing to GitHub, use the developer install above and then run the +following commands: -1. Update conda environment: `make conda-env-update` or `uv venv` and `source .venv/bin/activate` followed by `uv sync --upgrade` -1. Install this package: `pip install -e .` +1. Update conda environment: `make conda-env-update` or `uv venv` and `source .venv/bin/activate` followed by `uv sync --extra dev --upgrade` +1. If you are using pip instead of uv, install the editable developer environment: `pip install -e ".[dev]"` 1. Sync with the latest [template](https://github.com/ecmwf-projects/cookiecutter-conda-package) (optional): `make template-update` 1. Run quality assurance checks: `make qa` 1. Run tests: `make unit-tests` diff --git a/ci/environment-ci.yml b/ci/environment-ci.yml index b7136d7..7ea50bc 100644 --- a/ci/environment-ci.yml +++ b/ci/environment-ci.yml @@ -7,6 +7,8 @@ dependencies: - mypy - myst-parser - pip +- pip: + - types-PyYAML - pre-commit - pydata-sphinx-theme - pytest diff --git a/docs/index.md b/docs/index.md index d414197..58315f4 100644 --- a/docs/index.md +++ b/docs/index.md @@ -5,7 +5,8 @@ Python version of the OpenPTV library. Start with the README for installation and backend selection details. The short version is: -- install with `uv sync` on Python 3.12 or 3.13 +- install runtime dependencies with `uv sync` on Python 3.12 or 3.13 +- install contributor tooling with `uv sync --extra dev` - use the same Python API regardless of backend - get pure Python behavior everywhere, Numba acceleration in selected kernels, and automatic native `optv` delegation for preprocessing and full-frame diff --git a/plans.md b/plans.md deleted file mode 100644 index a7521ae..0000000 --- a/plans.md +++ /dev/null @@ -1,2 +0,0 @@ -openptv-python-2026-03-07-backend-design.md -openptv-python-2026-03-07-pybind-delegation-map.md diff --git a/pyproject.toml b/pyproject.toml index 561046f..14aa1e2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,32 +10,49 @@ classifiers = [ "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", "Topic :: Scientific/Engineering" ] dependencies = [ - "mypy>=1.19.1", - "myst-parser>=5.0.0", "numba>=0.64.0", "numpy>=2.4.2", + "pyyaml>=6.0.3", + "scipy>=1.17.1" +] +description = "Python version of the OpenPTV library" +dynamic = ["version"] +license = {file = "LICENSE"} +name = "openptv-python" +readme = "README.md" +requires-python = ">=3.12,<3.14" + +[project.optional-dependencies] +docs = [ + "myst-parser>=5.0.0", + "pydata-sphinx-theme>=0.16.1", + "sphinx>=9.1.0", + "sphinx-autoapi>=3.7.0" +] +native = [ + "optv>=0.3.2" +] +test = [ + "pytest>=9.0.2", + "pytest-cov>=7.0.0" +] +dev = [ + "mypy>=1.19.1", + "myst-parser>=5.0.0", "optv>=0.3.2", "pre-commit>=4.5.1", "pydata-sphinx-theme>=0.16.1", "pytest>=9.0.2", "pytest-cov>=7.0.0", - "pyyaml>=6.0.3", - "scipy>=1.17.1", "sphinx>=9.1.0", "sphinx-autoapi>=3.7.0", "types-pyyaml>=6.0.12.20250915" ] -description = "Python version of the OpenPTV library" -dynamic = ["version"] -license = {file = "LICENSE"} -name = "openptv-python" -readme = "README.md" -requires-python = ">=3.12,<3.14" [tool.coverage.run] branch = true diff --git a/uv.lock b/uv.lock index 0b3e19c..10f575c 100644 --- a/uv.lock +++ b/uv.lock @@ -1,929 +1,954 @@ -requires-python = ">=3.12, <3.14" -revision = 3 version = 1 +revision = 3 +requires-python = ">=3.12, <3.14" [[package]] -dependencies = [ - {name = "pygments"} -] name = "accessible-pygments" -sdist = {url = "https://files.pythonhosted.org/packages/bc/c1/bbac6a50d02774f91572938964c582fff4270eee73ab822a4aeea4d8b11b/accessible_pygments-0.0.5.tar.gz", hash = "sha256:40918d3e6a2b619ad424cb91e556bd3bd8865443d9f22f1dcdf79e33c8046872", size = 1377899, upload-time = "2024-05-10T11:23:10.216Z"} -source = {registry = "https://pypi.org/simple"} version = "0.0.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bc/c1/bbac6a50d02774f91572938964c582fff4270eee73ab822a4aeea4d8b11b/accessible_pygments-0.0.5.tar.gz", hash = "sha256:40918d3e6a2b619ad424cb91e556bd3bd8865443d9f22f1dcdf79e33c8046872", size = 1377899, upload-time = "2024-05-10T11:23:10.216Z" } wheels = [ - {url = "https://files.pythonhosted.org/packages/8d/3f/95338030883d8c8b91223b4e21744b04d11b161a3ef117295d8241f50ab4/accessible_pygments-0.0.5-py3-none-any.whl", hash = "sha256:88ae3211e68a1d0b011504b2ffc1691feafce124b845bd072ab6f9f66f34d4b7", size = 1395903, upload-time = "2024-05-10T11:23:08.421Z"} + { url = "https://files.pythonhosted.org/packages/8d/3f/95338030883d8c8b91223b4e21744b04d11b161a3ef117295d8241f50ab4/accessible_pygments-0.0.5-py3-none-any.whl", hash = "sha256:88ae3211e68a1d0b011504b2ffc1691feafce124b845bd072ab6f9f66f34d4b7", size = 1395903, upload-time = "2024-05-10T11:23:08.421Z" }, ] [[package]] name = "alabaster" -sdist = {url = "https://files.pythonhosted.org/packages/a6/f8/d9c74d0daf3f742840fd818d69cfae176fa332022fd44e3469487d5a9420/alabaster-1.0.0.tar.gz", hash = "sha256:c00dca57bca26fa62a6d7d0a9fcce65f3e026e9bfe33e9c538fd3fbb2144fd9e", size = 24210, upload-time = "2024-07-26T18:15:03.762Z"} -source = {registry = "https://pypi.org/simple"} version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a6/f8/d9c74d0daf3f742840fd818d69cfae176fa332022fd44e3469487d5a9420/alabaster-1.0.0.tar.gz", hash = "sha256:c00dca57bca26fa62a6d7d0a9fcce65f3e026e9bfe33e9c538fd3fbb2144fd9e", size = 24210, upload-time = "2024-07-26T18:15:03.762Z" } wheels = [ - {url = "https://files.pythonhosted.org/packages/7e/b3/6b4067be973ae96ba0d615946e314c5ae35f9f993eca561b356540bb0c2b/alabaster-1.0.0-py3-none-any.whl", hash = "sha256:fc6786402dc3fcb2de3cabd5fe455a2db534b371124f1f21de8731783dec828b", size = 13929, upload-time = "2024-07-26T18:15:02.05Z"} + { url = "https://files.pythonhosted.org/packages/7e/b3/6b4067be973ae96ba0d615946e314c5ae35f9f993eca561b356540bb0c2b/alabaster-1.0.0-py3-none-any.whl", hash = "sha256:fc6786402dc3fcb2de3cabd5fe455a2db534b371124f1f21de8731783dec828b", size = 13929, upload-time = "2024-07-26T18:15:02.05Z" }, ] [[package]] name = "astroid" -sdist = {url = "https://files.pythonhosted.org/packages/a2/4c/569eefb533ce71bc9f4f12a4a0d7f0ba27e500681dec1312d4657e849d20/astroid-4.1.1.tar.gz", hash = "sha256:445d831fe785df8c670bbb46b900b8424b82f85b4af187103f71a63a63ebed43", size = 412522, upload-time = "2026-02-23T02:36:30.315Z"} -source = {registry = "https://pypi.org/simple"} version = "4.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/4c/569eefb533ce71bc9f4f12a4a0d7f0ba27e500681dec1312d4657e849d20/astroid-4.1.1.tar.gz", hash = "sha256:445d831fe785df8c670bbb46b900b8424b82f85b4af187103f71a63a63ebed43", size = 412522, upload-time = "2026-02-23T02:36:30.315Z" } wheels = [ - {url = "https://files.pythonhosted.org/packages/32/53/bac4724684064bfee95ece0bb6caf3887e509006845e25388a12cac26d0c/astroid-4.1.1-py3-none-any.whl", hash = "sha256:6b28522096f7e7a36ffcf3be60e77de15e2411ab3a713184beac33fb8f20c0c9", size = 279273, upload-time = "2026-02-23T02:36:28.676Z"} + { url = "https://files.pythonhosted.org/packages/32/53/bac4724684064bfee95ece0bb6caf3887e509006845e25388a12cac26d0c/astroid-4.1.1-py3-none-any.whl", hash = "sha256:6b28522096f7e7a36ffcf3be60e77de15e2411ab3a713184beac33fb8f20c0c9", size = 279273, upload-time = "2026-02-23T02:36:28.676Z" }, ] [[package]] name = "babel" -sdist = {url = "https://files.pythonhosted.org/packages/7d/b2/51899539b6ceeeb420d40ed3cd4b7a40519404f9baf3d4ac99dc413a834b/babel-2.18.0.tar.gz", hash = "sha256:b80b99a14bd085fcacfa15c9165f651fbb3406e66cc603abf11c5750937c992d", size = 9959554, upload-time = "2026-02-01T12:30:56.078Z"} -source = {registry = "https://pypi.org/simple"} version = "2.18.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/b2/51899539b6ceeeb420d40ed3cd4b7a40519404f9baf3d4ac99dc413a834b/babel-2.18.0.tar.gz", hash = "sha256:b80b99a14bd085fcacfa15c9165f651fbb3406e66cc603abf11c5750937c992d", size = 9959554, upload-time = "2026-02-01T12:30:56.078Z" } wheels = [ - {url = "https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl", hash = "sha256:e2b422b277c2b9a9630c1d7903c2a00d0830c409c59ac8cae9081c92f1aeba35", size = 10196845, upload-time = "2026-02-01T12:30:53.445Z"} + { url = "https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl", hash = "sha256:e2b422b277c2b9a9630c1d7903c2a00d0830c409c59ac8cae9081c92f1aeba35", size = 10196845, upload-time = "2026-02-01T12:30:53.445Z" }, ] [[package]] -dependencies = [ - {name = "soupsieve"}, - {name = "typing-extensions"} -] name = "beautifulsoup4" -sdist = {url = "https://files.pythonhosted.org/packages/c3/b0/1c6a16426d389813b48d95e26898aff79abbde42ad353958ad95cc8c9b21/beautifulsoup4-4.14.3.tar.gz", hash = "sha256:6292b1c5186d356bba669ef9f7f051757099565ad9ada5dd630bd9de5fa7fb86", size = 627737, upload-time = "2025-11-30T15:08:26.084Z"} -source = {registry = "https://pypi.org/simple"} version = "4.14.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "soupsieve" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c3/b0/1c6a16426d389813b48d95e26898aff79abbde42ad353958ad95cc8c9b21/beautifulsoup4-4.14.3.tar.gz", hash = "sha256:6292b1c5186d356bba669ef9f7f051757099565ad9ada5dd630bd9de5fa7fb86", size = 627737, upload-time = "2025-11-30T15:08:26.084Z" } wheels = [ - {url = "https://files.pythonhosted.org/packages/1a/39/47f9197bdd44df24d67ac8893641e16f386c984a0619ef2ee4c51fbbc019/beautifulsoup4-4.14.3-py3-none-any.whl", hash = "sha256:0918bfe44902e6ad8d57732ba310582e98da931428d231a5ecb9e7c703a735bb", size = 107721, upload-time = "2025-11-30T15:08:24.087Z"} + { url = "https://files.pythonhosted.org/packages/1a/39/47f9197bdd44df24d67ac8893641e16f386c984a0619ef2ee4c51fbbc019/beautifulsoup4-4.14.3-py3-none-any.whl", hash = "sha256:0918bfe44902e6ad8d57732ba310582e98da931428d231a5ecb9e7c703a735bb", size = 107721, upload-time = "2025-11-30T15:08:24.087Z" }, ] [[package]] name = "certifi" -sdist = {url = "https://files.pythonhosted.org/packages/af/2d/7bf41579a8986e348fa033a31cdd0e4121114f6bce2457e8876010b092dd/certifi-2026.2.25.tar.gz", hash = "sha256:e887ab5cee78ea814d3472169153c2d12cd43b14bd03329a39a9c6e2e80bfba7", size = 155029, upload-time = "2026-02-25T02:54:17.342Z"} -source = {registry = "https://pypi.org/simple"} version = "2026.2.25" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/af/2d/7bf41579a8986e348fa033a31cdd0e4121114f6bce2457e8876010b092dd/certifi-2026.2.25.tar.gz", hash = "sha256:e887ab5cee78ea814d3472169153c2d12cd43b14bd03329a39a9c6e2e80bfba7", size = 155029, upload-time = "2026-02-25T02:54:17.342Z" } wheels = [ - {url = "https://files.pythonhosted.org/packages/9a/3c/c17fb3ca2d9c3acff52e30b309f538586f9f5b9c9cf454f3845fc9af4881/certifi-2026.2.25-py3-none-any.whl", hash = "sha256:027692e4402ad994f1c42e52a4997a9763c646b73e4096e4d5d6db8af1d6f0fa", size = 153684, upload-time = "2026-02-25T02:54:15.766Z"} + { url = "https://files.pythonhosted.org/packages/9a/3c/c17fb3ca2d9c3acff52e30b309f538586f9f5b9c9cf454f3845fc9af4881/certifi-2026.2.25-py3-none-any.whl", hash = "sha256:027692e4402ad994f1c42e52a4997a9763c646b73e4096e4d5d6db8af1d6f0fa", size = 153684, upload-time = "2026-02-25T02:54:15.766Z" }, ] [[package]] name = "cfgv" -sdist = {url = "https://files.pythonhosted.org/packages/4e/b5/721b8799b04bf9afe054a3899c6cf4e880fcf8563cc71c15610242490a0c/cfgv-3.5.0.tar.gz", hash = "sha256:d5b1034354820651caa73ede66a6294d6e95c1b00acc5e9b098e917404669132", size = 7334, upload-time = "2025-11-19T20:55:51.612Z"} -source = {registry = "https://pypi.org/simple"} version = "3.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4e/b5/721b8799b04bf9afe054a3899c6cf4e880fcf8563cc71c15610242490a0c/cfgv-3.5.0.tar.gz", hash = "sha256:d5b1034354820651caa73ede66a6294d6e95c1b00acc5e9b098e917404669132", size = 7334, upload-time = "2025-11-19T20:55:51.612Z" } wheels = [ - {url = "https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl", hash = "sha256:a8dc6b26ad22ff227d2634a65cb388215ce6cc96bbcc5cfde7641ae87e8dacc0", size = 7445, upload-time = "2025-11-19T20:55:50.744Z"} + { url = "https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl", hash = "sha256:a8dc6b26ad22ff227d2634a65cb388215ce6cc96bbcc5cfde7641ae87e8dacc0", size = 7445, upload-time = "2025-11-19T20:55:50.744Z" }, ] [[package]] name = "charset-normalizer" -sdist = {url = "https://files.pythonhosted.org/packages/1d/35/02daf95b9cd686320bb622eb148792655c9412dbb9b67abb5694e5910a24/charset_normalizer-3.4.5.tar.gz", hash = "sha256:95adae7b6c42a6c5b5b559b1a99149f090a57128155daeea91732c8d970d8644", size = 134804, upload-time = "2026-03-06T06:03:19.46Z"} -source = {registry = "https://pypi.org/simple"} version = "3.4.5" -wheels = [ - {url = "https://files.pythonhosted.org/packages/9c/b6/9ee9c1a608916ca5feae81a344dffbaa53b26b90be58cc2159e3332d44ec/charset_normalizer-3.4.5-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ed97c282ee4f994ef814042423a529df9497e3c666dca19be1d4cd1129dc7ade", size = 280976, upload-time = "2026-03-06T06:01:15.276Z"}, - {url = "https://files.pythonhosted.org/packages/f8/d8/a54f7c0b96f1df3563e9190f04daf981e365a9b397eedfdfb5dbef7e5c6c/charset_normalizer-3.4.5-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0294916d6ccf2d069727d65973c3a1ca477d68708db25fd758dd28b0827cff54", size = 189356, upload-time = "2026-03-06T06:01:16.511Z"}, - {url = "https://files.pythonhosted.org/packages/42/69/2bf7f76ce1446759a5787cb87d38f6a61eb47dbbdf035cfebf6347292a65/charset_normalizer-3.4.5-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:dc57a0baa3eeedd99fafaef7511b5a6ef4581494e8168ee086031744e2679467", size = 206369, upload-time = "2026-03-06T06:01:17.853Z"}, - {url = "https://files.pythonhosted.org/packages/10/9c/949d1a46dab56b959d9a87272482195f1840b515a3380e39986989a893ae/charset_normalizer-3.4.5-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ed1a9a204f317ef879b32f9af507d47e49cd5e7f8e8d5d96358c98373314fc60", size = 203285, upload-time = "2026-03-06T06:01:19.473Z"}, - {url = "https://files.pythonhosted.org/packages/67/5c/ae30362a88b4da237d71ea214a8c7eb915db3eec941adda511729ac25fa2/charset_normalizer-3.4.5-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7ad83b8f9379176c841f8865884f3514d905bcd2a9a3b210eaa446e7d2223e4d", size = 196274, upload-time = "2026-03-06T06:01:20.728Z"}, - {url = "https://files.pythonhosted.org/packages/b2/07/c9f2cb0e46cb6d64fdcc4f95953747b843bb2181bda678dc4e699b8f0f9a/charset_normalizer-3.4.5-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:a118e2e0b5ae6b0120d5efa5f866e58f2bb826067a646431da4d6a2bdae7950e", size = 184715, upload-time = "2026-03-06T06:01:22.194Z"}, - {url = "https://files.pythonhosted.org/packages/36/64/6b0ca95c44fddf692cd06d642b28f63009d0ce325fad6e9b2b4d0ef86a52/charset_normalizer-3.4.5-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:754f96058e61a5e22e91483f823e07df16416ce76afa4ebf306f8e1d1296d43f", size = 193426, upload-time = "2026-03-06T06:01:23.795Z"}, - {url = "https://files.pythonhosted.org/packages/50/bc/a730690d726403743795ca3f5bb2baf67838c5fea78236098f324b965e40/charset_normalizer-3.4.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0c300cefd9b0970381a46394902cd18eaf2aa00163f999590ace991989dcd0fc", size = 191780, upload-time = "2026-03-06T06:01:25.053Z"}, - {url = "https://files.pythonhosted.org/packages/97/4f/6c0bc9af68222b22951552d73df4532b5be6447cee32d58e7e8c74ecbb7b/charset_normalizer-3.4.5-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:c108f8619e504140569ee7de3f97d234f0fbae338a7f9f360455071ef9855a95", size = 185805, upload-time = "2026-03-06T06:01:26.294Z"}, - {url = "https://files.pythonhosted.org/packages/dd/b9/a523fb9b0ee90814b503452b2600e4cbc118cd68714d57041564886e7325/charset_normalizer-3.4.5-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:d1028de43596a315e2720a9849ee79007ab742c06ad8b45a50db8cdb7ed4a82a", size = 208342, upload-time = "2026-03-06T06:01:27.55Z"}, - {url = "https://files.pythonhosted.org/packages/4d/61/c59e761dee4464050713e50e27b58266cc8e209e518c0b378c1580c959ba/charset_normalizer-3.4.5-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:19092dde50335accf365cce21998a1c6dd8eafd42c7b226eb54b2747cdce2fac", size = 193661, upload-time = "2026-03-06T06:01:29.051Z"}, - {url = "https://files.pythonhosted.org/packages/1c/43/729fa30aad69783f755c5ad8649da17ee095311ca42024742701e202dc59/charset_normalizer-3.4.5-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:4354e401eb6dab9aed3c7b4030514328a6c748d05e1c3e19175008ca7de84fb1", size = 204819, upload-time = "2026-03-06T06:01:30.298Z"}, - {url = "https://files.pythonhosted.org/packages/87/33/d9b442ce5a91b96fc0840455a9e49a611bbadae6122778d0a6a79683dd31/charset_normalizer-3.4.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a68766a3c58fde7f9aaa22b3786276f62ab2f594efb02d0a1421b6282e852e98", size = 198080, upload-time = "2026-03-06T06:01:31.478Z"}, - {url = "https://files.pythonhosted.org/packages/56/5a/b8b5a23134978ee9885cee2d6995f4c27cc41f9baded0a9685eabc5338f0/charset_normalizer-3.4.5-cp312-cp312-win32.whl", hash = "sha256:1827734a5b308b65ac54e86a618de66f935a4f63a8a462ff1e19a6788d6c2262", size = 132630, upload-time = "2026-03-06T06:01:33.056Z"}, - {url = "https://files.pythonhosted.org/packages/70/53/e44a4c07e8904500aec95865dc3f6464dc3586a039ef0df606eb3ac38e35/charset_normalizer-3.4.5-cp312-cp312-win_amd64.whl", hash = "sha256:728c6a963dfab66ef865f49286e45239384249672cd598576765acc2a640a636", size = 142856, upload-time = "2026-03-06T06:01:34.489Z"}, - {url = "https://files.pythonhosted.org/packages/ea/aa/c5628f7cad591b1cf45790b7a61483c3e36cf41349c98af7813c483fd6e8/charset_normalizer-3.4.5-cp312-cp312-win_arm64.whl", hash = "sha256:75dfd1afe0b1647449e852f4fb428195a7ed0588947218f7ba929f6538487f02", size = 132982, upload-time = "2026-03-06T06:01:35.641Z"}, - {url = "https://files.pythonhosted.org/packages/f5/48/9f34ec4bb24aa3fdba1890c1bddb97c8a4be1bd84ef5c42ac2352563ad05/charset_normalizer-3.4.5-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ac59c15e3f1465f722607800c68713f9fbc2f672b9eb649fe831da4019ae9b23", size = 280788, upload-time = "2026-03-06T06:01:37.126Z"}, - {url = "https://files.pythonhosted.org/packages/0e/09/6003e7ffeb90cc0560da893e3208396a44c210c5ee42efff539639def59b/charset_normalizer-3.4.5-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:165c7b21d19365464e8f70e5ce5e12524c58b48c78c1f5a57524603c1ab003f8", size = 188890, upload-time = "2026-03-06T06:01:38.73Z"}, - {url = "https://files.pythonhosted.org/packages/42/1e/02706edf19e390680daa694d17e2b8eab4b5f7ac285e2a51168b4b22ee6b/charset_normalizer-3.4.5-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:28269983f25a4da0425743d0d257a2d6921ea7d9b83599d4039486ec5b9f911d", size = 206136, upload-time = "2026-03-06T06:01:40.016Z"}, - {url = "https://files.pythonhosted.org/packages/c7/87/942c3def1b37baf3cf786bad01249190f3ca3d5e63a84f831e704977de1f/charset_normalizer-3.4.5-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d27ce22ec453564770d29d03a9506d449efbb9fa13c00842262b2f6801c48cce", size = 202551, upload-time = "2026-03-06T06:01:41.522Z"}, - {url = "https://files.pythonhosted.org/packages/94/0a/af49691938dfe175d71b8a929bd7e4ace2809c0c5134e28bc535660d5262/charset_normalizer-3.4.5-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0625665e4ebdddb553ab185de5db7054393af8879fb0c87bd5690d14379d6819", size = 195572, upload-time = "2026-03-06T06:01:43.208Z"}, - {url = "https://files.pythonhosted.org/packages/20/ea/dfb1792a8050a8e694cfbde1570ff97ff74e48afd874152d38163d1df9ae/charset_normalizer-3.4.5-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:c23eb3263356d94858655b3e63f85ac5d50970c6e8febcdde7830209139cc37d", size = 184438, upload-time = "2026-03-06T06:01:44.755Z"}, - {url = "https://files.pythonhosted.org/packages/72/12/c281e2067466e3ddd0595bfaea58a6946765ace5c72dfa3edc2f5f118026/charset_normalizer-3.4.5-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e6302ca4ae283deb0af68d2fbf467474b8b6aedcd3dab4db187e07f94c109763", size = 193035, upload-time = "2026-03-06T06:01:46.051Z"}, - {url = "https://files.pythonhosted.org/packages/ba/4f/3792c056e7708e10464bad0438a44708886fb8f92e3c3d29ec5e2d964d42/charset_normalizer-3.4.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e51ae7d81c825761d941962450f50d041db028b7278e7b08930b4541b3e45cb9", size = 191340, upload-time = "2026-03-06T06:01:47.547Z"}, - {url = "https://files.pythonhosted.org/packages/e7/86/80ddba897127b5c7a9bccc481b0cd36c8fefa485d113262f0fe4332f0bf4/charset_normalizer-3.4.5-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:597d10dec876923e5c59e48dbd366e852eacb2b806029491d307daea6b917d7c", size = 185464, upload-time = "2026-03-06T06:01:48.764Z"}, - {url = "https://files.pythonhosted.org/packages/4d/00/b5eff85ba198faacab83e0e4b6f0648155f072278e3b392a82478f8b988b/charset_normalizer-3.4.5-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:5cffde4032a197bd3b42fd0b9509ec60fb70918d6970e4cc773f20fc9180ca67", size = 208014, upload-time = "2026-03-06T06:01:50.371Z"}, - {url = "https://files.pythonhosted.org/packages/c8/11/d36f70be01597fd30850dde8a1269ebc8efadd23ba5785808454f2389bde/charset_normalizer-3.4.5-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:2da4eedcb6338e2321e831a0165759c0c620e37f8cd044a263ff67493be8ffb3", size = 193297, upload-time = "2026-03-06T06:01:51.933Z"}, - {url = "https://files.pythonhosted.org/packages/1a/1d/259eb0a53d4910536c7c2abb9cb25f4153548efb42800c6a9456764649c0/charset_normalizer-3.4.5-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:65a126fb4b070d05340a84fc709dd9e7c75d9b063b610ece8a60197a291d0adf", size = 204321, upload-time = "2026-03-06T06:01:53.887Z"}, - {url = "https://files.pythonhosted.org/packages/84/31/faa6c5b9d3688715e1ed1bb9d124c384fe2fc1633a409e503ffe1c6398c1/charset_normalizer-3.4.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c7a80a9242963416bd81f99349d5f3fce1843c303bd404f204918b6d75a75fd6", size = 197509, upload-time = "2026-03-06T06:01:56.439Z"}, - {url = "https://files.pythonhosted.org/packages/fd/a5/c7d9dd1503ffc08950b3260f5d39ec2366dd08254f0900ecbcf3a6197c7c/charset_normalizer-3.4.5-cp313-cp313-win32.whl", hash = "sha256:f1d725b754e967e648046f00c4facc42d414840f5ccc670c5670f59f83693e4f", size = 132284, upload-time = "2026-03-06T06:01:57.812Z"}, - {url = "https://files.pythonhosted.org/packages/b9/0f/57072b253af40c8aa6636e6de7d75985624c1eb392815b2f934199340a89/charset_normalizer-3.4.5-cp313-cp313-win_amd64.whl", hash = "sha256:e37bd100d2c5d3ba35db9c7c5ba5a9228cbcffe5c4778dc824b164e5257813d7", size = 142630, upload-time = "2026-03-06T06:01:59.062Z"}, - {url = "https://files.pythonhosted.org/packages/31/41/1c4b7cc9f13bd9d369ce3bc993e13d374ce25fa38a2663644283ecf422c1/charset_normalizer-3.4.5-cp313-cp313-win_arm64.whl", hash = "sha256:93b3b2cc5cf1b8743660ce77a4f45f3f6d1172068207c1defc779a36eea6bb36", size = 133254, upload-time = "2026-03-06T06:02:00.281Z"}, - {url = "https://files.pythonhosted.org/packages/c5/60/3a621758945513adfd4db86827a5bafcc615f913dbd0b4c2ed64a65731be/charset_normalizer-3.4.5-py3-none-any.whl", hash = "sha256:9db5e3fcdcee89a78c04dffb3fe33c79f77bd741a624946db2591c81b2fc85b0", size = 55455, upload-time = "2026-03-06T06:03:17.827Z"} +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1d/35/02daf95b9cd686320bb622eb148792655c9412dbb9b67abb5694e5910a24/charset_normalizer-3.4.5.tar.gz", hash = "sha256:95adae7b6c42a6c5b5b559b1a99149f090a57128155daeea91732c8d970d8644", size = 134804, upload-time = "2026-03-06T06:03:19.46Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9c/b6/9ee9c1a608916ca5feae81a344dffbaa53b26b90be58cc2159e3332d44ec/charset_normalizer-3.4.5-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ed97c282ee4f994ef814042423a529df9497e3c666dca19be1d4cd1129dc7ade", size = 280976, upload-time = "2026-03-06T06:01:15.276Z" }, + { url = "https://files.pythonhosted.org/packages/f8/d8/a54f7c0b96f1df3563e9190f04daf981e365a9b397eedfdfb5dbef7e5c6c/charset_normalizer-3.4.5-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0294916d6ccf2d069727d65973c3a1ca477d68708db25fd758dd28b0827cff54", size = 189356, upload-time = "2026-03-06T06:01:16.511Z" }, + { url = "https://files.pythonhosted.org/packages/42/69/2bf7f76ce1446759a5787cb87d38f6a61eb47dbbdf035cfebf6347292a65/charset_normalizer-3.4.5-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:dc57a0baa3eeedd99fafaef7511b5a6ef4581494e8168ee086031744e2679467", size = 206369, upload-time = "2026-03-06T06:01:17.853Z" }, + { url = "https://files.pythonhosted.org/packages/10/9c/949d1a46dab56b959d9a87272482195f1840b515a3380e39986989a893ae/charset_normalizer-3.4.5-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ed1a9a204f317ef879b32f9af507d47e49cd5e7f8e8d5d96358c98373314fc60", size = 203285, upload-time = "2026-03-06T06:01:19.473Z" }, + { url = "https://files.pythonhosted.org/packages/67/5c/ae30362a88b4da237d71ea214a8c7eb915db3eec941adda511729ac25fa2/charset_normalizer-3.4.5-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7ad83b8f9379176c841f8865884f3514d905bcd2a9a3b210eaa446e7d2223e4d", size = 196274, upload-time = "2026-03-06T06:01:20.728Z" }, + { url = "https://files.pythonhosted.org/packages/b2/07/c9f2cb0e46cb6d64fdcc4f95953747b843bb2181bda678dc4e699b8f0f9a/charset_normalizer-3.4.5-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:a118e2e0b5ae6b0120d5efa5f866e58f2bb826067a646431da4d6a2bdae7950e", size = 184715, upload-time = "2026-03-06T06:01:22.194Z" }, + { url = "https://files.pythonhosted.org/packages/36/64/6b0ca95c44fddf692cd06d642b28f63009d0ce325fad6e9b2b4d0ef86a52/charset_normalizer-3.4.5-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:754f96058e61a5e22e91483f823e07df16416ce76afa4ebf306f8e1d1296d43f", size = 193426, upload-time = "2026-03-06T06:01:23.795Z" }, + { url = "https://files.pythonhosted.org/packages/50/bc/a730690d726403743795ca3f5bb2baf67838c5fea78236098f324b965e40/charset_normalizer-3.4.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0c300cefd9b0970381a46394902cd18eaf2aa00163f999590ace991989dcd0fc", size = 191780, upload-time = "2026-03-06T06:01:25.053Z" }, + { url = "https://files.pythonhosted.org/packages/97/4f/6c0bc9af68222b22951552d73df4532b5be6447cee32d58e7e8c74ecbb7b/charset_normalizer-3.4.5-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:c108f8619e504140569ee7de3f97d234f0fbae338a7f9f360455071ef9855a95", size = 185805, upload-time = "2026-03-06T06:01:26.294Z" }, + { url = "https://files.pythonhosted.org/packages/dd/b9/a523fb9b0ee90814b503452b2600e4cbc118cd68714d57041564886e7325/charset_normalizer-3.4.5-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:d1028de43596a315e2720a9849ee79007ab742c06ad8b45a50db8cdb7ed4a82a", size = 208342, upload-time = "2026-03-06T06:01:27.55Z" }, + { url = "https://files.pythonhosted.org/packages/4d/61/c59e761dee4464050713e50e27b58266cc8e209e518c0b378c1580c959ba/charset_normalizer-3.4.5-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:19092dde50335accf365cce21998a1c6dd8eafd42c7b226eb54b2747cdce2fac", size = 193661, upload-time = "2026-03-06T06:01:29.051Z" }, + { url = "https://files.pythonhosted.org/packages/1c/43/729fa30aad69783f755c5ad8649da17ee095311ca42024742701e202dc59/charset_normalizer-3.4.5-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:4354e401eb6dab9aed3c7b4030514328a6c748d05e1c3e19175008ca7de84fb1", size = 204819, upload-time = "2026-03-06T06:01:30.298Z" }, + { url = "https://files.pythonhosted.org/packages/87/33/d9b442ce5a91b96fc0840455a9e49a611bbadae6122778d0a6a79683dd31/charset_normalizer-3.4.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a68766a3c58fde7f9aaa22b3786276f62ab2f594efb02d0a1421b6282e852e98", size = 198080, upload-time = "2026-03-06T06:01:31.478Z" }, + { url = "https://files.pythonhosted.org/packages/56/5a/b8b5a23134978ee9885cee2d6995f4c27cc41f9baded0a9685eabc5338f0/charset_normalizer-3.4.5-cp312-cp312-win32.whl", hash = "sha256:1827734a5b308b65ac54e86a618de66f935a4f63a8a462ff1e19a6788d6c2262", size = 132630, upload-time = "2026-03-06T06:01:33.056Z" }, + { url = "https://files.pythonhosted.org/packages/70/53/e44a4c07e8904500aec95865dc3f6464dc3586a039ef0df606eb3ac38e35/charset_normalizer-3.4.5-cp312-cp312-win_amd64.whl", hash = "sha256:728c6a963dfab66ef865f49286e45239384249672cd598576765acc2a640a636", size = 142856, upload-time = "2026-03-06T06:01:34.489Z" }, + { url = "https://files.pythonhosted.org/packages/ea/aa/c5628f7cad591b1cf45790b7a61483c3e36cf41349c98af7813c483fd6e8/charset_normalizer-3.4.5-cp312-cp312-win_arm64.whl", hash = "sha256:75dfd1afe0b1647449e852f4fb428195a7ed0588947218f7ba929f6538487f02", size = 132982, upload-time = "2026-03-06T06:01:35.641Z" }, + { url = "https://files.pythonhosted.org/packages/f5/48/9f34ec4bb24aa3fdba1890c1bddb97c8a4be1bd84ef5c42ac2352563ad05/charset_normalizer-3.4.5-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ac59c15e3f1465f722607800c68713f9fbc2f672b9eb649fe831da4019ae9b23", size = 280788, upload-time = "2026-03-06T06:01:37.126Z" }, + { url = "https://files.pythonhosted.org/packages/0e/09/6003e7ffeb90cc0560da893e3208396a44c210c5ee42efff539639def59b/charset_normalizer-3.4.5-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:165c7b21d19365464e8f70e5ce5e12524c58b48c78c1f5a57524603c1ab003f8", size = 188890, upload-time = "2026-03-06T06:01:38.73Z" }, + { url = "https://files.pythonhosted.org/packages/42/1e/02706edf19e390680daa694d17e2b8eab4b5f7ac285e2a51168b4b22ee6b/charset_normalizer-3.4.5-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:28269983f25a4da0425743d0d257a2d6921ea7d9b83599d4039486ec5b9f911d", size = 206136, upload-time = "2026-03-06T06:01:40.016Z" }, + { url = "https://files.pythonhosted.org/packages/c7/87/942c3def1b37baf3cf786bad01249190f3ca3d5e63a84f831e704977de1f/charset_normalizer-3.4.5-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d27ce22ec453564770d29d03a9506d449efbb9fa13c00842262b2f6801c48cce", size = 202551, upload-time = "2026-03-06T06:01:41.522Z" }, + { url = "https://files.pythonhosted.org/packages/94/0a/af49691938dfe175d71b8a929bd7e4ace2809c0c5134e28bc535660d5262/charset_normalizer-3.4.5-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0625665e4ebdddb553ab185de5db7054393af8879fb0c87bd5690d14379d6819", size = 195572, upload-time = "2026-03-06T06:01:43.208Z" }, + { url = "https://files.pythonhosted.org/packages/20/ea/dfb1792a8050a8e694cfbde1570ff97ff74e48afd874152d38163d1df9ae/charset_normalizer-3.4.5-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:c23eb3263356d94858655b3e63f85ac5d50970c6e8febcdde7830209139cc37d", size = 184438, upload-time = "2026-03-06T06:01:44.755Z" }, + { url = "https://files.pythonhosted.org/packages/72/12/c281e2067466e3ddd0595bfaea58a6946765ace5c72dfa3edc2f5f118026/charset_normalizer-3.4.5-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e6302ca4ae283deb0af68d2fbf467474b8b6aedcd3dab4db187e07f94c109763", size = 193035, upload-time = "2026-03-06T06:01:46.051Z" }, + { url = "https://files.pythonhosted.org/packages/ba/4f/3792c056e7708e10464bad0438a44708886fb8f92e3c3d29ec5e2d964d42/charset_normalizer-3.4.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e51ae7d81c825761d941962450f50d041db028b7278e7b08930b4541b3e45cb9", size = 191340, upload-time = "2026-03-06T06:01:47.547Z" }, + { url = "https://files.pythonhosted.org/packages/e7/86/80ddba897127b5c7a9bccc481b0cd36c8fefa485d113262f0fe4332f0bf4/charset_normalizer-3.4.5-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:597d10dec876923e5c59e48dbd366e852eacb2b806029491d307daea6b917d7c", size = 185464, upload-time = "2026-03-06T06:01:48.764Z" }, + { url = "https://files.pythonhosted.org/packages/4d/00/b5eff85ba198faacab83e0e4b6f0648155f072278e3b392a82478f8b988b/charset_normalizer-3.4.5-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:5cffde4032a197bd3b42fd0b9509ec60fb70918d6970e4cc773f20fc9180ca67", size = 208014, upload-time = "2026-03-06T06:01:50.371Z" }, + { url = "https://files.pythonhosted.org/packages/c8/11/d36f70be01597fd30850dde8a1269ebc8efadd23ba5785808454f2389bde/charset_normalizer-3.4.5-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:2da4eedcb6338e2321e831a0165759c0c620e37f8cd044a263ff67493be8ffb3", size = 193297, upload-time = "2026-03-06T06:01:51.933Z" }, + { url = "https://files.pythonhosted.org/packages/1a/1d/259eb0a53d4910536c7c2abb9cb25f4153548efb42800c6a9456764649c0/charset_normalizer-3.4.5-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:65a126fb4b070d05340a84fc709dd9e7c75d9b063b610ece8a60197a291d0adf", size = 204321, upload-time = "2026-03-06T06:01:53.887Z" }, + { url = "https://files.pythonhosted.org/packages/84/31/faa6c5b9d3688715e1ed1bb9d124c384fe2fc1633a409e503ffe1c6398c1/charset_normalizer-3.4.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c7a80a9242963416bd81f99349d5f3fce1843c303bd404f204918b6d75a75fd6", size = 197509, upload-time = "2026-03-06T06:01:56.439Z" }, + { url = "https://files.pythonhosted.org/packages/fd/a5/c7d9dd1503ffc08950b3260f5d39ec2366dd08254f0900ecbcf3a6197c7c/charset_normalizer-3.4.5-cp313-cp313-win32.whl", hash = "sha256:f1d725b754e967e648046f00c4facc42d414840f5ccc670c5670f59f83693e4f", size = 132284, upload-time = "2026-03-06T06:01:57.812Z" }, + { url = "https://files.pythonhosted.org/packages/b9/0f/57072b253af40c8aa6636e6de7d75985624c1eb392815b2f934199340a89/charset_normalizer-3.4.5-cp313-cp313-win_amd64.whl", hash = "sha256:e37bd100d2c5d3ba35db9c7c5ba5a9228cbcffe5c4778dc824b164e5257813d7", size = 142630, upload-time = "2026-03-06T06:01:59.062Z" }, + { url = "https://files.pythonhosted.org/packages/31/41/1c4b7cc9f13bd9d369ce3bc993e13d374ce25fa38a2663644283ecf422c1/charset_normalizer-3.4.5-cp313-cp313-win_arm64.whl", hash = "sha256:93b3b2cc5cf1b8743660ce77a4f45f3f6d1172068207c1defc779a36eea6bb36", size = 133254, upload-time = "2026-03-06T06:02:00.281Z" }, + { url = "https://files.pythonhosted.org/packages/c5/60/3a621758945513adfd4db86827a5bafcc615f913dbd0b4c2ed64a65731be/charset_normalizer-3.4.5-py3-none-any.whl", hash = "sha256:9db5e3fcdcee89a78c04dffb3fe33c79f77bd741a624946db2591c81b2fc85b0", size = 55455, upload-time = "2026-03-06T06:03:17.827Z" }, ] [[package]] name = "colorama" -sdist = {url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z"} -source = {registry = "https://pypi.org/simple"} version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } wheels = [ - {url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z"} + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, ] [[package]] name = "coverage" -sdist = {url = "https://files.pythonhosted.org/packages/24/56/95b7e30fa389756cb56630faa728da46a27b8c6eb46f9d557c68fff12b65/coverage-7.13.4.tar.gz", hash = "sha256:e5c8f6ed1e61a8b2dcdf31eb0b9bbf0130750ca79c1c49eb898e2ad86f5ccc91", size = 827239, upload-time = "2026-02-09T12:59:03.86Z"} -source = {registry = "https://pypi.org/simple"} version = "7.13.4" -wheels = [ - {url = "https://files.pythonhosted.org/packages/d1/81/4ce2fdd909c5a0ed1f6dedb88aa57ab79b6d1fbd9b588c1ac7ef45659566/coverage-7.13.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:02231499b08dabbe2b96612993e5fc34217cdae907a51b906ac7fca8027a4459", size = 219449, upload-time = "2026-02-09T12:56:54.889Z"}, - {url = "https://files.pythonhosted.org/packages/5d/96/5238b1efc5922ddbdc9b0db9243152c09777804fb7c02ad1741eb18a11c0/coverage-7.13.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40aa8808140e55dc022b15d8aa7f651b6b3d68b365ea0398f1441e0b04d859c3", size = 219810, upload-time = "2026-02-09T12:56:56.33Z"}, - {url = "https://files.pythonhosted.org/packages/78/72/2f372b726d433c9c35e56377cf1d513b4c16fe51841060d826b95caacec1/coverage-7.13.4-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5b856a8ccf749480024ff3bd7310adaef57bf31fd17e1bfc404b7940b6986634", size = 251308, upload-time = "2026-02-09T12:56:57.858Z"}, - {url = "https://files.pythonhosted.org/packages/5d/a0/2ea570925524ef4e00bb6c82649f5682a77fac5ab910a65c9284de422600/coverage-7.13.4-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2c048ea43875fbf8b45d476ad79f179809c590ec7b79e2035c662e7afa3192e3", size = 254052, upload-time = "2026-02-09T12:56:59.754Z"}, - {url = "https://files.pythonhosted.org/packages/e8/ac/45dc2e19a1939098d783c846e130b8f862fbb50d09e0af663988f2f21973/coverage-7.13.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b7b38448866e83176e28086674fe7368ab8590e4610fb662b44e345b86d63ffa", size = 255165, upload-time = "2026-02-09T12:57:01.287Z"}, - {url = "https://files.pythonhosted.org/packages/2d/4d/26d236ff35abc3b5e63540d3386e4c3b192168c1d96da5cb2f43c640970f/coverage-7.13.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:de6defc1c9badbf8b9e67ae90fd00519186d6ab64e5cc5f3d21359c2a9b2c1d3", size = 257432, upload-time = "2026-02-09T12:57:02.637Z"}, - {url = "https://files.pythonhosted.org/packages/ec/55/14a966c757d1348b2e19caf699415a2a4c4f7feaa4bbc6326a51f5c7dd1b/coverage-7.13.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7eda778067ad7ffccd23ecffce537dface96212576a07924cbf0d8799d2ded5a", size = 251716, upload-time = "2026-02-09T12:57:04.056Z"}, - {url = "https://files.pythonhosted.org/packages/77/33/50116647905837c66d28b2af1321b845d5f5d19be9655cb84d4a0ea806b4/coverage-7.13.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e87f6c587c3f34356c3759f0420693e35e7eb0e2e41e4c011cb6ec6ecbbf1db7", size = 253089, upload-time = "2026-02-09T12:57:05.503Z"}, - {url = "https://files.pythonhosted.org/packages/c2/b4/8efb11a46e3665d92635a56e4f2d4529de6d33f2cb38afd47d779d15fc99/coverage-7.13.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:8248977c2e33aecb2ced42fef99f2d319e9904a36e55a8a68b69207fb7e43edc", size = 251232, upload-time = "2026-02-09T12:57:06.879Z"}, - {url = "https://files.pythonhosted.org/packages/51/24/8cd73dd399b812cc76bb0ac260e671c4163093441847ffe058ac9fda1e32/coverage-7.13.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:25381386e80ae727608e662474db537d4df1ecd42379b5ba33c84633a2b36d47", size = 255299, upload-time = "2026-02-09T12:57:08.245Z"}, - {url = "https://files.pythonhosted.org/packages/03/94/0a4b12f1d0e029ce1ccc1c800944a9984cbe7d678e470bb6d3c6bc38a0da/coverage-7.13.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:ee756f00726693e5ba94d6df2bdfd64d4852d23b09bb0bc700e3b30e6f333985", size = 250796, upload-time = "2026-02-09T12:57:10.142Z"}, - {url = "https://files.pythonhosted.org/packages/73/44/6002fbf88f6698ca034360ce474c406be6d5a985b3fdb3401128031eef6b/coverage-7.13.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fdfc1e28e7c7cdce44985b3043bc13bbd9c747520f94a4d7164af8260b3d91f0", size = 252673, upload-time = "2026-02-09T12:57:12.197Z"}, - {url = "https://files.pythonhosted.org/packages/de/c6/a0279f7c00e786be75a749a5674e6fa267bcbd8209cd10c9a450c655dfa7/coverage-7.13.4-cp312-cp312-win32.whl", hash = "sha256:01d4cbc3c283a17fc1e42d614a119f7f438eabb593391283adca8dc86eff1246", size = 221990, upload-time = "2026-02-09T12:57:14.085Z"}, - {url = "https://files.pythonhosted.org/packages/77/4e/c0a25a425fcf5557d9abd18419c95b63922e897bc86c1f327f155ef234a9/coverage-7.13.4-cp312-cp312-win_amd64.whl", hash = "sha256:9401ebc7ef522f01d01d45532c68c5ac40fb27113019b6b7d8b208f6e9baa126", size = 222800, upload-time = "2026-02-09T12:57:15.944Z"}, - {url = "https://files.pythonhosted.org/packages/47/ac/92da44ad9a6f4e3a7debd178949d6f3769bedca33830ce9b1dcdab589a37/coverage-7.13.4-cp312-cp312-win_arm64.whl", hash = "sha256:b1ec7b6b6e93255f952e27ab58fbc68dcc468844b16ecbee881aeb29b6ab4d8d", size = 221415, upload-time = "2026-02-09T12:57:17.497Z"}, - {url = "https://files.pythonhosted.org/packages/db/23/aad45061a31677d68e47499197a131eea55da4875d16c1f42021ab963503/coverage-7.13.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b66a2da594b6068b48b2692f043f35d4d3693fb639d5ea8b39533c2ad9ac3ab9", size = 219474, upload-time = "2026-02-09T12:57:19.332Z"}, - {url = "https://files.pythonhosted.org/packages/a5/70/9b8b67a0945f3dfec1fd896c5cefb7c19d5a3a6d74630b99a895170999ae/coverage-7.13.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3599eb3992d814d23b35c536c28df1a882caa950f8f507cef23d1cbf334995ac", size = 219844, upload-time = "2026-02-09T12:57:20.66Z"}, - {url = "https://files.pythonhosted.org/packages/97/fd/7e859f8fab324cef6c4ad7cff156ca7c489fef9179d5749b0c8d321281c2/coverage-7.13.4-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:93550784d9281e374fb5a12bf1324cc8a963fd63b2d2f223503ef0fd4aa339ea", size = 250832, upload-time = "2026-02-09T12:57:22.007Z"}, - {url = "https://files.pythonhosted.org/packages/e4/dc/b2442d10020c2f52617828862d8b6ee337859cd8f3a1f13d607dddda9cf7/coverage-7.13.4-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b720ce6a88a2755f7c697c23268ddc47a571b88052e6b155224347389fdf6a3b", size = 253434, upload-time = "2026-02-09T12:57:23.339Z"}, - {url = "https://files.pythonhosted.org/packages/5a/88/6728a7ad17428b18d836540630487231f5470fb82454871149502f5e5aa2/coverage-7.13.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7b322db1284a2ed3aa28ffd8ebe3db91c929b7a333c0820abec3d838ef5b3525", size = 254676, upload-time = "2026-02-09T12:57:24.774Z"}, - {url = "https://files.pythonhosted.org/packages/7c/bc/21244b1b8cedf0dff0a2b53b208015fe798d5f2a8d5348dbfece04224fff/coverage-7.13.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f4594c67d8a7c89cf922d9df0438c7c7bb022ad506eddb0fdb2863359ff78242", size = 256807, upload-time = "2026-02-09T12:57:26.125Z"}, - {url = "https://files.pythonhosted.org/packages/97/a0/ddba7ed3251cff51006737a727d84e05b61517d1784a9988a846ba508877/coverage-7.13.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:53d133df809c743eb8bce33b24bcababb371f4441340578cd406e084d94a6148", size = 251058, upload-time = "2026-02-09T12:57:27.614Z"}, - {url = "https://files.pythonhosted.org/packages/9b/55/e289addf7ff54d3a540526f33751951bf0878f3809b47f6dfb3def69c6f7/coverage-7.13.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:76451d1978b95ba6507a039090ba076105c87cc76fc3efd5d35d72093964d49a", size = 252805, upload-time = "2026-02-09T12:57:29.066Z"}, - {url = "https://files.pythonhosted.org/packages/13/4e/cc276b1fa4a59be56d96f1dabddbdc30f4ba22e3b1cd42504c37b3313255/coverage-7.13.4-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:7f57b33491e281e962021de110b451ab8a24182589be17e12a22c79047935e23", size = 250766, upload-time = "2026-02-09T12:57:30.522Z"}, - {url = "https://files.pythonhosted.org/packages/94/44/1093b8f93018f8b41a8cf29636c9292502f05e4a113d4d107d14a3acd044/coverage-7.13.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:1731dc33dc276dafc410a885cbf5992f1ff171393e48a21453b78727d090de80", size = 254923, upload-time = "2026-02-09T12:57:31.946Z"}, - {url = "https://files.pythonhosted.org/packages/8b/55/ea2796da2d42257f37dbea1aab239ba9263b31bd91d5527cdd6db5efe174/coverage-7.13.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:bd60d4fe2f6fa7dff9223ca1bbc9f05d2b6697bc5961072e5d3b952d46e1b1ea", size = 250591, upload-time = "2026-02-09T12:57:33.842Z"}, - {url = "https://files.pythonhosted.org/packages/d4/fa/7c4bb72aacf8af5020675aa633e59c1fbe296d22aed191b6a5b711eb2bc7/coverage-7.13.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9181a3ccead280b828fae232df12b16652702b49d41e99d657f46cc7b1f6ec7a", size = 252364, upload-time = "2026-02-09T12:57:35.743Z"}, - {url = "https://files.pythonhosted.org/packages/5c/38/a8d2ec0146479c20bbaa7181b5b455a0c41101eed57f10dd19a78ab44c80/coverage-7.13.4-cp313-cp313-win32.whl", hash = "sha256:f53d492307962561ac7de4cd1de3e363589b000ab69617c6156a16ba7237998d", size = 222010, upload-time = "2026-02-09T12:57:37.25Z"}, - {url = "https://files.pythonhosted.org/packages/e2/0c/dbfafbe90a185943dcfbc766fe0e1909f658811492d79b741523a414a6cc/coverage-7.13.4-cp313-cp313-win_amd64.whl", hash = "sha256:e6f70dec1cc557e52df5306d051ef56003f74d56e9c4dd7ddb07e07ef32a84dd", size = 222818, upload-time = "2026-02-09T12:57:38.734Z"}, - {url = "https://files.pythonhosted.org/packages/04/d1/934918a138c932c90d78301f45f677fb05c39a3112b96fd2c8e60503cdc7/coverage-7.13.4-cp313-cp313-win_arm64.whl", hash = "sha256:fb07dc5da7e849e2ad31a5d74e9bece81f30ecf5a42909d0a695f8bd1874d6af", size = 221438, upload-time = "2026-02-09T12:57:40.223Z"}, - {url = "https://files.pythonhosted.org/packages/52/57/ee93ced533bcb3e6df961c0c6e42da2fc6addae53fb95b94a89b1e33ebd7/coverage-7.13.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:40d74da8e6c4b9ac18b15331c4b5ebc35a17069410cad462ad4f40dcd2d50c0d", size = 220165, upload-time = "2026-02-09T12:57:41.639Z"}, - {url = "https://files.pythonhosted.org/packages/c5/e0/969fc285a6fbdda49d91af278488d904dcd7651b2693872f0ff94e40e84a/coverage-7.13.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4223b4230a376138939a9173f1bdd6521994f2aff8047fae100d6d94d50c5a12", size = 220516, upload-time = "2026-02-09T12:57:44.215Z"}, - {url = "https://files.pythonhosted.org/packages/b1/b8/9531944e16267e2735a30a9641ff49671f07e8138ecf1ca13db9fd2560c7/coverage-7.13.4-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1d4be36a5114c499f9f1f9195e95ebf979460dbe2d88e6816ea202010ba1c34b", size = 261804, upload-time = "2026-02-09T12:57:45.989Z"}, - {url = "https://files.pythonhosted.org/packages/8a/f3/e63df6d500314a2a60390d1989240d5f27318a7a68fa30ad3806e2a9323e/coverage-7.13.4-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:200dea7d1e8095cc6e98cdabe3fd1d21ab17d3cee6dab00cadbb2fe35d9c15b9", size = 263885, upload-time = "2026-02-09T12:57:47.42Z"}, - {url = "https://files.pythonhosted.org/packages/f3/67/7654810de580e14b37670b60a09c599fa348e48312db5b216d730857ffe6/coverage-7.13.4-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b8eb931ee8e6d8243e253e5ed7336deea6904369d2fd8ae6e43f68abbf167092", size = 266308, upload-time = "2026-02-09T12:57:49.345Z"}, - {url = "https://files.pythonhosted.org/packages/37/6f/39d41eca0eab3cc82115953ad41c4e77935286c930e8fad15eaed1389d83/coverage-7.13.4-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:75eab1ebe4f2f64d9509b984f9314d4aa788540368218b858dad56dc8f3e5eb9", size = 267452, upload-time = "2026-02-09T12:57:50.811Z"}, - {url = "https://files.pythonhosted.org/packages/50/6d/39c0fbb8fc5cd4d2090811e553c2108cf5112e882f82505ee7495349a6bf/coverage-7.13.4-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c35eb28c1d085eb7d8c9b3296567a1bebe03ce72962e932431b9a61f28facf26", size = 261057, upload-time = "2026-02-09T12:57:52.447Z"}, - {url = "https://files.pythonhosted.org/packages/a4/a2/60010c669df5fa603bb5a97fb75407e191a846510da70ac657eb696b7fce/coverage-7.13.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:eb88b316ec33760714a4720feb2816a3a59180fd58c1985012054fa7aebee4c2", size = 263875, upload-time = "2026-02-09T12:57:53.938Z"}, - {url = "https://files.pythonhosted.org/packages/3e/d9/63b22a6bdbd17f1f96e9ed58604c2a6b0e72a9133e37d663bef185877cf6/coverage-7.13.4-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:7d41eead3cc673cbd38a4417deb7fd0b4ca26954ff7dc6078e33f6ff97bed940", size = 261500, upload-time = "2026-02-09T12:57:56.012Z"}, - {url = "https://files.pythonhosted.org/packages/70/bf/69f86ba1ad85bc3ad240e4c0e57a2e620fbc0e1645a47b5c62f0e941ad7f/coverage-7.13.4-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:fb26a934946a6afe0e326aebe0730cdff393a8bc0bbb65a2f41e30feddca399c", size = 265212, upload-time = "2026-02-09T12:57:57.5Z"}, - {url = "https://files.pythonhosted.org/packages/ae/f2/5f65a278a8c2148731831574c73e42f57204243d33bedaaf18fa79c5958f/coverage-7.13.4-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:dae88bc0fc77edaa65c14be099bd57ee140cf507e6bfdeea7938457ab387efb0", size = 260398, upload-time = "2026-02-09T12:57:59.027Z"}, - {url = "https://files.pythonhosted.org/packages/ef/80/6e8280a350ee9fea92f14b8357448a242dcaa243cb2c72ab0ca591f66c8c/coverage-7.13.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:845f352911777a8e722bfce168958214951e07e47e5d5d9744109fa5fe77f79b", size = 262584, upload-time = "2026-02-09T12:58:01.129Z"}, - {url = "https://files.pythonhosted.org/packages/22/63/01ff182fc95f260b539590fb12c11ad3e21332c15f9799cb5e2386f71d9f/coverage-7.13.4-cp313-cp313t-win32.whl", hash = "sha256:2fa8d5f8de70688a28240de9e139fa16b153cc3cbb01c5f16d88d6505ebdadf9", size = 222688, upload-time = "2026-02-09T12:58:02.736Z"}, - {url = "https://files.pythonhosted.org/packages/a9/43/89de4ef5d3cd53b886afa114065f7e9d3707bdb3e5efae13535b46ae483d/coverage-7.13.4-cp313-cp313t-win_amd64.whl", hash = "sha256:9351229c8c8407645840edcc277f4a2d44814d1bc34a2128c11c2a031d45a5dd", size = 223746, upload-time = "2026-02-09T12:58:05.362Z"}, - {url = "https://files.pythonhosted.org/packages/35/39/7cf0aa9a10d470a5309b38b289b9bb07ddeac5d61af9b664fe9775a4cb3e/coverage-7.13.4-cp313-cp313t-win_arm64.whl", hash = "sha256:30b8d0512f2dc8c8747557e8fb459d6176a2c9e5731e2b74d311c03b78451997", size = 222003, upload-time = "2026-02-09T12:58:06.952Z"}, - {url = "https://files.pythonhosted.org/packages/0d/4a/331fe2caf6799d591109bb9c08083080f6de90a823695d412a935622abb2/coverage-7.13.4-py3-none-any.whl", hash = "sha256:1af1641e57cf7ba1bd67d677c9abdbcd6cc2ab7da3bca7fa1e2b7e50e65f2ad0", size = 211242, upload-time = "2026-02-09T12:59:02.032Z"} +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/24/56/95b7e30fa389756cb56630faa728da46a27b8c6eb46f9d557c68fff12b65/coverage-7.13.4.tar.gz", hash = "sha256:e5c8f6ed1e61a8b2dcdf31eb0b9bbf0130750ca79c1c49eb898e2ad86f5ccc91", size = 827239, upload-time = "2026-02-09T12:59:03.86Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/81/4ce2fdd909c5a0ed1f6dedb88aa57ab79b6d1fbd9b588c1ac7ef45659566/coverage-7.13.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:02231499b08dabbe2b96612993e5fc34217cdae907a51b906ac7fca8027a4459", size = 219449, upload-time = "2026-02-09T12:56:54.889Z" }, + { url = "https://files.pythonhosted.org/packages/5d/96/5238b1efc5922ddbdc9b0db9243152c09777804fb7c02ad1741eb18a11c0/coverage-7.13.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40aa8808140e55dc022b15d8aa7f651b6b3d68b365ea0398f1441e0b04d859c3", size = 219810, upload-time = "2026-02-09T12:56:56.33Z" }, + { url = "https://files.pythonhosted.org/packages/78/72/2f372b726d433c9c35e56377cf1d513b4c16fe51841060d826b95caacec1/coverage-7.13.4-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5b856a8ccf749480024ff3bd7310adaef57bf31fd17e1bfc404b7940b6986634", size = 251308, upload-time = "2026-02-09T12:56:57.858Z" }, + { url = "https://files.pythonhosted.org/packages/5d/a0/2ea570925524ef4e00bb6c82649f5682a77fac5ab910a65c9284de422600/coverage-7.13.4-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2c048ea43875fbf8b45d476ad79f179809c590ec7b79e2035c662e7afa3192e3", size = 254052, upload-time = "2026-02-09T12:56:59.754Z" }, + { url = "https://files.pythonhosted.org/packages/e8/ac/45dc2e19a1939098d783c846e130b8f862fbb50d09e0af663988f2f21973/coverage-7.13.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b7b38448866e83176e28086674fe7368ab8590e4610fb662b44e345b86d63ffa", size = 255165, upload-time = "2026-02-09T12:57:01.287Z" }, + { url = "https://files.pythonhosted.org/packages/2d/4d/26d236ff35abc3b5e63540d3386e4c3b192168c1d96da5cb2f43c640970f/coverage-7.13.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:de6defc1c9badbf8b9e67ae90fd00519186d6ab64e5cc5f3d21359c2a9b2c1d3", size = 257432, upload-time = "2026-02-09T12:57:02.637Z" }, + { url = "https://files.pythonhosted.org/packages/ec/55/14a966c757d1348b2e19caf699415a2a4c4f7feaa4bbc6326a51f5c7dd1b/coverage-7.13.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7eda778067ad7ffccd23ecffce537dface96212576a07924cbf0d8799d2ded5a", size = 251716, upload-time = "2026-02-09T12:57:04.056Z" }, + { url = "https://files.pythonhosted.org/packages/77/33/50116647905837c66d28b2af1321b845d5f5d19be9655cb84d4a0ea806b4/coverage-7.13.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e87f6c587c3f34356c3759f0420693e35e7eb0e2e41e4c011cb6ec6ecbbf1db7", size = 253089, upload-time = "2026-02-09T12:57:05.503Z" }, + { url = "https://files.pythonhosted.org/packages/c2/b4/8efb11a46e3665d92635a56e4f2d4529de6d33f2cb38afd47d779d15fc99/coverage-7.13.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:8248977c2e33aecb2ced42fef99f2d319e9904a36e55a8a68b69207fb7e43edc", size = 251232, upload-time = "2026-02-09T12:57:06.879Z" }, + { url = "https://files.pythonhosted.org/packages/51/24/8cd73dd399b812cc76bb0ac260e671c4163093441847ffe058ac9fda1e32/coverage-7.13.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:25381386e80ae727608e662474db537d4df1ecd42379b5ba33c84633a2b36d47", size = 255299, upload-time = "2026-02-09T12:57:08.245Z" }, + { url = "https://files.pythonhosted.org/packages/03/94/0a4b12f1d0e029ce1ccc1c800944a9984cbe7d678e470bb6d3c6bc38a0da/coverage-7.13.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:ee756f00726693e5ba94d6df2bdfd64d4852d23b09bb0bc700e3b30e6f333985", size = 250796, upload-time = "2026-02-09T12:57:10.142Z" }, + { url = "https://files.pythonhosted.org/packages/73/44/6002fbf88f6698ca034360ce474c406be6d5a985b3fdb3401128031eef6b/coverage-7.13.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fdfc1e28e7c7cdce44985b3043bc13bbd9c747520f94a4d7164af8260b3d91f0", size = 252673, upload-time = "2026-02-09T12:57:12.197Z" }, + { url = "https://files.pythonhosted.org/packages/de/c6/a0279f7c00e786be75a749a5674e6fa267bcbd8209cd10c9a450c655dfa7/coverage-7.13.4-cp312-cp312-win32.whl", hash = "sha256:01d4cbc3c283a17fc1e42d614a119f7f438eabb593391283adca8dc86eff1246", size = 221990, upload-time = "2026-02-09T12:57:14.085Z" }, + { url = "https://files.pythonhosted.org/packages/77/4e/c0a25a425fcf5557d9abd18419c95b63922e897bc86c1f327f155ef234a9/coverage-7.13.4-cp312-cp312-win_amd64.whl", hash = "sha256:9401ebc7ef522f01d01d45532c68c5ac40fb27113019b6b7d8b208f6e9baa126", size = 222800, upload-time = "2026-02-09T12:57:15.944Z" }, + { url = "https://files.pythonhosted.org/packages/47/ac/92da44ad9a6f4e3a7debd178949d6f3769bedca33830ce9b1dcdab589a37/coverage-7.13.4-cp312-cp312-win_arm64.whl", hash = "sha256:b1ec7b6b6e93255f952e27ab58fbc68dcc468844b16ecbee881aeb29b6ab4d8d", size = 221415, upload-time = "2026-02-09T12:57:17.497Z" }, + { url = "https://files.pythonhosted.org/packages/db/23/aad45061a31677d68e47499197a131eea55da4875d16c1f42021ab963503/coverage-7.13.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b66a2da594b6068b48b2692f043f35d4d3693fb639d5ea8b39533c2ad9ac3ab9", size = 219474, upload-time = "2026-02-09T12:57:19.332Z" }, + { url = "https://files.pythonhosted.org/packages/a5/70/9b8b67a0945f3dfec1fd896c5cefb7c19d5a3a6d74630b99a895170999ae/coverage-7.13.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3599eb3992d814d23b35c536c28df1a882caa950f8f507cef23d1cbf334995ac", size = 219844, upload-time = "2026-02-09T12:57:20.66Z" }, + { url = "https://files.pythonhosted.org/packages/97/fd/7e859f8fab324cef6c4ad7cff156ca7c489fef9179d5749b0c8d321281c2/coverage-7.13.4-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:93550784d9281e374fb5a12bf1324cc8a963fd63b2d2f223503ef0fd4aa339ea", size = 250832, upload-time = "2026-02-09T12:57:22.007Z" }, + { url = "https://files.pythonhosted.org/packages/e4/dc/b2442d10020c2f52617828862d8b6ee337859cd8f3a1f13d607dddda9cf7/coverage-7.13.4-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b720ce6a88a2755f7c697c23268ddc47a571b88052e6b155224347389fdf6a3b", size = 253434, upload-time = "2026-02-09T12:57:23.339Z" }, + { url = "https://files.pythonhosted.org/packages/5a/88/6728a7ad17428b18d836540630487231f5470fb82454871149502f5e5aa2/coverage-7.13.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7b322db1284a2ed3aa28ffd8ebe3db91c929b7a333c0820abec3d838ef5b3525", size = 254676, upload-time = "2026-02-09T12:57:24.774Z" }, + { url = "https://files.pythonhosted.org/packages/7c/bc/21244b1b8cedf0dff0a2b53b208015fe798d5f2a8d5348dbfece04224fff/coverage-7.13.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f4594c67d8a7c89cf922d9df0438c7c7bb022ad506eddb0fdb2863359ff78242", size = 256807, upload-time = "2026-02-09T12:57:26.125Z" }, + { url = "https://files.pythonhosted.org/packages/97/a0/ddba7ed3251cff51006737a727d84e05b61517d1784a9988a846ba508877/coverage-7.13.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:53d133df809c743eb8bce33b24bcababb371f4441340578cd406e084d94a6148", size = 251058, upload-time = "2026-02-09T12:57:27.614Z" }, + { url = "https://files.pythonhosted.org/packages/9b/55/e289addf7ff54d3a540526f33751951bf0878f3809b47f6dfb3def69c6f7/coverage-7.13.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:76451d1978b95ba6507a039090ba076105c87cc76fc3efd5d35d72093964d49a", size = 252805, upload-time = "2026-02-09T12:57:29.066Z" }, + { url = "https://files.pythonhosted.org/packages/13/4e/cc276b1fa4a59be56d96f1dabddbdc30f4ba22e3b1cd42504c37b3313255/coverage-7.13.4-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:7f57b33491e281e962021de110b451ab8a24182589be17e12a22c79047935e23", size = 250766, upload-time = "2026-02-09T12:57:30.522Z" }, + { url = "https://files.pythonhosted.org/packages/94/44/1093b8f93018f8b41a8cf29636c9292502f05e4a113d4d107d14a3acd044/coverage-7.13.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:1731dc33dc276dafc410a885cbf5992f1ff171393e48a21453b78727d090de80", size = 254923, upload-time = "2026-02-09T12:57:31.946Z" }, + { url = "https://files.pythonhosted.org/packages/8b/55/ea2796da2d42257f37dbea1aab239ba9263b31bd91d5527cdd6db5efe174/coverage-7.13.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:bd60d4fe2f6fa7dff9223ca1bbc9f05d2b6697bc5961072e5d3b952d46e1b1ea", size = 250591, upload-time = "2026-02-09T12:57:33.842Z" }, + { url = "https://files.pythonhosted.org/packages/d4/fa/7c4bb72aacf8af5020675aa633e59c1fbe296d22aed191b6a5b711eb2bc7/coverage-7.13.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9181a3ccead280b828fae232df12b16652702b49d41e99d657f46cc7b1f6ec7a", size = 252364, upload-time = "2026-02-09T12:57:35.743Z" }, + { url = "https://files.pythonhosted.org/packages/5c/38/a8d2ec0146479c20bbaa7181b5b455a0c41101eed57f10dd19a78ab44c80/coverage-7.13.4-cp313-cp313-win32.whl", hash = "sha256:f53d492307962561ac7de4cd1de3e363589b000ab69617c6156a16ba7237998d", size = 222010, upload-time = "2026-02-09T12:57:37.25Z" }, + { url = "https://files.pythonhosted.org/packages/e2/0c/dbfafbe90a185943dcfbc766fe0e1909f658811492d79b741523a414a6cc/coverage-7.13.4-cp313-cp313-win_amd64.whl", hash = "sha256:e6f70dec1cc557e52df5306d051ef56003f74d56e9c4dd7ddb07e07ef32a84dd", size = 222818, upload-time = "2026-02-09T12:57:38.734Z" }, + { url = "https://files.pythonhosted.org/packages/04/d1/934918a138c932c90d78301f45f677fb05c39a3112b96fd2c8e60503cdc7/coverage-7.13.4-cp313-cp313-win_arm64.whl", hash = "sha256:fb07dc5da7e849e2ad31a5d74e9bece81f30ecf5a42909d0a695f8bd1874d6af", size = 221438, upload-time = "2026-02-09T12:57:40.223Z" }, + { url = "https://files.pythonhosted.org/packages/52/57/ee93ced533bcb3e6df961c0c6e42da2fc6addae53fb95b94a89b1e33ebd7/coverage-7.13.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:40d74da8e6c4b9ac18b15331c4b5ebc35a17069410cad462ad4f40dcd2d50c0d", size = 220165, upload-time = "2026-02-09T12:57:41.639Z" }, + { url = "https://files.pythonhosted.org/packages/c5/e0/969fc285a6fbdda49d91af278488d904dcd7651b2693872f0ff94e40e84a/coverage-7.13.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4223b4230a376138939a9173f1bdd6521994f2aff8047fae100d6d94d50c5a12", size = 220516, upload-time = "2026-02-09T12:57:44.215Z" }, + { url = "https://files.pythonhosted.org/packages/b1/b8/9531944e16267e2735a30a9641ff49671f07e8138ecf1ca13db9fd2560c7/coverage-7.13.4-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1d4be36a5114c499f9f1f9195e95ebf979460dbe2d88e6816ea202010ba1c34b", size = 261804, upload-time = "2026-02-09T12:57:45.989Z" }, + { url = "https://files.pythonhosted.org/packages/8a/f3/e63df6d500314a2a60390d1989240d5f27318a7a68fa30ad3806e2a9323e/coverage-7.13.4-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:200dea7d1e8095cc6e98cdabe3fd1d21ab17d3cee6dab00cadbb2fe35d9c15b9", size = 263885, upload-time = "2026-02-09T12:57:47.42Z" }, + { url = "https://files.pythonhosted.org/packages/f3/67/7654810de580e14b37670b60a09c599fa348e48312db5b216d730857ffe6/coverage-7.13.4-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b8eb931ee8e6d8243e253e5ed7336deea6904369d2fd8ae6e43f68abbf167092", size = 266308, upload-time = "2026-02-09T12:57:49.345Z" }, + { url = "https://files.pythonhosted.org/packages/37/6f/39d41eca0eab3cc82115953ad41c4e77935286c930e8fad15eaed1389d83/coverage-7.13.4-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:75eab1ebe4f2f64d9509b984f9314d4aa788540368218b858dad56dc8f3e5eb9", size = 267452, upload-time = "2026-02-09T12:57:50.811Z" }, + { url = "https://files.pythonhosted.org/packages/50/6d/39c0fbb8fc5cd4d2090811e553c2108cf5112e882f82505ee7495349a6bf/coverage-7.13.4-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c35eb28c1d085eb7d8c9b3296567a1bebe03ce72962e932431b9a61f28facf26", size = 261057, upload-time = "2026-02-09T12:57:52.447Z" }, + { url = "https://files.pythonhosted.org/packages/a4/a2/60010c669df5fa603bb5a97fb75407e191a846510da70ac657eb696b7fce/coverage-7.13.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:eb88b316ec33760714a4720feb2816a3a59180fd58c1985012054fa7aebee4c2", size = 263875, upload-time = "2026-02-09T12:57:53.938Z" }, + { url = "https://files.pythonhosted.org/packages/3e/d9/63b22a6bdbd17f1f96e9ed58604c2a6b0e72a9133e37d663bef185877cf6/coverage-7.13.4-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:7d41eead3cc673cbd38a4417deb7fd0b4ca26954ff7dc6078e33f6ff97bed940", size = 261500, upload-time = "2026-02-09T12:57:56.012Z" }, + { url = "https://files.pythonhosted.org/packages/70/bf/69f86ba1ad85bc3ad240e4c0e57a2e620fbc0e1645a47b5c62f0e941ad7f/coverage-7.13.4-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:fb26a934946a6afe0e326aebe0730cdff393a8bc0bbb65a2f41e30feddca399c", size = 265212, upload-time = "2026-02-09T12:57:57.5Z" }, + { url = "https://files.pythonhosted.org/packages/ae/f2/5f65a278a8c2148731831574c73e42f57204243d33bedaaf18fa79c5958f/coverage-7.13.4-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:dae88bc0fc77edaa65c14be099bd57ee140cf507e6bfdeea7938457ab387efb0", size = 260398, upload-time = "2026-02-09T12:57:59.027Z" }, + { url = "https://files.pythonhosted.org/packages/ef/80/6e8280a350ee9fea92f14b8357448a242dcaa243cb2c72ab0ca591f66c8c/coverage-7.13.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:845f352911777a8e722bfce168958214951e07e47e5d5d9744109fa5fe77f79b", size = 262584, upload-time = "2026-02-09T12:58:01.129Z" }, + { url = "https://files.pythonhosted.org/packages/22/63/01ff182fc95f260b539590fb12c11ad3e21332c15f9799cb5e2386f71d9f/coverage-7.13.4-cp313-cp313t-win32.whl", hash = "sha256:2fa8d5f8de70688a28240de9e139fa16b153cc3cbb01c5f16d88d6505ebdadf9", size = 222688, upload-time = "2026-02-09T12:58:02.736Z" }, + { url = "https://files.pythonhosted.org/packages/a9/43/89de4ef5d3cd53b886afa114065f7e9d3707bdb3e5efae13535b46ae483d/coverage-7.13.4-cp313-cp313t-win_amd64.whl", hash = "sha256:9351229c8c8407645840edcc277f4a2d44814d1bc34a2128c11c2a031d45a5dd", size = 223746, upload-time = "2026-02-09T12:58:05.362Z" }, + { url = "https://files.pythonhosted.org/packages/35/39/7cf0aa9a10d470a5309b38b289b9bb07ddeac5d61af9b664fe9775a4cb3e/coverage-7.13.4-cp313-cp313t-win_arm64.whl", hash = "sha256:30b8d0512f2dc8c8747557e8fb459d6176a2c9e5731e2b74d311c03b78451997", size = 222003, upload-time = "2026-02-09T12:58:06.952Z" }, + { url = "https://files.pythonhosted.org/packages/0d/4a/331fe2caf6799d591109bb9c08083080f6de90a823695d412a935622abb2/coverage-7.13.4-py3-none-any.whl", hash = "sha256:1af1641e57cf7ba1bd67d677c9abdbcd6cc2ab7da3bca7fa1e2b7e50e65f2ad0", size = 211242, upload-time = "2026-02-09T12:59:02.032Z" }, ] [[package]] name = "distlib" -sdist = {url = "https://files.pythonhosted.org/packages/96/8e/709914eb2b5749865801041647dc7f4e6d00b549cfe88b65ca192995f07c/distlib-0.4.0.tar.gz", hash = "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d", size = 614605, upload-time = "2025-07-17T16:52:00.465Z"} -source = {registry = "https://pypi.org/simple"} version = "0.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/96/8e/709914eb2b5749865801041647dc7f4e6d00b549cfe88b65ca192995f07c/distlib-0.4.0.tar.gz", hash = "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d", size = 614605, upload-time = "2025-07-17T16:52:00.465Z" } wheels = [ - {url = "https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16", size = 469047, upload-time = "2025-07-17T16:51:58.613Z"} + { url = "https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16", size = 469047, upload-time = "2025-07-17T16:51:58.613Z" }, ] [[package]] name = "docutils" -sdist = {url = "https://files.pythonhosted.org/packages/ae/b6/03bb70946330e88ffec97aefd3ea75ba575cb2e762061e0e62a213befee8/docutils-0.22.4.tar.gz", hash = "sha256:4db53b1fde9abecbb74d91230d32ab626d94f6badfc575d6db9194a49df29968", size = 2291750, upload-time = "2025-12-18T19:00:26.443Z"} -source = {registry = "https://pypi.org/simple"} version = "0.22.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ae/b6/03bb70946330e88ffec97aefd3ea75ba575cb2e762061e0e62a213befee8/docutils-0.22.4.tar.gz", hash = "sha256:4db53b1fde9abecbb74d91230d32ab626d94f6badfc575d6db9194a49df29968", size = 2291750, upload-time = "2025-12-18T19:00:26.443Z" } wheels = [ - {url = "https://files.pythonhosted.org/packages/02/10/5da547df7a391dcde17f59520a231527b8571e6f46fc8efb02ccb370ab12/docutils-0.22.4-py3-none-any.whl", hash = "sha256:d0013f540772d1420576855455d050a2180186c91c15779301ac2ccb3eeb68de", size = 633196, upload-time = "2025-12-18T19:00:18.077Z"} + { url = "https://files.pythonhosted.org/packages/02/10/5da547df7a391dcde17f59520a231527b8571e6f46fc8efb02ccb370ab12/docutils-0.22.4-py3-none-any.whl", hash = "sha256:d0013f540772d1420576855455d050a2180186c91c15779301ac2ccb3eeb68de", size = 633196, upload-time = "2025-12-18T19:00:18.077Z" }, ] [[package]] name = "filelock" -sdist = {url = "https://files.pythonhosted.org/packages/77/18/a1fd2231c679dcb9726204645721b12498aeac28e1ad0601038f94b42556/filelock-3.25.0.tar.gz", hash = "sha256:8f00faf3abf9dc730a1ffe9c354ae5c04e079ab7d3a683b7c32da5dd05f26af3", size = 40158, upload-time = "2026-03-01T15:08:45.916Z"} -source = {registry = "https://pypi.org/simple"} version = "3.25.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/77/18/a1fd2231c679dcb9726204645721b12498aeac28e1ad0601038f94b42556/filelock-3.25.0.tar.gz", hash = "sha256:8f00faf3abf9dc730a1ffe9c354ae5c04e079ab7d3a683b7c32da5dd05f26af3", size = 40158, upload-time = "2026-03-01T15:08:45.916Z" } wheels = [ - {url = "https://files.pythonhosted.org/packages/f9/0b/de6f54d4a8bedfe8645c41497f3c18d749f0bd3218170c667bf4b81d0cdd/filelock-3.25.0-py3-none-any.whl", hash = "sha256:5ccf8069f7948f494968fc0713c10e5c182a9c9d9eef3a636307a20c2490f047", size = 26427, upload-time = "2026-03-01T15:08:44.593Z"} + { url = "https://files.pythonhosted.org/packages/f9/0b/de6f54d4a8bedfe8645c41497f3c18d749f0bd3218170c667bf4b81d0cdd/filelock-3.25.0-py3-none-any.whl", hash = "sha256:5ccf8069f7948f494968fc0713c10e5c182a9c9d9eef3a636307a20c2490f047", size = 26427, upload-time = "2026-03-01T15:08:44.593Z" }, ] [[package]] name = "identify" -sdist = {url = "https://files.pythonhosted.org/packages/57/84/376a3b96e5a8d33a7aa2c5b3b31a4b3c364117184bf0b17418055f6ace66/identify-2.6.17.tar.gz", hash = "sha256:f816b0b596b204c9fdf076ded172322f2723cf958d02f9c3587504834c8ff04d", size = 99579, upload-time = "2026-03-01T20:04:12.702Z"} -source = {registry = "https://pypi.org/simple"} version = "2.6.17" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/57/84/376a3b96e5a8d33a7aa2c5b3b31a4b3c364117184bf0b17418055f6ace66/identify-2.6.17.tar.gz", hash = "sha256:f816b0b596b204c9fdf076ded172322f2723cf958d02f9c3587504834c8ff04d", size = 99579, upload-time = "2026-03-01T20:04:12.702Z" } wheels = [ - {url = "https://files.pythonhosted.org/packages/40/66/71c1227dff78aaeb942fed29dd5651f2aec166cc7c9aeea3e8b26a539b7d/identify-2.6.17-py2.py3-none-any.whl", hash = "sha256:be5f8412d5ed4b20f2bd41a65f920990bdccaa6a4a18a08f1eefdcd0bdd885f0", size = 99382, upload-time = "2026-03-01T20:04:11.439Z"} + { url = "https://files.pythonhosted.org/packages/40/66/71c1227dff78aaeb942fed29dd5651f2aec166cc7c9aeea3e8b26a539b7d/identify-2.6.17-py2.py3-none-any.whl", hash = "sha256:be5f8412d5ed4b20f2bd41a65f920990bdccaa6a4a18a08f1eefdcd0bdd885f0", size = 99382, upload-time = "2026-03-01T20:04:11.439Z" }, ] [[package]] name = "idna" -sdist = {url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z"} -source = {registry = "https://pypi.org/simple"} version = "3.11" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" } wheels = [ - {url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z"} + { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, ] [[package]] name = "imagesize" -sdist = {url = "https://files.pythonhosted.org/packages/6c/e6/7bf14eeb8f8b7251141944835abd42eb20a658d89084b7e1f3e5fe394090/imagesize-2.0.0.tar.gz", hash = "sha256:8e8358c4a05c304f1fccf7ff96f036e7243a189e9e42e90851993c558cfe9ee3", size = 1773045, upload-time = "2026-03-03T14:18:29.941Z"} -source = {registry = "https://pypi.org/simple"} version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6c/e6/7bf14eeb8f8b7251141944835abd42eb20a658d89084b7e1f3e5fe394090/imagesize-2.0.0.tar.gz", hash = "sha256:8e8358c4a05c304f1fccf7ff96f036e7243a189e9e42e90851993c558cfe9ee3", size = 1773045, upload-time = "2026-03-03T14:18:29.941Z" } wheels = [ - {url = "https://files.pythonhosted.org/packages/5f/53/fb7122b71361a0d121b669dcf3d31244ef75badbbb724af388948de543e2/imagesize-2.0.0-py2.py3-none-any.whl", hash = "sha256:5667c5bbb57ab3f1fa4bc366f4fbc971db3d5ed011fd2715fd8001f782718d96", size = 9441, upload-time = "2026-03-03T14:18:27.892Z"} + { url = "https://files.pythonhosted.org/packages/5f/53/fb7122b71361a0d121b669dcf3d31244ef75badbbb724af388948de543e2/imagesize-2.0.0-py2.py3-none-any.whl", hash = "sha256:5667c5bbb57ab3f1fa4bc366f4fbc971db3d5ed011fd2715fd8001f782718d96", size = 9441, upload-time = "2026-03-03T14:18:27.892Z" }, ] [[package]] name = "iniconfig" -sdist = {url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z"} -source = {registry = "https://pypi.org/simple"} version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } wheels = [ - {url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z"} + { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, ] [[package]] -dependencies = [ - {name = "markupsafe"} -] name = "jinja2" -sdist = {url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z"} -source = {registry = "https://pypi.org/simple"} version = "3.1.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" } wheels = [ - {url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z"} + { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, ] [[package]] name = "librt" -sdist = {url = "https://files.pythonhosted.org/packages/56/9c/b4b0c54d84da4a94b37bd44151e46d5e583c9534c7e02250b961b1b6d8a8/librt-0.8.1.tar.gz", hash = "sha256:be46a14693955b3bd96014ccbdb8339ee8c9346fbe11c1b78901b55125f14c73", size = 177471, upload-time = "2026-02-17T16:13:06.101Z"} -source = {registry = "https://pypi.org/simple"} version = "0.8.1" -wheels = [ - {url = "https://files.pythonhosted.org/packages/95/21/d39b0a87ac52fc98f621fb6f8060efb017a767ebbbac2f99fbcbc9ddc0d7/librt-0.8.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a28f2612ab566b17f3698b0da021ff9960610301607c9a5e8eaca62f5e1c350a", size = 66516, upload-time = "2026-02-17T16:11:41.604Z"}, - {url = "https://files.pythonhosted.org/packages/69/f1/46375e71441c43e8ae335905e069f1c54febee63a146278bcee8782c84fd/librt-0.8.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:60a78b694c9aee2a0f1aaeaa7d101cf713e92e8423a941d2897f4fa37908dab9", size = 68634, upload-time = "2026-02-17T16:11:43.268Z"}, - {url = "https://files.pythonhosted.org/packages/0a/33/c510de7f93bf1fa19e13423a606d8189a02624a800710f6e6a0a0f0784b3/librt-0.8.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:758509ea3f1eba2a57558e7e98f4659d0ea7670bff49673b0dde18a3c7e6c0eb", size = 198941, upload-time = "2026-02-17T16:11:44.28Z"}, - {url = "https://files.pythonhosted.org/packages/dd/36/e725903416409a533d92398e88ce665476f275081d0d7d42f9c4951999e5/librt-0.8.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:039b9f2c506bd0ab0f8725aa5ba339c6f0cd19d3b514b50d134789809c24285d", size = 209991, upload-time = "2026-02-17T16:11:45.462Z"}, - {url = "https://files.pythonhosted.org/packages/30/7a/8d908a152e1875c9f8eac96c97a480df425e657cdb47854b9efaa4998889/librt-0.8.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5bb54f1205a3a6ab41a6fd71dfcdcbd278670d3a90ca502a30d9da583105b6f7", size = 224476, upload-time = "2026-02-17T16:11:46.542Z"}, - {url = "https://files.pythonhosted.org/packages/a8/b8/a22c34f2c485b8903a06f3fe3315341fe6876ef3599792344669db98fcff/librt-0.8.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:05bd41cdee35b0c59c259f870f6da532a2c5ca57db95b5f23689fcb5c9e42440", size = 217518, upload-time = "2026-02-17T16:11:47.746Z"}, - {url = "https://files.pythonhosted.org/packages/79/6f/5c6fea00357e4f82ba44f81dbfb027921f1ab10e320d4a64e1c408d035d9/librt-0.8.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:adfab487facf03f0d0857b8710cf82d0704a309d8ffc33b03d9302b4c64e91a9", size = 225116, upload-time = "2026-02-17T16:11:49.298Z"}, - {url = "https://files.pythonhosted.org/packages/f2/a0/95ced4e7b1267fe1e2720a111685bcddf0e781f7e9e0ce59d751c44dcfe5/librt-0.8.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:153188fe98a72f206042be10a2c6026139852805215ed9539186312d50a8e972", size = 217751, upload-time = "2026-02-17T16:11:50.49Z"}, - {url = "https://files.pythonhosted.org/packages/93/c2/0517281cb4d4101c27ab59472924e67f55e375bc46bedae94ac6dc6e1902/librt-0.8.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:dd3c41254ee98604b08bd5b3af5bf0a89740d4ee0711de95b65166bf44091921", size = 218378, upload-time = "2026-02-17T16:11:51.783Z"}, - {url = "https://files.pythonhosted.org/packages/43/e8/37b3ac108e8976888e559a7b227d0ceac03c384cfd3e7a1c2ee248dbae79/librt-0.8.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e0d138c7ae532908cbb342162b2611dbd4d90c941cd25ab82084aaf71d2c0bd0", size = 241199, upload-time = "2026-02-17T16:11:53.561Z"}, - {url = "https://files.pythonhosted.org/packages/4b/5b/35812d041c53967fedf551a39399271bbe4257e681236a2cf1a69c8e7fa1/librt-0.8.1-cp312-cp312-win32.whl", hash = "sha256:43353b943613c5d9c49a25aaffdba46f888ec354e71e3529a00cca3f04d66a7a", size = 54917, upload-time = "2026-02-17T16:11:54.758Z"}, - {url = "https://files.pythonhosted.org/packages/de/d1/fa5d5331b862b9775aaf2a100f5ef86854e5d4407f71bddf102f4421e034/librt-0.8.1-cp312-cp312-win_amd64.whl", hash = "sha256:ff8baf1f8d3f4b6b7257fcb75a501f2a5499d0dda57645baa09d4d0d34b19444", size = 62017, upload-time = "2026-02-17T16:11:55.748Z"}, - {url = "https://files.pythonhosted.org/packages/c7/7c/c614252f9acda59b01a66e2ddfd243ed1c7e1deab0293332dfbccf862808/librt-0.8.1-cp312-cp312-win_arm64.whl", hash = "sha256:0f2ae3725904f7377e11cc37722d5d401e8b3d5851fb9273d7f4fe04f6b3d37d", size = 52441, upload-time = "2026-02-17T16:11:56.801Z"}, - {url = "https://files.pythonhosted.org/packages/c5/3c/f614c8e4eaac7cbf2bbdf9528790b21d89e277ee20d57dc6e559c626105f/librt-0.8.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7e6bad1cd94f6764e1e21950542f818a09316645337fd5ab9a7acc45d99a8f35", size = 66529, upload-time = "2026-02-17T16:11:57.809Z"}, - {url = "https://files.pythonhosted.org/packages/ab/96/5836544a45100ae411eda07d29e3d99448e5258b6e9c8059deb92945f5c2/librt-0.8.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cf450f498c30af55551ba4f66b9123b7185362ec8b625a773b3d39aa1a717583", size = 68669, upload-time = "2026-02-17T16:11:58.843Z"}, - {url = "https://files.pythonhosted.org/packages/06/53/f0b992b57af6d5531bf4677d75c44f095f2366a1741fb695ee462ae04b05/librt-0.8.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:eca45e982fa074090057132e30585a7e8674e9e885d402eae85633e9f449ce6c", size = 199279, upload-time = "2026-02-17T16:11:59.862Z"}, - {url = "https://files.pythonhosted.org/packages/f3/ad/4848cc16e268d14280d8168aee4f31cea92bbd2b79ce33d3e166f2b4e4fc/librt-0.8.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0c3811485fccfda840861905b8c70bba5ec094e02825598bb9d4ca3936857a04", size = 210288, upload-time = "2026-02-17T16:12:00.954Z"}, - {url = "https://files.pythonhosted.org/packages/52/05/27fdc2e95de26273d83b96742d8d3b7345f2ea2bdbd2405cc504644f2096/librt-0.8.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5e4af413908f77294605e28cfd98063f54b2c790561383971d2f52d113d9c363", size = 224809, upload-time = "2026-02-17T16:12:02.108Z"}, - {url = "https://files.pythonhosted.org/packages/7a/d0/78200a45ba3240cb042bc597d6f2accba9193a2c57d0356268cbbe2d0925/librt-0.8.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5212a5bd7fae98dae95710032902edcd2ec4dc994e883294f75c857b83f9aba0", size = 218075, upload-time = "2026-02-17T16:12:03.631Z"}, - {url = "https://files.pythonhosted.org/packages/af/72/a210839fa74c90474897124c064ffca07f8d4b347b6574d309686aae7ca6/librt-0.8.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e692aa2d1d604e6ca12d35e51fdc36f4cda6345e28e36374579f7ef3611b3012", size = 225486, upload-time = "2026-02-17T16:12:04.725Z"}, - {url = "https://files.pythonhosted.org/packages/a3/c1/a03cc63722339ddbf087485f253493e2b013039f5b707e8e6016141130fa/librt-0.8.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4be2a5c926b9770c9e08e717f05737a269b9d0ebc5d2f0060f0fe3fe9ce47acb", size = 218219, upload-time = "2026-02-17T16:12:05.828Z"}, - {url = "https://files.pythonhosted.org/packages/58/f5/fff6108af0acf941c6f274a946aea0e484bd10cd2dc37610287ce49388c5/librt-0.8.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:fd1a720332ea335ceb544cf0a03f81df92abd4bb887679fd1e460976b0e6214b", size = 218750, upload-time = "2026-02-17T16:12:07.09Z"}, - {url = "https://files.pythonhosted.org/packages/71/67/5a387bfef30ec1e4b4f30562c8586566faf87e47d696768c19feb49e3646/librt-0.8.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:93c2af9e01e0ef80d95ae3c720be101227edae5f2fe7e3dc63d8857fadfc5a1d", size = 241624, upload-time = "2026-02-17T16:12:08.43Z"}, - {url = "https://files.pythonhosted.org/packages/d4/be/24f8502db11d405232ac1162eb98069ca49c3306c1d75c6ccc61d9af8789/librt-0.8.1-cp313-cp313-win32.whl", hash = "sha256:086a32dbb71336627e78cc1d6ee305a68d038ef7d4c39aaff41ae8c9aa46e91a", size = 54969, upload-time = "2026-02-17T16:12:09.633Z"}, - {url = "https://files.pythonhosted.org/packages/5c/73/c9fdf6cb2a529c1a092ce769a12d88c8cca991194dfe641b6af12fa964d2/librt-0.8.1-cp313-cp313-win_amd64.whl", hash = "sha256:e11769a1dbda4da7b00a76cfffa67aa47cfa66921d2724539eee4b9ede780b79", size = 62000, upload-time = "2026-02-17T16:12:10.632Z"}, - {url = "https://files.pythonhosted.org/packages/d3/97/68f80ca3ac4924f250cdfa6e20142a803e5e50fca96ef5148c52ee8c10ea/librt-0.8.1-cp313-cp313-win_arm64.whl", hash = "sha256:924817ab3141aca17893386ee13261f1d100d1ef410d70afe4389f2359fea4f0", size = 52495, upload-time = "2026-02-17T16:12:11.633Z"} +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/56/9c/b4b0c54d84da4a94b37bd44151e46d5e583c9534c7e02250b961b1b6d8a8/librt-0.8.1.tar.gz", hash = "sha256:be46a14693955b3bd96014ccbdb8339ee8c9346fbe11c1b78901b55125f14c73", size = 177471, upload-time = "2026-02-17T16:13:06.101Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/95/21/d39b0a87ac52fc98f621fb6f8060efb017a767ebbbac2f99fbcbc9ddc0d7/librt-0.8.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a28f2612ab566b17f3698b0da021ff9960610301607c9a5e8eaca62f5e1c350a", size = 66516, upload-time = "2026-02-17T16:11:41.604Z" }, + { url = "https://files.pythonhosted.org/packages/69/f1/46375e71441c43e8ae335905e069f1c54febee63a146278bcee8782c84fd/librt-0.8.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:60a78b694c9aee2a0f1aaeaa7d101cf713e92e8423a941d2897f4fa37908dab9", size = 68634, upload-time = "2026-02-17T16:11:43.268Z" }, + { url = "https://files.pythonhosted.org/packages/0a/33/c510de7f93bf1fa19e13423a606d8189a02624a800710f6e6a0a0f0784b3/librt-0.8.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:758509ea3f1eba2a57558e7e98f4659d0ea7670bff49673b0dde18a3c7e6c0eb", size = 198941, upload-time = "2026-02-17T16:11:44.28Z" }, + { url = "https://files.pythonhosted.org/packages/dd/36/e725903416409a533d92398e88ce665476f275081d0d7d42f9c4951999e5/librt-0.8.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:039b9f2c506bd0ab0f8725aa5ba339c6f0cd19d3b514b50d134789809c24285d", size = 209991, upload-time = "2026-02-17T16:11:45.462Z" }, + { url = "https://files.pythonhosted.org/packages/30/7a/8d908a152e1875c9f8eac96c97a480df425e657cdb47854b9efaa4998889/librt-0.8.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5bb54f1205a3a6ab41a6fd71dfcdcbd278670d3a90ca502a30d9da583105b6f7", size = 224476, upload-time = "2026-02-17T16:11:46.542Z" }, + { url = "https://files.pythonhosted.org/packages/a8/b8/a22c34f2c485b8903a06f3fe3315341fe6876ef3599792344669db98fcff/librt-0.8.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:05bd41cdee35b0c59c259f870f6da532a2c5ca57db95b5f23689fcb5c9e42440", size = 217518, upload-time = "2026-02-17T16:11:47.746Z" }, + { url = "https://files.pythonhosted.org/packages/79/6f/5c6fea00357e4f82ba44f81dbfb027921f1ab10e320d4a64e1c408d035d9/librt-0.8.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:adfab487facf03f0d0857b8710cf82d0704a309d8ffc33b03d9302b4c64e91a9", size = 225116, upload-time = "2026-02-17T16:11:49.298Z" }, + { url = "https://files.pythonhosted.org/packages/f2/a0/95ced4e7b1267fe1e2720a111685bcddf0e781f7e9e0ce59d751c44dcfe5/librt-0.8.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:153188fe98a72f206042be10a2c6026139852805215ed9539186312d50a8e972", size = 217751, upload-time = "2026-02-17T16:11:50.49Z" }, + { url = "https://files.pythonhosted.org/packages/93/c2/0517281cb4d4101c27ab59472924e67f55e375bc46bedae94ac6dc6e1902/librt-0.8.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:dd3c41254ee98604b08bd5b3af5bf0a89740d4ee0711de95b65166bf44091921", size = 218378, upload-time = "2026-02-17T16:11:51.783Z" }, + { url = "https://files.pythonhosted.org/packages/43/e8/37b3ac108e8976888e559a7b227d0ceac03c384cfd3e7a1c2ee248dbae79/librt-0.8.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e0d138c7ae532908cbb342162b2611dbd4d90c941cd25ab82084aaf71d2c0bd0", size = 241199, upload-time = "2026-02-17T16:11:53.561Z" }, + { url = "https://files.pythonhosted.org/packages/4b/5b/35812d041c53967fedf551a39399271bbe4257e681236a2cf1a69c8e7fa1/librt-0.8.1-cp312-cp312-win32.whl", hash = "sha256:43353b943613c5d9c49a25aaffdba46f888ec354e71e3529a00cca3f04d66a7a", size = 54917, upload-time = "2026-02-17T16:11:54.758Z" }, + { url = "https://files.pythonhosted.org/packages/de/d1/fa5d5331b862b9775aaf2a100f5ef86854e5d4407f71bddf102f4421e034/librt-0.8.1-cp312-cp312-win_amd64.whl", hash = "sha256:ff8baf1f8d3f4b6b7257fcb75a501f2a5499d0dda57645baa09d4d0d34b19444", size = 62017, upload-time = "2026-02-17T16:11:55.748Z" }, + { url = "https://files.pythonhosted.org/packages/c7/7c/c614252f9acda59b01a66e2ddfd243ed1c7e1deab0293332dfbccf862808/librt-0.8.1-cp312-cp312-win_arm64.whl", hash = "sha256:0f2ae3725904f7377e11cc37722d5d401e8b3d5851fb9273d7f4fe04f6b3d37d", size = 52441, upload-time = "2026-02-17T16:11:56.801Z" }, + { url = "https://files.pythonhosted.org/packages/c5/3c/f614c8e4eaac7cbf2bbdf9528790b21d89e277ee20d57dc6e559c626105f/librt-0.8.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7e6bad1cd94f6764e1e21950542f818a09316645337fd5ab9a7acc45d99a8f35", size = 66529, upload-time = "2026-02-17T16:11:57.809Z" }, + { url = "https://files.pythonhosted.org/packages/ab/96/5836544a45100ae411eda07d29e3d99448e5258b6e9c8059deb92945f5c2/librt-0.8.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cf450f498c30af55551ba4f66b9123b7185362ec8b625a773b3d39aa1a717583", size = 68669, upload-time = "2026-02-17T16:11:58.843Z" }, + { url = "https://files.pythonhosted.org/packages/06/53/f0b992b57af6d5531bf4677d75c44f095f2366a1741fb695ee462ae04b05/librt-0.8.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:eca45e982fa074090057132e30585a7e8674e9e885d402eae85633e9f449ce6c", size = 199279, upload-time = "2026-02-17T16:11:59.862Z" }, + { url = "https://files.pythonhosted.org/packages/f3/ad/4848cc16e268d14280d8168aee4f31cea92bbd2b79ce33d3e166f2b4e4fc/librt-0.8.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0c3811485fccfda840861905b8c70bba5ec094e02825598bb9d4ca3936857a04", size = 210288, upload-time = "2026-02-17T16:12:00.954Z" }, + { url = "https://files.pythonhosted.org/packages/52/05/27fdc2e95de26273d83b96742d8d3b7345f2ea2bdbd2405cc504644f2096/librt-0.8.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5e4af413908f77294605e28cfd98063f54b2c790561383971d2f52d113d9c363", size = 224809, upload-time = "2026-02-17T16:12:02.108Z" }, + { url = "https://files.pythonhosted.org/packages/7a/d0/78200a45ba3240cb042bc597d6f2accba9193a2c57d0356268cbbe2d0925/librt-0.8.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5212a5bd7fae98dae95710032902edcd2ec4dc994e883294f75c857b83f9aba0", size = 218075, upload-time = "2026-02-17T16:12:03.631Z" }, + { url = "https://files.pythonhosted.org/packages/af/72/a210839fa74c90474897124c064ffca07f8d4b347b6574d309686aae7ca6/librt-0.8.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e692aa2d1d604e6ca12d35e51fdc36f4cda6345e28e36374579f7ef3611b3012", size = 225486, upload-time = "2026-02-17T16:12:04.725Z" }, + { url = "https://files.pythonhosted.org/packages/a3/c1/a03cc63722339ddbf087485f253493e2b013039f5b707e8e6016141130fa/librt-0.8.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4be2a5c926b9770c9e08e717f05737a269b9d0ebc5d2f0060f0fe3fe9ce47acb", size = 218219, upload-time = "2026-02-17T16:12:05.828Z" }, + { url = "https://files.pythonhosted.org/packages/58/f5/fff6108af0acf941c6f274a946aea0e484bd10cd2dc37610287ce49388c5/librt-0.8.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:fd1a720332ea335ceb544cf0a03f81df92abd4bb887679fd1e460976b0e6214b", size = 218750, upload-time = "2026-02-17T16:12:07.09Z" }, + { url = "https://files.pythonhosted.org/packages/71/67/5a387bfef30ec1e4b4f30562c8586566faf87e47d696768c19feb49e3646/librt-0.8.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:93c2af9e01e0ef80d95ae3c720be101227edae5f2fe7e3dc63d8857fadfc5a1d", size = 241624, upload-time = "2026-02-17T16:12:08.43Z" }, + { url = "https://files.pythonhosted.org/packages/d4/be/24f8502db11d405232ac1162eb98069ca49c3306c1d75c6ccc61d9af8789/librt-0.8.1-cp313-cp313-win32.whl", hash = "sha256:086a32dbb71336627e78cc1d6ee305a68d038ef7d4c39aaff41ae8c9aa46e91a", size = 54969, upload-time = "2026-02-17T16:12:09.633Z" }, + { url = "https://files.pythonhosted.org/packages/5c/73/c9fdf6cb2a529c1a092ce769a12d88c8cca991194dfe641b6af12fa964d2/librt-0.8.1-cp313-cp313-win_amd64.whl", hash = "sha256:e11769a1dbda4da7b00a76cfffa67aa47cfa66921d2724539eee4b9ede780b79", size = 62000, upload-time = "2026-02-17T16:12:10.632Z" }, + { url = "https://files.pythonhosted.org/packages/d3/97/68f80ca3ac4924f250cdfa6e20142a803e5e50fca96ef5148c52ee8c10ea/librt-0.8.1-cp313-cp313-win_arm64.whl", hash = "sha256:924817ab3141aca17893386ee13261f1d100d1ef410d70afe4389f2359fea4f0", size = 52495, upload-time = "2026-02-17T16:12:11.633Z" }, ] [[package]] name = "llvmlite" -sdist = {url = "https://files.pythonhosted.org/packages/74/cd/08ae687ba099c7e3d21fe2ea536500563ef1943c5105bf6ab4ee3829f68e/llvmlite-0.46.0.tar.gz", hash = "sha256:227c9fd6d09dce2783c18b754b7cd9d9b3b3515210c46acc2d3c5badd9870ceb", size = 193456, upload-time = "2025-12-08T18:15:36.295Z"} -source = {registry = "https://pypi.org/simple"} version = "0.46.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/74/cd/08ae687ba099c7e3d21fe2ea536500563ef1943c5105bf6ab4ee3829f68e/llvmlite-0.46.0.tar.gz", hash = "sha256:227c9fd6d09dce2783c18b754b7cd9d9b3b3515210c46acc2d3c5badd9870ceb", size = 193456, upload-time = "2025-12-08T18:15:36.295Z" } wheels = [ - {url = "https://files.pythonhosted.org/packages/2b/f8/4db016a5e547d4e054ff2f3b99203d63a497465f81ab78ec8eb2ff7b2304/llvmlite-0.46.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6b9588ad4c63b4f0175a3984b85494f0c927c6b001e3a246a3a7fb3920d9a137", size = 37232767, upload-time = "2025-12-08T18:15:00.737Z"}, - {url = "https://files.pythonhosted.org/packages/aa/85/4890a7c14b4fa54400945cb52ac3cd88545bbdb973c440f98ca41591cdc5/llvmlite-0.46.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3535bd2bb6a2d7ae4012681ac228e5132cdb75fefb1bcb24e33f2f3e0c865ed4", size = 56275176, upload-time = "2025-12-08T18:15:03.936Z"}, - {url = "https://files.pythonhosted.org/packages/6a/07/3d31d39c1a1a08cd5337e78299fca77e6aebc07c059fbd0033e3edfab45c/llvmlite-0.46.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4cbfd366e60ff87ea6cc62f50bc4cd800ebb13ed4c149466f50cf2163a473d1e", size = 55128630, upload-time = "2025-12-08T18:15:07.196Z"}, - {url = "https://files.pythonhosted.org/packages/2a/6b/d139535d7590a1bba1ceb68751bef22fadaa5b815bbdf0e858e3875726b2/llvmlite-0.46.0-cp312-cp312-win_amd64.whl", hash = "sha256:398b39db462c39563a97b912d4f2866cd37cba60537975a09679b28fbbc0fb38", size = 38138940, upload-time = "2025-12-08T18:15:10.162Z"}, - {url = "https://files.pythonhosted.org/packages/e6/ff/3eba7eb0aed4b6fca37125387cd417e8c458e750621fce56d2c541f67fa8/llvmlite-0.46.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:30b60892d034bc560e0ec6654737aaa74e5ca327bd8114d82136aa071d611172", size = 37232767, upload-time = "2025-12-08T18:15:13.22Z"}, - {url = "https://files.pythonhosted.org/packages/0e/54/737755c0a91558364b9200702c3c9c15d70ed63f9b98a2c32f1c2aa1f3ba/llvmlite-0.46.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6cc19b051753368a9c9f31dc041299059ee91aceec81bd57b0e385e5d5bf1a54", size = 56275176, upload-time = "2025-12-08T18:15:16.339Z"}, - {url = "https://files.pythonhosted.org/packages/e6/91/14f32e1d70905c1c0aa4e6609ab5d705c3183116ca02ac6df2091868413a/llvmlite-0.46.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bca185892908f9ede48c0acd547fe4dc1bafefb8a4967d47db6cf664f9332d12", size = 55128629, upload-time = "2025-12-08T18:15:19.493Z"}, - {url = "https://files.pythonhosted.org/packages/4a/a7/d526ae86708cea531935ae777b6dbcabe7db52718e6401e0fb9c5edea80e/llvmlite-0.46.0-cp313-cp313-win_amd64.whl", hash = "sha256:67438fd30e12349ebb054d86a5a1a57fd5e87d264d2451bcfafbbbaa25b82a35", size = 38138941, upload-time = "2025-12-08T18:15:22.536Z"} + { url = "https://files.pythonhosted.org/packages/2b/f8/4db016a5e547d4e054ff2f3b99203d63a497465f81ab78ec8eb2ff7b2304/llvmlite-0.46.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6b9588ad4c63b4f0175a3984b85494f0c927c6b001e3a246a3a7fb3920d9a137", size = 37232767, upload-time = "2025-12-08T18:15:00.737Z" }, + { url = "https://files.pythonhosted.org/packages/aa/85/4890a7c14b4fa54400945cb52ac3cd88545bbdb973c440f98ca41591cdc5/llvmlite-0.46.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3535bd2bb6a2d7ae4012681ac228e5132cdb75fefb1bcb24e33f2f3e0c865ed4", size = 56275176, upload-time = "2025-12-08T18:15:03.936Z" }, + { url = "https://files.pythonhosted.org/packages/6a/07/3d31d39c1a1a08cd5337e78299fca77e6aebc07c059fbd0033e3edfab45c/llvmlite-0.46.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4cbfd366e60ff87ea6cc62f50bc4cd800ebb13ed4c149466f50cf2163a473d1e", size = 55128630, upload-time = "2025-12-08T18:15:07.196Z" }, + { url = "https://files.pythonhosted.org/packages/2a/6b/d139535d7590a1bba1ceb68751bef22fadaa5b815bbdf0e858e3875726b2/llvmlite-0.46.0-cp312-cp312-win_amd64.whl", hash = "sha256:398b39db462c39563a97b912d4f2866cd37cba60537975a09679b28fbbc0fb38", size = 38138940, upload-time = "2025-12-08T18:15:10.162Z" }, + { url = "https://files.pythonhosted.org/packages/e6/ff/3eba7eb0aed4b6fca37125387cd417e8c458e750621fce56d2c541f67fa8/llvmlite-0.46.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:30b60892d034bc560e0ec6654737aaa74e5ca327bd8114d82136aa071d611172", size = 37232767, upload-time = "2025-12-08T18:15:13.22Z" }, + { url = "https://files.pythonhosted.org/packages/0e/54/737755c0a91558364b9200702c3c9c15d70ed63f9b98a2c32f1c2aa1f3ba/llvmlite-0.46.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6cc19b051753368a9c9f31dc041299059ee91aceec81bd57b0e385e5d5bf1a54", size = 56275176, upload-time = "2025-12-08T18:15:16.339Z" }, + { url = "https://files.pythonhosted.org/packages/e6/91/14f32e1d70905c1c0aa4e6609ab5d705c3183116ca02ac6df2091868413a/llvmlite-0.46.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bca185892908f9ede48c0acd547fe4dc1bafefb8a4967d47db6cf664f9332d12", size = 55128629, upload-time = "2025-12-08T18:15:19.493Z" }, + { url = "https://files.pythonhosted.org/packages/4a/a7/d526ae86708cea531935ae777b6dbcabe7db52718e6401e0fb9c5edea80e/llvmlite-0.46.0-cp313-cp313-win_amd64.whl", hash = "sha256:67438fd30e12349ebb054d86a5a1a57fd5e87d264d2451bcfafbbbaa25b82a35", size = 38138941, upload-time = "2025-12-08T18:15:22.536Z" }, ] [[package]] -dependencies = [ - {name = "mdurl"} -] name = "markdown-it-py" -sdist = {url = "https://files.pythonhosted.org/packages/5b/f5/4ec618ed16cc4f8fb3b701563655a69816155e79e24a17b651541804721d/markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3", size = 73070, upload-time = "2025-08-11T12:57:52.854Z"} -source = {registry = "https://pypi.org/simple"} version = "4.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mdurl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5b/f5/4ec618ed16cc4f8fb3b701563655a69816155e79e24a17b651541804721d/markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3", size = 73070, upload-time = "2025-08-11T12:57:52.854Z" } wheels = [ - {url = "https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147", size = 87321, upload-time = "2025-08-11T12:57:51.923Z"} + { url = "https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147", size = 87321, upload-time = "2025-08-11T12:57:51.923Z" }, ] [[package]] name = "markupsafe" -sdist = {url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z"} -source = {registry = "https://pypi.org/simple"} version = "3.0.3" -wheels = [ - {url = "https://files.pythonhosted.org/packages/5a/72/147da192e38635ada20e0a2e1a51cf8823d2119ce8883f7053879c2199b5/markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e", size = 11615, upload-time = "2025-09-27T18:36:30.854Z"}, - {url = "https://files.pythonhosted.org/packages/9a/81/7e4e08678a1f98521201c3079f77db69fb552acd56067661f8c2f534a718/markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce", size = 12020, upload-time = "2025-09-27T18:36:31.971Z"}, - {url = "https://files.pythonhosted.org/packages/1e/2c/799f4742efc39633a1b54a92eec4082e4f815314869865d876824c257c1e/markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d", size = 24332, upload-time = "2025-09-27T18:36:32.813Z"}, - {url = "https://files.pythonhosted.org/packages/3c/2e/8d0c2ab90a8c1d9a24f0399058ab8519a3279d1bd4289511d74e909f060e/markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d", size = 22947, upload-time = "2025-09-27T18:36:33.86Z"}, - {url = "https://files.pythonhosted.org/packages/2c/54/887f3092a85238093a0b2154bd629c89444f395618842e8b0c41783898ea/markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a", size = 21962, upload-time = "2025-09-27T18:36:35.099Z"}, - {url = "https://files.pythonhosted.org/packages/c9/2f/336b8c7b6f4a4d95e91119dc8521402461b74a485558d8f238a68312f11c/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b", size = 23760, upload-time = "2025-09-27T18:36:36.001Z"}, - {url = "https://files.pythonhosted.org/packages/32/43/67935f2b7e4982ffb50a4d169b724d74b62a3964bc1a9a527f5ac4f1ee2b/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f", size = 21529, upload-time = "2025-09-27T18:36:36.906Z"}, - {url = "https://files.pythonhosted.org/packages/89/e0/4486f11e51bbba8b0c041098859e869e304d1c261e59244baa3d295d47b7/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b", size = 23015, upload-time = "2025-09-27T18:36:37.868Z"}, - {url = "https://files.pythonhosted.org/packages/2f/e1/78ee7a023dac597a5825441ebd17170785a9dab23de95d2c7508ade94e0e/markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d", size = 14540, upload-time = "2025-09-27T18:36:38.761Z"}, - {url = "https://files.pythonhosted.org/packages/aa/5b/bec5aa9bbbb2c946ca2733ef9c4ca91c91b6a24580193e891b5f7dbe8e1e/markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c", size = 15105, upload-time = "2025-09-27T18:36:39.701Z"}, - {url = "https://files.pythonhosted.org/packages/e5/f1/216fc1bbfd74011693a4fd837e7026152e89c4bcf3e77b6692fba9923123/markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f", size = 13906, upload-time = "2025-09-27T18:36:40.689Z"}, - {url = "https://files.pythonhosted.org/packages/38/2f/907b9c7bbba283e68f20259574b13d005c121a0fa4c175f9bed27c4597ff/markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795", size = 11622, upload-time = "2025-09-27T18:36:41.777Z"}, - {url = "https://files.pythonhosted.org/packages/9c/d9/5f7756922cdd676869eca1c4e3c0cd0df60ed30199ffd775e319089cb3ed/markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219", size = 12029, upload-time = "2025-09-27T18:36:43.257Z"}, - {url = "https://files.pythonhosted.org/packages/00/07/575a68c754943058c78f30db02ee03a64b3c638586fba6a6dd56830b30a3/markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6", size = 24374, upload-time = "2025-09-27T18:36:44.508Z"}, - {url = "https://files.pythonhosted.org/packages/a9/21/9b05698b46f218fc0e118e1f8168395c65c8a2c750ae2bab54fc4bd4e0e8/markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676", size = 22980, upload-time = "2025-09-27T18:36:45.385Z"}, - {url = "https://files.pythonhosted.org/packages/7f/71/544260864f893f18b6827315b988c146b559391e6e7e8f7252839b1b846a/markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9", size = 21990, upload-time = "2025-09-27T18:36:46.916Z"}, - {url = "https://files.pythonhosted.org/packages/c2/28/b50fc2f74d1ad761af2f5dcce7492648b983d00a65b8c0e0cb457c82ebbe/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1", size = 23784, upload-time = "2025-09-27T18:36:47.884Z"}, - {url = "https://files.pythonhosted.org/packages/ed/76/104b2aa106a208da8b17a2fb72e033a5a9d7073c68f7e508b94916ed47a9/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc", size = 21588, upload-time = "2025-09-27T18:36:48.82Z"}, - {url = "https://files.pythonhosted.org/packages/b5/99/16a5eb2d140087ebd97180d95249b00a03aa87e29cc224056274f2e45fd6/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12", size = 23041, upload-time = "2025-09-27T18:36:49.797Z"}, - {url = "https://files.pythonhosted.org/packages/19/bc/e7140ed90c5d61d77cea142eed9f9c303f4c4806f60a1044c13e3f1471d0/markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed", size = 14543, upload-time = "2025-09-27T18:36:51.584Z"}, - {url = "https://files.pythonhosted.org/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5", size = 15113, upload-time = "2025-09-27T18:36:52.537Z"}, - {url = "https://files.pythonhosted.org/packages/f0/3a/fa34a0f7cfef23cf9500d68cb7c32dd64ffd58a12b09225fb03dd37d5b80/markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485", size = 13911, upload-time = "2025-09-27T18:36:53.513Z"}, - {url = "https://files.pythonhosted.org/packages/e4/d7/e05cd7efe43a88a17a37b3ae96e79a19e846f3f456fe79c57ca61356ef01/markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73", size = 11658, upload-time = "2025-09-27T18:36:54.819Z"}, - {url = "https://files.pythonhosted.org/packages/99/9e/e412117548182ce2148bdeacdda3bb494260c0b0184360fe0d56389b523b/markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37", size = 12066, upload-time = "2025-09-27T18:36:55.714Z"}, - {url = "https://files.pythonhosted.org/packages/bc/e6/fa0ffcda717ef64a5108eaa7b4f5ed28d56122c9a6d70ab8b72f9f715c80/markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19", size = 25639, upload-time = "2025-09-27T18:36:56.908Z"}, - {url = "https://files.pythonhosted.org/packages/96/ec/2102e881fe9d25fc16cb4b25d5f5cde50970967ffa5dddafdb771237062d/markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025", size = 23569, upload-time = "2025-09-27T18:36:57.913Z"}, - {url = "https://files.pythonhosted.org/packages/4b/30/6f2fce1f1f205fc9323255b216ca8a235b15860c34b6798f810f05828e32/markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6", size = 23284, upload-time = "2025-09-27T18:36:58.833Z"}, - {url = "https://files.pythonhosted.org/packages/58/47/4a0ccea4ab9f5dcb6f79c0236d954acb382202721e704223a8aafa38b5c8/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f", size = 24801, upload-time = "2025-09-27T18:36:59.739Z"}, - {url = "https://files.pythonhosted.org/packages/6a/70/3780e9b72180b6fecb83a4814d84c3bf4b4ae4bf0b19c27196104149734c/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb", size = 22769, upload-time = "2025-09-27T18:37:00.719Z"}, - {url = "https://files.pythonhosted.org/packages/98/c5/c03c7f4125180fc215220c035beac6b9cb684bc7a067c84fc69414d315f5/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009", size = 23642, upload-time = "2025-09-27T18:37:01.673Z"}, - {url = "https://files.pythonhosted.org/packages/80/d6/2d1b89f6ca4bff1036499b1e29a1d02d282259f3681540e16563f27ebc23/markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354", size = 14612, upload-time = "2025-09-27T18:37:02.639Z"}, - {url = "https://files.pythonhosted.org/packages/2b/98/e48a4bfba0a0ffcf9925fe2d69240bfaa19c6f7507b8cd09c70684a53c1e/markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218", size = 15200, upload-time = "2025-09-27T18:37:03.582Z"}, - {url = "https://files.pythonhosted.org/packages/0e/72/e3cc540f351f316e9ed0f092757459afbc595824ca724cbc5a5d4263713f/markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287", size = 13973, upload-time = "2025-09-27T18:37:04.929Z"} +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5a/72/147da192e38635ada20e0a2e1a51cf8823d2119ce8883f7053879c2199b5/markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e", size = 11615, upload-time = "2025-09-27T18:36:30.854Z" }, + { url = "https://files.pythonhosted.org/packages/9a/81/7e4e08678a1f98521201c3079f77db69fb552acd56067661f8c2f534a718/markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce", size = 12020, upload-time = "2025-09-27T18:36:31.971Z" }, + { url = "https://files.pythonhosted.org/packages/1e/2c/799f4742efc39633a1b54a92eec4082e4f815314869865d876824c257c1e/markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d", size = 24332, upload-time = "2025-09-27T18:36:32.813Z" }, + { url = "https://files.pythonhosted.org/packages/3c/2e/8d0c2ab90a8c1d9a24f0399058ab8519a3279d1bd4289511d74e909f060e/markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d", size = 22947, upload-time = "2025-09-27T18:36:33.86Z" }, + { url = "https://files.pythonhosted.org/packages/2c/54/887f3092a85238093a0b2154bd629c89444f395618842e8b0c41783898ea/markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a", size = 21962, upload-time = "2025-09-27T18:36:35.099Z" }, + { url = "https://files.pythonhosted.org/packages/c9/2f/336b8c7b6f4a4d95e91119dc8521402461b74a485558d8f238a68312f11c/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b", size = 23760, upload-time = "2025-09-27T18:36:36.001Z" }, + { url = "https://files.pythonhosted.org/packages/32/43/67935f2b7e4982ffb50a4d169b724d74b62a3964bc1a9a527f5ac4f1ee2b/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f", size = 21529, upload-time = "2025-09-27T18:36:36.906Z" }, + { url = "https://files.pythonhosted.org/packages/89/e0/4486f11e51bbba8b0c041098859e869e304d1c261e59244baa3d295d47b7/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b", size = 23015, upload-time = "2025-09-27T18:36:37.868Z" }, + { url = "https://files.pythonhosted.org/packages/2f/e1/78ee7a023dac597a5825441ebd17170785a9dab23de95d2c7508ade94e0e/markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d", size = 14540, upload-time = "2025-09-27T18:36:38.761Z" }, + { url = "https://files.pythonhosted.org/packages/aa/5b/bec5aa9bbbb2c946ca2733ef9c4ca91c91b6a24580193e891b5f7dbe8e1e/markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c", size = 15105, upload-time = "2025-09-27T18:36:39.701Z" }, + { url = "https://files.pythonhosted.org/packages/e5/f1/216fc1bbfd74011693a4fd837e7026152e89c4bcf3e77b6692fba9923123/markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f", size = 13906, upload-time = "2025-09-27T18:36:40.689Z" }, + { url = "https://files.pythonhosted.org/packages/38/2f/907b9c7bbba283e68f20259574b13d005c121a0fa4c175f9bed27c4597ff/markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795", size = 11622, upload-time = "2025-09-27T18:36:41.777Z" }, + { url = "https://files.pythonhosted.org/packages/9c/d9/5f7756922cdd676869eca1c4e3c0cd0df60ed30199ffd775e319089cb3ed/markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219", size = 12029, upload-time = "2025-09-27T18:36:43.257Z" }, + { url = "https://files.pythonhosted.org/packages/00/07/575a68c754943058c78f30db02ee03a64b3c638586fba6a6dd56830b30a3/markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6", size = 24374, upload-time = "2025-09-27T18:36:44.508Z" }, + { url = "https://files.pythonhosted.org/packages/a9/21/9b05698b46f218fc0e118e1f8168395c65c8a2c750ae2bab54fc4bd4e0e8/markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676", size = 22980, upload-time = "2025-09-27T18:36:45.385Z" }, + { url = "https://files.pythonhosted.org/packages/7f/71/544260864f893f18b6827315b988c146b559391e6e7e8f7252839b1b846a/markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9", size = 21990, upload-time = "2025-09-27T18:36:46.916Z" }, + { url = "https://files.pythonhosted.org/packages/c2/28/b50fc2f74d1ad761af2f5dcce7492648b983d00a65b8c0e0cb457c82ebbe/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1", size = 23784, upload-time = "2025-09-27T18:36:47.884Z" }, + { url = "https://files.pythonhosted.org/packages/ed/76/104b2aa106a208da8b17a2fb72e033a5a9d7073c68f7e508b94916ed47a9/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc", size = 21588, upload-time = "2025-09-27T18:36:48.82Z" }, + { url = "https://files.pythonhosted.org/packages/b5/99/16a5eb2d140087ebd97180d95249b00a03aa87e29cc224056274f2e45fd6/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12", size = 23041, upload-time = "2025-09-27T18:36:49.797Z" }, + { url = "https://files.pythonhosted.org/packages/19/bc/e7140ed90c5d61d77cea142eed9f9c303f4c4806f60a1044c13e3f1471d0/markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed", size = 14543, upload-time = "2025-09-27T18:36:51.584Z" }, + { url = "https://files.pythonhosted.org/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5", size = 15113, upload-time = "2025-09-27T18:36:52.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/3a/fa34a0f7cfef23cf9500d68cb7c32dd64ffd58a12b09225fb03dd37d5b80/markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485", size = 13911, upload-time = "2025-09-27T18:36:53.513Z" }, + { url = "https://files.pythonhosted.org/packages/e4/d7/e05cd7efe43a88a17a37b3ae96e79a19e846f3f456fe79c57ca61356ef01/markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73", size = 11658, upload-time = "2025-09-27T18:36:54.819Z" }, + { url = "https://files.pythonhosted.org/packages/99/9e/e412117548182ce2148bdeacdda3bb494260c0b0184360fe0d56389b523b/markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37", size = 12066, upload-time = "2025-09-27T18:36:55.714Z" }, + { url = "https://files.pythonhosted.org/packages/bc/e6/fa0ffcda717ef64a5108eaa7b4f5ed28d56122c9a6d70ab8b72f9f715c80/markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19", size = 25639, upload-time = "2025-09-27T18:36:56.908Z" }, + { url = "https://files.pythonhosted.org/packages/96/ec/2102e881fe9d25fc16cb4b25d5f5cde50970967ffa5dddafdb771237062d/markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025", size = 23569, upload-time = "2025-09-27T18:36:57.913Z" }, + { url = "https://files.pythonhosted.org/packages/4b/30/6f2fce1f1f205fc9323255b216ca8a235b15860c34b6798f810f05828e32/markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6", size = 23284, upload-time = "2025-09-27T18:36:58.833Z" }, + { url = "https://files.pythonhosted.org/packages/58/47/4a0ccea4ab9f5dcb6f79c0236d954acb382202721e704223a8aafa38b5c8/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f", size = 24801, upload-time = "2025-09-27T18:36:59.739Z" }, + { url = "https://files.pythonhosted.org/packages/6a/70/3780e9b72180b6fecb83a4814d84c3bf4b4ae4bf0b19c27196104149734c/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb", size = 22769, upload-time = "2025-09-27T18:37:00.719Z" }, + { url = "https://files.pythonhosted.org/packages/98/c5/c03c7f4125180fc215220c035beac6b9cb684bc7a067c84fc69414d315f5/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009", size = 23642, upload-time = "2025-09-27T18:37:01.673Z" }, + { url = "https://files.pythonhosted.org/packages/80/d6/2d1b89f6ca4bff1036499b1e29a1d02d282259f3681540e16563f27ebc23/markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354", size = 14612, upload-time = "2025-09-27T18:37:02.639Z" }, + { url = "https://files.pythonhosted.org/packages/2b/98/e48a4bfba0a0ffcf9925fe2d69240bfaa19c6f7507b8cd09c70684a53c1e/markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218", size = 15200, upload-time = "2025-09-27T18:37:03.582Z" }, + { url = "https://files.pythonhosted.org/packages/0e/72/e3cc540f351f316e9ed0f092757459afbc595824ca724cbc5a5d4263713f/markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287", size = 13973, upload-time = "2025-09-27T18:37:04.929Z" }, ] [[package]] -dependencies = [ - {name = "markdown-it-py"} -] name = "mdit-py-plugins" -sdist = {url = "https://files.pythonhosted.org/packages/b2/fd/a756d36c0bfba5f6e39a1cdbdbfdd448dc02692467d83816dff4592a1ebc/mdit_py_plugins-0.5.0.tar.gz", hash = "sha256:f4918cb50119f50446560513a8e311d574ff6aaed72606ddae6d35716fe809c6", size = 44655, upload-time = "2025-08-11T07:25:49.083Z"} -source = {registry = "https://pypi.org/simple"} version = "0.5.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown-it-py" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b2/fd/a756d36c0bfba5f6e39a1cdbdbfdd448dc02692467d83816dff4592a1ebc/mdit_py_plugins-0.5.0.tar.gz", hash = "sha256:f4918cb50119f50446560513a8e311d574ff6aaed72606ddae6d35716fe809c6", size = 44655, upload-time = "2025-08-11T07:25:49.083Z" } wheels = [ - {url = "https://files.pythonhosted.org/packages/fb/86/dd6e5db36df29e76c7a7699123569a4a18c1623ce68d826ed96c62643cae/mdit_py_plugins-0.5.0-py3-none-any.whl", hash = "sha256:07a08422fc1936a5d26d146759e9155ea466e842f5ab2f7d2266dd084c8dab1f", size = 57205, upload-time = "2025-08-11T07:25:47.597Z"} + { url = "https://files.pythonhosted.org/packages/fb/86/dd6e5db36df29e76c7a7699123569a4a18c1623ce68d826ed96c62643cae/mdit_py_plugins-0.5.0-py3-none-any.whl", hash = "sha256:07a08422fc1936a5d26d146759e9155ea466e842f5ab2f7d2266dd084c8dab1f", size = 57205, upload-time = "2025-08-11T07:25:47.597Z" }, ] [[package]] name = "mdurl" -sdist = {url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z"} -source = {registry = "https://pypi.org/simple"} version = "0.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" } wheels = [ - {url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z"} + { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, ] [[package]] -dependencies = [ - {name = "librt", marker = "platform_python_implementation != 'PyPy'"}, - {name = "mypy-extensions"}, - {name = "pathspec"}, - {name = "typing-extensions"} -] name = "mypy" -sdist = {url = "https://files.pythonhosted.org/packages/f5/db/4efed9504bc01309ab9c2da7e352cc223569f05478012b5d9ece38fd44d2/mypy-1.19.1.tar.gz", hash = "sha256:19d88bb05303fe63f71dd2c6270daca27cb9401c4ca8255fe50d1d920e0eb9ba", size = 3582404, upload-time = "2025-12-15T05:03:48.42Z"} -source = {registry = "https://pypi.org/simple"} version = "1.19.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "librt", marker = "platform_python_implementation != 'PyPy'" }, + { name = "mypy-extensions" }, + { name = "pathspec" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f5/db/4efed9504bc01309ab9c2da7e352cc223569f05478012b5d9ece38fd44d2/mypy-1.19.1.tar.gz", hash = "sha256:19d88bb05303fe63f71dd2c6270daca27cb9401c4ca8255fe50d1d920e0eb9ba", size = 3582404, upload-time = "2025-12-15T05:03:48.42Z" } wheels = [ - {url = "https://files.pythonhosted.org/packages/06/8a/19bfae96f6615aa8a0604915512e0289b1fad33d5909bf7244f02935d33a/mypy-1.19.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a8174a03289288c1f6c46d55cef02379b478bfbc8e358e02047487cad44c6ca1", size = 13206053, upload-time = "2025-12-15T05:03:46.622Z"}, - {url = "https://files.pythonhosted.org/packages/a5/34/3e63879ab041602154ba2a9f99817bb0c85c4df19a23a1443c8986e4d565/mypy-1.19.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ffcebe56eb09ff0c0885e750036a095e23793ba6c2e894e7e63f6d89ad51f22e", size = 12219134, upload-time = "2025-12-15T05:03:24.367Z"}, - {url = "https://files.pythonhosted.org/packages/89/cc/2db6f0e95366b630364e09845672dbee0cbf0bbe753a204b29a944967cd9/mypy-1.19.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b64d987153888790bcdb03a6473d321820597ab8dd9243b27a92153c4fa50fd2", size = 12731616, upload-time = "2025-12-15T05:02:44.725Z"}, - {url = "https://files.pythonhosted.org/packages/00/be/dd56c1fd4807bc1eba1cf18b2a850d0de7bacb55e158755eb79f77c41f8e/mypy-1.19.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c35d298c2c4bba75feb2195655dfea8124d855dfd7343bf8b8c055421eaf0cf8", size = 13620847, upload-time = "2025-12-15T05:03:39.633Z"}, - {url = "https://files.pythonhosted.org/packages/6d/42/332951aae42b79329f743bf1da088cd75d8d4d9acc18fbcbd84f26c1af4e/mypy-1.19.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:34c81968774648ab5ac09c29a375fdede03ba253f8f8287847bd480782f73a6a", size = 13834976, upload-time = "2025-12-15T05:03:08.786Z"}, - {url = "https://files.pythonhosted.org/packages/6f/63/e7493e5f90e1e085c562bb06e2eb32cae27c5057b9653348d38b47daaecc/mypy-1.19.1-cp312-cp312-win_amd64.whl", hash = "sha256:b10e7c2cd7870ba4ad9b2d8a6102eb5ffc1f16ca35e3de6bfa390c1113029d13", size = 10118104, upload-time = "2025-12-15T05:03:10.834Z"}, - {url = "https://files.pythonhosted.org/packages/de/9f/a6abae693f7a0c697dbb435aac52e958dc8da44e92e08ba88d2e42326176/mypy-1.19.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e3157c7594ff2ef1634ee058aafc56a82db665c9438fd41b390f3bde1ab12250", size = 13201927, upload-time = "2025-12-15T05:02:29.138Z"}, - {url = "https://files.pythonhosted.org/packages/9a/a4/45c35ccf6e1c65afc23a069f50e2c66f46bd3798cbe0d680c12d12935caa/mypy-1.19.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bdb12f69bcc02700c2b47e070238f42cb87f18c0bc1fc4cdb4fb2bc5fd7a3b8b", size = 12206730, upload-time = "2025-12-15T05:03:01.325Z"}, - {url = "https://files.pythonhosted.org/packages/05/bb/cdcf89678e26b187650512620eec8368fded4cfd99cfcb431e4cdfd19dec/mypy-1.19.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f859fb09d9583a985be9a493d5cfc5515b56b08f7447759a0c5deaf68d80506e", size = 12724581, upload-time = "2025-12-15T05:03:20.087Z"}, - {url = "https://files.pythonhosted.org/packages/d1/32/dd260d52babf67bad8e6770f8e1102021877ce0edea106e72df5626bb0ec/mypy-1.19.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c9a6538e0415310aad77cb94004ca6482330fece18036b5f360b62c45814c4ef", size = 13616252, upload-time = "2025-12-15T05:02:49.036Z"}, - {url = "https://files.pythonhosted.org/packages/71/d0/5e60a9d2e3bd48432ae2b454b7ef2b62a960ab51292b1eda2a95edd78198/mypy-1.19.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:da4869fc5e7f62a88f3fe0b5c919d1d9f7ea3cef92d3689de2823fd27e40aa75", size = 13840848, upload-time = "2025-12-15T05:02:55.95Z"}, - {url = "https://files.pythonhosted.org/packages/98/76/d32051fa65ecf6cc8c6610956473abdc9b4c43301107476ac03559507843/mypy-1.19.1-cp313-cp313-win_amd64.whl", hash = "sha256:016f2246209095e8eda7538944daa1d60e1e8134d98983b9fc1e92c1fc0cb8dd", size = 10135510, upload-time = "2025-12-15T05:02:58.438Z"}, - {url = "https://files.pythonhosted.org/packages/8d/f4/4ce9a05ce5ded1de3ec1c1d96cf9f9504a04e54ce0ed55cfa38619a32b8d/mypy-1.19.1-py3-none-any.whl", hash = "sha256:f1235f5ea01b7db5468d53ece6aaddf1ad0b88d9e7462b86ef96fe04995d7247", size = 2471239, upload-time = "2025-12-15T05:03:07.248Z"} + { url = "https://files.pythonhosted.org/packages/06/8a/19bfae96f6615aa8a0604915512e0289b1fad33d5909bf7244f02935d33a/mypy-1.19.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a8174a03289288c1f6c46d55cef02379b478bfbc8e358e02047487cad44c6ca1", size = 13206053, upload-time = "2025-12-15T05:03:46.622Z" }, + { url = "https://files.pythonhosted.org/packages/a5/34/3e63879ab041602154ba2a9f99817bb0c85c4df19a23a1443c8986e4d565/mypy-1.19.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ffcebe56eb09ff0c0885e750036a095e23793ba6c2e894e7e63f6d89ad51f22e", size = 12219134, upload-time = "2025-12-15T05:03:24.367Z" }, + { url = "https://files.pythonhosted.org/packages/89/cc/2db6f0e95366b630364e09845672dbee0cbf0bbe753a204b29a944967cd9/mypy-1.19.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b64d987153888790bcdb03a6473d321820597ab8dd9243b27a92153c4fa50fd2", size = 12731616, upload-time = "2025-12-15T05:02:44.725Z" }, + { url = "https://files.pythonhosted.org/packages/00/be/dd56c1fd4807bc1eba1cf18b2a850d0de7bacb55e158755eb79f77c41f8e/mypy-1.19.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c35d298c2c4bba75feb2195655dfea8124d855dfd7343bf8b8c055421eaf0cf8", size = 13620847, upload-time = "2025-12-15T05:03:39.633Z" }, + { url = "https://files.pythonhosted.org/packages/6d/42/332951aae42b79329f743bf1da088cd75d8d4d9acc18fbcbd84f26c1af4e/mypy-1.19.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:34c81968774648ab5ac09c29a375fdede03ba253f8f8287847bd480782f73a6a", size = 13834976, upload-time = "2025-12-15T05:03:08.786Z" }, + { url = "https://files.pythonhosted.org/packages/6f/63/e7493e5f90e1e085c562bb06e2eb32cae27c5057b9653348d38b47daaecc/mypy-1.19.1-cp312-cp312-win_amd64.whl", hash = "sha256:b10e7c2cd7870ba4ad9b2d8a6102eb5ffc1f16ca35e3de6bfa390c1113029d13", size = 10118104, upload-time = "2025-12-15T05:03:10.834Z" }, + { url = "https://files.pythonhosted.org/packages/de/9f/a6abae693f7a0c697dbb435aac52e958dc8da44e92e08ba88d2e42326176/mypy-1.19.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e3157c7594ff2ef1634ee058aafc56a82db665c9438fd41b390f3bde1ab12250", size = 13201927, upload-time = "2025-12-15T05:02:29.138Z" }, + { url = "https://files.pythonhosted.org/packages/9a/a4/45c35ccf6e1c65afc23a069f50e2c66f46bd3798cbe0d680c12d12935caa/mypy-1.19.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bdb12f69bcc02700c2b47e070238f42cb87f18c0bc1fc4cdb4fb2bc5fd7a3b8b", size = 12206730, upload-time = "2025-12-15T05:03:01.325Z" }, + { url = "https://files.pythonhosted.org/packages/05/bb/cdcf89678e26b187650512620eec8368fded4cfd99cfcb431e4cdfd19dec/mypy-1.19.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f859fb09d9583a985be9a493d5cfc5515b56b08f7447759a0c5deaf68d80506e", size = 12724581, upload-time = "2025-12-15T05:03:20.087Z" }, + { url = "https://files.pythonhosted.org/packages/d1/32/dd260d52babf67bad8e6770f8e1102021877ce0edea106e72df5626bb0ec/mypy-1.19.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c9a6538e0415310aad77cb94004ca6482330fece18036b5f360b62c45814c4ef", size = 13616252, upload-time = "2025-12-15T05:02:49.036Z" }, + { url = "https://files.pythonhosted.org/packages/71/d0/5e60a9d2e3bd48432ae2b454b7ef2b62a960ab51292b1eda2a95edd78198/mypy-1.19.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:da4869fc5e7f62a88f3fe0b5c919d1d9f7ea3cef92d3689de2823fd27e40aa75", size = 13840848, upload-time = "2025-12-15T05:02:55.95Z" }, + { url = "https://files.pythonhosted.org/packages/98/76/d32051fa65ecf6cc8c6610956473abdc9b4c43301107476ac03559507843/mypy-1.19.1-cp313-cp313-win_amd64.whl", hash = "sha256:016f2246209095e8eda7538944daa1d60e1e8134d98983b9fc1e92c1fc0cb8dd", size = 10135510, upload-time = "2025-12-15T05:02:58.438Z" }, + { url = "https://files.pythonhosted.org/packages/8d/f4/4ce9a05ce5ded1de3ec1c1d96cf9f9504a04e54ce0ed55cfa38619a32b8d/mypy-1.19.1-py3-none-any.whl", hash = "sha256:f1235f5ea01b7db5468d53ece6aaddf1ad0b88d9e7462b86ef96fe04995d7247", size = 2471239, upload-time = "2025-12-15T05:03:07.248Z" }, ] [[package]] name = "mypy-extensions" -sdist = {url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343, upload-time = "2025-04-22T14:54:24.164Z"} -source = {registry = "https://pypi.org/simple"} version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343, upload-time = "2025-04-22T14:54:24.164Z" } wheels = [ - {url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z"} + { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" }, ] [[package]] -dependencies = [ - {name = "docutils"}, - {name = "jinja2"}, - {name = "markdown-it-py"}, - {name = "mdit-py-plugins"}, - {name = "pyyaml"}, - {name = "sphinx"} -] name = "myst-parser" -sdist = {url = "https://files.pythonhosted.org/packages/33/fa/7b45eef11b7971f0beb29d27b7bfe0d747d063aa29e170d9edd004733c8a/myst_parser-5.0.0.tar.gz", hash = "sha256:f6f231452c56e8baa662cc352c548158f6a16fcbd6e3800fc594978002b94f3a", size = 98535, upload-time = "2026-01-15T09:08:18.036Z"} -source = {registry = "https://pypi.org/simple"} version = "5.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "docutils" }, + { name = "jinja2" }, + { name = "markdown-it-py" }, + { name = "mdit-py-plugins" }, + { name = "pyyaml" }, + { name = "sphinx" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/33/fa/7b45eef11b7971f0beb29d27b7bfe0d747d063aa29e170d9edd004733c8a/myst_parser-5.0.0.tar.gz", hash = "sha256:f6f231452c56e8baa662cc352c548158f6a16fcbd6e3800fc594978002b94f3a", size = 98535, upload-time = "2026-01-15T09:08:18.036Z" } wheels = [ - {url = "https://files.pythonhosted.org/packages/d3/ac/686789b9145413f1a61878c407210e41bfdb097976864e0913078b24098c/myst_parser-5.0.0-py3-none-any.whl", hash = "sha256:ab31e516024918296e169139072b81592336f2fef55b8986aa31c9f04b5f7211", size = 84533, upload-time = "2026-01-15T09:08:16.788Z"} + { url = "https://files.pythonhosted.org/packages/d3/ac/686789b9145413f1a61878c407210e41bfdb097976864e0913078b24098c/myst_parser-5.0.0-py3-none-any.whl", hash = "sha256:ab31e516024918296e169139072b81592336f2fef55b8986aa31c9f04b5f7211", size = 84533, upload-time = "2026-01-15T09:08:16.788Z" }, ] [[package]] name = "nodeenv" -sdist = {url = "https://files.pythonhosted.org/packages/24/bf/d1bda4f6168e0b2e9e5958945e01910052158313224ada5ce1fb2e1113b8/nodeenv-1.10.0.tar.gz", hash = "sha256:996c191ad80897d076bdfba80a41994c2b47c68e224c542b48feba42ba00f8bb", size = 55611, upload-time = "2025-12-20T14:08:54.006Z"} -source = {registry = "https://pypi.org/simple"} version = "1.10.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/24/bf/d1bda4f6168e0b2e9e5958945e01910052158313224ada5ce1fb2e1113b8/nodeenv-1.10.0.tar.gz", hash = "sha256:996c191ad80897d076bdfba80a41994c2b47c68e224c542b48feba42ba00f8bb", size = 55611, upload-time = "2025-12-20T14:08:54.006Z" } wheels = [ - {url = "https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl", hash = "sha256:5bb13e3eed2923615535339b3c620e76779af4cb4c6a90deccc9e36b274d3827", size = 23438, upload-time = "2025-12-20T14:08:52.782Z"} + { url = "https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl", hash = "sha256:5bb13e3eed2923615535339b3c620e76779af4cb4c6a90deccc9e36b274d3827", size = 23438, upload-time = "2025-12-20T14:08:52.782Z" }, ] [[package]] -dependencies = [ - {name = "llvmlite"}, - {name = "numpy"} -] name = "numba" -sdist = {url = "https://files.pythonhosted.org/packages/23/c9/a0fb41787d01d621046138da30f6c2100d80857bf34b3390dd68040f27a3/numba-0.64.0.tar.gz", hash = "sha256:95e7300af648baa3308127b1955b52ce6d11889d16e8cfe637b4f85d2fca52b1", size = 2765679, upload-time = "2026-02-18T18:41:20.974Z"} -source = {registry = "https://pypi.org/simple"} version = "0.64.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "llvmlite" }, + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/23/c9/a0fb41787d01d621046138da30f6c2100d80857bf34b3390dd68040f27a3/numba-0.64.0.tar.gz", hash = "sha256:95e7300af648baa3308127b1955b52ce6d11889d16e8cfe637b4f85d2fca52b1", size = 2765679, upload-time = "2026-02-18T18:41:20.974Z" } wheels = [ - {url = "https://files.pythonhosted.org/packages/70/a6/9fc52cb4f0d5e6d8b5f4d81615bc01012e3cf24e1052a60f17a68deb8092/numba-0.64.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:69440a8e8bc1a81028446f06b363e28635aa67bd51b1e498023f03b812e0ce68", size = 2683418, upload-time = "2026-02-18T18:40:59.886Z"}, - {url = "https://files.pythonhosted.org/packages/9b/89/1a74ea99b180b7a5587b0301ed1b183a2937c4b4b67f7994689b5d36fc34/numba-0.64.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f13721011f693ba558b8dd4e4db7f2640462bba1b855bdc804be45bbeb55031a", size = 3804087, upload-time = "2026-02-18T18:41:01.699Z"}, - {url = "https://files.pythonhosted.org/packages/91/e1/583c647404b15f807410510fec1eb9b80cb8474165940b7749f026f21cbc/numba-0.64.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e0b180b1133f2b5d8b3f09d96b6d7a9e51a7da5dda3c09e998b5bcfac85d222c", size = 3504309, upload-time = "2026-02-18T18:41:03.252Z"}, - {url = "https://files.pythonhosted.org/packages/85/23/0fce5789b8a5035e7ace21216a468143f3144e02013252116616c58339aa/numba-0.64.0-cp312-cp312-win_amd64.whl", hash = "sha256:e63dc94023b47894849b8b106db28ccb98b49d5498b98878fac1a38f83ac007a", size = 2752740, upload-time = "2026-02-18T18:41:05.097Z"}, - {url = "https://files.pythonhosted.org/packages/52/80/2734de90f9300a6e2503b35ee50d9599926b90cbb7ac54f9e40074cd07f1/numba-0.64.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:3bab2c872194dcd985f1153b70782ec0fbbe348fffef340264eacd3a76d59fd6", size = 2683392, upload-time = "2026-02-18T18:41:06.563Z"}, - {url = "https://files.pythonhosted.org/packages/42/e8/14b5853ebefd5b37723ef365c5318a30ce0702d39057eaa8d7d76392859d/numba-0.64.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:703a246c60832cad231d2e73c1182f25bf3cc8b699759ec8fe58a2dbc689a70c", size = 3812245, upload-time = "2026-02-18T18:41:07.963Z"}, - {url = "https://files.pythonhosted.org/packages/8a/a2/f60dc6c96d19b7185144265a5fbf01c14993d37ff4cd324b09d0212aa7ce/numba-0.64.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7e2e49a7900ee971d32af7609adc0cfe6aa7477c6f6cccdf6d8138538cf7756f", size = 3511328, upload-time = "2026-02-18T18:41:09.504Z"}, - {url = "https://files.pythonhosted.org/packages/9c/2a/fe7003ea7e7237ee7014f8eaeeb7b0d228a2db22572ca85bab2648cf52cb/numba-0.64.0-cp313-cp313-win_amd64.whl", hash = "sha256:396f43c3f77e78d7ec84cdfc6b04969c78f8f169351b3c4db814b97e7acf4245", size = 2752668, upload-time = "2026-02-18T18:41:11.455Z"} + { url = "https://files.pythonhosted.org/packages/70/a6/9fc52cb4f0d5e6d8b5f4d81615bc01012e3cf24e1052a60f17a68deb8092/numba-0.64.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:69440a8e8bc1a81028446f06b363e28635aa67bd51b1e498023f03b812e0ce68", size = 2683418, upload-time = "2026-02-18T18:40:59.886Z" }, + { url = "https://files.pythonhosted.org/packages/9b/89/1a74ea99b180b7a5587b0301ed1b183a2937c4b4b67f7994689b5d36fc34/numba-0.64.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f13721011f693ba558b8dd4e4db7f2640462bba1b855bdc804be45bbeb55031a", size = 3804087, upload-time = "2026-02-18T18:41:01.699Z" }, + { url = "https://files.pythonhosted.org/packages/91/e1/583c647404b15f807410510fec1eb9b80cb8474165940b7749f026f21cbc/numba-0.64.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e0b180b1133f2b5d8b3f09d96b6d7a9e51a7da5dda3c09e998b5bcfac85d222c", size = 3504309, upload-time = "2026-02-18T18:41:03.252Z" }, + { url = "https://files.pythonhosted.org/packages/85/23/0fce5789b8a5035e7ace21216a468143f3144e02013252116616c58339aa/numba-0.64.0-cp312-cp312-win_amd64.whl", hash = "sha256:e63dc94023b47894849b8b106db28ccb98b49d5498b98878fac1a38f83ac007a", size = 2752740, upload-time = "2026-02-18T18:41:05.097Z" }, + { url = "https://files.pythonhosted.org/packages/52/80/2734de90f9300a6e2503b35ee50d9599926b90cbb7ac54f9e40074cd07f1/numba-0.64.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:3bab2c872194dcd985f1153b70782ec0fbbe348fffef340264eacd3a76d59fd6", size = 2683392, upload-time = "2026-02-18T18:41:06.563Z" }, + { url = "https://files.pythonhosted.org/packages/42/e8/14b5853ebefd5b37723ef365c5318a30ce0702d39057eaa8d7d76392859d/numba-0.64.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:703a246c60832cad231d2e73c1182f25bf3cc8b699759ec8fe58a2dbc689a70c", size = 3812245, upload-time = "2026-02-18T18:41:07.963Z" }, + { url = "https://files.pythonhosted.org/packages/8a/a2/f60dc6c96d19b7185144265a5fbf01c14993d37ff4cd324b09d0212aa7ce/numba-0.64.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7e2e49a7900ee971d32af7609adc0cfe6aa7477c6f6cccdf6d8138538cf7756f", size = 3511328, upload-time = "2026-02-18T18:41:09.504Z" }, + { url = "https://files.pythonhosted.org/packages/9c/2a/fe7003ea7e7237ee7014f8eaeeb7b0d228a2db22572ca85bab2648cf52cb/numba-0.64.0-cp313-cp313-win_amd64.whl", hash = "sha256:396f43c3f77e78d7ec84cdfc6b04969c78f8f169351b3c4db814b97e7acf4245", size = 2752668, upload-time = "2026-02-18T18:41:11.455Z" }, ] [[package]] name = "numpy" -sdist = {url = "https://files.pythonhosted.org/packages/57/fd/0005efbd0af48e55eb3c7208af93f2862d4b1a56cd78e84309a2d959208d/numpy-2.4.2.tar.gz", hash = "sha256:659a6107e31a83c4e33f763942275fd278b21d095094044eb35569e86a21ddae", size = 20723651, upload-time = "2026-01-31T23:13:10.135Z"} -source = {registry = "https://pypi.org/simple"} version = "2.4.2" -wheels = [ - {url = "https://files.pythonhosted.org/packages/51/6e/6f394c9c77668153e14d4da83bcc247beb5952f6ead7699a1a2992613bea/numpy-2.4.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:21982668592194c609de53ba4933a7471880ccbaadcc52352694a59ecc860b3a", size = 16667963, upload-time = "2026-01-31T23:10:52.147Z"}, - {url = "https://files.pythonhosted.org/packages/1f/f8/55483431f2b2fd015ae6ed4fe62288823ce908437ed49db5a03d15151678/numpy-2.4.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40397bda92382fcec844066efb11f13e1c9a3e2a8e8f318fb72ed8b6db9f60f1", size = 14693571, upload-time = "2026-01-31T23:10:54.789Z"}, - {url = "https://files.pythonhosted.org/packages/2f/20/18026832b1845cdc82248208dd929ca14c9d8f2bac391f67440707fff27c/numpy-2.4.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:b3a24467af63c67829bfaa61eecf18d5432d4f11992688537be59ecd6ad32f5e", size = 5203469, upload-time = "2026-01-31T23:10:57.343Z"}, - {url = "https://files.pythonhosted.org/packages/7d/33/2eb97c8a77daaba34eaa3fa7241a14ac5f51c46a6bd5911361b644c4a1e2/numpy-2.4.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:805cc8de9fd6e7a22da5aed858e0ab16be5a4db6c873dde1d7451c541553aa27", size = 6550820, upload-time = "2026-01-31T23:10:59.429Z"}, - {url = "https://files.pythonhosted.org/packages/b1/91/b97fdfd12dc75b02c44e26c6638241cc004d4079a0321a69c62f51470c4c/numpy-2.4.2-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6d82351358ffbcdcd7b686b90742a9b86632d6c1c051016484fa0b326a0a1548", size = 15663067, upload-time = "2026-01-31T23:11:01.291Z"}, - {url = "https://files.pythonhosted.org/packages/f5/c6/a18e59f3f0b8071cc85cbc8d80cd02d68aa9710170b2553a117203d46936/numpy-2.4.2-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e35d3e0144137d9fdae62912e869136164534d64a169f86438bc9561b6ad49f", size = 16619782, upload-time = "2026-01-31T23:11:03.669Z"}, - {url = "https://files.pythonhosted.org/packages/b7/83/9751502164601a79e18847309f5ceec0b1446d7b6aa12305759b72cf98b2/numpy-2.4.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:adb6ed2ad29b9e15321d167d152ee909ec73395901b70936f029c3bc6d7f4460", size = 17013128, upload-time = "2026-01-31T23:11:05.913Z"}, - {url = "https://files.pythonhosted.org/packages/61/c4/c4066322256ec740acc1c8923a10047818691d2f8aec254798f3dd90f5f2/numpy-2.4.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8906e71fd8afcb76580404e2a950caef2685df3d2a57fe82a86ac8d33cc007ba", size = 18345324, upload-time = "2026-01-31T23:11:08.248Z"}, - {url = "https://files.pythonhosted.org/packages/ab/af/6157aa6da728fa4525a755bfad486ae7e3f76d4c1864138003eb84328497/numpy-2.4.2-cp312-cp312-win32.whl", hash = "sha256:ec055f6dae239a6299cace477b479cca2fc125c5675482daf1dd886933a1076f", size = 5960282, upload-time = "2026-01-31T23:11:10.497Z"}, - {url = "https://files.pythonhosted.org/packages/92/0f/7ceaaeaacb40567071e94dbf2c9480c0ae453d5bb4f52bea3892c39dc83c/numpy-2.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:209fae046e62d0ce6435fcfe3b1a10537e858249b3d9b05829e2a05218296a85", size = 12314210, upload-time = "2026-01-31T23:11:12.176Z"}, - {url = "https://files.pythonhosted.org/packages/2f/a3/56c5c604fae6dd40fa2ed3040d005fca97e91bd320d232ac9931d77ba13c/numpy-2.4.2-cp312-cp312-win_arm64.whl", hash = "sha256:fbde1b0c6e81d56f5dccd95dd4a711d9b95df1ae4009a60887e56b27e8d903fa", size = 10220171, upload-time = "2026-01-31T23:11:14.684Z"}, - {url = "https://files.pythonhosted.org/packages/a1/22/815b9fe25d1d7ae7d492152adbc7226d3eff731dffc38fe970589fcaaa38/numpy-2.4.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:25f2059807faea4b077a2b6837391b5d830864b3543627f381821c646f31a63c", size = 16663696, upload-time = "2026-01-31T23:11:17.516Z"}, - {url = "https://files.pythonhosted.org/packages/09/f0/817d03a03f93ba9c6c8993de509277d84e69f9453601915e4a69554102a1/numpy-2.4.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bd3a7a9f5847d2fb8c2c6d1c862fa109c31a9abeca1a3c2bd5a64572955b2979", size = 14688322, upload-time = "2026-01-31T23:11:19.883Z"}, - {url = "https://files.pythonhosted.org/packages/da/b4/f805ab79293c728b9a99438775ce51885fd4f31b76178767cfc718701a39/numpy-2.4.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:8e4549f8a3c6d13d55041925e912bfd834285ef1dd64d6bc7d542583355e2e98", size = 5198157, upload-time = "2026-01-31T23:11:22.375Z"}, - {url = "https://files.pythonhosted.org/packages/74/09/826e4289844eccdcd64aac27d13b0fd3f32039915dd5b9ba01baae1f436c/numpy-2.4.2-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:aea4f66ff44dfddf8c2cffd66ba6538c5ec67d389285292fe428cb2c738c8aef", size = 6546330, upload-time = "2026-01-31T23:11:23.958Z"}, - {url = "https://files.pythonhosted.org/packages/19/fb/cbfdbfa3057a10aea5422c558ac57538e6acc87ec1669e666d32ac198da7/numpy-2.4.2-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c3cd545784805de05aafe1dde61752ea49a359ccba9760c1e5d1c88a93bbf2b7", size = 15660968, upload-time = "2026-01-31T23:11:25.713Z"}, - {url = "https://files.pythonhosted.org/packages/04/dc/46066ce18d01645541f0186877377b9371b8fa8017fa8262002b4ef22612/numpy-2.4.2-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d0d9b7c93578baafcbc5f0b83eaf17b79d345c6f36917ba0c67f45226911d499", size = 16607311, upload-time = "2026-01-31T23:11:28.117Z"}, - {url = "https://files.pythonhosted.org/packages/14/d9/4b5adfc39a43fa6bf918c6d544bc60c05236cc2f6339847fc5b35e6cb5b0/numpy-2.4.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f74f0f7779cc7ae07d1810aab8ac6b1464c3eafb9e283a40da7309d5e6e48fbb", size = 17012850, upload-time = "2026-01-31T23:11:30.888Z"}, - {url = "https://files.pythonhosted.org/packages/b7/20/adb6e6adde6d0130046e6fdfb7675cc62bc2f6b7b02239a09eb58435753d/numpy-2.4.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c7ac672d699bf36275c035e16b65539931347d68b70667d28984c9fb34e07fa7", size = 18334210, upload-time = "2026-01-31T23:11:33.214Z"}, - {url = "https://files.pythonhosted.org/packages/78/0e/0a73b3dff26803a8c02baa76398015ea2a5434d9b8265a7898a6028c1591/numpy-2.4.2-cp313-cp313-win32.whl", hash = "sha256:8e9afaeb0beff068b4d9cd20d322ba0ee1cecfb0b08db145e4ab4dd44a6b5110", size = 5958199, upload-time = "2026-01-31T23:11:35.385Z"}, - {url = "https://files.pythonhosted.org/packages/43/bc/6352f343522fcb2c04dbaf94cb30cca6fd32c1a750c06ad6231b4293708c/numpy-2.4.2-cp313-cp313-win_amd64.whl", hash = "sha256:7df2de1e4fba69a51c06c28f5a3de36731eb9639feb8e1cf7e4a7b0daf4cf622", size = 12310848, upload-time = "2026-01-31T23:11:38.001Z"}, - {url = "https://files.pythonhosted.org/packages/6e/8d/6da186483e308da5da1cc6918ce913dcfe14ffde98e710bfeff2a6158d4e/numpy-2.4.2-cp313-cp313-win_arm64.whl", hash = "sha256:0fece1d1f0a89c16b03442eae5c56dc0be0c7883b5d388e0c03f53019a4bfd71", size = 10221082, upload-time = "2026-01-31T23:11:40.392Z"}, - {url = "https://files.pythonhosted.org/packages/25/a1/9510aa43555b44781968935c7548a8926274f815de42ad3997e9e83680dd/numpy-2.4.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5633c0da313330fd20c484c78cdd3f9b175b55e1a766c4a174230c6b70ad8262", size = 14815866, upload-time = "2026-01-31T23:11:42.495Z"}, - {url = "https://files.pythonhosted.org/packages/36/30/6bbb5e76631a5ae46e7923dd16ca9d3f1c93cfa8d4ed79a129814a9d8db3/numpy-2.4.2-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:d9f64d786b3b1dd742c946c42d15b07497ed14af1a1f3ce840cce27daa0ce913", size = 5325631, upload-time = "2026-01-31T23:11:44.7Z"}, - {url = "https://files.pythonhosted.org/packages/46/00/3a490938800c1923b567b3a15cd17896e68052e2145d8662aaf3e1ffc58f/numpy-2.4.2-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:b21041e8cb6a1eb5312dd1d2f80a94d91efffb7a06b70597d44f1bd2dfc315ab", size = 6646254, upload-time = "2026-01-31T23:11:46.341Z"}, - {url = "https://files.pythonhosted.org/packages/d3/e9/fac0890149898a9b609caa5af7455a948b544746e4b8fe7c212c8edd71f8/numpy-2.4.2-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:00ab83c56211a1d7c07c25e3217ea6695e50a3e2f255053686b081dc0b091a82", size = 15720138, upload-time = "2026-01-31T23:11:48.082Z"}, - {url = "https://files.pythonhosted.org/packages/ea/5c/08887c54e68e1e28df53709f1893ce92932cc6f01f7c3d4dc952f61ffd4e/numpy-2.4.2-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2fb882da679409066b4603579619341c6d6898fc83a8995199d5249f986e8e8f", size = 16655398, upload-time = "2026-01-31T23:11:50.293Z"}, - {url = "https://files.pythonhosted.org/packages/4d/89/253db0fa0e66e9129c745e4ef25631dc37d5f1314dad2b53e907b8538e6d/numpy-2.4.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:66cb9422236317f9d44b67b4d18f44efe6e9c7f8794ac0462978513359461554", size = 17079064, upload-time = "2026-01-31T23:11:52.927Z"}, - {url = "https://files.pythonhosted.org/packages/2a/d5/cbade46ce97c59c6c3da525e8d95b7abe8a42974a1dc5c1d489c10433e88/numpy-2.4.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:0f01dcf33e73d80bd8dc0f20a71303abbafa26a19e23f6b68d1aa9990af90257", size = 18379680, upload-time = "2026-01-31T23:11:55.22Z"}, - {url = "https://files.pythonhosted.org/packages/40/62/48f99ae172a4b63d981babe683685030e8a3df4f246c893ea5c6ef99f018/numpy-2.4.2-cp313-cp313t-win32.whl", hash = "sha256:52b913ec40ff7ae845687b0b34d8d93b60cb66dcee06996dd5c99f2fc9328657", size = 6082433, upload-time = "2026-01-31T23:11:58.096Z"}, - {url = "https://files.pythonhosted.org/packages/07/38/e054a61cfe48ad9f1ed0d188e78b7e26859d0b60ef21cd9de4897cdb5326/numpy-2.4.2-cp313-cp313t-win_amd64.whl", hash = "sha256:5eea80d908b2c1f91486eb95b3fb6fab187e569ec9752ab7d9333d2e66bf2d6b", size = 12451181, upload-time = "2026-01-31T23:11:59.782Z"}, - {url = "https://files.pythonhosted.org/packages/6e/a4/a05c3a6418575e185dd84d0b9680b6bb2e2dc3e4202f036b7b4e22d6e9dc/numpy-2.4.2-cp313-cp313t-win_arm64.whl", hash = "sha256:fd49860271d52127d61197bb50b64f58454e9f578cb4b2c001a6de8b1f50b0b1", size = 10290756, upload-time = "2026-01-31T23:12:02.438Z"} +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/57/fd/0005efbd0af48e55eb3c7208af93f2862d4b1a56cd78e84309a2d959208d/numpy-2.4.2.tar.gz", hash = "sha256:659a6107e31a83c4e33f763942275fd278b21d095094044eb35569e86a21ddae", size = 20723651, upload-time = "2026-01-31T23:13:10.135Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/51/6e/6f394c9c77668153e14d4da83bcc247beb5952f6ead7699a1a2992613bea/numpy-2.4.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:21982668592194c609de53ba4933a7471880ccbaadcc52352694a59ecc860b3a", size = 16667963, upload-time = "2026-01-31T23:10:52.147Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f8/55483431f2b2fd015ae6ed4fe62288823ce908437ed49db5a03d15151678/numpy-2.4.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40397bda92382fcec844066efb11f13e1c9a3e2a8e8f318fb72ed8b6db9f60f1", size = 14693571, upload-time = "2026-01-31T23:10:54.789Z" }, + { url = "https://files.pythonhosted.org/packages/2f/20/18026832b1845cdc82248208dd929ca14c9d8f2bac391f67440707fff27c/numpy-2.4.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:b3a24467af63c67829bfaa61eecf18d5432d4f11992688537be59ecd6ad32f5e", size = 5203469, upload-time = "2026-01-31T23:10:57.343Z" }, + { url = "https://files.pythonhosted.org/packages/7d/33/2eb97c8a77daaba34eaa3fa7241a14ac5f51c46a6bd5911361b644c4a1e2/numpy-2.4.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:805cc8de9fd6e7a22da5aed858e0ab16be5a4db6c873dde1d7451c541553aa27", size = 6550820, upload-time = "2026-01-31T23:10:59.429Z" }, + { url = "https://files.pythonhosted.org/packages/b1/91/b97fdfd12dc75b02c44e26c6638241cc004d4079a0321a69c62f51470c4c/numpy-2.4.2-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6d82351358ffbcdcd7b686b90742a9b86632d6c1c051016484fa0b326a0a1548", size = 15663067, upload-time = "2026-01-31T23:11:01.291Z" }, + { url = "https://files.pythonhosted.org/packages/f5/c6/a18e59f3f0b8071cc85cbc8d80cd02d68aa9710170b2553a117203d46936/numpy-2.4.2-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e35d3e0144137d9fdae62912e869136164534d64a169f86438bc9561b6ad49f", size = 16619782, upload-time = "2026-01-31T23:11:03.669Z" }, + { url = "https://files.pythonhosted.org/packages/b7/83/9751502164601a79e18847309f5ceec0b1446d7b6aa12305759b72cf98b2/numpy-2.4.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:adb6ed2ad29b9e15321d167d152ee909ec73395901b70936f029c3bc6d7f4460", size = 17013128, upload-time = "2026-01-31T23:11:05.913Z" }, + { url = "https://files.pythonhosted.org/packages/61/c4/c4066322256ec740acc1c8923a10047818691d2f8aec254798f3dd90f5f2/numpy-2.4.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8906e71fd8afcb76580404e2a950caef2685df3d2a57fe82a86ac8d33cc007ba", size = 18345324, upload-time = "2026-01-31T23:11:08.248Z" }, + { url = "https://files.pythonhosted.org/packages/ab/af/6157aa6da728fa4525a755bfad486ae7e3f76d4c1864138003eb84328497/numpy-2.4.2-cp312-cp312-win32.whl", hash = "sha256:ec055f6dae239a6299cace477b479cca2fc125c5675482daf1dd886933a1076f", size = 5960282, upload-time = "2026-01-31T23:11:10.497Z" }, + { url = "https://files.pythonhosted.org/packages/92/0f/7ceaaeaacb40567071e94dbf2c9480c0ae453d5bb4f52bea3892c39dc83c/numpy-2.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:209fae046e62d0ce6435fcfe3b1a10537e858249b3d9b05829e2a05218296a85", size = 12314210, upload-time = "2026-01-31T23:11:12.176Z" }, + { url = "https://files.pythonhosted.org/packages/2f/a3/56c5c604fae6dd40fa2ed3040d005fca97e91bd320d232ac9931d77ba13c/numpy-2.4.2-cp312-cp312-win_arm64.whl", hash = "sha256:fbde1b0c6e81d56f5dccd95dd4a711d9b95df1ae4009a60887e56b27e8d903fa", size = 10220171, upload-time = "2026-01-31T23:11:14.684Z" }, + { url = "https://files.pythonhosted.org/packages/a1/22/815b9fe25d1d7ae7d492152adbc7226d3eff731dffc38fe970589fcaaa38/numpy-2.4.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:25f2059807faea4b077a2b6837391b5d830864b3543627f381821c646f31a63c", size = 16663696, upload-time = "2026-01-31T23:11:17.516Z" }, + { url = "https://files.pythonhosted.org/packages/09/f0/817d03a03f93ba9c6c8993de509277d84e69f9453601915e4a69554102a1/numpy-2.4.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bd3a7a9f5847d2fb8c2c6d1c862fa109c31a9abeca1a3c2bd5a64572955b2979", size = 14688322, upload-time = "2026-01-31T23:11:19.883Z" }, + { url = "https://files.pythonhosted.org/packages/da/b4/f805ab79293c728b9a99438775ce51885fd4f31b76178767cfc718701a39/numpy-2.4.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:8e4549f8a3c6d13d55041925e912bfd834285ef1dd64d6bc7d542583355e2e98", size = 5198157, upload-time = "2026-01-31T23:11:22.375Z" }, + { url = "https://files.pythonhosted.org/packages/74/09/826e4289844eccdcd64aac27d13b0fd3f32039915dd5b9ba01baae1f436c/numpy-2.4.2-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:aea4f66ff44dfddf8c2cffd66ba6538c5ec67d389285292fe428cb2c738c8aef", size = 6546330, upload-time = "2026-01-31T23:11:23.958Z" }, + { url = "https://files.pythonhosted.org/packages/19/fb/cbfdbfa3057a10aea5422c558ac57538e6acc87ec1669e666d32ac198da7/numpy-2.4.2-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c3cd545784805de05aafe1dde61752ea49a359ccba9760c1e5d1c88a93bbf2b7", size = 15660968, upload-time = "2026-01-31T23:11:25.713Z" }, + { url = "https://files.pythonhosted.org/packages/04/dc/46066ce18d01645541f0186877377b9371b8fa8017fa8262002b4ef22612/numpy-2.4.2-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d0d9b7c93578baafcbc5f0b83eaf17b79d345c6f36917ba0c67f45226911d499", size = 16607311, upload-time = "2026-01-31T23:11:28.117Z" }, + { url = "https://files.pythonhosted.org/packages/14/d9/4b5adfc39a43fa6bf918c6d544bc60c05236cc2f6339847fc5b35e6cb5b0/numpy-2.4.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f74f0f7779cc7ae07d1810aab8ac6b1464c3eafb9e283a40da7309d5e6e48fbb", size = 17012850, upload-time = "2026-01-31T23:11:30.888Z" }, + { url = "https://files.pythonhosted.org/packages/b7/20/adb6e6adde6d0130046e6fdfb7675cc62bc2f6b7b02239a09eb58435753d/numpy-2.4.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c7ac672d699bf36275c035e16b65539931347d68b70667d28984c9fb34e07fa7", size = 18334210, upload-time = "2026-01-31T23:11:33.214Z" }, + { url = "https://files.pythonhosted.org/packages/78/0e/0a73b3dff26803a8c02baa76398015ea2a5434d9b8265a7898a6028c1591/numpy-2.4.2-cp313-cp313-win32.whl", hash = "sha256:8e9afaeb0beff068b4d9cd20d322ba0ee1cecfb0b08db145e4ab4dd44a6b5110", size = 5958199, upload-time = "2026-01-31T23:11:35.385Z" }, + { url = "https://files.pythonhosted.org/packages/43/bc/6352f343522fcb2c04dbaf94cb30cca6fd32c1a750c06ad6231b4293708c/numpy-2.4.2-cp313-cp313-win_amd64.whl", hash = "sha256:7df2de1e4fba69a51c06c28f5a3de36731eb9639feb8e1cf7e4a7b0daf4cf622", size = 12310848, upload-time = "2026-01-31T23:11:38.001Z" }, + { url = "https://files.pythonhosted.org/packages/6e/8d/6da186483e308da5da1cc6918ce913dcfe14ffde98e710bfeff2a6158d4e/numpy-2.4.2-cp313-cp313-win_arm64.whl", hash = "sha256:0fece1d1f0a89c16b03442eae5c56dc0be0c7883b5d388e0c03f53019a4bfd71", size = 10221082, upload-time = "2026-01-31T23:11:40.392Z" }, + { url = "https://files.pythonhosted.org/packages/25/a1/9510aa43555b44781968935c7548a8926274f815de42ad3997e9e83680dd/numpy-2.4.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5633c0da313330fd20c484c78cdd3f9b175b55e1a766c4a174230c6b70ad8262", size = 14815866, upload-time = "2026-01-31T23:11:42.495Z" }, + { url = "https://files.pythonhosted.org/packages/36/30/6bbb5e76631a5ae46e7923dd16ca9d3f1c93cfa8d4ed79a129814a9d8db3/numpy-2.4.2-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:d9f64d786b3b1dd742c946c42d15b07497ed14af1a1f3ce840cce27daa0ce913", size = 5325631, upload-time = "2026-01-31T23:11:44.7Z" }, + { url = "https://files.pythonhosted.org/packages/46/00/3a490938800c1923b567b3a15cd17896e68052e2145d8662aaf3e1ffc58f/numpy-2.4.2-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:b21041e8cb6a1eb5312dd1d2f80a94d91efffb7a06b70597d44f1bd2dfc315ab", size = 6646254, upload-time = "2026-01-31T23:11:46.341Z" }, + { url = "https://files.pythonhosted.org/packages/d3/e9/fac0890149898a9b609caa5af7455a948b544746e4b8fe7c212c8edd71f8/numpy-2.4.2-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:00ab83c56211a1d7c07c25e3217ea6695e50a3e2f255053686b081dc0b091a82", size = 15720138, upload-time = "2026-01-31T23:11:48.082Z" }, + { url = "https://files.pythonhosted.org/packages/ea/5c/08887c54e68e1e28df53709f1893ce92932cc6f01f7c3d4dc952f61ffd4e/numpy-2.4.2-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2fb882da679409066b4603579619341c6d6898fc83a8995199d5249f986e8e8f", size = 16655398, upload-time = "2026-01-31T23:11:50.293Z" }, + { url = "https://files.pythonhosted.org/packages/4d/89/253db0fa0e66e9129c745e4ef25631dc37d5f1314dad2b53e907b8538e6d/numpy-2.4.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:66cb9422236317f9d44b67b4d18f44efe6e9c7f8794ac0462978513359461554", size = 17079064, upload-time = "2026-01-31T23:11:52.927Z" }, + { url = "https://files.pythonhosted.org/packages/2a/d5/cbade46ce97c59c6c3da525e8d95b7abe8a42974a1dc5c1d489c10433e88/numpy-2.4.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:0f01dcf33e73d80bd8dc0f20a71303abbafa26a19e23f6b68d1aa9990af90257", size = 18379680, upload-time = "2026-01-31T23:11:55.22Z" }, + { url = "https://files.pythonhosted.org/packages/40/62/48f99ae172a4b63d981babe683685030e8a3df4f246c893ea5c6ef99f018/numpy-2.4.2-cp313-cp313t-win32.whl", hash = "sha256:52b913ec40ff7ae845687b0b34d8d93b60cb66dcee06996dd5c99f2fc9328657", size = 6082433, upload-time = "2026-01-31T23:11:58.096Z" }, + { url = "https://files.pythonhosted.org/packages/07/38/e054a61cfe48ad9f1ed0d188e78b7e26859d0b60ef21cd9de4897cdb5326/numpy-2.4.2-cp313-cp313t-win_amd64.whl", hash = "sha256:5eea80d908b2c1f91486eb95b3fb6fab187e569ec9752ab7d9333d2e66bf2d6b", size = 12451181, upload-time = "2026-01-31T23:11:59.782Z" }, + { url = "https://files.pythonhosted.org/packages/6e/a4/a05c3a6418575e185dd84d0b9680b6bb2e2dc3e4202f036b7b4e22d6e9dc/numpy-2.4.2-cp313-cp313t-win_arm64.whl", hash = "sha256:fd49860271d52127d61197bb50b64f58454e9f578cb4b2c001a6de8b1f50b0b1", size = 10290756, upload-time = "2026-01-31T23:12:02.438Z" }, ] [[package]] +name = "openptv-python" +source = { editable = "." } dependencies = [ - {name = "mypy"}, - {name = "myst-parser"}, - {name = "numba"}, - {name = "numpy"}, - {name = "optv"}, - {name = "pre-commit"}, - {name = "pydata-sphinx-theme"}, - {name = "pytest"}, - {name = "pytest-cov"}, - {name = "pyyaml"}, - {name = "scipy"}, - {name = "sphinx"}, - {name = "sphinx-autoapi"}, - {name = "types-pyyaml"} + { name = "numba" }, + { name = "numpy" }, + { name = "pyyaml" }, + { name = "scipy" }, +] + +[package.optional-dependencies] +dev = [ + { name = "mypy" }, + { name = "myst-parser" }, + { name = "optv" }, + { name = "pre-commit" }, + { name = "pydata-sphinx-theme" }, + { name = "pytest" }, + { name = "pytest-cov" }, + { name = "sphinx" }, + { name = "sphinx-autoapi" }, + { name = "types-pyyaml" }, +] +docs = [ + { name = "myst-parser" }, + { name = "pydata-sphinx-theme" }, + { name = "sphinx" }, + { name = "sphinx-autoapi" }, +] +native = [ + { name = "optv" }, +] +test = [ + { name = "pytest" }, + { name = "pytest-cov" }, ] -name = "openptv-python" -source = {editable = "."} [package.metadata] requires-dist = [ - {name = "mypy", specifier = ">=1.19.1"}, - {name = "myst-parser", specifier = ">=5.0.0"}, - {name = "numba", specifier = ">=0.64.0"}, - {name = "numpy", specifier = ">=2.4.2"}, - {name = "optv", specifier = ">=0.3.2"}, - {name = "pre-commit", specifier = ">=4.5.1"}, - {name = "pydata-sphinx-theme", specifier = ">=0.16.1"}, - {name = "pytest", specifier = ">=9.0.2"}, - {name = "pytest-cov", specifier = ">=7.0.0"}, - {name = "pyyaml", specifier = ">=6.0.3"}, - {name = "scipy", specifier = ">=1.17.1"}, - {name = "sphinx", specifier = ">=9.1.0"}, - {name = "sphinx-autoapi", specifier = ">=3.7.0"}, - {name = "types-pyyaml", specifier = ">=6.0.12.20250915"} -] + { name = "mypy", marker = "extra == 'dev'", specifier = ">=1.19.1" }, + { name = "myst-parser", marker = "extra == 'dev'", specifier = ">=5.0.0" }, + { name = "myst-parser", marker = "extra == 'docs'", specifier = ">=5.0.0" }, + { name = "numba", specifier = ">=0.64.0" }, + { name = "numpy", specifier = ">=2.4.2" }, + { name = "optv", marker = "extra == 'dev'", specifier = ">=0.3.2" }, + { name = "optv", marker = "extra == 'native'", specifier = ">=0.3.2" }, + { name = "pre-commit", marker = "extra == 'dev'", specifier = ">=4.5.1" }, + { name = "pydata-sphinx-theme", marker = "extra == 'dev'", specifier = ">=0.16.1" }, + { name = "pydata-sphinx-theme", marker = "extra == 'docs'", specifier = ">=0.16.1" }, + { name = "pytest", marker = "extra == 'dev'", specifier = ">=9.0.2" }, + { name = "pytest", marker = "extra == 'test'", specifier = ">=9.0.2" }, + { name = "pytest-cov", marker = "extra == 'dev'", specifier = ">=7.0.0" }, + { name = "pytest-cov", marker = "extra == 'test'", specifier = ">=7.0.0" }, + { name = "pyyaml", specifier = ">=6.0.3" }, + { name = "scipy", specifier = ">=1.17.1" }, + { name = "sphinx", marker = "extra == 'dev'", specifier = ">=9.1.0" }, + { name = "sphinx", marker = "extra == 'docs'", specifier = ">=9.1.0" }, + { name = "sphinx-autoapi", marker = "extra == 'dev'", specifier = ">=3.7.0" }, + { name = "sphinx-autoapi", marker = "extra == 'docs'", specifier = ">=3.7.0" }, + { name = "types-pyyaml", marker = "extra == 'dev'", specifier = ">=6.0.12.20250915" }, +] +provides-extras = ["docs", "native", "test", "dev"] [[package]] -dependencies = [ - {name = "numpy"}, - {name = "pyyaml"} -] name = "optv" -source = {registry = "https://pypi.org/simple"} version = "0.3.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, + { name = "pyyaml" }, +] wheels = [ - {url = "https://files.pythonhosted.org/packages/93/29/ac2ab83e885a53ae2d4b6537b26c9029ce663618a56eb8e9ab10a0c53330/optv-0.3.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:9cf47f3756a95ce4aad6895ccd7a8ca9fdc51d513900fb68761a16450289f84e", size = 2347739, upload-time = "2026-03-01T23:03:35.73Z"}, - {url = "https://files.pythonhosted.org/packages/07/0a/cca02bf1a478450f1ba2a3e44db4a917b56e15c6eb6274d195ed217c0950/optv-0.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4404e00d0bf0d21dfeaa1595ac453fe66ffed26d889e70f6727630280895916b", size = 2262784, upload-time = "2026-03-01T23:03:39.472Z"}, - {url = "https://files.pythonhosted.org/packages/90/f6/47953d940fc82f78a629e59afe0251f936c49452065715349faf2803cb14/optv-0.3.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9df069358f9283560ba707e23c87f2071ad2c1815c201f3891ed95e3216eb457", size = 6179852, upload-time = "2026-03-01T23:03:47.464Z"}, - {url = "https://files.pythonhosted.org/packages/28/3b/1c38b9b6b9d19e432cd1dbba8c2bd658fd0b852692cd1c9574a67b3bc58d/optv-0.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:555c6a06ab28e1ae93d9c3d21ec89a9a2d6bb573ebf09efd1606553ccf13eeb1", size = 12215450, upload-time = "2026-03-01T23:03:59.265Z"}, - {url = "https://files.pythonhosted.org/packages/c0/6b/6afeed9e26263fd3a6f1cded81efc031c43985aee42bea7686dbc554f7cc/optv-0.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:e36e3093abdee39a35680e4a2304e5fdcddbb392096741326b7632a13c65b991", size = 1731896, upload-time = "2026-03-01T23:04:03.206Z"}, - {url = "https://files.pythonhosted.org/packages/c6/40/9b8e9edda07f9f5f9d2469cf26edc8ee674a02069145f5d21b8a9b789ad0/optv-0.3.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4c05949ea6c15dcc8fcdb36ca4e935aff273ac81d80405930e7a2b64ff2a710e", size = 2342514, upload-time = "2026-03-01T23:04:06.56Z"}, - {url = "https://files.pythonhosted.org/packages/b4/76/30921ef229074276aebfdd18f0603d7a14461c3cf74a9964678bbf82d80a/optv-0.3.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a549e36df4d3f1c3f30b7a667e6595f52da50bcb3727f57de045a652ab9a1194", size = 2257415, upload-time = "2026-03-01T23:04:10.218Z"}, - {url = "https://files.pythonhosted.org/packages/ae/e1/bbeb1dd50759ec6c88d074570a1790741fb373fde4c66915980e192eb51c/optv-0.3.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3fdc240569f2804c6d74951a27e0b7f168e85de2a9e4ab5b09a290efc8d70417", size = 6070665, upload-time = "2026-03-01T23:04:17.879Z"}, - {url = "https://files.pythonhosted.org/packages/f3/3a/76b420f2c7937fb4b5231a05dacc45eb66c46d88515b56b96701e89b2708/optv-0.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:80529429fed3f984cd400e77fcef986b7409322b0b14fdd6b8aad47fde69cef4", size = 5968060, upload-time = "2026-03-01T23:04:25.215Z"}, - {url = "https://files.pythonhosted.org/packages/31/d3/475e6f830eca6f886f5d0d74e542d059978d6dabfc16ddd61f004ea727d8/optv-0.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:c863518e8c91e018887f24d0cf99ca1720f8f2155fe92b9b46fa794e97e43259", size = 1728112, upload-time = "2026-03-01T23:04:28.062Z"} + { url = "https://files.pythonhosted.org/packages/93/29/ac2ab83e885a53ae2d4b6537b26c9029ce663618a56eb8e9ab10a0c53330/optv-0.3.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:9cf47f3756a95ce4aad6895ccd7a8ca9fdc51d513900fb68761a16450289f84e", size = 2347739, upload-time = "2026-03-01T23:03:35.73Z" }, + { url = "https://files.pythonhosted.org/packages/07/0a/cca02bf1a478450f1ba2a3e44db4a917b56e15c6eb6274d195ed217c0950/optv-0.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4404e00d0bf0d21dfeaa1595ac453fe66ffed26d889e70f6727630280895916b", size = 2262784, upload-time = "2026-03-01T23:03:39.472Z" }, + { url = "https://files.pythonhosted.org/packages/90/f6/47953d940fc82f78a629e59afe0251f936c49452065715349faf2803cb14/optv-0.3.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9df069358f9283560ba707e23c87f2071ad2c1815c201f3891ed95e3216eb457", size = 6179852, upload-time = "2026-03-01T23:03:47.464Z" }, + { url = "https://files.pythonhosted.org/packages/28/3b/1c38b9b6b9d19e432cd1dbba8c2bd658fd0b852692cd1c9574a67b3bc58d/optv-0.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:555c6a06ab28e1ae93d9c3d21ec89a9a2d6bb573ebf09efd1606553ccf13eeb1", size = 12215450, upload-time = "2026-03-01T23:03:59.265Z" }, + { url = "https://files.pythonhosted.org/packages/c0/6b/6afeed9e26263fd3a6f1cded81efc031c43985aee42bea7686dbc554f7cc/optv-0.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:e36e3093abdee39a35680e4a2304e5fdcddbb392096741326b7632a13c65b991", size = 1731896, upload-time = "2026-03-01T23:04:03.206Z" }, + { url = "https://files.pythonhosted.org/packages/c6/40/9b8e9edda07f9f5f9d2469cf26edc8ee674a02069145f5d21b8a9b789ad0/optv-0.3.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4c05949ea6c15dcc8fcdb36ca4e935aff273ac81d80405930e7a2b64ff2a710e", size = 2342514, upload-time = "2026-03-01T23:04:06.56Z" }, + { url = "https://files.pythonhosted.org/packages/b4/76/30921ef229074276aebfdd18f0603d7a14461c3cf74a9964678bbf82d80a/optv-0.3.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a549e36df4d3f1c3f30b7a667e6595f52da50bcb3727f57de045a652ab9a1194", size = 2257415, upload-time = "2026-03-01T23:04:10.218Z" }, + { url = "https://files.pythonhosted.org/packages/ae/e1/bbeb1dd50759ec6c88d074570a1790741fb373fde4c66915980e192eb51c/optv-0.3.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3fdc240569f2804c6d74951a27e0b7f168e85de2a9e4ab5b09a290efc8d70417", size = 6070665, upload-time = "2026-03-01T23:04:17.879Z" }, + { url = "https://files.pythonhosted.org/packages/f3/3a/76b420f2c7937fb4b5231a05dacc45eb66c46d88515b56b96701e89b2708/optv-0.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:80529429fed3f984cd400e77fcef986b7409322b0b14fdd6b8aad47fde69cef4", size = 5968060, upload-time = "2026-03-01T23:04:25.215Z" }, + { url = "https://files.pythonhosted.org/packages/31/d3/475e6f830eca6f886f5d0d74e542d059978d6dabfc16ddd61f004ea727d8/optv-0.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:c863518e8c91e018887f24d0cf99ca1720f8f2155fe92b9b46fa794e97e43259", size = 1728112, upload-time = "2026-03-01T23:04:28.062Z" }, ] [[package]] name = "packaging" -sdist = {url = "https://files.pythonhosted.org/packages/65/ee/299d360cdc32edc7d2cf530f3accf79c4fca01e96ffc950d8a52213bd8e4/packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4", size = 143416, upload-time = "2026-01-21T20:50:39.064Z"} -source = {registry = "https://pypi.org/simple"} version = "26.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/65/ee/299d360cdc32edc7d2cf530f3accf79c4fca01e96ffc950d8a52213bd8e4/packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4", size = 143416, upload-time = "2026-01-21T20:50:39.064Z" } wheels = [ - {url = "https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529", size = 74366, upload-time = "2026-01-21T20:50:37.788Z"} + { url = "https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529", size = 74366, upload-time = "2026-01-21T20:50:37.788Z" }, ] [[package]] name = "pathspec" -sdist = {url = "https://files.pythonhosted.org/packages/fa/36/e27608899f9b8d4dff0617b2d9ab17ca5608956ca44461ac14ac48b44015/pathspec-1.0.4.tar.gz", hash = "sha256:0210e2ae8a21a9137c0d470578cb0e595af87edaa6ebf12ff176f14a02e0e645", size = 131200, upload-time = "2026-01-27T03:59:46.938Z"} -source = {registry = "https://pypi.org/simple"} version = "1.0.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fa/36/e27608899f9b8d4dff0617b2d9ab17ca5608956ca44461ac14ac48b44015/pathspec-1.0.4.tar.gz", hash = "sha256:0210e2ae8a21a9137c0d470578cb0e595af87edaa6ebf12ff176f14a02e0e645", size = 131200, upload-time = "2026-01-27T03:59:46.938Z" } wheels = [ - {url = "https://files.pythonhosted.org/packages/ef/3c/2c197d226f9ea224a9ab8d197933f9da0ae0aac5b6e0f884e2b8d9c8e9f7/pathspec-1.0.4-py3-none-any.whl", hash = "sha256:fb6ae2fd4e7c921a165808a552060e722767cfa526f99ca5156ed2ce45a5c723", size = 55206, upload-time = "2026-01-27T03:59:45.137Z"} + { url = "https://files.pythonhosted.org/packages/ef/3c/2c197d226f9ea224a9ab8d197933f9da0ae0aac5b6e0f884e2b8d9c8e9f7/pathspec-1.0.4-py3-none-any.whl", hash = "sha256:fb6ae2fd4e7c921a165808a552060e722767cfa526f99ca5156ed2ce45a5c723", size = 55206, upload-time = "2026-01-27T03:59:45.137Z" }, ] [[package]] name = "platformdirs" -sdist = {url = "https://files.pythonhosted.org/packages/19/56/8d4c30c8a1d07013911a8fdbd8f89440ef9f08d07a1b50ab8ca8be5a20f9/platformdirs-4.9.4.tar.gz", hash = "sha256:1ec356301b7dc906d83f371c8f487070e99d3ccf9e501686456394622a01a934", size = 28737, upload-time = "2026-03-05T18:34:13.271Z"} -source = {registry = "https://pypi.org/simple"} version = "4.9.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/19/56/8d4c30c8a1d07013911a8fdbd8f89440ef9f08d07a1b50ab8ca8be5a20f9/platformdirs-4.9.4.tar.gz", hash = "sha256:1ec356301b7dc906d83f371c8f487070e99d3ccf9e501686456394622a01a934", size = 28737, upload-time = "2026-03-05T18:34:13.271Z" } wheels = [ - {url = "https://files.pythonhosted.org/packages/63/d7/97f7e3a6abb67d8080dd406fd4df842c2be0efaf712d1c899c32a075027c/platformdirs-4.9.4-py3-none-any.whl", hash = "sha256:68a9a4619a666ea6439f2ff250c12a853cd1cbd5158d258bd824a7df6be2f868", size = 21216, upload-time = "2026-03-05T18:34:12.172Z"} + { url = "https://files.pythonhosted.org/packages/63/d7/97f7e3a6abb67d8080dd406fd4df842c2be0efaf712d1c899c32a075027c/platformdirs-4.9.4-py3-none-any.whl", hash = "sha256:68a9a4619a666ea6439f2ff250c12a853cd1cbd5158d258bd824a7df6be2f868", size = 21216, upload-time = "2026-03-05T18:34:12.172Z" }, ] [[package]] name = "pluggy" -sdist = {url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z"} -source = {registry = "https://pypi.org/simple"} version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } wheels = [ - {url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z"} + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, ] [[package]] -dependencies = [ - {name = "cfgv"}, - {name = "identify"}, - {name = "nodeenv"}, - {name = "pyyaml"}, - {name = "virtualenv"} -] name = "pre-commit" -sdist = {url = "https://files.pythonhosted.org/packages/40/f1/6d86a29246dfd2e9b6237f0b5823717f60cad94d47ddc26afa916d21f525/pre_commit-4.5.1.tar.gz", hash = "sha256:eb545fcff725875197837263e977ea257a402056661f09dae08e4b149b030a61", size = 198232, upload-time = "2025-12-16T21:14:33.552Z"} -source = {registry = "https://pypi.org/simple"} version = "4.5.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cfgv" }, + { name = "identify" }, + { name = "nodeenv" }, + { name = "pyyaml" }, + { name = "virtualenv" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/40/f1/6d86a29246dfd2e9b6237f0b5823717f60cad94d47ddc26afa916d21f525/pre_commit-4.5.1.tar.gz", hash = "sha256:eb545fcff725875197837263e977ea257a402056661f09dae08e4b149b030a61", size = 198232, upload-time = "2025-12-16T21:14:33.552Z" } wheels = [ - {url = "https://files.pythonhosted.org/packages/5d/19/fd3ef348460c80af7bb4669ea7926651d1f95c23ff2df18b9d24bab4f3fa/pre_commit-4.5.1-py2.py3-none-any.whl", hash = "sha256:3b3afd891e97337708c1674210f8eba659b52a38ea5f822ff142d10786221f77", size = 226437, upload-time = "2025-12-16T21:14:32.409Z"} + { url = "https://files.pythonhosted.org/packages/5d/19/fd3ef348460c80af7bb4669ea7926651d1f95c23ff2df18b9d24bab4f3fa/pre_commit-4.5.1-py2.py3-none-any.whl", hash = "sha256:3b3afd891e97337708c1674210f8eba659b52a38ea5f822ff142d10786221f77", size = 226437, upload-time = "2025-12-16T21:14:32.409Z" }, ] [[package]] -dependencies = [ - {name = "accessible-pygments"}, - {name = "babel"}, - {name = "beautifulsoup4"}, - {name = "docutils"}, - {name = "pygments"}, - {name = "sphinx"}, - {name = "typing-extensions"} -] name = "pydata-sphinx-theme" -sdist = {url = "https://files.pythonhosted.org/packages/00/20/bb50f9de3a6de69e6abd6b087b52fa2418a0418b19597601605f855ad044/pydata_sphinx_theme-0.16.1.tar.gz", hash = "sha256:a08b7f0b7f70387219dc659bff0893a7554d5eb39b59d3b8ef37b8401b7642d7", size = 2412693, upload-time = "2024-12-17T10:53:39.537Z"} -source = {registry = "https://pypi.org/simple"} version = "0.16.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "accessible-pygments" }, + { name = "babel" }, + { name = "beautifulsoup4" }, + { name = "docutils" }, + { name = "pygments" }, + { name = "sphinx" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/00/20/bb50f9de3a6de69e6abd6b087b52fa2418a0418b19597601605f855ad044/pydata_sphinx_theme-0.16.1.tar.gz", hash = "sha256:a08b7f0b7f70387219dc659bff0893a7554d5eb39b59d3b8ef37b8401b7642d7", size = 2412693, upload-time = "2024-12-17T10:53:39.537Z" } wheels = [ - {url = "https://files.pythonhosted.org/packages/e2/0d/8ba33fa83a7dcde13eb3c1c2a0c1cc29950a048bfed6d9b0d8b6bd710b4c/pydata_sphinx_theme-0.16.1-py3-none-any.whl", hash = "sha256:225331e8ac4b32682c18fcac5a57a6f717c4e632cea5dd0e247b55155faeccde", size = 6723264, upload-time = "2024-12-17T10:53:35.645Z"} + { url = "https://files.pythonhosted.org/packages/e2/0d/8ba33fa83a7dcde13eb3c1c2a0c1cc29950a048bfed6d9b0d8b6bd710b4c/pydata_sphinx_theme-0.16.1-py3-none-any.whl", hash = "sha256:225331e8ac4b32682c18fcac5a57a6f717c4e632cea5dd0e247b55155faeccde", size = 6723264, upload-time = "2024-12-17T10:53:35.645Z" }, ] [[package]] name = "pygments" -sdist = {url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z"} -source = {registry = "https://pypi.org/simple"} version = "2.19.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } wheels = [ - {url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z"} + { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, ] [[package]] -dependencies = [ - {name = "colorama", marker = "sys_platform == 'win32'"}, - {name = "iniconfig"}, - {name = "packaging"}, - {name = "pluggy"}, - {name = "pygments"} -] name = "pytest" -sdist = {url = "https://files.pythonhosted.org/packages/d1/db/7ef3487e0fb0049ddb5ce41d3a49c235bf9ad299b6a25d5780a89f19230f/pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11", size = 1568901, upload-time = "2025-12-06T21:30:51.014Z"} -source = {registry = "https://pypi.org/simple"} version = "9.0.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d1/db/7ef3487e0fb0049ddb5ce41d3a49c235bf9ad299b6a25d5780a89f19230f/pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11", size = 1568901, upload-time = "2025-12-06T21:30:51.014Z" } wheels = [ - {url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801, upload-time = "2025-12-06T21:30:49.154Z"} + { url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801, upload-time = "2025-12-06T21:30:49.154Z" }, ] [[package]] -dependencies = [ - {name = "coverage"}, - {name = "pluggy"}, - {name = "pytest"} -] name = "pytest-cov" -sdist = {url = "https://files.pythonhosted.org/packages/5e/f7/c933acc76f5208b3b00089573cf6a2bc26dc80a8aece8f52bb7d6b1855ca/pytest_cov-7.0.0.tar.gz", hash = "sha256:33c97eda2e049a0c5298e91f519302a1334c26ac65c1a483d6206fd458361af1", size = 54328, upload-time = "2025-09-09T10:57:02.113Z"} -source = {registry = "https://pypi.org/simple"} version = "7.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "coverage" }, + { name = "pluggy" }, + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5e/f7/c933acc76f5208b3b00089573cf6a2bc26dc80a8aece8f52bb7d6b1855ca/pytest_cov-7.0.0.tar.gz", hash = "sha256:33c97eda2e049a0c5298e91f519302a1334c26ac65c1a483d6206fd458361af1", size = 54328, upload-time = "2025-09-09T10:57:02.113Z" } wheels = [ - {url = "https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl", hash = "sha256:3b8e9558b16cc1479da72058bdecf8073661c7f57f7d3c5f22a1c23507f2d861", size = 22424, upload-time = "2025-09-09T10:57:00.695Z"} + { url = "https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl", hash = "sha256:3b8e9558b16cc1479da72058bdecf8073661c7f57f7d3c5f22a1c23507f2d861", size = 22424, upload-time = "2025-09-09T10:57:00.695Z" }, ] [[package]] -dependencies = [ - {name = "filelock"}, - {name = "platformdirs"} -] name = "python-discovery" -sdist = {url = "https://files.pythonhosted.org/packages/ec/67/09765eacf4e44413c4f8943ba5a317fcb9c7b447c3b8b0b7fce7e3090b0b/python_discovery-1.1.1.tar.gz", hash = "sha256:584c08b141c5b7029f206b4e8b78b1a1764b22121e21519b89dec56936e95b0a", size = 56016, upload-time = "2026-03-07T00:00:56.354Z"} -source = {registry = "https://pypi.org/simple"} version = "1.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "filelock" }, + { name = "platformdirs" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ec/67/09765eacf4e44413c4f8943ba5a317fcb9c7b447c3b8b0b7fce7e3090b0b/python_discovery-1.1.1.tar.gz", hash = "sha256:584c08b141c5b7029f206b4e8b78b1a1764b22121e21519b89dec56936e95b0a", size = 56016, upload-time = "2026-03-07T00:00:56.354Z" } wheels = [ - {url = "https://files.pythonhosted.org/packages/75/0f/2bf7e3b5a4a65f623cb820feb5793e243fad58ae561015ee15a6152f67a2/python_discovery-1.1.1-py3-none-any.whl", hash = "sha256:69f11073fa2392251e405d4e847d60ffffd25fd762a0dc4d1a7d6b9c3f79f1a3", size = 30732, upload-time = "2026-03-07T00:00:55.143Z"} + { url = "https://files.pythonhosted.org/packages/75/0f/2bf7e3b5a4a65f623cb820feb5793e243fad58ae561015ee15a6152f67a2/python_discovery-1.1.1-py3-none-any.whl", hash = "sha256:69f11073fa2392251e405d4e847d60ffffd25fd762a0dc4d1a7d6b9c3f79f1a3", size = 30732, upload-time = "2026-03-07T00:00:55.143Z" }, ] [[package]] name = "pyyaml" -sdist = {url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z"} -source = {registry = "https://pypi.org/simple"} version = "6.0.3" -wheels = [ - {url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z"}, - {url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z"}, - {url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z"}, - {url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z"}, - {url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z"}, - {url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z"}, - {url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z"}, - {url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z"}, - {url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z"}, - {url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z"}, - {url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z"}, - {url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z"}, - {url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z"}, - {url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z"}, - {url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z"}, - {url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z"}, - {url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z"}, - {url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z"}, - {url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z"}, - {url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z"} +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" }, + { url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" }, + { url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" }, + { url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z" }, + { url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z" }, + { url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z" }, + { url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z" }, + { url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z" }, + { url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z" }, + { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" }, + { url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" }, + { url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" }, + { url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" }, + { url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" }, + { url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" }, + { url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" }, + { url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" }, + { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" }, + { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" }, + { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" }, ] [[package]] -dependencies = [ - {name = "certifi"}, - {name = "charset-normalizer"}, - {name = "idna"}, - {name = "urllib3"} -] name = "requests" -sdist = {url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z"} -source = {registry = "https://pypi.org/simple"} version = "2.32.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" } wheels = [ - {url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z"} + { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" }, ] [[package]] name = "roman-numerals" -sdist = {url = "https://files.pythonhosted.org/packages/ae/f9/41dc953bbeb056c17d5f7a519f50fdf010bd0553be2d630bc69d1e022703/roman_numerals-4.1.0.tar.gz", hash = "sha256:1af8b147eb1405d5839e78aeb93131690495fe9da5c91856cb33ad55a7f1e5b2", size = 9077, upload-time = "2025-12-17T18:25:34.381Z"} -source = {registry = "https://pypi.org/simple"} version = "4.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ae/f9/41dc953bbeb056c17d5f7a519f50fdf010bd0553be2d630bc69d1e022703/roman_numerals-4.1.0.tar.gz", hash = "sha256:1af8b147eb1405d5839e78aeb93131690495fe9da5c91856cb33ad55a7f1e5b2", size = 9077, upload-time = "2025-12-17T18:25:34.381Z" } wheels = [ - {url = "https://files.pythonhosted.org/packages/04/54/6f679c435d28e0a568d8e8a7c0a93a09010818634c3c3907fc98d8983770/roman_numerals-4.1.0-py3-none-any.whl", hash = "sha256:647ba99caddc2cc1e55a51e4360689115551bf4476d90e8162cf8c345fe233c7", size = 7676, upload-time = "2025-12-17T18:25:33.098Z"} + { url = "https://files.pythonhosted.org/packages/04/54/6f679c435d28e0a568d8e8a7c0a93a09010818634c3c3907fc98d8983770/roman_numerals-4.1.0-py3-none-any.whl", hash = "sha256:647ba99caddc2cc1e55a51e4360689115551bf4476d90e8162cf8c345fe233c7", size = 7676, upload-time = "2025-12-17T18:25:33.098Z" }, ] [[package]] -dependencies = [ - {name = "numpy"} -] name = "scipy" -sdist = {url = "https://files.pythonhosted.org/packages/7a/97/5a3609c4f8d58b039179648e62dd220f89864f56f7357f5d4f45c29eb2cc/scipy-1.17.1.tar.gz", hash = "sha256:95d8e012d8cb8816c226aef832200b1d45109ed4464303e997c5b13122b297c0", size = 30573822, upload-time = "2026-02-23T00:26:24.851Z"} -source = {registry = "https://pypi.org/simple"} version = "1.17.1" -wheels = [ - {url = "https://files.pythonhosted.org/packages/35/48/b992b488d6f299dbe3f11a20b24d3dda3d46f1a635ede1c46b5b17a7b163/scipy-1.17.1-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:35c3a56d2ef83efc372eaec584314bd0ef2e2f0d2adb21c55e6ad5b344c0dcb8", size = 31610954, upload-time = "2026-02-23T00:17:49.855Z"}, - {url = "https://files.pythonhosted.org/packages/b2/02/cf107b01494c19dc100f1d0b7ac3cc08666e96ba2d64db7626066cee895e/scipy-1.17.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:fcb310ddb270a06114bb64bbe53c94926b943f5b7f0842194d585c65eb4edd76", size = 28172662, upload-time = "2026-02-23T00:18:01.64Z"}, - {url = "https://files.pythonhosted.org/packages/cf/a9/599c28631bad314d219cf9ffd40e985b24d603fc8a2f4ccc5ae8419a535b/scipy-1.17.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:cc90d2e9c7e5c7f1a482c9875007c095c3194b1cfedca3c2f3291cdc2bc7c086", size = 20344366, upload-time = "2026-02-23T00:18:12.015Z"}, - {url = "https://files.pythonhosted.org/packages/35/f5/906eda513271c8deb5af284e5ef0206d17a96239af79f9fa0aebfe0e36b4/scipy-1.17.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:c80be5ede8f3f8eded4eff73cc99a25c388ce98e555b17d31da05287015ffa5b", size = 22704017, upload-time = "2026-02-23T00:18:21.502Z"}, - {url = "https://files.pythonhosted.org/packages/da/34/16f10e3042d2f1d6b66e0428308ab52224b6a23049cb2f5c1756f713815f/scipy-1.17.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e19ebea31758fac5893a2ac360fedd00116cbb7628e650842a6691ba7ca28a21", size = 32927842, upload-time = "2026-02-23T00:18:35.367Z"}, - {url = "https://files.pythonhosted.org/packages/01/8e/1e35281b8ab6d5d72ebe9911edcdffa3f36b04ed9d51dec6dd140396e220/scipy-1.17.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:02ae3b274fde71c5e92ac4d54bc06c42d80e399fec704383dcd99b301df37458", size = 35235890, upload-time = "2026-02-23T00:18:49.188Z"}, - {url = "https://files.pythonhosted.org/packages/c5/5c/9d7f4c88bea6e0d5a4f1bc0506a53a00e9fcb198de372bfe4d3652cef482/scipy-1.17.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8a604bae87c6195d8b1045eddece0514d041604b14f2727bbc2b3020172045eb", size = 35003557, upload-time = "2026-02-23T00:18:54.74Z"}, - {url = "https://files.pythonhosted.org/packages/65/94/7698add8f276dbab7a9de9fb6b0e02fc13ee61d51c7c3f85ac28b65e1239/scipy-1.17.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f590cd684941912d10becc07325a3eeb77886fe981415660d9265c4c418d0bea", size = 37625856, upload-time = "2026-02-23T00:19:00.307Z"}, - {url = "https://files.pythonhosted.org/packages/a2/84/dc08d77fbf3d87d3ee27f6a0c6dcce1de5829a64f2eae85a0ecc1f0daa73/scipy-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:41b71f4a3a4cab9d366cd9065b288efc4d4f3c0b37a91a8e0947fb5bd7f31d87", size = 36549682, upload-time = "2026-02-23T00:19:07.67Z"}, - {url = "https://files.pythonhosted.org/packages/bc/98/fe9ae9ffb3b54b62559f52dedaebe204b408db8109a8c66fdd04869e6424/scipy-1.17.1-cp312-cp312-win_arm64.whl", hash = "sha256:f4115102802df98b2b0db3cce5cb9b92572633a1197c77b7553e5203f284a5b3", size = 24547340, upload-time = "2026-02-23T00:19:12.024Z"}, - {url = "https://files.pythonhosted.org/packages/76/27/07ee1b57b65e92645f219b37148a7e7928b82e2b5dbeccecb4dff7c64f0b/scipy-1.17.1-cp313-cp313-macosx_10_14_x86_64.whl", hash = "sha256:5e3c5c011904115f88a39308379c17f91546f77c1667cea98739fe0fccea804c", size = 31590199, upload-time = "2026-02-23T00:19:17.192Z"}, - {url = "https://files.pythonhosted.org/packages/ec/ae/db19f8ab842e9b724bf5dbb7db29302a91f1e55bc4d04b1025d6d605a2c5/scipy-1.17.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:6fac755ca3d2c3edcb22f479fceaa241704111414831ddd3bc6056e18516892f", size = 28154001, upload-time = "2026-02-23T00:19:22.241Z"}, - {url = "https://files.pythonhosted.org/packages/5b/58/3ce96251560107b381cbd6e8413c483bbb1228a6b919fa8652b0d4090e7f/scipy-1.17.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:7ff200bf9d24f2e4d5dc6ee8c3ac64d739d3a89e2326ba68aaf6c4a2b838fd7d", size = 20325719, upload-time = "2026-02-23T00:19:26.329Z"}, - {url = "https://files.pythonhosted.org/packages/b2/83/15087d945e0e4d48ce2377498abf5ad171ae013232ae31d06f336e64c999/scipy-1.17.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:4b400bdc6f79fa02a4d86640310dde87a21fba0c979efff5248908c6f15fad1b", size = 22683595, upload-time = "2026-02-23T00:19:30.304Z"}, - {url = "https://files.pythonhosted.org/packages/b4/e0/e58fbde4a1a594c8be8114eb4aac1a55bcd6587047efc18a61eb1f5c0d30/scipy-1.17.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2b64ca7d4aee0102a97f3ba22124052b4bd2152522355073580bf4845e2550b6", size = 32896429, upload-time = "2026-02-23T00:19:35.536Z"}, - {url = "https://files.pythonhosted.org/packages/f5/5f/f17563f28ff03c7b6799c50d01d5d856a1d55f2676f537ca8d28c7f627cd/scipy-1.17.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:581b2264fc0aa555f3f435a5944da7504ea3a065d7029ad60e7c3d1ae09c5464", size = 35203952, upload-time = "2026-02-23T00:19:42.259Z"}, - {url = "https://files.pythonhosted.org/packages/8d/a5/9afd17de24f657fdfe4df9a3f1ea049b39aef7c06000c13db1530d81ccca/scipy-1.17.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:beeda3d4ae615106d7094f7e7cef6218392e4465cc95d25f900bebabfded0950", size = 34979063, upload-time = "2026-02-23T00:19:47.547Z"}, - {url = "https://files.pythonhosted.org/packages/8b/13/88b1d2384b424bf7c924f2038c1c409f8d88bb2a8d49d097861dd64a57b2/scipy-1.17.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6609bc224e9568f65064cfa72edc0f24ee6655b47575954ec6339534b2798369", size = 37598449, upload-time = "2026-02-23T00:19:53.238Z"}, - {url = "https://files.pythonhosted.org/packages/35/e5/d6d0e51fc888f692a35134336866341c08655d92614f492c6860dc45bb2c/scipy-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:37425bc9175607b0268f493d79a292c39f9d001a357bebb6b88fdfaff13f6448", size = 36510943, upload-time = "2026-02-23T00:20:50.89Z"}, - {url = "https://files.pythonhosted.org/packages/2a/fd/3be73c564e2a01e690e19cc618811540ba5354c67c8680dce3281123fb79/scipy-1.17.1-cp313-cp313-win_arm64.whl", hash = "sha256:5cf36e801231b6a2059bf354720274b7558746f3b1a4efb43fcf557ccd484a87", size = 24545621, upload-time = "2026-02-23T00:20:55.871Z"}, - {url = "https://files.pythonhosted.org/packages/6f/6b/17787db8b8114933a66f9dcc479a8272e4b4da75fe03b0c282f7b0ade8cd/scipy-1.17.1-cp313-cp313t-macosx_10_14_x86_64.whl", hash = "sha256:d59c30000a16d8edc7e64152e30220bfbd724c9bbb08368c054e24c651314f0a", size = 31936708, upload-time = "2026-02-23T00:19:58.694Z"}, - {url = "https://files.pythonhosted.org/packages/38/2e/524405c2b6392765ab1e2b722a41d5da33dc5c7b7278184a8ad29b6cb206/scipy-1.17.1-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:010f4333c96c9bb1a4516269e33cb5917b08ef2166d5556ca2fd9f082a9e6ea0", size = 28570135, upload-time = "2026-02-23T00:20:03.934Z"}, - {url = "https://files.pythonhosted.org/packages/fd/c3/5bd7199f4ea8556c0c8e39f04ccb014ac37d1468e6cfa6a95c6b3562b76e/scipy-1.17.1-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:2ceb2d3e01c5f1d83c4189737a42d9cb2fc38a6eeed225e7515eef71ad301dce", size = 20741977, upload-time = "2026-02-23T00:20:07.935Z"}, - {url = "https://files.pythonhosted.org/packages/d9/b8/8ccd9b766ad14c78386599708eb745f6b44f08400a5fd0ade7cf89b6fc93/scipy-1.17.1-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:844e165636711ef41f80b4103ed234181646b98a53c8f05da12ca5ca289134f6", size = 23029601, upload-time = "2026-02-23T00:20:12.161Z"}, - {url = "https://files.pythonhosted.org/packages/6d/a0/3cb6f4d2fb3e17428ad2880333cac878909ad1a89f678527b5328b93c1d4/scipy-1.17.1-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:158dd96d2207e21c966063e1635b1063cd7787b627b6f07305315dd73d9c679e", size = 33019667, upload-time = "2026-02-23T00:20:17.208Z"}, - {url = "https://files.pythonhosted.org/packages/f3/c3/2d834a5ac7bf3a0c806ad1508efc02dda3c8c61472a56132d7894c312dea/scipy-1.17.1-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:74cbb80d93260fe2ffa334efa24cb8f2f0f622a9b9febf8b483c0b865bfb3475", size = 35264159, upload-time = "2026-02-23T00:20:23.087Z"}, - {url = "https://files.pythonhosted.org/packages/4d/77/d3ed4becfdbd217c52062fafe35a72388d1bd82c2d0ba5ca19d6fcc93e11/scipy-1.17.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:dbc12c9f3d185f5c737d801da555fb74b3dcfa1a50b66a1a93e09190f41fab50", size = 35102771, upload-time = "2026-02-23T00:20:28.636Z"}, - {url = "https://files.pythonhosted.org/packages/bd/12/d19da97efde68ca1ee5538bb261d5d2c062f0c055575128f11a2730e3ac1/scipy-1.17.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:94055a11dfebe37c656e70317e1996dc197e1a15bbcc351bcdd4610e128fe1ca", size = 37665910, upload-time = "2026-02-23T00:20:34.743Z"}, - {url = "https://files.pythonhosted.org/packages/06/1c/1172a88d507a4baaf72c5a09bb6c018fe2ae0ab622e5830b703a46cc9e44/scipy-1.17.1-cp313-cp313t-win_amd64.whl", hash = "sha256:e30bdeaa5deed6bc27b4cc490823cd0347d7dae09119b8803ae576ea0ce52e4c", size = 36562980, upload-time = "2026-02-23T00:20:40.575Z"}, - {url = "https://files.pythonhosted.org/packages/70/b0/eb757336e5a76dfa7911f63252e3b7d1de00935d7705cf772db5b45ec238/scipy-1.17.1-cp313-cp313t-win_arm64.whl", hash = "sha256:a720477885a9d2411f94a93d16f9d89bad0f28ca23c3f8daa521e2dcc3f44d49", size = 24856543, upload-time = "2026-02-23T00:20:45.313Z"} +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7a/97/5a3609c4f8d58b039179648e62dd220f89864f56f7357f5d4f45c29eb2cc/scipy-1.17.1.tar.gz", hash = "sha256:95d8e012d8cb8816c226aef832200b1d45109ed4464303e997c5b13122b297c0", size = 30573822, upload-time = "2026-02-23T00:26:24.851Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/35/48/b992b488d6f299dbe3f11a20b24d3dda3d46f1a635ede1c46b5b17a7b163/scipy-1.17.1-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:35c3a56d2ef83efc372eaec584314bd0ef2e2f0d2adb21c55e6ad5b344c0dcb8", size = 31610954, upload-time = "2026-02-23T00:17:49.855Z" }, + { url = "https://files.pythonhosted.org/packages/b2/02/cf107b01494c19dc100f1d0b7ac3cc08666e96ba2d64db7626066cee895e/scipy-1.17.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:fcb310ddb270a06114bb64bbe53c94926b943f5b7f0842194d585c65eb4edd76", size = 28172662, upload-time = "2026-02-23T00:18:01.64Z" }, + { url = "https://files.pythonhosted.org/packages/cf/a9/599c28631bad314d219cf9ffd40e985b24d603fc8a2f4ccc5ae8419a535b/scipy-1.17.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:cc90d2e9c7e5c7f1a482c9875007c095c3194b1cfedca3c2f3291cdc2bc7c086", size = 20344366, upload-time = "2026-02-23T00:18:12.015Z" }, + { url = "https://files.pythonhosted.org/packages/35/f5/906eda513271c8deb5af284e5ef0206d17a96239af79f9fa0aebfe0e36b4/scipy-1.17.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:c80be5ede8f3f8eded4eff73cc99a25c388ce98e555b17d31da05287015ffa5b", size = 22704017, upload-time = "2026-02-23T00:18:21.502Z" }, + { url = "https://files.pythonhosted.org/packages/da/34/16f10e3042d2f1d6b66e0428308ab52224b6a23049cb2f5c1756f713815f/scipy-1.17.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e19ebea31758fac5893a2ac360fedd00116cbb7628e650842a6691ba7ca28a21", size = 32927842, upload-time = "2026-02-23T00:18:35.367Z" }, + { url = "https://files.pythonhosted.org/packages/01/8e/1e35281b8ab6d5d72ebe9911edcdffa3f36b04ed9d51dec6dd140396e220/scipy-1.17.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:02ae3b274fde71c5e92ac4d54bc06c42d80e399fec704383dcd99b301df37458", size = 35235890, upload-time = "2026-02-23T00:18:49.188Z" }, + { url = "https://files.pythonhosted.org/packages/c5/5c/9d7f4c88bea6e0d5a4f1bc0506a53a00e9fcb198de372bfe4d3652cef482/scipy-1.17.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8a604bae87c6195d8b1045eddece0514d041604b14f2727bbc2b3020172045eb", size = 35003557, upload-time = "2026-02-23T00:18:54.74Z" }, + { url = "https://files.pythonhosted.org/packages/65/94/7698add8f276dbab7a9de9fb6b0e02fc13ee61d51c7c3f85ac28b65e1239/scipy-1.17.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f590cd684941912d10becc07325a3eeb77886fe981415660d9265c4c418d0bea", size = 37625856, upload-time = "2026-02-23T00:19:00.307Z" }, + { url = "https://files.pythonhosted.org/packages/a2/84/dc08d77fbf3d87d3ee27f6a0c6dcce1de5829a64f2eae85a0ecc1f0daa73/scipy-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:41b71f4a3a4cab9d366cd9065b288efc4d4f3c0b37a91a8e0947fb5bd7f31d87", size = 36549682, upload-time = "2026-02-23T00:19:07.67Z" }, + { url = "https://files.pythonhosted.org/packages/bc/98/fe9ae9ffb3b54b62559f52dedaebe204b408db8109a8c66fdd04869e6424/scipy-1.17.1-cp312-cp312-win_arm64.whl", hash = "sha256:f4115102802df98b2b0db3cce5cb9b92572633a1197c77b7553e5203f284a5b3", size = 24547340, upload-time = "2026-02-23T00:19:12.024Z" }, + { url = "https://files.pythonhosted.org/packages/76/27/07ee1b57b65e92645f219b37148a7e7928b82e2b5dbeccecb4dff7c64f0b/scipy-1.17.1-cp313-cp313-macosx_10_14_x86_64.whl", hash = "sha256:5e3c5c011904115f88a39308379c17f91546f77c1667cea98739fe0fccea804c", size = 31590199, upload-time = "2026-02-23T00:19:17.192Z" }, + { url = "https://files.pythonhosted.org/packages/ec/ae/db19f8ab842e9b724bf5dbb7db29302a91f1e55bc4d04b1025d6d605a2c5/scipy-1.17.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:6fac755ca3d2c3edcb22f479fceaa241704111414831ddd3bc6056e18516892f", size = 28154001, upload-time = "2026-02-23T00:19:22.241Z" }, + { url = "https://files.pythonhosted.org/packages/5b/58/3ce96251560107b381cbd6e8413c483bbb1228a6b919fa8652b0d4090e7f/scipy-1.17.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:7ff200bf9d24f2e4d5dc6ee8c3ac64d739d3a89e2326ba68aaf6c4a2b838fd7d", size = 20325719, upload-time = "2026-02-23T00:19:26.329Z" }, + { url = "https://files.pythonhosted.org/packages/b2/83/15087d945e0e4d48ce2377498abf5ad171ae013232ae31d06f336e64c999/scipy-1.17.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:4b400bdc6f79fa02a4d86640310dde87a21fba0c979efff5248908c6f15fad1b", size = 22683595, upload-time = "2026-02-23T00:19:30.304Z" }, + { url = "https://files.pythonhosted.org/packages/b4/e0/e58fbde4a1a594c8be8114eb4aac1a55bcd6587047efc18a61eb1f5c0d30/scipy-1.17.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2b64ca7d4aee0102a97f3ba22124052b4bd2152522355073580bf4845e2550b6", size = 32896429, upload-time = "2026-02-23T00:19:35.536Z" }, + { url = "https://files.pythonhosted.org/packages/f5/5f/f17563f28ff03c7b6799c50d01d5d856a1d55f2676f537ca8d28c7f627cd/scipy-1.17.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:581b2264fc0aa555f3f435a5944da7504ea3a065d7029ad60e7c3d1ae09c5464", size = 35203952, upload-time = "2026-02-23T00:19:42.259Z" }, + { url = "https://files.pythonhosted.org/packages/8d/a5/9afd17de24f657fdfe4df9a3f1ea049b39aef7c06000c13db1530d81ccca/scipy-1.17.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:beeda3d4ae615106d7094f7e7cef6218392e4465cc95d25f900bebabfded0950", size = 34979063, upload-time = "2026-02-23T00:19:47.547Z" }, + { url = "https://files.pythonhosted.org/packages/8b/13/88b1d2384b424bf7c924f2038c1c409f8d88bb2a8d49d097861dd64a57b2/scipy-1.17.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6609bc224e9568f65064cfa72edc0f24ee6655b47575954ec6339534b2798369", size = 37598449, upload-time = "2026-02-23T00:19:53.238Z" }, + { url = "https://files.pythonhosted.org/packages/35/e5/d6d0e51fc888f692a35134336866341c08655d92614f492c6860dc45bb2c/scipy-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:37425bc9175607b0268f493d79a292c39f9d001a357bebb6b88fdfaff13f6448", size = 36510943, upload-time = "2026-02-23T00:20:50.89Z" }, + { url = "https://files.pythonhosted.org/packages/2a/fd/3be73c564e2a01e690e19cc618811540ba5354c67c8680dce3281123fb79/scipy-1.17.1-cp313-cp313-win_arm64.whl", hash = "sha256:5cf36e801231b6a2059bf354720274b7558746f3b1a4efb43fcf557ccd484a87", size = 24545621, upload-time = "2026-02-23T00:20:55.871Z" }, + { url = "https://files.pythonhosted.org/packages/6f/6b/17787db8b8114933a66f9dcc479a8272e4b4da75fe03b0c282f7b0ade8cd/scipy-1.17.1-cp313-cp313t-macosx_10_14_x86_64.whl", hash = "sha256:d59c30000a16d8edc7e64152e30220bfbd724c9bbb08368c054e24c651314f0a", size = 31936708, upload-time = "2026-02-23T00:19:58.694Z" }, + { url = "https://files.pythonhosted.org/packages/38/2e/524405c2b6392765ab1e2b722a41d5da33dc5c7b7278184a8ad29b6cb206/scipy-1.17.1-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:010f4333c96c9bb1a4516269e33cb5917b08ef2166d5556ca2fd9f082a9e6ea0", size = 28570135, upload-time = "2026-02-23T00:20:03.934Z" }, + { url = "https://files.pythonhosted.org/packages/fd/c3/5bd7199f4ea8556c0c8e39f04ccb014ac37d1468e6cfa6a95c6b3562b76e/scipy-1.17.1-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:2ceb2d3e01c5f1d83c4189737a42d9cb2fc38a6eeed225e7515eef71ad301dce", size = 20741977, upload-time = "2026-02-23T00:20:07.935Z" }, + { url = "https://files.pythonhosted.org/packages/d9/b8/8ccd9b766ad14c78386599708eb745f6b44f08400a5fd0ade7cf89b6fc93/scipy-1.17.1-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:844e165636711ef41f80b4103ed234181646b98a53c8f05da12ca5ca289134f6", size = 23029601, upload-time = "2026-02-23T00:20:12.161Z" }, + { url = "https://files.pythonhosted.org/packages/6d/a0/3cb6f4d2fb3e17428ad2880333cac878909ad1a89f678527b5328b93c1d4/scipy-1.17.1-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:158dd96d2207e21c966063e1635b1063cd7787b627b6f07305315dd73d9c679e", size = 33019667, upload-time = "2026-02-23T00:20:17.208Z" }, + { url = "https://files.pythonhosted.org/packages/f3/c3/2d834a5ac7bf3a0c806ad1508efc02dda3c8c61472a56132d7894c312dea/scipy-1.17.1-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:74cbb80d93260fe2ffa334efa24cb8f2f0f622a9b9febf8b483c0b865bfb3475", size = 35264159, upload-time = "2026-02-23T00:20:23.087Z" }, + { url = "https://files.pythonhosted.org/packages/4d/77/d3ed4becfdbd217c52062fafe35a72388d1bd82c2d0ba5ca19d6fcc93e11/scipy-1.17.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:dbc12c9f3d185f5c737d801da555fb74b3dcfa1a50b66a1a93e09190f41fab50", size = 35102771, upload-time = "2026-02-23T00:20:28.636Z" }, + { url = "https://files.pythonhosted.org/packages/bd/12/d19da97efde68ca1ee5538bb261d5d2c062f0c055575128f11a2730e3ac1/scipy-1.17.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:94055a11dfebe37c656e70317e1996dc197e1a15bbcc351bcdd4610e128fe1ca", size = 37665910, upload-time = "2026-02-23T00:20:34.743Z" }, + { url = "https://files.pythonhosted.org/packages/06/1c/1172a88d507a4baaf72c5a09bb6c018fe2ae0ab622e5830b703a46cc9e44/scipy-1.17.1-cp313-cp313t-win_amd64.whl", hash = "sha256:e30bdeaa5deed6bc27b4cc490823cd0347d7dae09119b8803ae576ea0ce52e4c", size = 36562980, upload-time = "2026-02-23T00:20:40.575Z" }, + { url = "https://files.pythonhosted.org/packages/70/b0/eb757336e5a76dfa7911f63252e3b7d1de00935d7705cf772db5b45ec238/scipy-1.17.1-cp313-cp313t-win_arm64.whl", hash = "sha256:a720477885a9d2411f94a93d16f9d89bad0f28ca23c3f8daa521e2dcc3f44d49", size = 24856543, upload-time = "2026-02-23T00:20:45.313Z" }, ] [[package]] name = "snowballstemmer" -sdist = {url = "https://files.pythonhosted.org/packages/75/a7/9810d872919697c9d01295633f5d574fb416d47e535f258272ca1f01f447/snowballstemmer-3.0.1.tar.gz", hash = "sha256:6d5eeeec8e9f84d4d56b847692bacf79bc2c8e90c7f80ca4444ff8b6f2e52895", size = 105575, upload-time = "2025-05-09T16:34:51.843Z"} -source = {registry = "https://pypi.org/simple"} version = "3.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/75/a7/9810d872919697c9d01295633f5d574fb416d47e535f258272ca1f01f447/snowballstemmer-3.0.1.tar.gz", hash = "sha256:6d5eeeec8e9f84d4d56b847692bacf79bc2c8e90c7f80ca4444ff8b6f2e52895", size = 105575, upload-time = "2025-05-09T16:34:51.843Z" } wheels = [ - {url = "https://files.pythonhosted.org/packages/c8/78/3565d011c61f5a43488987ee32b6f3f656e7f107ac2782dd57bdd7d91d9a/snowballstemmer-3.0.1-py3-none-any.whl", hash = "sha256:6cd7b3897da8d6c9ffb968a6781fa6532dce9c3618a4b127d920dab764a19064", size = 103274, upload-time = "2025-05-09T16:34:50.371Z"} + { url = "https://files.pythonhosted.org/packages/c8/78/3565d011c61f5a43488987ee32b6f3f656e7f107ac2782dd57bdd7d91d9a/snowballstemmer-3.0.1-py3-none-any.whl", hash = "sha256:6cd7b3897da8d6c9ffb968a6781fa6532dce9c3618a4b127d920dab764a19064", size = 103274, upload-time = "2025-05-09T16:34:50.371Z" }, ] [[package]] name = "soupsieve" -sdist = {url = "https://files.pythonhosted.org/packages/7b/ae/2d9c981590ed9999a0d91755b47fc74f74de286b0f5cee14c9269041e6c4/soupsieve-2.8.3.tar.gz", hash = "sha256:3267f1eeea4251fb42728b6dfb746edc9acaffc4a45b27e19450b676586e8349", size = 118627, upload-time = "2026-01-20T04:27:02.457Z"} -source = {registry = "https://pypi.org/simple"} version = "2.8.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7b/ae/2d9c981590ed9999a0d91755b47fc74f74de286b0f5cee14c9269041e6c4/soupsieve-2.8.3.tar.gz", hash = "sha256:3267f1eeea4251fb42728b6dfb746edc9acaffc4a45b27e19450b676586e8349", size = 118627, upload-time = "2026-01-20T04:27:02.457Z" } wheels = [ - {url = "https://files.pythonhosted.org/packages/46/2c/1462b1d0a634697ae9e55b3cecdcb64788e8b7d63f54d923fcd0bb140aed/soupsieve-2.8.3-py3-none-any.whl", hash = "sha256:ed64f2ba4eebeab06cc4962affce381647455978ffc1e36bb79a545b91f45a95", size = 37016, upload-time = "2026-01-20T04:27:01.012Z"} + { url = "https://files.pythonhosted.org/packages/46/2c/1462b1d0a634697ae9e55b3cecdcb64788e8b7d63f54d923fcd0bb140aed/soupsieve-2.8.3-py3-none-any.whl", hash = "sha256:ed64f2ba4eebeab06cc4962affce381647455978ffc1e36bb79a545b91f45a95", size = 37016, upload-time = "2026-01-20T04:27:01.012Z" }, ] [[package]] -dependencies = [ - {name = "alabaster"}, - {name = "babel"}, - {name = "colorama", marker = "sys_platform == 'win32'"}, - {name = "docutils"}, - {name = "imagesize"}, - {name = "jinja2"}, - {name = "packaging"}, - {name = "pygments"}, - {name = "requests"}, - {name = "roman-numerals"}, - {name = "snowballstemmer"}, - {name = "sphinxcontrib-applehelp"}, - {name = "sphinxcontrib-devhelp"}, - {name = "sphinxcontrib-htmlhelp"}, - {name = "sphinxcontrib-jsmath"}, - {name = "sphinxcontrib-qthelp"}, - {name = "sphinxcontrib-serializinghtml"} -] name = "sphinx" -sdist = {url = "https://files.pythonhosted.org/packages/cd/bd/f08eb0f4eed5c83f1ba2a3bd18f7745a2b1525fad70660a1c00224ec468a/sphinx-9.1.0.tar.gz", hash = "sha256:7741722357dd75f8190766926071fed3bdc211c74dd2d7d4df5404da95930ddb", size = 8718324, upload-time = "2025-12-31T15:09:27.646Z"} -source = {registry = "https://pypi.org/simple"} version = "9.1.0" -wheels = [ - {url = "https://files.pythonhosted.org/packages/73/f7/b1884cb3188ab181fc81fa00c266699dab600f927a964df02ec3d5d1916a/sphinx-9.1.0-py3-none-any.whl", hash = "sha256:c84fdd4e782504495fe4f2c0b3413d6c2bf388589bb352d439b2a3bb99991978", size = 3921742, upload-time = "2025-12-31T15:09:25.561Z"} +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "alabaster" }, + { name = "babel" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "docutils" }, + { name = "imagesize" }, + { name = "jinja2" }, + { name = "packaging" }, + { name = "pygments" }, + { name = "requests" }, + { name = "roman-numerals" }, + { name = "snowballstemmer" }, + { name = "sphinxcontrib-applehelp" }, + { name = "sphinxcontrib-devhelp" }, + { name = "sphinxcontrib-htmlhelp" }, + { name = "sphinxcontrib-jsmath" }, + { name = "sphinxcontrib-qthelp" }, + { name = "sphinxcontrib-serializinghtml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cd/bd/f08eb0f4eed5c83f1ba2a3bd18f7745a2b1525fad70660a1c00224ec468a/sphinx-9.1.0.tar.gz", hash = "sha256:7741722357dd75f8190766926071fed3bdc211c74dd2d7d4df5404da95930ddb", size = 8718324, upload-time = "2025-12-31T15:09:27.646Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/73/f7/b1884cb3188ab181fc81fa00c266699dab600f927a964df02ec3d5d1916a/sphinx-9.1.0-py3-none-any.whl", hash = "sha256:c84fdd4e782504495fe4f2c0b3413d6c2bf388589bb352d439b2a3bb99991978", size = 3921742, upload-time = "2025-12-31T15:09:25.561Z" }, ] [[package]] -dependencies = [ - {name = "astroid"}, - {name = "jinja2"}, - {name = "pyyaml"}, - {name = "sphinx"} -] name = "sphinx-autoapi" -sdist = {url = "https://files.pythonhosted.org/packages/47/22/7ea4b660e98ffa271dbec5847b64738127955746d887f9596940279d30bf/sphinx_autoapi-3.7.0.tar.gz", hash = "sha256:5c8a934d788f00d11b8c8092536c7373592cce986820de8dd7daf6dfd79afd91", size = 58136, upload-time = "2026-02-11T05:24:29.86Z"} -source = {registry = "https://pypi.org/simple"} version = "3.7.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "astroid" }, + { name = "jinja2" }, + { name = "pyyaml" }, + { name = "sphinx" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/47/22/7ea4b660e98ffa271dbec5847b64738127955746d887f9596940279d30bf/sphinx_autoapi-3.7.0.tar.gz", hash = "sha256:5c8a934d788f00d11b8c8092536c7373592cce986820de8dd7daf6dfd79afd91", size = 58136, upload-time = "2026-02-11T05:24:29.86Z" } wheels = [ - {url = "https://files.pythonhosted.org/packages/d8/11/9825443890d3ef6b6a0db938054e7c806e9911509e0315fe984d00a090c1/sphinx_autoapi-3.7.0-py3-none-any.whl", hash = "sha256:5ea37271b50de08538cf6e33044ef4fb6e981ddb693060ec84f297005014fdfd", size = 36021, upload-time = "2026-02-11T05:24:28.657Z"} + { url = "https://files.pythonhosted.org/packages/d8/11/9825443890d3ef6b6a0db938054e7c806e9911509e0315fe984d00a090c1/sphinx_autoapi-3.7.0-py3-none-any.whl", hash = "sha256:5ea37271b50de08538cf6e33044ef4fb6e981ddb693060ec84f297005014fdfd", size = 36021, upload-time = "2026-02-11T05:24:28.657Z" }, ] [[package]] name = "sphinxcontrib-applehelp" -sdist = {url = "https://files.pythonhosted.org/packages/ba/6e/b837e84a1a704953c62ef8776d45c3e8d759876b4a84fe14eba2859106fe/sphinxcontrib_applehelp-2.0.0.tar.gz", hash = "sha256:2f29ef331735ce958efa4734873f084941970894c6090408b079c61b2e1c06d1", size = 20053, upload-time = "2024-07-29T01:09:00.465Z"} -source = {registry = "https://pypi.org/simple"} version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ba/6e/b837e84a1a704953c62ef8776d45c3e8d759876b4a84fe14eba2859106fe/sphinxcontrib_applehelp-2.0.0.tar.gz", hash = "sha256:2f29ef331735ce958efa4734873f084941970894c6090408b079c61b2e1c06d1", size = 20053, upload-time = "2024-07-29T01:09:00.465Z" } wheels = [ - {url = "https://files.pythonhosted.org/packages/5d/85/9ebeae2f76e9e77b952f4b274c27238156eae7979c5421fba91a28f4970d/sphinxcontrib_applehelp-2.0.0-py3-none-any.whl", hash = "sha256:4cd3f0ec4ac5dd9c17ec65e9ab272c9b867ea77425228e68ecf08d6b28ddbdb5", size = 119300, upload-time = "2024-07-29T01:08:58.99Z"} + { url = "https://files.pythonhosted.org/packages/5d/85/9ebeae2f76e9e77b952f4b274c27238156eae7979c5421fba91a28f4970d/sphinxcontrib_applehelp-2.0.0-py3-none-any.whl", hash = "sha256:4cd3f0ec4ac5dd9c17ec65e9ab272c9b867ea77425228e68ecf08d6b28ddbdb5", size = 119300, upload-time = "2024-07-29T01:08:58.99Z" }, ] [[package]] name = "sphinxcontrib-devhelp" -sdist = {url = "https://files.pythonhosted.org/packages/f6/d2/5beee64d3e4e747f316bae86b55943f51e82bb86ecd325883ef65741e7da/sphinxcontrib_devhelp-2.0.0.tar.gz", hash = "sha256:411f5d96d445d1d73bb5d52133377b4248ec79db5c793ce7dbe59e074b4dd1ad", size = 12967, upload-time = "2024-07-29T01:09:23.417Z"} -source = {registry = "https://pypi.org/simple"} version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/d2/5beee64d3e4e747f316bae86b55943f51e82bb86ecd325883ef65741e7da/sphinxcontrib_devhelp-2.0.0.tar.gz", hash = "sha256:411f5d96d445d1d73bb5d52133377b4248ec79db5c793ce7dbe59e074b4dd1ad", size = 12967, upload-time = "2024-07-29T01:09:23.417Z" } wheels = [ - {url = "https://files.pythonhosted.org/packages/35/7a/987e583882f985fe4d7323774889ec58049171828b58c2217e7f79cdf44e/sphinxcontrib_devhelp-2.0.0-py3-none-any.whl", hash = "sha256:aefb8b83854e4b0998877524d1029fd3e6879210422ee3780459e28a1f03a8a2", size = 82530, upload-time = "2024-07-29T01:09:21.945Z"} + { url = "https://files.pythonhosted.org/packages/35/7a/987e583882f985fe4d7323774889ec58049171828b58c2217e7f79cdf44e/sphinxcontrib_devhelp-2.0.0-py3-none-any.whl", hash = "sha256:aefb8b83854e4b0998877524d1029fd3e6879210422ee3780459e28a1f03a8a2", size = 82530, upload-time = "2024-07-29T01:09:21.945Z" }, ] [[package]] name = "sphinxcontrib-htmlhelp" -sdist = {url = "https://files.pythonhosted.org/packages/43/93/983afd9aa001e5201eab16b5a444ed5b9b0a7a010541e0ddfbbfd0b2470c/sphinxcontrib_htmlhelp-2.1.0.tar.gz", hash = "sha256:c9e2916ace8aad64cc13a0d233ee22317f2b9025b9cf3295249fa985cc7082e9", size = 22617, upload-time = "2024-07-29T01:09:37.889Z"} -source = {registry = "https://pypi.org/simple"} version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/93/983afd9aa001e5201eab16b5a444ed5b9b0a7a010541e0ddfbbfd0b2470c/sphinxcontrib_htmlhelp-2.1.0.tar.gz", hash = "sha256:c9e2916ace8aad64cc13a0d233ee22317f2b9025b9cf3295249fa985cc7082e9", size = 22617, upload-time = "2024-07-29T01:09:37.889Z" } wheels = [ - {url = "https://files.pythonhosted.org/packages/0a/7b/18a8c0bcec9182c05a0b3ec2a776bba4ead82750a55ff798e8d406dae604/sphinxcontrib_htmlhelp-2.1.0-py3-none-any.whl", hash = "sha256:166759820b47002d22914d64a075ce08f4c46818e17cfc9470a9786b759b19f8", size = 98705, upload-time = "2024-07-29T01:09:36.407Z"} + { url = "https://files.pythonhosted.org/packages/0a/7b/18a8c0bcec9182c05a0b3ec2a776bba4ead82750a55ff798e8d406dae604/sphinxcontrib_htmlhelp-2.1.0-py3-none-any.whl", hash = "sha256:166759820b47002d22914d64a075ce08f4c46818e17cfc9470a9786b759b19f8", size = 98705, upload-time = "2024-07-29T01:09:36.407Z" }, ] [[package]] name = "sphinxcontrib-jsmath" -sdist = {url = "https://files.pythonhosted.org/packages/b2/e8/9ed3830aeed71f17c026a07a5097edcf44b692850ef215b161b8ad875729/sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8", size = 5787, upload-time = "2019-01-21T16:10:16.347Z"} -source = {registry = "https://pypi.org/simple"} version = "1.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b2/e8/9ed3830aeed71f17c026a07a5097edcf44b692850ef215b161b8ad875729/sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8", size = 5787, upload-time = "2019-01-21T16:10:16.347Z" } wheels = [ - {url = "https://files.pythonhosted.org/packages/c2/42/4c8646762ee83602e3fb3fbe774c2fac12f317deb0b5dbeeedd2d3ba4b77/sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178", size = 5071, upload-time = "2019-01-21T16:10:14.333Z"} + { url = "https://files.pythonhosted.org/packages/c2/42/4c8646762ee83602e3fb3fbe774c2fac12f317deb0b5dbeeedd2d3ba4b77/sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178", size = 5071, upload-time = "2019-01-21T16:10:14.333Z" }, ] [[package]] name = "sphinxcontrib-qthelp" -sdist = {url = "https://files.pythonhosted.org/packages/68/bc/9104308fc285eb3e0b31b67688235db556cd5b0ef31d96f30e45f2e51cae/sphinxcontrib_qthelp-2.0.0.tar.gz", hash = "sha256:4fe7d0ac8fc171045be623aba3e2a8f613f8682731f9153bb2e40ece16b9bbab", size = 17165, upload-time = "2024-07-29T01:09:56.435Z"} -source = {registry = "https://pypi.org/simple"} version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/68/bc/9104308fc285eb3e0b31b67688235db556cd5b0ef31d96f30e45f2e51cae/sphinxcontrib_qthelp-2.0.0.tar.gz", hash = "sha256:4fe7d0ac8fc171045be623aba3e2a8f613f8682731f9153bb2e40ece16b9bbab", size = 17165, upload-time = "2024-07-29T01:09:56.435Z" } wheels = [ - {url = "https://files.pythonhosted.org/packages/27/83/859ecdd180cacc13b1f7e857abf8582a64552ea7a061057a6c716e790fce/sphinxcontrib_qthelp-2.0.0-py3-none-any.whl", hash = "sha256:b18a828cdba941ccd6ee8445dbe72ffa3ef8cbe7505d8cd1fa0d42d3f2d5f3eb", size = 88743, upload-time = "2024-07-29T01:09:54.885Z"} + { url = "https://files.pythonhosted.org/packages/27/83/859ecdd180cacc13b1f7e857abf8582a64552ea7a061057a6c716e790fce/sphinxcontrib_qthelp-2.0.0-py3-none-any.whl", hash = "sha256:b18a828cdba941ccd6ee8445dbe72ffa3ef8cbe7505d8cd1fa0d42d3f2d5f3eb", size = 88743, upload-time = "2024-07-29T01:09:54.885Z" }, ] [[package]] name = "sphinxcontrib-serializinghtml" -sdist = {url = "https://files.pythonhosted.org/packages/3b/44/6716b257b0aa6bfd51a1b31665d1c205fb12cb5ad56de752dfa15657de2f/sphinxcontrib_serializinghtml-2.0.0.tar.gz", hash = "sha256:e9d912827f872c029017a53f0ef2180b327c3f7fd23c87229f7a8e8b70031d4d", size = 16080, upload-time = "2024-07-29T01:10:09.332Z"} -source = {registry = "https://pypi.org/simple"} version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3b/44/6716b257b0aa6bfd51a1b31665d1c205fb12cb5ad56de752dfa15657de2f/sphinxcontrib_serializinghtml-2.0.0.tar.gz", hash = "sha256:e9d912827f872c029017a53f0ef2180b327c3f7fd23c87229f7a8e8b70031d4d", size = 16080, upload-time = "2024-07-29T01:10:09.332Z" } wheels = [ - {url = "https://files.pythonhosted.org/packages/52/a7/d2782e4e3f77c8450f727ba74a8f12756d5ba823d81b941f1b04da9d033a/sphinxcontrib_serializinghtml-2.0.0-py3-none-any.whl", hash = "sha256:6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331", size = 92072, upload-time = "2024-07-29T01:10:08.203Z"} + { url = "https://files.pythonhosted.org/packages/52/a7/d2782e4e3f77c8450f727ba74a8f12756d5ba823d81b941f1b04da9d033a/sphinxcontrib_serializinghtml-2.0.0-py3-none-any.whl", hash = "sha256:6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331", size = 92072, upload-time = "2024-07-29T01:10:08.203Z" }, ] [[package]] name = "types-pyyaml" -sdist = {url = "https://files.pythonhosted.org/packages/7e/69/3c51b36d04da19b92f9e815be12753125bd8bc247ba0470a982e6979e71c/types_pyyaml-6.0.12.20250915.tar.gz", hash = "sha256:0f8b54a528c303f0e6f7165687dd33fafa81c807fcac23f632b63aa624ced1d3", size = 17522, upload-time = "2025-09-15T03:01:00.728Z"} -source = {registry = "https://pypi.org/simple"} version = "6.0.12.20250915" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7e/69/3c51b36d04da19b92f9e815be12753125bd8bc247ba0470a982e6979e71c/types_pyyaml-6.0.12.20250915.tar.gz", hash = "sha256:0f8b54a528c303f0e6f7165687dd33fafa81c807fcac23f632b63aa624ced1d3", size = 17522, upload-time = "2025-09-15T03:01:00.728Z" } wheels = [ - {url = "https://files.pythonhosted.org/packages/bd/e0/1eed384f02555dde685fff1a1ac805c1c7dcb6dd019c916fe659b1c1f9ec/types_pyyaml-6.0.12.20250915-py3-none-any.whl", hash = "sha256:e7d4d9e064e89a3b3cae120b4990cd370874d2bf12fa5f46c97018dd5d3c9ab6", size = 20338, upload-time = "2025-09-15T03:00:59.218Z"} + { url = "https://files.pythonhosted.org/packages/bd/e0/1eed384f02555dde685fff1a1ac805c1c7dcb6dd019c916fe659b1c1f9ec/types_pyyaml-6.0.12.20250915-py3-none-any.whl", hash = "sha256:e7d4d9e064e89a3b3cae120b4990cd370874d2bf12fa5f46c97018dd5d3c9ab6", size = 20338, upload-time = "2025-09-15T03:00:59.218Z" }, ] [[package]] name = "typing-extensions" -sdist = {url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z"} -source = {registry = "https://pypi.org/simple"} version = "4.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } wheels = [ - {url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z"} + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, ] [[package]] name = "urllib3" -sdist = {url = "https://files.pythonhosted.org/packages/c7/24/5f1b3bdffd70275f6661c76461e25f024d5a38a46f04aaca912426a2b1d3/urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed", size = 435556, upload-time = "2026-01-07T16:24:43.925Z"} -source = {registry = "https://pypi.org/simple"} version = "2.6.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c7/24/5f1b3bdffd70275f6661c76461e25f024d5a38a46f04aaca912426a2b1d3/urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed", size = 435556, upload-time = "2026-01-07T16:24:43.925Z" } wheels = [ - {url = "https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4", size = 131584, upload-time = "2026-01-07T16:24:42.685Z"} + { url = "https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4", size = 131584, upload-time = "2026-01-07T16:24:42.685Z" }, ] [[package]] -dependencies = [ - {name = "distlib"}, - {name = "filelock"}, - {name = "platformdirs"}, - {name = "python-discovery"} -] name = "virtualenv" -sdist = {url = "https://files.pythonhosted.org/packages/2f/c9/18d4b36606d6091844daa3bd93cf7dc78e6f5da21d9f21d06c221104b684/virtualenv-21.1.0.tar.gz", hash = "sha256:1990a0188c8f16b6b9cf65c9183049007375b26aad415514d377ccacf1e4fb44", size = 5840471, upload-time = "2026-02-27T08:49:29.702Z"} -source = {registry = "https://pypi.org/simple"} version = "21.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "distlib" }, + { name = "filelock" }, + { name = "platformdirs" }, + { name = "python-discovery" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2f/c9/18d4b36606d6091844daa3bd93cf7dc78e6f5da21d9f21d06c221104b684/virtualenv-21.1.0.tar.gz", hash = "sha256:1990a0188c8f16b6b9cf65c9183049007375b26aad415514d377ccacf1e4fb44", size = 5840471, upload-time = "2026-02-27T08:49:29.702Z" } wheels = [ - {url = "https://files.pythonhosted.org/packages/78/55/896b06bf93a49bec0f4ae2a6f1ed12bd05c8860744ac3a70eda041064e4d/virtualenv-21.1.0-py3-none-any.whl", hash = "sha256:164f5e14c5587d170cf98e60378eb91ea35bf037be313811905d3a24ea33cc07", size = 5825072, upload-time = "2026-02-27T08:49:27.516Z"} + { url = "https://files.pythonhosted.org/packages/78/55/896b06bf93a49bec0f4ae2a6f1ed12bd05c8860744ac3a70eda041064e4d/virtualenv-21.1.0-py3-none-any.whl", hash = "sha256:164f5e14c5587d170cf98e60378eb91ea35bf037be313811905d3a24ea33cc07", size = 5825072, upload-time = "2026-02-27T08:49:27.516Z" }, ] From 200cca4eead6b59c3498f76628170b79a693a6e7 Mon Sep 17 00:00:00 2001 From: Alex Liberzon Date: Sun, 15 Mar 2026 10:53:09 +0200 Subject: [PATCH 23/27] pre-commit also toml and lock --- pyproject.toml | 24 +- uv.lock | 1186 ++++++++++++++++++++++++------------------------ 2 files changed, 605 insertions(+), 605 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 14aa1e2..1a8218b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -28,6 +28,18 @@ readme = "README.md" requires-python = ">=3.12,<3.14" [project.optional-dependencies] +dev = [ + "mypy>=1.19.1", + "myst-parser>=5.0.0", + "optv>=0.3.2", + "pre-commit>=4.5.1", + "pydata-sphinx-theme>=0.16.1", + "pytest>=9.0.2", + "pytest-cov>=7.0.0", + "sphinx>=9.1.0", + "sphinx-autoapi>=3.7.0", + "types-pyyaml>=6.0.12.20250915" +] docs = [ "myst-parser>=5.0.0", "pydata-sphinx-theme>=0.16.1", @@ -41,18 +53,6 @@ test = [ "pytest>=9.0.2", "pytest-cov>=7.0.0" ] -dev = [ - "mypy>=1.19.1", - "myst-parser>=5.0.0", - "optv>=0.3.2", - "pre-commit>=4.5.1", - "pydata-sphinx-theme>=0.16.1", - "pytest>=9.0.2", - "pytest-cov>=7.0.0", - "sphinx>=9.1.0", - "sphinx-autoapi>=3.7.0", - "types-pyyaml>=6.0.12.20250915" -] [tool.coverage.run] branch = true diff --git a/uv.lock b/uv.lock index 10f575c..a96057f 100644 --- a/uv.lock +++ b/uv.lock @@ -1,954 +1,954 @@ -version = 1 -revision = 3 requires-python = ">=3.12, <3.14" +revision = 3 +version = 1 [[package]] -name = "accessible-pygments" -version = "0.0.5" -source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pygments" }, + {name = "pygments"} ] -sdist = { url = "https://files.pythonhosted.org/packages/bc/c1/bbac6a50d02774f91572938964c582fff4270eee73ab822a4aeea4d8b11b/accessible_pygments-0.0.5.tar.gz", hash = "sha256:40918d3e6a2b619ad424cb91e556bd3bd8865443d9f22f1dcdf79e33c8046872", size = 1377899, upload-time = "2024-05-10T11:23:10.216Z" } +name = "accessible-pygments" +sdist = {url = "https://files.pythonhosted.org/packages/bc/c1/bbac6a50d02774f91572938964c582fff4270eee73ab822a4aeea4d8b11b/accessible_pygments-0.0.5.tar.gz", hash = "sha256:40918d3e6a2b619ad424cb91e556bd3bd8865443d9f22f1dcdf79e33c8046872", size = 1377899, upload-time = "2024-05-10T11:23:10.216Z"} +source = {registry = "https://pypi.org/simple"} +version = "0.0.5" wheels = [ - { url = "https://files.pythonhosted.org/packages/8d/3f/95338030883d8c8b91223b4e21744b04d11b161a3ef117295d8241f50ab4/accessible_pygments-0.0.5-py3-none-any.whl", hash = "sha256:88ae3211e68a1d0b011504b2ffc1691feafce124b845bd072ab6f9f66f34d4b7", size = 1395903, upload-time = "2024-05-10T11:23:08.421Z" }, + {url = "https://files.pythonhosted.org/packages/8d/3f/95338030883d8c8b91223b4e21744b04d11b161a3ef117295d8241f50ab4/accessible_pygments-0.0.5-py3-none-any.whl", hash = "sha256:88ae3211e68a1d0b011504b2ffc1691feafce124b845bd072ab6f9f66f34d4b7", size = 1395903, upload-time = "2024-05-10T11:23:08.421Z"} ] [[package]] name = "alabaster" +sdist = {url = "https://files.pythonhosted.org/packages/a6/f8/d9c74d0daf3f742840fd818d69cfae176fa332022fd44e3469487d5a9420/alabaster-1.0.0.tar.gz", hash = "sha256:c00dca57bca26fa62a6d7d0a9fcce65f3e026e9bfe33e9c538fd3fbb2144fd9e", size = 24210, upload-time = "2024-07-26T18:15:03.762Z"} +source = {registry = "https://pypi.org/simple"} version = "1.0.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a6/f8/d9c74d0daf3f742840fd818d69cfae176fa332022fd44e3469487d5a9420/alabaster-1.0.0.tar.gz", hash = "sha256:c00dca57bca26fa62a6d7d0a9fcce65f3e026e9bfe33e9c538fd3fbb2144fd9e", size = 24210, upload-time = "2024-07-26T18:15:03.762Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7e/b3/6b4067be973ae96ba0d615946e314c5ae35f9f993eca561b356540bb0c2b/alabaster-1.0.0-py3-none-any.whl", hash = "sha256:fc6786402dc3fcb2de3cabd5fe455a2db534b371124f1f21de8731783dec828b", size = 13929, upload-time = "2024-07-26T18:15:02.05Z" }, + {url = "https://files.pythonhosted.org/packages/7e/b3/6b4067be973ae96ba0d615946e314c5ae35f9f993eca561b356540bb0c2b/alabaster-1.0.0-py3-none-any.whl", hash = "sha256:fc6786402dc3fcb2de3cabd5fe455a2db534b371124f1f21de8731783dec828b", size = 13929, upload-time = "2024-07-26T18:15:02.05Z"} ] [[package]] name = "astroid" +sdist = {url = "https://files.pythonhosted.org/packages/a2/4c/569eefb533ce71bc9f4f12a4a0d7f0ba27e500681dec1312d4657e849d20/astroid-4.1.1.tar.gz", hash = "sha256:445d831fe785df8c670bbb46b900b8424b82f85b4af187103f71a63a63ebed43", size = 412522, upload-time = "2026-02-23T02:36:30.315Z"} +source = {registry = "https://pypi.org/simple"} version = "4.1.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a2/4c/569eefb533ce71bc9f4f12a4a0d7f0ba27e500681dec1312d4657e849d20/astroid-4.1.1.tar.gz", hash = "sha256:445d831fe785df8c670bbb46b900b8424b82f85b4af187103f71a63a63ebed43", size = 412522, upload-time = "2026-02-23T02:36:30.315Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/32/53/bac4724684064bfee95ece0bb6caf3887e509006845e25388a12cac26d0c/astroid-4.1.1-py3-none-any.whl", hash = "sha256:6b28522096f7e7a36ffcf3be60e77de15e2411ab3a713184beac33fb8f20c0c9", size = 279273, upload-time = "2026-02-23T02:36:28.676Z" }, + {url = "https://files.pythonhosted.org/packages/32/53/bac4724684064bfee95ece0bb6caf3887e509006845e25388a12cac26d0c/astroid-4.1.1-py3-none-any.whl", hash = "sha256:6b28522096f7e7a36ffcf3be60e77de15e2411ab3a713184beac33fb8f20c0c9", size = 279273, upload-time = "2026-02-23T02:36:28.676Z"} ] [[package]] name = "babel" +sdist = {url = "https://files.pythonhosted.org/packages/7d/b2/51899539b6ceeeb420d40ed3cd4b7a40519404f9baf3d4ac99dc413a834b/babel-2.18.0.tar.gz", hash = "sha256:b80b99a14bd085fcacfa15c9165f651fbb3406e66cc603abf11c5750937c992d", size = 9959554, upload-time = "2026-02-01T12:30:56.078Z"} +source = {registry = "https://pypi.org/simple"} version = "2.18.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7d/b2/51899539b6ceeeb420d40ed3cd4b7a40519404f9baf3d4ac99dc413a834b/babel-2.18.0.tar.gz", hash = "sha256:b80b99a14bd085fcacfa15c9165f651fbb3406e66cc603abf11c5750937c992d", size = 9959554, upload-time = "2026-02-01T12:30:56.078Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl", hash = "sha256:e2b422b277c2b9a9630c1d7903c2a00d0830c409c59ac8cae9081c92f1aeba35", size = 10196845, upload-time = "2026-02-01T12:30:53.445Z" }, + {url = "https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl", hash = "sha256:e2b422b277c2b9a9630c1d7903c2a00d0830c409c59ac8cae9081c92f1aeba35", size = 10196845, upload-time = "2026-02-01T12:30:53.445Z"} ] [[package]] -name = "beautifulsoup4" -version = "4.14.3" -source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "soupsieve" }, - { name = "typing-extensions" }, + {name = "soupsieve"}, + {name = "typing-extensions"} ] -sdist = { url = "https://files.pythonhosted.org/packages/c3/b0/1c6a16426d389813b48d95e26898aff79abbde42ad353958ad95cc8c9b21/beautifulsoup4-4.14.3.tar.gz", hash = "sha256:6292b1c5186d356bba669ef9f7f051757099565ad9ada5dd630bd9de5fa7fb86", size = 627737, upload-time = "2025-11-30T15:08:26.084Z" } +name = "beautifulsoup4" +sdist = {url = "https://files.pythonhosted.org/packages/c3/b0/1c6a16426d389813b48d95e26898aff79abbde42ad353958ad95cc8c9b21/beautifulsoup4-4.14.3.tar.gz", hash = "sha256:6292b1c5186d356bba669ef9f7f051757099565ad9ada5dd630bd9de5fa7fb86", size = 627737, upload-time = "2025-11-30T15:08:26.084Z"} +source = {registry = "https://pypi.org/simple"} +version = "4.14.3" wheels = [ - { url = "https://files.pythonhosted.org/packages/1a/39/47f9197bdd44df24d67ac8893641e16f386c984a0619ef2ee4c51fbbc019/beautifulsoup4-4.14.3-py3-none-any.whl", hash = "sha256:0918bfe44902e6ad8d57732ba310582e98da931428d231a5ecb9e7c703a735bb", size = 107721, upload-time = "2025-11-30T15:08:24.087Z" }, + {url = "https://files.pythonhosted.org/packages/1a/39/47f9197bdd44df24d67ac8893641e16f386c984a0619ef2ee4c51fbbc019/beautifulsoup4-4.14.3-py3-none-any.whl", hash = "sha256:0918bfe44902e6ad8d57732ba310582e98da931428d231a5ecb9e7c703a735bb", size = 107721, upload-time = "2025-11-30T15:08:24.087Z"} ] [[package]] name = "certifi" +sdist = {url = "https://files.pythonhosted.org/packages/af/2d/7bf41579a8986e348fa033a31cdd0e4121114f6bce2457e8876010b092dd/certifi-2026.2.25.tar.gz", hash = "sha256:e887ab5cee78ea814d3472169153c2d12cd43b14bd03329a39a9c6e2e80bfba7", size = 155029, upload-time = "2026-02-25T02:54:17.342Z"} +source = {registry = "https://pypi.org/simple"} version = "2026.2.25" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/af/2d/7bf41579a8986e348fa033a31cdd0e4121114f6bce2457e8876010b092dd/certifi-2026.2.25.tar.gz", hash = "sha256:e887ab5cee78ea814d3472169153c2d12cd43b14bd03329a39a9c6e2e80bfba7", size = 155029, upload-time = "2026-02-25T02:54:17.342Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9a/3c/c17fb3ca2d9c3acff52e30b309f538586f9f5b9c9cf454f3845fc9af4881/certifi-2026.2.25-py3-none-any.whl", hash = "sha256:027692e4402ad994f1c42e52a4997a9763c646b73e4096e4d5d6db8af1d6f0fa", size = 153684, upload-time = "2026-02-25T02:54:15.766Z" }, + {url = "https://files.pythonhosted.org/packages/9a/3c/c17fb3ca2d9c3acff52e30b309f538586f9f5b9c9cf454f3845fc9af4881/certifi-2026.2.25-py3-none-any.whl", hash = "sha256:027692e4402ad994f1c42e52a4997a9763c646b73e4096e4d5d6db8af1d6f0fa", size = 153684, upload-time = "2026-02-25T02:54:15.766Z"} ] [[package]] name = "cfgv" +sdist = {url = "https://files.pythonhosted.org/packages/4e/b5/721b8799b04bf9afe054a3899c6cf4e880fcf8563cc71c15610242490a0c/cfgv-3.5.0.tar.gz", hash = "sha256:d5b1034354820651caa73ede66a6294d6e95c1b00acc5e9b098e917404669132", size = 7334, upload-time = "2025-11-19T20:55:51.612Z"} +source = {registry = "https://pypi.org/simple"} version = "3.5.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/4e/b5/721b8799b04bf9afe054a3899c6cf4e880fcf8563cc71c15610242490a0c/cfgv-3.5.0.tar.gz", hash = "sha256:d5b1034354820651caa73ede66a6294d6e95c1b00acc5e9b098e917404669132", size = 7334, upload-time = "2025-11-19T20:55:51.612Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl", hash = "sha256:a8dc6b26ad22ff227d2634a65cb388215ce6cc96bbcc5cfde7641ae87e8dacc0", size = 7445, upload-time = "2025-11-19T20:55:50.744Z" }, + {url = "https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl", hash = "sha256:a8dc6b26ad22ff227d2634a65cb388215ce6cc96bbcc5cfde7641ae87e8dacc0", size = 7445, upload-time = "2025-11-19T20:55:50.744Z"} ] [[package]] name = "charset-normalizer" +sdist = {url = "https://files.pythonhosted.org/packages/1d/35/02daf95b9cd686320bb622eb148792655c9412dbb9b67abb5694e5910a24/charset_normalizer-3.4.5.tar.gz", hash = "sha256:95adae7b6c42a6c5b5b559b1a99149f090a57128155daeea91732c8d970d8644", size = 134804, upload-time = "2026-03-06T06:03:19.46Z"} +source = {registry = "https://pypi.org/simple"} version = "3.4.5" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1d/35/02daf95b9cd686320bb622eb148792655c9412dbb9b67abb5694e5910a24/charset_normalizer-3.4.5.tar.gz", hash = "sha256:95adae7b6c42a6c5b5b559b1a99149f090a57128155daeea91732c8d970d8644", size = 134804, upload-time = "2026-03-06T06:03:19.46Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9c/b6/9ee9c1a608916ca5feae81a344dffbaa53b26b90be58cc2159e3332d44ec/charset_normalizer-3.4.5-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ed97c282ee4f994ef814042423a529df9497e3c666dca19be1d4cd1129dc7ade", size = 280976, upload-time = "2026-03-06T06:01:15.276Z" }, - { url = "https://files.pythonhosted.org/packages/f8/d8/a54f7c0b96f1df3563e9190f04daf981e365a9b397eedfdfb5dbef7e5c6c/charset_normalizer-3.4.5-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0294916d6ccf2d069727d65973c3a1ca477d68708db25fd758dd28b0827cff54", size = 189356, upload-time = "2026-03-06T06:01:16.511Z" }, - { url = "https://files.pythonhosted.org/packages/42/69/2bf7f76ce1446759a5787cb87d38f6a61eb47dbbdf035cfebf6347292a65/charset_normalizer-3.4.5-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:dc57a0baa3eeedd99fafaef7511b5a6ef4581494e8168ee086031744e2679467", size = 206369, upload-time = "2026-03-06T06:01:17.853Z" }, - { url = "https://files.pythonhosted.org/packages/10/9c/949d1a46dab56b959d9a87272482195f1840b515a3380e39986989a893ae/charset_normalizer-3.4.5-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ed1a9a204f317ef879b32f9af507d47e49cd5e7f8e8d5d96358c98373314fc60", size = 203285, upload-time = "2026-03-06T06:01:19.473Z" }, - { url = "https://files.pythonhosted.org/packages/67/5c/ae30362a88b4da237d71ea214a8c7eb915db3eec941adda511729ac25fa2/charset_normalizer-3.4.5-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7ad83b8f9379176c841f8865884f3514d905bcd2a9a3b210eaa446e7d2223e4d", size = 196274, upload-time = "2026-03-06T06:01:20.728Z" }, - { url = "https://files.pythonhosted.org/packages/b2/07/c9f2cb0e46cb6d64fdcc4f95953747b843bb2181bda678dc4e699b8f0f9a/charset_normalizer-3.4.5-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:a118e2e0b5ae6b0120d5efa5f866e58f2bb826067a646431da4d6a2bdae7950e", size = 184715, upload-time = "2026-03-06T06:01:22.194Z" }, - { url = "https://files.pythonhosted.org/packages/36/64/6b0ca95c44fddf692cd06d642b28f63009d0ce325fad6e9b2b4d0ef86a52/charset_normalizer-3.4.5-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:754f96058e61a5e22e91483f823e07df16416ce76afa4ebf306f8e1d1296d43f", size = 193426, upload-time = "2026-03-06T06:01:23.795Z" }, - { url = "https://files.pythonhosted.org/packages/50/bc/a730690d726403743795ca3f5bb2baf67838c5fea78236098f324b965e40/charset_normalizer-3.4.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0c300cefd9b0970381a46394902cd18eaf2aa00163f999590ace991989dcd0fc", size = 191780, upload-time = "2026-03-06T06:01:25.053Z" }, - { url = "https://files.pythonhosted.org/packages/97/4f/6c0bc9af68222b22951552d73df4532b5be6447cee32d58e7e8c74ecbb7b/charset_normalizer-3.4.5-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:c108f8619e504140569ee7de3f97d234f0fbae338a7f9f360455071ef9855a95", size = 185805, upload-time = "2026-03-06T06:01:26.294Z" }, - { url = "https://files.pythonhosted.org/packages/dd/b9/a523fb9b0ee90814b503452b2600e4cbc118cd68714d57041564886e7325/charset_normalizer-3.4.5-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:d1028de43596a315e2720a9849ee79007ab742c06ad8b45a50db8cdb7ed4a82a", size = 208342, upload-time = "2026-03-06T06:01:27.55Z" }, - { url = "https://files.pythonhosted.org/packages/4d/61/c59e761dee4464050713e50e27b58266cc8e209e518c0b378c1580c959ba/charset_normalizer-3.4.5-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:19092dde50335accf365cce21998a1c6dd8eafd42c7b226eb54b2747cdce2fac", size = 193661, upload-time = "2026-03-06T06:01:29.051Z" }, - { url = "https://files.pythonhosted.org/packages/1c/43/729fa30aad69783f755c5ad8649da17ee095311ca42024742701e202dc59/charset_normalizer-3.4.5-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:4354e401eb6dab9aed3c7b4030514328a6c748d05e1c3e19175008ca7de84fb1", size = 204819, upload-time = "2026-03-06T06:01:30.298Z" }, - { url = "https://files.pythonhosted.org/packages/87/33/d9b442ce5a91b96fc0840455a9e49a611bbadae6122778d0a6a79683dd31/charset_normalizer-3.4.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a68766a3c58fde7f9aaa22b3786276f62ab2f594efb02d0a1421b6282e852e98", size = 198080, upload-time = "2026-03-06T06:01:31.478Z" }, - { url = "https://files.pythonhosted.org/packages/56/5a/b8b5a23134978ee9885cee2d6995f4c27cc41f9baded0a9685eabc5338f0/charset_normalizer-3.4.5-cp312-cp312-win32.whl", hash = "sha256:1827734a5b308b65ac54e86a618de66f935a4f63a8a462ff1e19a6788d6c2262", size = 132630, upload-time = "2026-03-06T06:01:33.056Z" }, - { url = "https://files.pythonhosted.org/packages/70/53/e44a4c07e8904500aec95865dc3f6464dc3586a039ef0df606eb3ac38e35/charset_normalizer-3.4.5-cp312-cp312-win_amd64.whl", hash = "sha256:728c6a963dfab66ef865f49286e45239384249672cd598576765acc2a640a636", size = 142856, upload-time = "2026-03-06T06:01:34.489Z" }, - { url = "https://files.pythonhosted.org/packages/ea/aa/c5628f7cad591b1cf45790b7a61483c3e36cf41349c98af7813c483fd6e8/charset_normalizer-3.4.5-cp312-cp312-win_arm64.whl", hash = "sha256:75dfd1afe0b1647449e852f4fb428195a7ed0588947218f7ba929f6538487f02", size = 132982, upload-time = "2026-03-06T06:01:35.641Z" }, - { url = "https://files.pythonhosted.org/packages/f5/48/9f34ec4bb24aa3fdba1890c1bddb97c8a4be1bd84ef5c42ac2352563ad05/charset_normalizer-3.4.5-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ac59c15e3f1465f722607800c68713f9fbc2f672b9eb649fe831da4019ae9b23", size = 280788, upload-time = "2026-03-06T06:01:37.126Z" }, - { url = "https://files.pythonhosted.org/packages/0e/09/6003e7ffeb90cc0560da893e3208396a44c210c5ee42efff539639def59b/charset_normalizer-3.4.5-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:165c7b21d19365464e8f70e5ce5e12524c58b48c78c1f5a57524603c1ab003f8", size = 188890, upload-time = "2026-03-06T06:01:38.73Z" }, - { url = "https://files.pythonhosted.org/packages/42/1e/02706edf19e390680daa694d17e2b8eab4b5f7ac285e2a51168b4b22ee6b/charset_normalizer-3.4.5-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:28269983f25a4da0425743d0d257a2d6921ea7d9b83599d4039486ec5b9f911d", size = 206136, upload-time = "2026-03-06T06:01:40.016Z" }, - { url = "https://files.pythonhosted.org/packages/c7/87/942c3def1b37baf3cf786bad01249190f3ca3d5e63a84f831e704977de1f/charset_normalizer-3.4.5-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d27ce22ec453564770d29d03a9506d449efbb9fa13c00842262b2f6801c48cce", size = 202551, upload-time = "2026-03-06T06:01:41.522Z" }, - { url = "https://files.pythonhosted.org/packages/94/0a/af49691938dfe175d71b8a929bd7e4ace2809c0c5134e28bc535660d5262/charset_normalizer-3.4.5-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0625665e4ebdddb553ab185de5db7054393af8879fb0c87bd5690d14379d6819", size = 195572, upload-time = "2026-03-06T06:01:43.208Z" }, - { url = "https://files.pythonhosted.org/packages/20/ea/dfb1792a8050a8e694cfbde1570ff97ff74e48afd874152d38163d1df9ae/charset_normalizer-3.4.5-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:c23eb3263356d94858655b3e63f85ac5d50970c6e8febcdde7830209139cc37d", size = 184438, upload-time = "2026-03-06T06:01:44.755Z" }, - { url = "https://files.pythonhosted.org/packages/72/12/c281e2067466e3ddd0595bfaea58a6946765ace5c72dfa3edc2f5f118026/charset_normalizer-3.4.5-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e6302ca4ae283deb0af68d2fbf467474b8b6aedcd3dab4db187e07f94c109763", size = 193035, upload-time = "2026-03-06T06:01:46.051Z" }, - { url = "https://files.pythonhosted.org/packages/ba/4f/3792c056e7708e10464bad0438a44708886fb8f92e3c3d29ec5e2d964d42/charset_normalizer-3.4.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e51ae7d81c825761d941962450f50d041db028b7278e7b08930b4541b3e45cb9", size = 191340, upload-time = "2026-03-06T06:01:47.547Z" }, - { url = "https://files.pythonhosted.org/packages/e7/86/80ddba897127b5c7a9bccc481b0cd36c8fefa485d113262f0fe4332f0bf4/charset_normalizer-3.4.5-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:597d10dec876923e5c59e48dbd366e852eacb2b806029491d307daea6b917d7c", size = 185464, upload-time = "2026-03-06T06:01:48.764Z" }, - { url = "https://files.pythonhosted.org/packages/4d/00/b5eff85ba198faacab83e0e4b6f0648155f072278e3b392a82478f8b988b/charset_normalizer-3.4.5-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:5cffde4032a197bd3b42fd0b9509ec60fb70918d6970e4cc773f20fc9180ca67", size = 208014, upload-time = "2026-03-06T06:01:50.371Z" }, - { url = "https://files.pythonhosted.org/packages/c8/11/d36f70be01597fd30850dde8a1269ebc8efadd23ba5785808454f2389bde/charset_normalizer-3.4.5-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:2da4eedcb6338e2321e831a0165759c0c620e37f8cd044a263ff67493be8ffb3", size = 193297, upload-time = "2026-03-06T06:01:51.933Z" }, - { url = "https://files.pythonhosted.org/packages/1a/1d/259eb0a53d4910536c7c2abb9cb25f4153548efb42800c6a9456764649c0/charset_normalizer-3.4.5-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:65a126fb4b070d05340a84fc709dd9e7c75d9b063b610ece8a60197a291d0adf", size = 204321, upload-time = "2026-03-06T06:01:53.887Z" }, - { url = "https://files.pythonhosted.org/packages/84/31/faa6c5b9d3688715e1ed1bb9d124c384fe2fc1633a409e503ffe1c6398c1/charset_normalizer-3.4.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c7a80a9242963416bd81f99349d5f3fce1843c303bd404f204918b6d75a75fd6", size = 197509, upload-time = "2026-03-06T06:01:56.439Z" }, - { url = "https://files.pythonhosted.org/packages/fd/a5/c7d9dd1503ffc08950b3260f5d39ec2366dd08254f0900ecbcf3a6197c7c/charset_normalizer-3.4.5-cp313-cp313-win32.whl", hash = "sha256:f1d725b754e967e648046f00c4facc42d414840f5ccc670c5670f59f83693e4f", size = 132284, upload-time = "2026-03-06T06:01:57.812Z" }, - { url = "https://files.pythonhosted.org/packages/b9/0f/57072b253af40c8aa6636e6de7d75985624c1eb392815b2f934199340a89/charset_normalizer-3.4.5-cp313-cp313-win_amd64.whl", hash = "sha256:e37bd100d2c5d3ba35db9c7c5ba5a9228cbcffe5c4778dc824b164e5257813d7", size = 142630, upload-time = "2026-03-06T06:01:59.062Z" }, - { url = "https://files.pythonhosted.org/packages/31/41/1c4b7cc9f13bd9d369ce3bc993e13d374ce25fa38a2663644283ecf422c1/charset_normalizer-3.4.5-cp313-cp313-win_arm64.whl", hash = "sha256:93b3b2cc5cf1b8743660ce77a4f45f3f6d1172068207c1defc779a36eea6bb36", size = 133254, upload-time = "2026-03-06T06:02:00.281Z" }, - { url = "https://files.pythonhosted.org/packages/c5/60/3a621758945513adfd4db86827a5bafcc615f913dbd0b4c2ed64a65731be/charset_normalizer-3.4.5-py3-none-any.whl", hash = "sha256:9db5e3fcdcee89a78c04dffb3fe33c79f77bd741a624946db2591c81b2fc85b0", size = 55455, upload-time = "2026-03-06T06:03:17.827Z" }, +wheels = [ + {url = "https://files.pythonhosted.org/packages/9c/b6/9ee9c1a608916ca5feae81a344dffbaa53b26b90be58cc2159e3332d44ec/charset_normalizer-3.4.5-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ed97c282ee4f994ef814042423a529df9497e3c666dca19be1d4cd1129dc7ade", size = 280976, upload-time = "2026-03-06T06:01:15.276Z"}, + {url = "https://files.pythonhosted.org/packages/f8/d8/a54f7c0b96f1df3563e9190f04daf981e365a9b397eedfdfb5dbef7e5c6c/charset_normalizer-3.4.5-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0294916d6ccf2d069727d65973c3a1ca477d68708db25fd758dd28b0827cff54", size = 189356, upload-time = "2026-03-06T06:01:16.511Z"}, + {url = "https://files.pythonhosted.org/packages/42/69/2bf7f76ce1446759a5787cb87d38f6a61eb47dbbdf035cfebf6347292a65/charset_normalizer-3.4.5-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:dc57a0baa3eeedd99fafaef7511b5a6ef4581494e8168ee086031744e2679467", size = 206369, upload-time = "2026-03-06T06:01:17.853Z"}, + {url = "https://files.pythonhosted.org/packages/10/9c/949d1a46dab56b959d9a87272482195f1840b515a3380e39986989a893ae/charset_normalizer-3.4.5-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ed1a9a204f317ef879b32f9af507d47e49cd5e7f8e8d5d96358c98373314fc60", size = 203285, upload-time = "2026-03-06T06:01:19.473Z"}, + {url = "https://files.pythonhosted.org/packages/67/5c/ae30362a88b4da237d71ea214a8c7eb915db3eec941adda511729ac25fa2/charset_normalizer-3.4.5-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7ad83b8f9379176c841f8865884f3514d905bcd2a9a3b210eaa446e7d2223e4d", size = 196274, upload-time = "2026-03-06T06:01:20.728Z"}, + {url = "https://files.pythonhosted.org/packages/b2/07/c9f2cb0e46cb6d64fdcc4f95953747b843bb2181bda678dc4e699b8f0f9a/charset_normalizer-3.4.5-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:a118e2e0b5ae6b0120d5efa5f866e58f2bb826067a646431da4d6a2bdae7950e", size = 184715, upload-time = "2026-03-06T06:01:22.194Z"}, + {url = "https://files.pythonhosted.org/packages/36/64/6b0ca95c44fddf692cd06d642b28f63009d0ce325fad6e9b2b4d0ef86a52/charset_normalizer-3.4.5-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:754f96058e61a5e22e91483f823e07df16416ce76afa4ebf306f8e1d1296d43f", size = 193426, upload-time = "2026-03-06T06:01:23.795Z"}, + {url = "https://files.pythonhosted.org/packages/50/bc/a730690d726403743795ca3f5bb2baf67838c5fea78236098f324b965e40/charset_normalizer-3.4.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0c300cefd9b0970381a46394902cd18eaf2aa00163f999590ace991989dcd0fc", size = 191780, upload-time = "2026-03-06T06:01:25.053Z"}, + {url = "https://files.pythonhosted.org/packages/97/4f/6c0bc9af68222b22951552d73df4532b5be6447cee32d58e7e8c74ecbb7b/charset_normalizer-3.4.5-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:c108f8619e504140569ee7de3f97d234f0fbae338a7f9f360455071ef9855a95", size = 185805, upload-time = "2026-03-06T06:01:26.294Z"}, + {url = "https://files.pythonhosted.org/packages/dd/b9/a523fb9b0ee90814b503452b2600e4cbc118cd68714d57041564886e7325/charset_normalizer-3.4.5-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:d1028de43596a315e2720a9849ee79007ab742c06ad8b45a50db8cdb7ed4a82a", size = 208342, upload-time = "2026-03-06T06:01:27.55Z"}, + {url = "https://files.pythonhosted.org/packages/4d/61/c59e761dee4464050713e50e27b58266cc8e209e518c0b378c1580c959ba/charset_normalizer-3.4.5-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:19092dde50335accf365cce21998a1c6dd8eafd42c7b226eb54b2747cdce2fac", size = 193661, upload-time = "2026-03-06T06:01:29.051Z"}, + {url = "https://files.pythonhosted.org/packages/1c/43/729fa30aad69783f755c5ad8649da17ee095311ca42024742701e202dc59/charset_normalizer-3.4.5-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:4354e401eb6dab9aed3c7b4030514328a6c748d05e1c3e19175008ca7de84fb1", size = 204819, upload-time = "2026-03-06T06:01:30.298Z"}, + {url = "https://files.pythonhosted.org/packages/87/33/d9b442ce5a91b96fc0840455a9e49a611bbadae6122778d0a6a79683dd31/charset_normalizer-3.4.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a68766a3c58fde7f9aaa22b3786276f62ab2f594efb02d0a1421b6282e852e98", size = 198080, upload-time = "2026-03-06T06:01:31.478Z"}, + {url = "https://files.pythonhosted.org/packages/56/5a/b8b5a23134978ee9885cee2d6995f4c27cc41f9baded0a9685eabc5338f0/charset_normalizer-3.4.5-cp312-cp312-win32.whl", hash = "sha256:1827734a5b308b65ac54e86a618de66f935a4f63a8a462ff1e19a6788d6c2262", size = 132630, upload-time = "2026-03-06T06:01:33.056Z"}, + {url = "https://files.pythonhosted.org/packages/70/53/e44a4c07e8904500aec95865dc3f6464dc3586a039ef0df606eb3ac38e35/charset_normalizer-3.4.5-cp312-cp312-win_amd64.whl", hash = "sha256:728c6a963dfab66ef865f49286e45239384249672cd598576765acc2a640a636", size = 142856, upload-time = "2026-03-06T06:01:34.489Z"}, + {url = "https://files.pythonhosted.org/packages/ea/aa/c5628f7cad591b1cf45790b7a61483c3e36cf41349c98af7813c483fd6e8/charset_normalizer-3.4.5-cp312-cp312-win_arm64.whl", hash = "sha256:75dfd1afe0b1647449e852f4fb428195a7ed0588947218f7ba929f6538487f02", size = 132982, upload-time = "2026-03-06T06:01:35.641Z"}, + {url = "https://files.pythonhosted.org/packages/f5/48/9f34ec4bb24aa3fdba1890c1bddb97c8a4be1bd84ef5c42ac2352563ad05/charset_normalizer-3.4.5-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ac59c15e3f1465f722607800c68713f9fbc2f672b9eb649fe831da4019ae9b23", size = 280788, upload-time = "2026-03-06T06:01:37.126Z"}, + {url = "https://files.pythonhosted.org/packages/0e/09/6003e7ffeb90cc0560da893e3208396a44c210c5ee42efff539639def59b/charset_normalizer-3.4.5-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:165c7b21d19365464e8f70e5ce5e12524c58b48c78c1f5a57524603c1ab003f8", size = 188890, upload-time = "2026-03-06T06:01:38.73Z"}, + {url = "https://files.pythonhosted.org/packages/42/1e/02706edf19e390680daa694d17e2b8eab4b5f7ac285e2a51168b4b22ee6b/charset_normalizer-3.4.5-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:28269983f25a4da0425743d0d257a2d6921ea7d9b83599d4039486ec5b9f911d", size = 206136, upload-time = "2026-03-06T06:01:40.016Z"}, + {url = "https://files.pythonhosted.org/packages/c7/87/942c3def1b37baf3cf786bad01249190f3ca3d5e63a84f831e704977de1f/charset_normalizer-3.4.5-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d27ce22ec453564770d29d03a9506d449efbb9fa13c00842262b2f6801c48cce", size = 202551, upload-time = "2026-03-06T06:01:41.522Z"}, + {url = "https://files.pythonhosted.org/packages/94/0a/af49691938dfe175d71b8a929bd7e4ace2809c0c5134e28bc535660d5262/charset_normalizer-3.4.5-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0625665e4ebdddb553ab185de5db7054393af8879fb0c87bd5690d14379d6819", size = 195572, upload-time = "2026-03-06T06:01:43.208Z"}, + {url = "https://files.pythonhosted.org/packages/20/ea/dfb1792a8050a8e694cfbde1570ff97ff74e48afd874152d38163d1df9ae/charset_normalizer-3.4.5-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:c23eb3263356d94858655b3e63f85ac5d50970c6e8febcdde7830209139cc37d", size = 184438, upload-time = "2026-03-06T06:01:44.755Z"}, + {url = "https://files.pythonhosted.org/packages/72/12/c281e2067466e3ddd0595bfaea58a6946765ace5c72dfa3edc2f5f118026/charset_normalizer-3.4.5-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e6302ca4ae283deb0af68d2fbf467474b8b6aedcd3dab4db187e07f94c109763", size = 193035, upload-time = "2026-03-06T06:01:46.051Z"}, + {url = "https://files.pythonhosted.org/packages/ba/4f/3792c056e7708e10464bad0438a44708886fb8f92e3c3d29ec5e2d964d42/charset_normalizer-3.4.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e51ae7d81c825761d941962450f50d041db028b7278e7b08930b4541b3e45cb9", size = 191340, upload-time = "2026-03-06T06:01:47.547Z"}, + {url = "https://files.pythonhosted.org/packages/e7/86/80ddba897127b5c7a9bccc481b0cd36c8fefa485d113262f0fe4332f0bf4/charset_normalizer-3.4.5-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:597d10dec876923e5c59e48dbd366e852eacb2b806029491d307daea6b917d7c", size = 185464, upload-time = "2026-03-06T06:01:48.764Z"}, + {url = "https://files.pythonhosted.org/packages/4d/00/b5eff85ba198faacab83e0e4b6f0648155f072278e3b392a82478f8b988b/charset_normalizer-3.4.5-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:5cffde4032a197bd3b42fd0b9509ec60fb70918d6970e4cc773f20fc9180ca67", size = 208014, upload-time = "2026-03-06T06:01:50.371Z"}, + {url = "https://files.pythonhosted.org/packages/c8/11/d36f70be01597fd30850dde8a1269ebc8efadd23ba5785808454f2389bde/charset_normalizer-3.4.5-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:2da4eedcb6338e2321e831a0165759c0c620e37f8cd044a263ff67493be8ffb3", size = 193297, upload-time = "2026-03-06T06:01:51.933Z"}, + {url = "https://files.pythonhosted.org/packages/1a/1d/259eb0a53d4910536c7c2abb9cb25f4153548efb42800c6a9456764649c0/charset_normalizer-3.4.5-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:65a126fb4b070d05340a84fc709dd9e7c75d9b063b610ece8a60197a291d0adf", size = 204321, upload-time = "2026-03-06T06:01:53.887Z"}, + {url = "https://files.pythonhosted.org/packages/84/31/faa6c5b9d3688715e1ed1bb9d124c384fe2fc1633a409e503ffe1c6398c1/charset_normalizer-3.4.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c7a80a9242963416bd81f99349d5f3fce1843c303bd404f204918b6d75a75fd6", size = 197509, upload-time = "2026-03-06T06:01:56.439Z"}, + {url = "https://files.pythonhosted.org/packages/fd/a5/c7d9dd1503ffc08950b3260f5d39ec2366dd08254f0900ecbcf3a6197c7c/charset_normalizer-3.4.5-cp313-cp313-win32.whl", hash = "sha256:f1d725b754e967e648046f00c4facc42d414840f5ccc670c5670f59f83693e4f", size = 132284, upload-time = "2026-03-06T06:01:57.812Z"}, + {url = "https://files.pythonhosted.org/packages/b9/0f/57072b253af40c8aa6636e6de7d75985624c1eb392815b2f934199340a89/charset_normalizer-3.4.5-cp313-cp313-win_amd64.whl", hash = "sha256:e37bd100d2c5d3ba35db9c7c5ba5a9228cbcffe5c4778dc824b164e5257813d7", size = 142630, upload-time = "2026-03-06T06:01:59.062Z"}, + {url = "https://files.pythonhosted.org/packages/31/41/1c4b7cc9f13bd9d369ce3bc993e13d374ce25fa38a2663644283ecf422c1/charset_normalizer-3.4.5-cp313-cp313-win_arm64.whl", hash = "sha256:93b3b2cc5cf1b8743660ce77a4f45f3f6d1172068207c1defc779a36eea6bb36", size = 133254, upload-time = "2026-03-06T06:02:00.281Z"}, + {url = "https://files.pythonhosted.org/packages/c5/60/3a621758945513adfd4db86827a5bafcc615f913dbd0b4c2ed64a65731be/charset_normalizer-3.4.5-py3-none-any.whl", hash = "sha256:9db5e3fcdcee89a78c04dffb3fe33c79f77bd741a624946db2591c81b2fc85b0", size = 55455, upload-time = "2026-03-06T06:03:17.827Z"} ] [[package]] name = "colorama" +sdist = {url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z"} +source = {registry = "https://pypi.org/simple"} version = "0.4.6" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, + {url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z"} ] [[package]] name = "coverage" +sdist = {url = "https://files.pythonhosted.org/packages/24/56/95b7e30fa389756cb56630faa728da46a27b8c6eb46f9d557c68fff12b65/coverage-7.13.4.tar.gz", hash = "sha256:e5c8f6ed1e61a8b2dcdf31eb0b9bbf0130750ca79c1c49eb898e2ad86f5ccc91", size = 827239, upload-time = "2026-02-09T12:59:03.86Z"} +source = {registry = "https://pypi.org/simple"} version = "7.13.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/24/56/95b7e30fa389756cb56630faa728da46a27b8c6eb46f9d557c68fff12b65/coverage-7.13.4.tar.gz", hash = "sha256:e5c8f6ed1e61a8b2dcdf31eb0b9bbf0130750ca79c1c49eb898e2ad86f5ccc91", size = 827239, upload-time = "2026-02-09T12:59:03.86Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d1/81/4ce2fdd909c5a0ed1f6dedb88aa57ab79b6d1fbd9b588c1ac7ef45659566/coverage-7.13.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:02231499b08dabbe2b96612993e5fc34217cdae907a51b906ac7fca8027a4459", size = 219449, upload-time = "2026-02-09T12:56:54.889Z" }, - { url = "https://files.pythonhosted.org/packages/5d/96/5238b1efc5922ddbdc9b0db9243152c09777804fb7c02ad1741eb18a11c0/coverage-7.13.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40aa8808140e55dc022b15d8aa7f651b6b3d68b365ea0398f1441e0b04d859c3", size = 219810, upload-time = "2026-02-09T12:56:56.33Z" }, - { url = "https://files.pythonhosted.org/packages/78/72/2f372b726d433c9c35e56377cf1d513b4c16fe51841060d826b95caacec1/coverage-7.13.4-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5b856a8ccf749480024ff3bd7310adaef57bf31fd17e1bfc404b7940b6986634", size = 251308, upload-time = "2026-02-09T12:56:57.858Z" }, - { url = "https://files.pythonhosted.org/packages/5d/a0/2ea570925524ef4e00bb6c82649f5682a77fac5ab910a65c9284de422600/coverage-7.13.4-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2c048ea43875fbf8b45d476ad79f179809c590ec7b79e2035c662e7afa3192e3", size = 254052, upload-time = "2026-02-09T12:56:59.754Z" }, - { url = "https://files.pythonhosted.org/packages/e8/ac/45dc2e19a1939098d783c846e130b8f862fbb50d09e0af663988f2f21973/coverage-7.13.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b7b38448866e83176e28086674fe7368ab8590e4610fb662b44e345b86d63ffa", size = 255165, upload-time = "2026-02-09T12:57:01.287Z" }, - { url = "https://files.pythonhosted.org/packages/2d/4d/26d236ff35abc3b5e63540d3386e4c3b192168c1d96da5cb2f43c640970f/coverage-7.13.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:de6defc1c9badbf8b9e67ae90fd00519186d6ab64e5cc5f3d21359c2a9b2c1d3", size = 257432, upload-time = "2026-02-09T12:57:02.637Z" }, - { url = "https://files.pythonhosted.org/packages/ec/55/14a966c757d1348b2e19caf699415a2a4c4f7feaa4bbc6326a51f5c7dd1b/coverage-7.13.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7eda778067ad7ffccd23ecffce537dface96212576a07924cbf0d8799d2ded5a", size = 251716, upload-time = "2026-02-09T12:57:04.056Z" }, - { url = "https://files.pythonhosted.org/packages/77/33/50116647905837c66d28b2af1321b845d5f5d19be9655cb84d4a0ea806b4/coverage-7.13.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e87f6c587c3f34356c3759f0420693e35e7eb0e2e41e4c011cb6ec6ecbbf1db7", size = 253089, upload-time = "2026-02-09T12:57:05.503Z" }, - { url = "https://files.pythonhosted.org/packages/c2/b4/8efb11a46e3665d92635a56e4f2d4529de6d33f2cb38afd47d779d15fc99/coverage-7.13.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:8248977c2e33aecb2ced42fef99f2d319e9904a36e55a8a68b69207fb7e43edc", size = 251232, upload-time = "2026-02-09T12:57:06.879Z" }, - { url = "https://files.pythonhosted.org/packages/51/24/8cd73dd399b812cc76bb0ac260e671c4163093441847ffe058ac9fda1e32/coverage-7.13.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:25381386e80ae727608e662474db537d4df1ecd42379b5ba33c84633a2b36d47", size = 255299, upload-time = "2026-02-09T12:57:08.245Z" }, - { url = "https://files.pythonhosted.org/packages/03/94/0a4b12f1d0e029ce1ccc1c800944a9984cbe7d678e470bb6d3c6bc38a0da/coverage-7.13.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:ee756f00726693e5ba94d6df2bdfd64d4852d23b09bb0bc700e3b30e6f333985", size = 250796, upload-time = "2026-02-09T12:57:10.142Z" }, - { url = "https://files.pythonhosted.org/packages/73/44/6002fbf88f6698ca034360ce474c406be6d5a985b3fdb3401128031eef6b/coverage-7.13.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fdfc1e28e7c7cdce44985b3043bc13bbd9c747520f94a4d7164af8260b3d91f0", size = 252673, upload-time = "2026-02-09T12:57:12.197Z" }, - { url = "https://files.pythonhosted.org/packages/de/c6/a0279f7c00e786be75a749a5674e6fa267bcbd8209cd10c9a450c655dfa7/coverage-7.13.4-cp312-cp312-win32.whl", hash = "sha256:01d4cbc3c283a17fc1e42d614a119f7f438eabb593391283adca8dc86eff1246", size = 221990, upload-time = "2026-02-09T12:57:14.085Z" }, - { url = "https://files.pythonhosted.org/packages/77/4e/c0a25a425fcf5557d9abd18419c95b63922e897bc86c1f327f155ef234a9/coverage-7.13.4-cp312-cp312-win_amd64.whl", hash = "sha256:9401ebc7ef522f01d01d45532c68c5ac40fb27113019b6b7d8b208f6e9baa126", size = 222800, upload-time = "2026-02-09T12:57:15.944Z" }, - { url = "https://files.pythonhosted.org/packages/47/ac/92da44ad9a6f4e3a7debd178949d6f3769bedca33830ce9b1dcdab589a37/coverage-7.13.4-cp312-cp312-win_arm64.whl", hash = "sha256:b1ec7b6b6e93255f952e27ab58fbc68dcc468844b16ecbee881aeb29b6ab4d8d", size = 221415, upload-time = "2026-02-09T12:57:17.497Z" }, - { url = "https://files.pythonhosted.org/packages/db/23/aad45061a31677d68e47499197a131eea55da4875d16c1f42021ab963503/coverage-7.13.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b66a2da594b6068b48b2692f043f35d4d3693fb639d5ea8b39533c2ad9ac3ab9", size = 219474, upload-time = "2026-02-09T12:57:19.332Z" }, - { url = "https://files.pythonhosted.org/packages/a5/70/9b8b67a0945f3dfec1fd896c5cefb7c19d5a3a6d74630b99a895170999ae/coverage-7.13.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3599eb3992d814d23b35c536c28df1a882caa950f8f507cef23d1cbf334995ac", size = 219844, upload-time = "2026-02-09T12:57:20.66Z" }, - { url = "https://files.pythonhosted.org/packages/97/fd/7e859f8fab324cef6c4ad7cff156ca7c489fef9179d5749b0c8d321281c2/coverage-7.13.4-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:93550784d9281e374fb5a12bf1324cc8a963fd63b2d2f223503ef0fd4aa339ea", size = 250832, upload-time = "2026-02-09T12:57:22.007Z" }, - { url = "https://files.pythonhosted.org/packages/e4/dc/b2442d10020c2f52617828862d8b6ee337859cd8f3a1f13d607dddda9cf7/coverage-7.13.4-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b720ce6a88a2755f7c697c23268ddc47a571b88052e6b155224347389fdf6a3b", size = 253434, upload-time = "2026-02-09T12:57:23.339Z" }, - { url = "https://files.pythonhosted.org/packages/5a/88/6728a7ad17428b18d836540630487231f5470fb82454871149502f5e5aa2/coverage-7.13.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7b322db1284a2ed3aa28ffd8ebe3db91c929b7a333c0820abec3d838ef5b3525", size = 254676, upload-time = "2026-02-09T12:57:24.774Z" }, - { url = "https://files.pythonhosted.org/packages/7c/bc/21244b1b8cedf0dff0a2b53b208015fe798d5f2a8d5348dbfece04224fff/coverage-7.13.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f4594c67d8a7c89cf922d9df0438c7c7bb022ad506eddb0fdb2863359ff78242", size = 256807, upload-time = "2026-02-09T12:57:26.125Z" }, - { url = "https://files.pythonhosted.org/packages/97/a0/ddba7ed3251cff51006737a727d84e05b61517d1784a9988a846ba508877/coverage-7.13.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:53d133df809c743eb8bce33b24bcababb371f4441340578cd406e084d94a6148", size = 251058, upload-time = "2026-02-09T12:57:27.614Z" }, - { url = "https://files.pythonhosted.org/packages/9b/55/e289addf7ff54d3a540526f33751951bf0878f3809b47f6dfb3def69c6f7/coverage-7.13.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:76451d1978b95ba6507a039090ba076105c87cc76fc3efd5d35d72093964d49a", size = 252805, upload-time = "2026-02-09T12:57:29.066Z" }, - { url = "https://files.pythonhosted.org/packages/13/4e/cc276b1fa4a59be56d96f1dabddbdc30f4ba22e3b1cd42504c37b3313255/coverage-7.13.4-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:7f57b33491e281e962021de110b451ab8a24182589be17e12a22c79047935e23", size = 250766, upload-time = "2026-02-09T12:57:30.522Z" }, - { url = "https://files.pythonhosted.org/packages/94/44/1093b8f93018f8b41a8cf29636c9292502f05e4a113d4d107d14a3acd044/coverage-7.13.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:1731dc33dc276dafc410a885cbf5992f1ff171393e48a21453b78727d090de80", size = 254923, upload-time = "2026-02-09T12:57:31.946Z" }, - { url = "https://files.pythonhosted.org/packages/8b/55/ea2796da2d42257f37dbea1aab239ba9263b31bd91d5527cdd6db5efe174/coverage-7.13.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:bd60d4fe2f6fa7dff9223ca1bbc9f05d2b6697bc5961072e5d3b952d46e1b1ea", size = 250591, upload-time = "2026-02-09T12:57:33.842Z" }, - { url = "https://files.pythonhosted.org/packages/d4/fa/7c4bb72aacf8af5020675aa633e59c1fbe296d22aed191b6a5b711eb2bc7/coverage-7.13.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9181a3ccead280b828fae232df12b16652702b49d41e99d657f46cc7b1f6ec7a", size = 252364, upload-time = "2026-02-09T12:57:35.743Z" }, - { url = "https://files.pythonhosted.org/packages/5c/38/a8d2ec0146479c20bbaa7181b5b455a0c41101eed57f10dd19a78ab44c80/coverage-7.13.4-cp313-cp313-win32.whl", hash = "sha256:f53d492307962561ac7de4cd1de3e363589b000ab69617c6156a16ba7237998d", size = 222010, upload-time = "2026-02-09T12:57:37.25Z" }, - { url = "https://files.pythonhosted.org/packages/e2/0c/dbfafbe90a185943dcfbc766fe0e1909f658811492d79b741523a414a6cc/coverage-7.13.4-cp313-cp313-win_amd64.whl", hash = "sha256:e6f70dec1cc557e52df5306d051ef56003f74d56e9c4dd7ddb07e07ef32a84dd", size = 222818, upload-time = "2026-02-09T12:57:38.734Z" }, - { url = "https://files.pythonhosted.org/packages/04/d1/934918a138c932c90d78301f45f677fb05c39a3112b96fd2c8e60503cdc7/coverage-7.13.4-cp313-cp313-win_arm64.whl", hash = "sha256:fb07dc5da7e849e2ad31a5d74e9bece81f30ecf5a42909d0a695f8bd1874d6af", size = 221438, upload-time = "2026-02-09T12:57:40.223Z" }, - { url = "https://files.pythonhosted.org/packages/52/57/ee93ced533bcb3e6df961c0c6e42da2fc6addae53fb95b94a89b1e33ebd7/coverage-7.13.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:40d74da8e6c4b9ac18b15331c4b5ebc35a17069410cad462ad4f40dcd2d50c0d", size = 220165, upload-time = "2026-02-09T12:57:41.639Z" }, - { url = "https://files.pythonhosted.org/packages/c5/e0/969fc285a6fbdda49d91af278488d904dcd7651b2693872f0ff94e40e84a/coverage-7.13.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4223b4230a376138939a9173f1bdd6521994f2aff8047fae100d6d94d50c5a12", size = 220516, upload-time = "2026-02-09T12:57:44.215Z" }, - { url = "https://files.pythonhosted.org/packages/b1/b8/9531944e16267e2735a30a9641ff49671f07e8138ecf1ca13db9fd2560c7/coverage-7.13.4-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1d4be36a5114c499f9f1f9195e95ebf979460dbe2d88e6816ea202010ba1c34b", size = 261804, upload-time = "2026-02-09T12:57:45.989Z" }, - { url = "https://files.pythonhosted.org/packages/8a/f3/e63df6d500314a2a60390d1989240d5f27318a7a68fa30ad3806e2a9323e/coverage-7.13.4-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:200dea7d1e8095cc6e98cdabe3fd1d21ab17d3cee6dab00cadbb2fe35d9c15b9", size = 263885, upload-time = "2026-02-09T12:57:47.42Z" }, - { url = "https://files.pythonhosted.org/packages/f3/67/7654810de580e14b37670b60a09c599fa348e48312db5b216d730857ffe6/coverage-7.13.4-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b8eb931ee8e6d8243e253e5ed7336deea6904369d2fd8ae6e43f68abbf167092", size = 266308, upload-time = "2026-02-09T12:57:49.345Z" }, - { url = "https://files.pythonhosted.org/packages/37/6f/39d41eca0eab3cc82115953ad41c4e77935286c930e8fad15eaed1389d83/coverage-7.13.4-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:75eab1ebe4f2f64d9509b984f9314d4aa788540368218b858dad56dc8f3e5eb9", size = 267452, upload-time = "2026-02-09T12:57:50.811Z" }, - { url = "https://files.pythonhosted.org/packages/50/6d/39c0fbb8fc5cd4d2090811e553c2108cf5112e882f82505ee7495349a6bf/coverage-7.13.4-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c35eb28c1d085eb7d8c9b3296567a1bebe03ce72962e932431b9a61f28facf26", size = 261057, upload-time = "2026-02-09T12:57:52.447Z" }, - { url = "https://files.pythonhosted.org/packages/a4/a2/60010c669df5fa603bb5a97fb75407e191a846510da70ac657eb696b7fce/coverage-7.13.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:eb88b316ec33760714a4720feb2816a3a59180fd58c1985012054fa7aebee4c2", size = 263875, upload-time = "2026-02-09T12:57:53.938Z" }, - { url = "https://files.pythonhosted.org/packages/3e/d9/63b22a6bdbd17f1f96e9ed58604c2a6b0e72a9133e37d663bef185877cf6/coverage-7.13.4-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:7d41eead3cc673cbd38a4417deb7fd0b4ca26954ff7dc6078e33f6ff97bed940", size = 261500, upload-time = "2026-02-09T12:57:56.012Z" }, - { url = "https://files.pythonhosted.org/packages/70/bf/69f86ba1ad85bc3ad240e4c0e57a2e620fbc0e1645a47b5c62f0e941ad7f/coverage-7.13.4-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:fb26a934946a6afe0e326aebe0730cdff393a8bc0bbb65a2f41e30feddca399c", size = 265212, upload-time = "2026-02-09T12:57:57.5Z" }, - { url = "https://files.pythonhosted.org/packages/ae/f2/5f65a278a8c2148731831574c73e42f57204243d33bedaaf18fa79c5958f/coverage-7.13.4-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:dae88bc0fc77edaa65c14be099bd57ee140cf507e6bfdeea7938457ab387efb0", size = 260398, upload-time = "2026-02-09T12:57:59.027Z" }, - { url = "https://files.pythonhosted.org/packages/ef/80/6e8280a350ee9fea92f14b8357448a242dcaa243cb2c72ab0ca591f66c8c/coverage-7.13.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:845f352911777a8e722bfce168958214951e07e47e5d5d9744109fa5fe77f79b", size = 262584, upload-time = "2026-02-09T12:58:01.129Z" }, - { url = "https://files.pythonhosted.org/packages/22/63/01ff182fc95f260b539590fb12c11ad3e21332c15f9799cb5e2386f71d9f/coverage-7.13.4-cp313-cp313t-win32.whl", hash = "sha256:2fa8d5f8de70688a28240de9e139fa16b153cc3cbb01c5f16d88d6505ebdadf9", size = 222688, upload-time = "2026-02-09T12:58:02.736Z" }, - { url = "https://files.pythonhosted.org/packages/a9/43/89de4ef5d3cd53b886afa114065f7e9d3707bdb3e5efae13535b46ae483d/coverage-7.13.4-cp313-cp313t-win_amd64.whl", hash = "sha256:9351229c8c8407645840edcc277f4a2d44814d1bc34a2128c11c2a031d45a5dd", size = 223746, upload-time = "2026-02-09T12:58:05.362Z" }, - { url = "https://files.pythonhosted.org/packages/35/39/7cf0aa9a10d470a5309b38b289b9bb07ddeac5d61af9b664fe9775a4cb3e/coverage-7.13.4-cp313-cp313t-win_arm64.whl", hash = "sha256:30b8d0512f2dc8c8747557e8fb459d6176a2c9e5731e2b74d311c03b78451997", size = 222003, upload-time = "2026-02-09T12:58:06.952Z" }, - { url = "https://files.pythonhosted.org/packages/0d/4a/331fe2caf6799d591109bb9c08083080f6de90a823695d412a935622abb2/coverage-7.13.4-py3-none-any.whl", hash = "sha256:1af1641e57cf7ba1bd67d677c9abdbcd6cc2ab7da3bca7fa1e2b7e50e65f2ad0", size = 211242, upload-time = "2026-02-09T12:59:02.032Z" }, +wheels = [ + {url = "https://files.pythonhosted.org/packages/d1/81/4ce2fdd909c5a0ed1f6dedb88aa57ab79b6d1fbd9b588c1ac7ef45659566/coverage-7.13.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:02231499b08dabbe2b96612993e5fc34217cdae907a51b906ac7fca8027a4459", size = 219449, upload-time = "2026-02-09T12:56:54.889Z"}, + {url = "https://files.pythonhosted.org/packages/5d/96/5238b1efc5922ddbdc9b0db9243152c09777804fb7c02ad1741eb18a11c0/coverage-7.13.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40aa8808140e55dc022b15d8aa7f651b6b3d68b365ea0398f1441e0b04d859c3", size = 219810, upload-time = "2026-02-09T12:56:56.33Z"}, + {url = "https://files.pythonhosted.org/packages/78/72/2f372b726d433c9c35e56377cf1d513b4c16fe51841060d826b95caacec1/coverage-7.13.4-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5b856a8ccf749480024ff3bd7310adaef57bf31fd17e1bfc404b7940b6986634", size = 251308, upload-time = "2026-02-09T12:56:57.858Z"}, + {url = "https://files.pythonhosted.org/packages/5d/a0/2ea570925524ef4e00bb6c82649f5682a77fac5ab910a65c9284de422600/coverage-7.13.4-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2c048ea43875fbf8b45d476ad79f179809c590ec7b79e2035c662e7afa3192e3", size = 254052, upload-time = "2026-02-09T12:56:59.754Z"}, + {url = "https://files.pythonhosted.org/packages/e8/ac/45dc2e19a1939098d783c846e130b8f862fbb50d09e0af663988f2f21973/coverage-7.13.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b7b38448866e83176e28086674fe7368ab8590e4610fb662b44e345b86d63ffa", size = 255165, upload-time = "2026-02-09T12:57:01.287Z"}, + {url = "https://files.pythonhosted.org/packages/2d/4d/26d236ff35abc3b5e63540d3386e4c3b192168c1d96da5cb2f43c640970f/coverage-7.13.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:de6defc1c9badbf8b9e67ae90fd00519186d6ab64e5cc5f3d21359c2a9b2c1d3", size = 257432, upload-time = "2026-02-09T12:57:02.637Z"}, + {url = "https://files.pythonhosted.org/packages/ec/55/14a966c757d1348b2e19caf699415a2a4c4f7feaa4bbc6326a51f5c7dd1b/coverage-7.13.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7eda778067ad7ffccd23ecffce537dface96212576a07924cbf0d8799d2ded5a", size = 251716, upload-time = "2026-02-09T12:57:04.056Z"}, + {url = "https://files.pythonhosted.org/packages/77/33/50116647905837c66d28b2af1321b845d5f5d19be9655cb84d4a0ea806b4/coverage-7.13.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e87f6c587c3f34356c3759f0420693e35e7eb0e2e41e4c011cb6ec6ecbbf1db7", size = 253089, upload-time = "2026-02-09T12:57:05.503Z"}, + {url = "https://files.pythonhosted.org/packages/c2/b4/8efb11a46e3665d92635a56e4f2d4529de6d33f2cb38afd47d779d15fc99/coverage-7.13.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:8248977c2e33aecb2ced42fef99f2d319e9904a36e55a8a68b69207fb7e43edc", size = 251232, upload-time = "2026-02-09T12:57:06.879Z"}, + {url = "https://files.pythonhosted.org/packages/51/24/8cd73dd399b812cc76bb0ac260e671c4163093441847ffe058ac9fda1e32/coverage-7.13.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:25381386e80ae727608e662474db537d4df1ecd42379b5ba33c84633a2b36d47", size = 255299, upload-time = "2026-02-09T12:57:08.245Z"}, + {url = "https://files.pythonhosted.org/packages/03/94/0a4b12f1d0e029ce1ccc1c800944a9984cbe7d678e470bb6d3c6bc38a0da/coverage-7.13.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:ee756f00726693e5ba94d6df2bdfd64d4852d23b09bb0bc700e3b30e6f333985", size = 250796, upload-time = "2026-02-09T12:57:10.142Z"}, + {url = "https://files.pythonhosted.org/packages/73/44/6002fbf88f6698ca034360ce474c406be6d5a985b3fdb3401128031eef6b/coverage-7.13.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fdfc1e28e7c7cdce44985b3043bc13bbd9c747520f94a4d7164af8260b3d91f0", size = 252673, upload-time = "2026-02-09T12:57:12.197Z"}, + {url = "https://files.pythonhosted.org/packages/de/c6/a0279f7c00e786be75a749a5674e6fa267bcbd8209cd10c9a450c655dfa7/coverage-7.13.4-cp312-cp312-win32.whl", hash = "sha256:01d4cbc3c283a17fc1e42d614a119f7f438eabb593391283adca8dc86eff1246", size = 221990, upload-time = "2026-02-09T12:57:14.085Z"}, + {url = "https://files.pythonhosted.org/packages/77/4e/c0a25a425fcf5557d9abd18419c95b63922e897bc86c1f327f155ef234a9/coverage-7.13.4-cp312-cp312-win_amd64.whl", hash = "sha256:9401ebc7ef522f01d01d45532c68c5ac40fb27113019b6b7d8b208f6e9baa126", size = 222800, upload-time = "2026-02-09T12:57:15.944Z"}, + {url = "https://files.pythonhosted.org/packages/47/ac/92da44ad9a6f4e3a7debd178949d6f3769bedca33830ce9b1dcdab589a37/coverage-7.13.4-cp312-cp312-win_arm64.whl", hash = "sha256:b1ec7b6b6e93255f952e27ab58fbc68dcc468844b16ecbee881aeb29b6ab4d8d", size = 221415, upload-time = "2026-02-09T12:57:17.497Z"}, + {url = "https://files.pythonhosted.org/packages/db/23/aad45061a31677d68e47499197a131eea55da4875d16c1f42021ab963503/coverage-7.13.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b66a2da594b6068b48b2692f043f35d4d3693fb639d5ea8b39533c2ad9ac3ab9", size = 219474, upload-time = "2026-02-09T12:57:19.332Z"}, + {url = "https://files.pythonhosted.org/packages/a5/70/9b8b67a0945f3dfec1fd896c5cefb7c19d5a3a6d74630b99a895170999ae/coverage-7.13.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3599eb3992d814d23b35c536c28df1a882caa950f8f507cef23d1cbf334995ac", size = 219844, upload-time = "2026-02-09T12:57:20.66Z"}, + {url = "https://files.pythonhosted.org/packages/97/fd/7e859f8fab324cef6c4ad7cff156ca7c489fef9179d5749b0c8d321281c2/coverage-7.13.4-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:93550784d9281e374fb5a12bf1324cc8a963fd63b2d2f223503ef0fd4aa339ea", size = 250832, upload-time = "2026-02-09T12:57:22.007Z"}, + {url = "https://files.pythonhosted.org/packages/e4/dc/b2442d10020c2f52617828862d8b6ee337859cd8f3a1f13d607dddda9cf7/coverage-7.13.4-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b720ce6a88a2755f7c697c23268ddc47a571b88052e6b155224347389fdf6a3b", size = 253434, upload-time = "2026-02-09T12:57:23.339Z"}, + {url = "https://files.pythonhosted.org/packages/5a/88/6728a7ad17428b18d836540630487231f5470fb82454871149502f5e5aa2/coverage-7.13.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7b322db1284a2ed3aa28ffd8ebe3db91c929b7a333c0820abec3d838ef5b3525", size = 254676, upload-time = "2026-02-09T12:57:24.774Z"}, + {url = "https://files.pythonhosted.org/packages/7c/bc/21244b1b8cedf0dff0a2b53b208015fe798d5f2a8d5348dbfece04224fff/coverage-7.13.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f4594c67d8a7c89cf922d9df0438c7c7bb022ad506eddb0fdb2863359ff78242", size = 256807, upload-time = "2026-02-09T12:57:26.125Z"}, + {url = "https://files.pythonhosted.org/packages/97/a0/ddba7ed3251cff51006737a727d84e05b61517d1784a9988a846ba508877/coverage-7.13.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:53d133df809c743eb8bce33b24bcababb371f4441340578cd406e084d94a6148", size = 251058, upload-time = "2026-02-09T12:57:27.614Z"}, + {url = "https://files.pythonhosted.org/packages/9b/55/e289addf7ff54d3a540526f33751951bf0878f3809b47f6dfb3def69c6f7/coverage-7.13.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:76451d1978b95ba6507a039090ba076105c87cc76fc3efd5d35d72093964d49a", size = 252805, upload-time = "2026-02-09T12:57:29.066Z"}, + {url = "https://files.pythonhosted.org/packages/13/4e/cc276b1fa4a59be56d96f1dabddbdc30f4ba22e3b1cd42504c37b3313255/coverage-7.13.4-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:7f57b33491e281e962021de110b451ab8a24182589be17e12a22c79047935e23", size = 250766, upload-time = "2026-02-09T12:57:30.522Z"}, + {url = "https://files.pythonhosted.org/packages/94/44/1093b8f93018f8b41a8cf29636c9292502f05e4a113d4d107d14a3acd044/coverage-7.13.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:1731dc33dc276dafc410a885cbf5992f1ff171393e48a21453b78727d090de80", size = 254923, upload-time = "2026-02-09T12:57:31.946Z"}, + {url = "https://files.pythonhosted.org/packages/8b/55/ea2796da2d42257f37dbea1aab239ba9263b31bd91d5527cdd6db5efe174/coverage-7.13.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:bd60d4fe2f6fa7dff9223ca1bbc9f05d2b6697bc5961072e5d3b952d46e1b1ea", size = 250591, upload-time = "2026-02-09T12:57:33.842Z"}, + {url = "https://files.pythonhosted.org/packages/d4/fa/7c4bb72aacf8af5020675aa633e59c1fbe296d22aed191b6a5b711eb2bc7/coverage-7.13.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9181a3ccead280b828fae232df12b16652702b49d41e99d657f46cc7b1f6ec7a", size = 252364, upload-time = "2026-02-09T12:57:35.743Z"}, + {url = "https://files.pythonhosted.org/packages/5c/38/a8d2ec0146479c20bbaa7181b5b455a0c41101eed57f10dd19a78ab44c80/coverage-7.13.4-cp313-cp313-win32.whl", hash = "sha256:f53d492307962561ac7de4cd1de3e363589b000ab69617c6156a16ba7237998d", size = 222010, upload-time = "2026-02-09T12:57:37.25Z"}, + {url = "https://files.pythonhosted.org/packages/e2/0c/dbfafbe90a185943dcfbc766fe0e1909f658811492d79b741523a414a6cc/coverage-7.13.4-cp313-cp313-win_amd64.whl", hash = "sha256:e6f70dec1cc557e52df5306d051ef56003f74d56e9c4dd7ddb07e07ef32a84dd", size = 222818, upload-time = "2026-02-09T12:57:38.734Z"}, + {url = "https://files.pythonhosted.org/packages/04/d1/934918a138c932c90d78301f45f677fb05c39a3112b96fd2c8e60503cdc7/coverage-7.13.4-cp313-cp313-win_arm64.whl", hash = "sha256:fb07dc5da7e849e2ad31a5d74e9bece81f30ecf5a42909d0a695f8bd1874d6af", size = 221438, upload-time = "2026-02-09T12:57:40.223Z"}, + {url = "https://files.pythonhosted.org/packages/52/57/ee93ced533bcb3e6df961c0c6e42da2fc6addae53fb95b94a89b1e33ebd7/coverage-7.13.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:40d74da8e6c4b9ac18b15331c4b5ebc35a17069410cad462ad4f40dcd2d50c0d", size = 220165, upload-time = "2026-02-09T12:57:41.639Z"}, + {url = "https://files.pythonhosted.org/packages/c5/e0/969fc285a6fbdda49d91af278488d904dcd7651b2693872f0ff94e40e84a/coverage-7.13.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4223b4230a376138939a9173f1bdd6521994f2aff8047fae100d6d94d50c5a12", size = 220516, upload-time = "2026-02-09T12:57:44.215Z"}, + {url = "https://files.pythonhosted.org/packages/b1/b8/9531944e16267e2735a30a9641ff49671f07e8138ecf1ca13db9fd2560c7/coverage-7.13.4-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1d4be36a5114c499f9f1f9195e95ebf979460dbe2d88e6816ea202010ba1c34b", size = 261804, upload-time = "2026-02-09T12:57:45.989Z"}, + {url = "https://files.pythonhosted.org/packages/8a/f3/e63df6d500314a2a60390d1989240d5f27318a7a68fa30ad3806e2a9323e/coverage-7.13.4-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:200dea7d1e8095cc6e98cdabe3fd1d21ab17d3cee6dab00cadbb2fe35d9c15b9", size = 263885, upload-time = "2026-02-09T12:57:47.42Z"}, + {url = "https://files.pythonhosted.org/packages/f3/67/7654810de580e14b37670b60a09c599fa348e48312db5b216d730857ffe6/coverage-7.13.4-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b8eb931ee8e6d8243e253e5ed7336deea6904369d2fd8ae6e43f68abbf167092", size = 266308, upload-time = "2026-02-09T12:57:49.345Z"}, + {url = "https://files.pythonhosted.org/packages/37/6f/39d41eca0eab3cc82115953ad41c4e77935286c930e8fad15eaed1389d83/coverage-7.13.4-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:75eab1ebe4f2f64d9509b984f9314d4aa788540368218b858dad56dc8f3e5eb9", size = 267452, upload-time = "2026-02-09T12:57:50.811Z"}, + {url = "https://files.pythonhosted.org/packages/50/6d/39c0fbb8fc5cd4d2090811e553c2108cf5112e882f82505ee7495349a6bf/coverage-7.13.4-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c35eb28c1d085eb7d8c9b3296567a1bebe03ce72962e932431b9a61f28facf26", size = 261057, upload-time = "2026-02-09T12:57:52.447Z"}, + {url = "https://files.pythonhosted.org/packages/a4/a2/60010c669df5fa603bb5a97fb75407e191a846510da70ac657eb696b7fce/coverage-7.13.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:eb88b316ec33760714a4720feb2816a3a59180fd58c1985012054fa7aebee4c2", size = 263875, upload-time = "2026-02-09T12:57:53.938Z"}, + {url = "https://files.pythonhosted.org/packages/3e/d9/63b22a6bdbd17f1f96e9ed58604c2a6b0e72a9133e37d663bef185877cf6/coverage-7.13.4-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:7d41eead3cc673cbd38a4417deb7fd0b4ca26954ff7dc6078e33f6ff97bed940", size = 261500, upload-time = "2026-02-09T12:57:56.012Z"}, + {url = "https://files.pythonhosted.org/packages/70/bf/69f86ba1ad85bc3ad240e4c0e57a2e620fbc0e1645a47b5c62f0e941ad7f/coverage-7.13.4-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:fb26a934946a6afe0e326aebe0730cdff393a8bc0bbb65a2f41e30feddca399c", size = 265212, upload-time = "2026-02-09T12:57:57.5Z"}, + {url = "https://files.pythonhosted.org/packages/ae/f2/5f65a278a8c2148731831574c73e42f57204243d33bedaaf18fa79c5958f/coverage-7.13.4-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:dae88bc0fc77edaa65c14be099bd57ee140cf507e6bfdeea7938457ab387efb0", size = 260398, upload-time = "2026-02-09T12:57:59.027Z"}, + {url = "https://files.pythonhosted.org/packages/ef/80/6e8280a350ee9fea92f14b8357448a242dcaa243cb2c72ab0ca591f66c8c/coverage-7.13.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:845f352911777a8e722bfce168958214951e07e47e5d5d9744109fa5fe77f79b", size = 262584, upload-time = "2026-02-09T12:58:01.129Z"}, + {url = "https://files.pythonhosted.org/packages/22/63/01ff182fc95f260b539590fb12c11ad3e21332c15f9799cb5e2386f71d9f/coverage-7.13.4-cp313-cp313t-win32.whl", hash = "sha256:2fa8d5f8de70688a28240de9e139fa16b153cc3cbb01c5f16d88d6505ebdadf9", size = 222688, upload-time = "2026-02-09T12:58:02.736Z"}, + {url = "https://files.pythonhosted.org/packages/a9/43/89de4ef5d3cd53b886afa114065f7e9d3707bdb3e5efae13535b46ae483d/coverage-7.13.4-cp313-cp313t-win_amd64.whl", hash = "sha256:9351229c8c8407645840edcc277f4a2d44814d1bc34a2128c11c2a031d45a5dd", size = 223746, upload-time = "2026-02-09T12:58:05.362Z"}, + {url = "https://files.pythonhosted.org/packages/35/39/7cf0aa9a10d470a5309b38b289b9bb07ddeac5d61af9b664fe9775a4cb3e/coverage-7.13.4-cp313-cp313t-win_arm64.whl", hash = "sha256:30b8d0512f2dc8c8747557e8fb459d6176a2c9e5731e2b74d311c03b78451997", size = 222003, upload-time = "2026-02-09T12:58:06.952Z"}, + {url = "https://files.pythonhosted.org/packages/0d/4a/331fe2caf6799d591109bb9c08083080f6de90a823695d412a935622abb2/coverage-7.13.4-py3-none-any.whl", hash = "sha256:1af1641e57cf7ba1bd67d677c9abdbcd6cc2ab7da3bca7fa1e2b7e50e65f2ad0", size = 211242, upload-time = "2026-02-09T12:59:02.032Z"} ] [[package]] name = "distlib" +sdist = {url = "https://files.pythonhosted.org/packages/96/8e/709914eb2b5749865801041647dc7f4e6d00b549cfe88b65ca192995f07c/distlib-0.4.0.tar.gz", hash = "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d", size = 614605, upload-time = "2025-07-17T16:52:00.465Z"} +source = {registry = "https://pypi.org/simple"} version = "0.4.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/96/8e/709914eb2b5749865801041647dc7f4e6d00b549cfe88b65ca192995f07c/distlib-0.4.0.tar.gz", hash = "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d", size = 614605, upload-time = "2025-07-17T16:52:00.465Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16", size = 469047, upload-time = "2025-07-17T16:51:58.613Z" }, + {url = "https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16", size = 469047, upload-time = "2025-07-17T16:51:58.613Z"} ] [[package]] name = "docutils" +sdist = {url = "https://files.pythonhosted.org/packages/ae/b6/03bb70946330e88ffec97aefd3ea75ba575cb2e762061e0e62a213befee8/docutils-0.22.4.tar.gz", hash = "sha256:4db53b1fde9abecbb74d91230d32ab626d94f6badfc575d6db9194a49df29968", size = 2291750, upload-time = "2025-12-18T19:00:26.443Z"} +source = {registry = "https://pypi.org/simple"} version = "0.22.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ae/b6/03bb70946330e88ffec97aefd3ea75ba575cb2e762061e0e62a213befee8/docutils-0.22.4.tar.gz", hash = "sha256:4db53b1fde9abecbb74d91230d32ab626d94f6badfc575d6db9194a49df29968", size = 2291750, upload-time = "2025-12-18T19:00:26.443Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/02/10/5da547df7a391dcde17f59520a231527b8571e6f46fc8efb02ccb370ab12/docutils-0.22.4-py3-none-any.whl", hash = "sha256:d0013f540772d1420576855455d050a2180186c91c15779301ac2ccb3eeb68de", size = 633196, upload-time = "2025-12-18T19:00:18.077Z" }, + {url = "https://files.pythonhosted.org/packages/02/10/5da547df7a391dcde17f59520a231527b8571e6f46fc8efb02ccb370ab12/docutils-0.22.4-py3-none-any.whl", hash = "sha256:d0013f540772d1420576855455d050a2180186c91c15779301ac2ccb3eeb68de", size = 633196, upload-time = "2025-12-18T19:00:18.077Z"} ] [[package]] name = "filelock" +sdist = {url = "https://files.pythonhosted.org/packages/77/18/a1fd2231c679dcb9726204645721b12498aeac28e1ad0601038f94b42556/filelock-3.25.0.tar.gz", hash = "sha256:8f00faf3abf9dc730a1ffe9c354ae5c04e079ab7d3a683b7c32da5dd05f26af3", size = 40158, upload-time = "2026-03-01T15:08:45.916Z"} +source = {registry = "https://pypi.org/simple"} version = "3.25.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/77/18/a1fd2231c679dcb9726204645721b12498aeac28e1ad0601038f94b42556/filelock-3.25.0.tar.gz", hash = "sha256:8f00faf3abf9dc730a1ffe9c354ae5c04e079ab7d3a683b7c32da5dd05f26af3", size = 40158, upload-time = "2026-03-01T15:08:45.916Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f9/0b/de6f54d4a8bedfe8645c41497f3c18d749f0bd3218170c667bf4b81d0cdd/filelock-3.25.0-py3-none-any.whl", hash = "sha256:5ccf8069f7948f494968fc0713c10e5c182a9c9d9eef3a636307a20c2490f047", size = 26427, upload-time = "2026-03-01T15:08:44.593Z" }, + {url = "https://files.pythonhosted.org/packages/f9/0b/de6f54d4a8bedfe8645c41497f3c18d749f0bd3218170c667bf4b81d0cdd/filelock-3.25.0-py3-none-any.whl", hash = "sha256:5ccf8069f7948f494968fc0713c10e5c182a9c9d9eef3a636307a20c2490f047", size = 26427, upload-time = "2026-03-01T15:08:44.593Z"} ] [[package]] name = "identify" +sdist = {url = "https://files.pythonhosted.org/packages/57/84/376a3b96e5a8d33a7aa2c5b3b31a4b3c364117184bf0b17418055f6ace66/identify-2.6.17.tar.gz", hash = "sha256:f816b0b596b204c9fdf076ded172322f2723cf958d02f9c3587504834c8ff04d", size = 99579, upload-time = "2026-03-01T20:04:12.702Z"} +source = {registry = "https://pypi.org/simple"} version = "2.6.17" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/57/84/376a3b96e5a8d33a7aa2c5b3b31a4b3c364117184bf0b17418055f6ace66/identify-2.6.17.tar.gz", hash = "sha256:f816b0b596b204c9fdf076ded172322f2723cf958d02f9c3587504834c8ff04d", size = 99579, upload-time = "2026-03-01T20:04:12.702Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/40/66/71c1227dff78aaeb942fed29dd5651f2aec166cc7c9aeea3e8b26a539b7d/identify-2.6.17-py2.py3-none-any.whl", hash = "sha256:be5f8412d5ed4b20f2bd41a65f920990bdccaa6a4a18a08f1eefdcd0bdd885f0", size = 99382, upload-time = "2026-03-01T20:04:11.439Z" }, + {url = "https://files.pythonhosted.org/packages/40/66/71c1227dff78aaeb942fed29dd5651f2aec166cc7c9aeea3e8b26a539b7d/identify-2.6.17-py2.py3-none-any.whl", hash = "sha256:be5f8412d5ed4b20f2bd41a65f920990bdccaa6a4a18a08f1eefdcd0bdd885f0", size = 99382, upload-time = "2026-03-01T20:04:11.439Z"} ] [[package]] name = "idna" +sdist = {url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z"} +source = {registry = "https://pypi.org/simple"} version = "3.11" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, + {url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z"} ] [[package]] name = "imagesize" +sdist = {url = "https://files.pythonhosted.org/packages/6c/e6/7bf14eeb8f8b7251141944835abd42eb20a658d89084b7e1f3e5fe394090/imagesize-2.0.0.tar.gz", hash = "sha256:8e8358c4a05c304f1fccf7ff96f036e7243a189e9e42e90851993c558cfe9ee3", size = 1773045, upload-time = "2026-03-03T14:18:29.941Z"} +source = {registry = "https://pypi.org/simple"} version = "2.0.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6c/e6/7bf14eeb8f8b7251141944835abd42eb20a658d89084b7e1f3e5fe394090/imagesize-2.0.0.tar.gz", hash = "sha256:8e8358c4a05c304f1fccf7ff96f036e7243a189e9e42e90851993c558cfe9ee3", size = 1773045, upload-time = "2026-03-03T14:18:29.941Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5f/53/fb7122b71361a0d121b669dcf3d31244ef75badbbb724af388948de543e2/imagesize-2.0.0-py2.py3-none-any.whl", hash = "sha256:5667c5bbb57ab3f1fa4bc366f4fbc971db3d5ed011fd2715fd8001f782718d96", size = 9441, upload-time = "2026-03-03T14:18:27.892Z" }, + {url = "https://files.pythonhosted.org/packages/5f/53/fb7122b71361a0d121b669dcf3d31244ef75badbbb724af388948de543e2/imagesize-2.0.0-py2.py3-none-any.whl", hash = "sha256:5667c5bbb57ab3f1fa4bc366f4fbc971db3d5ed011fd2715fd8001f782718d96", size = 9441, upload-time = "2026-03-03T14:18:27.892Z"} ] [[package]] name = "iniconfig" +sdist = {url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z"} +source = {registry = "https://pypi.org/simple"} version = "2.3.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, + {url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z"} ] [[package]] -name = "jinja2" -version = "3.1.6" -source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "markupsafe" }, + {name = "markupsafe"} ] -sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" } +name = "jinja2" +sdist = {url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z"} +source = {registry = "https://pypi.org/simple"} +version = "3.1.6" wheels = [ - { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, + {url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z"} ] [[package]] name = "librt" +sdist = {url = "https://files.pythonhosted.org/packages/56/9c/b4b0c54d84da4a94b37bd44151e46d5e583c9534c7e02250b961b1b6d8a8/librt-0.8.1.tar.gz", hash = "sha256:be46a14693955b3bd96014ccbdb8339ee8c9346fbe11c1b78901b55125f14c73", size = 177471, upload-time = "2026-02-17T16:13:06.101Z"} +source = {registry = "https://pypi.org/simple"} version = "0.8.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/56/9c/b4b0c54d84da4a94b37bd44151e46d5e583c9534c7e02250b961b1b6d8a8/librt-0.8.1.tar.gz", hash = "sha256:be46a14693955b3bd96014ccbdb8339ee8c9346fbe11c1b78901b55125f14c73", size = 177471, upload-time = "2026-02-17T16:13:06.101Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/95/21/d39b0a87ac52fc98f621fb6f8060efb017a767ebbbac2f99fbcbc9ddc0d7/librt-0.8.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a28f2612ab566b17f3698b0da021ff9960610301607c9a5e8eaca62f5e1c350a", size = 66516, upload-time = "2026-02-17T16:11:41.604Z" }, - { url = "https://files.pythonhosted.org/packages/69/f1/46375e71441c43e8ae335905e069f1c54febee63a146278bcee8782c84fd/librt-0.8.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:60a78b694c9aee2a0f1aaeaa7d101cf713e92e8423a941d2897f4fa37908dab9", size = 68634, upload-time = "2026-02-17T16:11:43.268Z" }, - { url = "https://files.pythonhosted.org/packages/0a/33/c510de7f93bf1fa19e13423a606d8189a02624a800710f6e6a0a0f0784b3/librt-0.8.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:758509ea3f1eba2a57558e7e98f4659d0ea7670bff49673b0dde18a3c7e6c0eb", size = 198941, upload-time = "2026-02-17T16:11:44.28Z" }, - { url = "https://files.pythonhosted.org/packages/dd/36/e725903416409a533d92398e88ce665476f275081d0d7d42f9c4951999e5/librt-0.8.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:039b9f2c506bd0ab0f8725aa5ba339c6f0cd19d3b514b50d134789809c24285d", size = 209991, upload-time = "2026-02-17T16:11:45.462Z" }, - { url = "https://files.pythonhosted.org/packages/30/7a/8d908a152e1875c9f8eac96c97a480df425e657cdb47854b9efaa4998889/librt-0.8.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5bb54f1205a3a6ab41a6fd71dfcdcbd278670d3a90ca502a30d9da583105b6f7", size = 224476, upload-time = "2026-02-17T16:11:46.542Z" }, - { url = "https://files.pythonhosted.org/packages/a8/b8/a22c34f2c485b8903a06f3fe3315341fe6876ef3599792344669db98fcff/librt-0.8.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:05bd41cdee35b0c59c259f870f6da532a2c5ca57db95b5f23689fcb5c9e42440", size = 217518, upload-time = "2026-02-17T16:11:47.746Z" }, - { url = "https://files.pythonhosted.org/packages/79/6f/5c6fea00357e4f82ba44f81dbfb027921f1ab10e320d4a64e1c408d035d9/librt-0.8.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:adfab487facf03f0d0857b8710cf82d0704a309d8ffc33b03d9302b4c64e91a9", size = 225116, upload-time = "2026-02-17T16:11:49.298Z" }, - { url = "https://files.pythonhosted.org/packages/f2/a0/95ced4e7b1267fe1e2720a111685bcddf0e781f7e9e0ce59d751c44dcfe5/librt-0.8.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:153188fe98a72f206042be10a2c6026139852805215ed9539186312d50a8e972", size = 217751, upload-time = "2026-02-17T16:11:50.49Z" }, - { url = "https://files.pythonhosted.org/packages/93/c2/0517281cb4d4101c27ab59472924e67f55e375bc46bedae94ac6dc6e1902/librt-0.8.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:dd3c41254ee98604b08bd5b3af5bf0a89740d4ee0711de95b65166bf44091921", size = 218378, upload-time = "2026-02-17T16:11:51.783Z" }, - { url = "https://files.pythonhosted.org/packages/43/e8/37b3ac108e8976888e559a7b227d0ceac03c384cfd3e7a1c2ee248dbae79/librt-0.8.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e0d138c7ae532908cbb342162b2611dbd4d90c941cd25ab82084aaf71d2c0bd0", size = 241199, upload-time = "2026-02-17T16:11:53.561Z" }, - { url = "https://files.pythonhosted.org/packages/4b/5b/35812d041c53967fedf551a39399271bbe4257e681236a2cf1a69c8e7fa1/librt-0.8.1-cp312-cp312-win32.whl", hash = "sha256:43353b943613c5d9c49a25aaffdba46f888ec354e71e3529a00cca3f04d66a7a", size = 54917, upload-time = "2026-02-17T16:11:54.758Z" }, - { url = "https://files.pythonhosted.org/packages/de/d1/fa5d5331b862b9775aaf2a100f5ef86854e5d4407f71bddf102f4421e034/librt-0.8.1-cp312-cp312-win_amd64.whl", hash = "sha256:ff8baf1f8d3f4b6b7257fcb75a501f2a5499d0dda57645baa09d4d0d34b19444", size = 62017, upload-time = "2026-02-17T16:11:55.748Z" }, - { url = "https://files.pythonhosted.org/packages/c7/7c/c614252f9acda59b01a66e2ddfd243ed1c7e1deab0293332dfbccf862808/librt-0.8.1-cp312-cp312-win_arm64.whl", hash = "sha256:0f2ae3725904f7377e11cc37722d5d401e8b3d5851fb9273d7f4fe04f6b3d37d", size = 52441, upload-time = "2026-02-17T16:11:56.801Z" }, - { url = "https://files.pythonhosted.org/packages/c5/3c/f614c8e4eaac7cbf2bbdf9528790b21d89e277ee20d57dc6e559c626105f/librt-0.8.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7e6bad1cd94f6764e1e21950542f818a09316645337fd5ab9a7acc45d99a8f35", size = 66529, upload-time = "2026-02-17T16:11:57.809Z" }, - { url = "https://files.pythonhosted.org/packages/ab/96/5836544a45100ae411eda07d29e3d99448e5258b6e9c8059deb92945f5c2/librt-0.8.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cf450f498c30af55551ba4f66b9123b7185362ec8b625a773b3d39aa1a717583", size = 68669, upload-time = "2026-02-17T16:11:58.843Z" }, - { url = "https://files.pythonhosted.org/packages/06/53/f0b992b57af6d5531bf4677d75c44f095f2366a1741fb695ee462ae04b05/librt-0.8.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:eca45e982fa074090057132e30585a7e8674e9e885d402eae85633e9f449ce6c", size = 199279, upload-time = "2026-02-17T16:11:59.862Z" }, - { url = "https://files.pythonhosted.org/packages/f3/ad/4848cc16e268d14280d8168aee4f31cea92bbd2b79ce33d3e166f2b4e4fc/librt-0.8.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0c3811485fccfda840861905b8c70bba5ec094e02825598bb9d4ca3936857a04", size = 210288, upload-time = "2026-02-17T16:12:00.954Z" }, - { url = "https://files.pythonhosted.org/packages/52/05/27fdc2e95de26273d83b96742d8d3b7345f2ea2bdbd2405cc504644f2096/librt-0.8.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5e4af413908f77294605e28cfd98063f54b2c790561383971d2f52d113d9c363", size = 224809, upload-time = "2026-02-17T16:12:02.108Z" }, - { url = "https://files.pythonhosted.org/packages/7a/d0/78200a45ba3240cb042bc597d6f2accba9193a2c57d0356268cbbe2d0925/librt-0.8.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5212a5bd7fae98dae95710032902edcd2ec4dc994e883294f75c857b83f9aba0", size = 218075, upload-time = "2026-02-17T16:12:03.631Z" }, - { url = "https://files.pythonhosted.org/packages/af/72/a210839fa74c90474897124c064ffca07f8d4b347b6574d309686aae7ca6/librt-0.8.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e692aa2d1d604e6ca12d35e51fdc36f4cda6345e28e36374579f7ef3611b3012", size = 225486, upload-time = "2026-02-17T16:12:04.725Z" }, - { url = "https://files.pythonhosted.org/packages/a3/c1/a03cc63722339ddbf087485f253493e2b013039f5b707e8e6016141130fa/librt-0.8.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4be2a5c926b9770c9e08e717f05737a269b9d0ebc5d2f0060f0fe3fe9ce47acb", size = 218219, upload-time = "2026-02-17T16:12:05.828Z" }, - { url = "https://files.pythonhosted.org/packages/58/f5/fff6108af0acf941c6f274a946aea0e484bd10cd2dc37610287ce49388c5/librt-0.8.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:fd1a720332ea335ceb544cf0a03f81df92abd4bb887679fd1e460976b0e6214b", size = 218750, upload-time = "2026-02-17T16:12:07.09Z" }, - { url = "https://files.pythonhosted.org/packages/71/67/5a387bfef30ec1e4b4f30562c8586566faf87e47d696768c19feb49e3646/librt-0.8.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:93c2af9e01e0ef80d95ae3c720be101227edae5f2fe7e3dc63d8857fadfc5a1d", size = 241624, upload-time = "2026-02-17T16:12:08.43Z" }, - { url = "https://files.pythonhosted.org/packages/d4/be/24f8502db11d405232ac1162eb98069ca49c3306c1d75c6ccc61d9af8789/librt-0.8.1-cp313-cp313-win32.whl", hash = "sha256:086a32dbb71336627e78cc1d6ee305a68d038ef7d4c39aaff41ae8c9aa46e91a", size = 54969, upload-time = "2026-02-17T16:12:09.633Z" }, - { url = "https://files.pythonhosted.org/packages/5c/73/c9fdf6cb2a529c1a092ce769a12d88c8cca991194dfe641b6af12fa964d2/librt-0.8.1-cp313-cp313-win_amd64.whl", hash = "sha256:e11769a1dbda4da7b00a76cfffa67aa47cfa66921d2724539eee4b9ede780b79", size = 62000, upload-time = "2026-02-17T16:12:10.632Z" }, - { url = "https://files.pythonhosted.org/packages/d3/97/68f80ca3ac4924f250cdfa6e20142a803e5e50fca96ef5148c52ee8c10ea/librt-0.8.1-cp313-cp313-win_arm64.whl", hash = "sha256:924817ab3141aca17893386ee13261f1d100d1ef410d70afe4389f2359fea4f0", size = 52495, upload-time = "2026-02-17T16:12:11.633Z" }, +wheels = [ + {url = "https://files.pythonhosted.org/packages/95/21/d39b0a87ac52fc98f621fb6f8060efb017a767ebbbac2f99fbcbc9ddc0d7/librt-0.8.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a28f2612ab566b17f3698b0da021ff9960610301607c9a5e8eaca62f5e1c350a", size = 66516, upload-time = "2026-02-17T16:11:41.604Z"}, + {url = "https://files.pythonhosted.org/packages/69/f1/46375e71441c43e8ae335905e069f1c54febee63a146278bcee8782c84fd/librt-0.8.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:60a78b694c9aee2a0f1aaeaa7d101cf713e92e8423a941d2897f4fa37908dab9", size = 68634, upload-time = "2026-02-17T16:11:43.268Z"}, + {url = "https://files.pythonhosted.org/packages/0a/33/c510de7f93bf1fa19e13423a606d8189a02624a800710f6e6a0a0f0784b3/librt-0.8.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:758509ea3f1eba2a57558e7e98f4659d0ea7670bff49673b0dde18a3c7e6c0eb", size = 198941, upload-time = "2026-02-17T16:11:44.28Z"}, + {url = "https://files.pythonhosted.org/packages/dd/36/e725903416409a533d92398e88ce665476f275081d0d7d42f9c4951999e5/librt-0.8.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:039b9f2c506bd0ab0f8725aa5ba339c6f0cd19d3b514b50d134789809c24285d", size = 209991, upload-time = "2026-02-17T16:11:45.462Z"}, + {url = "https://files.pythonhosted.org/packages/30/7a/8d908a152e1875c9f8eac96c97a480df425e657cdb47854b9efaa4998889/librt-0.8.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5bb54f1205a3a6ab41a6fd71dfcdcbd278670d3a90ca502a30d9da583105b6f7", size = 224476, upload-time = "2026-02-17T16:11:46.542Z"}, + {url = "https://files.pythonhosted.org/packages/a8/b8/a22c34f2c485b8903a06f3fe3315341fe6876ef3599792344669db98fcff/librt-0.8.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:05bd41cdee35b0c59c259f870f6da532a2c5ca57db95b5f23689fcb5c9e42440", size = 217518, upload-time = "2026-02-17T16:11:47.746Z"}, + {url = "https://files.pythonhosted.org/packages/79/6f/5c6fea00357e4f82ba44f81dbfb027921f1ab10e320d4a64e1c408d035d9/librt-0.8.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:adfab487facf03f0d0857b8710cf82d0704a309d8ffc33b03d9302b4c64e91a9", size = 225116, upload-time = "2026-02-17T16:11:49.298Z"}, + {url = "https://files.pythonhosted.org/packages/f2/a0/95ced4e7b1267fe1e2720a111685bcddf0e781f7e9e0ce59d751c44dcfe5/librt-0.8.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:153188fe98a72f206042be10a2c6026139852805215ed9539186312d50a8e972", size = 217751, upload-time = "2026-02-17T16:11:50.49Z"}, + {url = "https://files.pythonhosted.org/packages/93/c2/0517281cb4d4101c27ab59472924e67f55e375bc46bedae94ac6dc6e1902/librt-0.8.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:dd3c41254ee98604b08bd5b3af5bf0a89740d4ee0711de95b65166bf44091921", size = 218378, upload-time = "2026-02-17T16:11:51.783Z"}, + {url = "https://files.pythonhosted.org/packages/43/e8/37b3ac108e8976888e559a7b227d0ceac03c384cfd3e7a1c2ee248dbae79/librt-0.8.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e0d138c7ae532908cbb342162b2611dbd4d90c941cd25ab82084aaf71d2c0bd0", size = 241199, upload-time = "2026-02-17T16:11:53.561Z"}, + {url = "https://files.pythonhosted.org/packages/4b/5b/35812d041c53967fedf551a39399271bbe4257e681236a2cf1a69c8e7fa1/librt-0.8.1-cp312-cp312-win32.whl", hash = "sha256:43353b943613c5d9c49a25aaffdba46f888ec354e71e3529a00cca3f04d66a7a", size = 54917, upload-time = "2026-02-17T16:11:54.758Z"}, + {url = "https://files.pythonhosted.org/packages/de/d1/fa5d5331b862b9775aaf2a100f5ef86854e5d4407f71bddf102f4421e034/librt-0.8.1-cp312-cp312-win_amd64.whl", hash = "sha256:ff8baf1f8d3f4b6b7257fcb75a501f2a5499d0dda57645baa09d4d0d34b19444", size = 62017, upload-time = "2026-02-17T16:11:55.748Z"}, + {url = "https://files.pythonhosted.org/packages/c7/7c/c614252f9acda59b01a66e2ddfd243ed1c7e1deab0293332dfbccf862808/librt-0.8.1-cp312-cp312-win_arm64.whl", hash = "sha256:0f2ae3725904f7377e11cc37722d5d401e8b3d5851fb9273d7f4fe04f6b3d37d", size = 52441, upload-time = "2026-02-17T16:11:56.801Z"}, + {url = "https://files.pythonhosted.org/packages/c5/3c/f614c8e4eaac7cbf2bbdf9528790b21d89e277ee20d57dc6e559c626105f/librt-0.8.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7e6bad1cd94f6764e1e21950542f818a09316645337fd5ab9a7acc45d99a8f35", size = 66529, upload-time = "2026-02-17T16:11:57.809Z"}, + {url = "https://files.pythonhosted.org/packages/ab/96/5836544a45100ae411eda07d29e3d99448e5258b6e9c8059deb92945f5c2/librt-0.8.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cf450f498c30af55551ba4f66b9123b7185362ec8b625a773b3d39aa1a717583", size = 68669, upload-time = "2026-02-17T16:11:58.843Z"}, + {url = "https://files.pythonhosted.org/packages/06/53/f0b992b57af6d5531bf4677d75c44f095f2366a1741fb695ee462ae04b05/librt-0.8.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:eca45e982fa074090057132e30585a7e8674e9e885d402eae85633e9f449ce6c", size = 199279, upload-time = "2026-02-17T16:11:59.862Z"}, + {url = "https://files.pythonhosted.org/packages/f3/ad/4848cc16e268d14280d8168aee4f31cea92bbd2b79ce33d3e166f2b4e4fc/librt-0.8.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0c3811485fccfda840861905b8c70bba5ec094e02825598bb9d4ca3936857a04", size = 210288, upload-time = "2026-02-17T16:12:00.954Z"}, + {url = "https://files.pythonhosted.org/packages/52/05/27fdc2e95de26273d83b96742d8d3b7345f2ea2bdbd2405cc504644f2096/librt-0.8.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5e4af413908f77294605e28cfd98063f54b2c790561383971d2f52d113d9c363", size = 224809, upload-time = "2026-02-17T16:12:02.108Z"}, + {url = "https://files.pythonhosted.org/packages/7a/d0/78200a45ba3240cb042bc597d6f2accba9193a2c57d0356268cbbe2d0925/librt-0.8.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5212a5bd7fae98dae95710032902edcd2ec4dc994e883294f75c857b83f9aba0", size = 218075, upload-time = "2026-02-17T16:12:03.631Z"}, + {url = "https://files.pythonhosted.org/packages/af/72/a210839fa74c90474897124c064ffca07f8d4b347b6574d309686aae7ca6/librt-0.8.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e692aa2d1d604e6ca12d35e51fdc36f4cda6345e28e36374579f7ef3611b3012", size = 225486, upload-time = "2026-02-17T16:12:04.725Z"}, + {url = "https://files.pythonhosted.org/packages/a3/c1/a03cc63722339ddbf087485f253493e2b013039f5b707e8e6016141130fa/librt-0.8.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4be2a5c926b9770c9e08e717f05737a269b9d0ebc5d2f0060f0fe3fe9ce47acb", size = 218219, upload-time = "2026-02-17T16:12:05.828Z"}, + {url = "https://files.pythonhosted.org/packages/58/f5/fff6108af0acf941c6f274a946aea0e484bd10cd2dc37610287ce49388c5/librt-0.8.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:fd1a720332ea335ceb544cf0a03f81df92abd4bb887679fd1e460976b0e6214b", size = 218750, upload-time = "2026-02-17T16:12:07.09Z"}, + {url = "https://files.pythonhosted.org/packages/71/67/5a387bfef30ec1e4b4f30562c8586566faf87e47d696768c19feb49e3646/librt-0.8.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:93c2af9e01e0ef80d95ae3c720be101227edae5f2fe7e3dc63d8857fadfc5a1d", size = 241624, upload-time = "2026-02-17T16:12:08.43Z"}, + {url = "https://files.pythonhosted.org/packages/d4/be/24f8502db11d405232ac1162eb98069ca49c3306c1d75c6ccc61d9af8789/librt-0.8.1-cp313-cp313-win32.whl", hash = "sha256:086a32dbb71336627e78cc1d6ee305a68d038ef7d4c39aaff41ae8c9aa46e91a", size = 54969, upload-time = "2026-02-17T16:12:09.633Z"}, + {url = "https://files.pythonhosted.org/packages/5c/73/c9fdf6cb2a529c1a092ce769a12d88c8cca991194dfe641b6af12fa964d2/librt-0.8.1-cp313-cp313-win_amd64.whl", hash = "sha256:e11769a1dbda4da7b00a76cfffa67aa47cfa66921d2724539eee4b9ede780b79", size = 62000, upload-time = "2026-02-17T16:12:10.632Z"}, + {url = "https://files.pythonhosted.org/packages/d3/97/68f80ca3ac4924f250cdfa6e20142a803e5e50fca96ef5148c52ee8c10ea/librt-0.8.1-cp313-cp313-win_arm64.whl", hash = "sha256:924817ab3141aca17893386ee13261f1d100d1ef410d70afe4389f2359fea4f0", size = 52495, upload-time = "2026-02-17T16:12:11.633Z"} ] [[package]] name = "llvmlite" +sdist = {url = "https://files.pythonhosted.org/packages/74/cd/08ae687ba099c7e3d21fe2ea536500563ef1943c5105bf6ab4ee3829f68e/llvmlite-0.46.0.tar.gz", hash = "sha256:227c9fd6d09dce2783c18b754b7cd9d9b3b3515210c46acc2d3c5badd9870ceb", size = 193456, upload-time = "2025-12-08T18:15:36.295Z"} +source = {registry = "https://pypi.org/simple"} version = "0.46.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/74/cd/08ae687ba099c7e3d21fe2ea536500563ef1943c5105bf6ab4ee3829f68e/llvmlite-0.46.0.tar.gz", hash = "sha256:227c9fd6d09dce2783c18b754b7cd9d9b3b3515210c46acc2d3c5badd9870ceb", size = 193456, upload-time = "2025-12-08T18:15:36.295Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2b/f8/4db016a5e547d4e054ff2f3b99203d63a497465f81ab78ec8eb2ff7b2304/llvmlite-0.46.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6b9588ad4c63b4f0175a3984b85494f0c927c6b001e3a246a3a7fb3920d9a137", size = 37232767, upload-time = "2025-12-08T18:15:00.737Z" }, - { url = "https://files.pythonhosted.org/packages/aa/85/4890a7c14b4fa54400945cb52ac3cd88545bbdb973c440f98ca41591cdc5/llvmlite-0.46.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3535bd2bb6a2d7ae4012681ac228e5132cdb75fefb1bcb24e33f2f3e0c865ed4", size = 56275176, upload-time = "2025-12-08T18:15:03.936Z" }, - { url = "https://files.pythonhosted.org/packages/6a/07/3d31d39c1a1a08cd5337e78299fca77e6aebc07c059fbd0033e3edfab45c/llvmlite-0.46.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4cbfd366e60ff87ea6cc62f50bc4cd800ebb13ed4c149466f50cf2163a473d1e", size = 55128630, upload-time = "2025-12-08T18:15:07.196Z" }, - { url = "https://files.pythonhosted.org/packages/2a/6b/d139535d7590a1bba1ceb68751bef22fadaa5b815bbdf0e858e3875726b2/llvmlite-0.46.0-cp312-cp312-win_amd64.whl", hash = "sha256:398b39db462c39563a97b912d4f2866cd37cba60537975a09679b28fbbc0fb38", size = 38138940, upload-time = "2025-12-08T18:15:10.162Z" }, - { url = "https://files.pythonhosted.org/packages/e6/ff/3eba7eb0aed4b6fca37125387cd417e8c458e750621fce56d2c541f67fa8/llvmlite-0.46.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:30b60892d034bc560e0ec6654737aaa74e5ca327bd8114d82136aa071d611172", size = 37232767, upload-time = "2025-12-08T18:15:13.22Z" }, - { url = "https://files.pythonhosted.org/packages/0e/54/737755c0a91558364b9200702c3c9c15d70ed63f9b98a2c32f1c2aa1f3ba/llvmlite-0.46.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6cc19b051753368a9c9f31dc041299059ee91aceec81bd57b0e385e5d5bf1a54", size = 56275176, upload-time = "2025-12-08T18:15:16.339Z" }, - { url = "https://files.pythonhosted.org/packages/e6/91/14f32e1d70905c1c0aa4e6609ab5d705c3183116ca02ac6df2091868413a/llvmlite-0.46.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bca185892908f9ede48c0acd547fe4dc1bafefb8a4967d47db6cf664f9332d12", size = 55128629, upload-time = "2025-12-08T18:15:19.493Z" }, - { url = "https://files.pythonhosted.org/packages/4a/a7/d526ae86708cea531935ae777b6dbcabe7db52718e6401e0fb9c5edea80e/llvmlite-0.46.0-cp313-cp313-win_amd64.whl", hash = "sha256:67438fd30e12349ebb054d86a5a1a57fd5e87d264d2451bcfafbbbaa25b82a35", size = 38138941, upload-time = "2025-12-08T18:15:22.536Z" }, + {url = "https://files.pythonhosted.org/packages/2b/f8/4db016a5e547d4e054ff2f3b99203d63a497465f81ab78ec8eb2ff7b2304/llvmlite-0.46.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6b9588ad4c63b4f0175a3984b85494f0c927c6b001e3a246a3a7fb3920d9a137", size = 37232767, upload-time = "2025-12-08T18:15:00.737Z"}, + {url = "https://files.pythonhosted.org/packages/aa/85/4890a7c14b4fa54400945cb52ac3cd88545bbdb973c440f98ca41591cdc5/llvmlite-0.46.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3535bd2bb6a2d7ae4012681ac228e5132cdb75fefb1bcb24e33f2f3e0c865ed4", size = 56275176, upload-time = "2025-12-08T18:15:03.936Z"}, + {url = "https://files.pythonhosted.org/packages/6a/07/3d31d39c1a1a08cd5337e78299fca77e6aebc07c059fbd0033e3edfab45c/llvmlite-0.46.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4cbfd366e60ff87ea6cc62f50bc4cd800ebb13ed4c149466f50cf2163a473d1e", size = 55128630, upload-time = "2025-12-08T18:15:07.196Z"}, + {url = "https://files.pythonhosted.org/packages/2a/6b/d139535d7590a1bba1ceb68751bef22fadaa5b815bbdf0e858e3875726b2/llvmlite-0.46.0-cp312-cp312-win_amd64.whl", hash = "sha256:398b39db462c39563a97b912d4f2866cd37cba60537975a09679b28fbbc0fb38", size = 38138940, upload-time = "2025-12-08T18:15:10.162Z"}, + {url = "https://files.pythonhosted.org/packages/e6/ff/3eba7eb0aed4b6fca37125387cd417e8c458e750621fce56d2c541f67fa8/llvmlite-0.46.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:30b60892d034bc560e0ec6654737aaa74e5ca327bd8114d82136aa071d611172", size = 37232767, upload-time = "2025-12-08T18:15:13.22Z"}, + {url = "https://files.pythonhosted.org/packages/0e/54/737755c0a91558364b9200702c3c9c15d70ed63f9b98a2c32f1c2aa1f3ba/llvmlite-0.46.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6cc19b051753368a9c9f31dc041299059ee91aceec81bd57b0e385e5d5bf1a54", size = 56275176, upload-time = "2025-12-08T18:15:16.339Z"}, + {url = "https://files.pythonhosted.org/packages/e6/91/14f32e1d70905c1c0aa4e6609ab5d705c3183116ca02ac6df2091868413a/llvmlite-0.46.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bca185892908f9ede48c0acd547fe4dc1bafefb8a4967d47db6cf664f9332d12", size = 55128629, upload-time = "2025-12-08T18:15:19.493Z"}, + {url = "https://files.pythonhosted.org/packages/4a/a7/d526ae86708cea531935ae777b6dbcabe7db52718e6401e0fb9c5edea80e/llvmlite-0.46.0-cp313-cp313-win_amd64.whl", hash = "sha256:67438fd30e12349ebb054d86a5a1a57fd5e87d264d2451bcfafbbbaa25b82a35", size = 38138941, upload-time = "2025-12-08T18:15:22.536Z"} ] [[package]] -name = "markdown-it-py" -version = "4.0.0" -source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "mdurl" }, + {name = "mdurl"} ] -sdist = { url = "https://files.pythonhosted.org/packages/5b/f5/4ec618ed16cc4f8fb3b701563655a69816155e79e24a17b651541804721d/markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3", size = 73070, upload-time = "2025-08-11T12:57:52.854Z" } +name = "markdown-it-py" +sdist = {url = "https://files.pythonhosted.org/packages/5b/f5/4ec618ed16cc4f8fb3b701563655a69816155e79e24a17b651541804721d/markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3", size = 73070, upload-time = "2025-08-11T12:57:52.854Z"} +source = {registry = "https://pypi.org/simple"} +version = "4.0.0" wheels = [ - { url = "https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147", size = 87321, upload-time = "2025-08-11T12:57:51.923Z" }, + {url = "https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147", size = 87321, upload-time = "2025-08-11T12:57:51.923Z"} ] [[package]] name = "markupsafe" +sdist = {url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z"} +source = {registry = "https://pypi.org/simple"} version = "3.0.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5a/72/147da192e38635ada20e0a2e1a51cf8823d2119ce8883f7053879c2199b5/markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e", size = 11615, upload-time = "2025-09-27T18:36:30.854Z" }, - { url = "https://files.pythonhosted.org/packages/9a/81/7e4e08678a1f98521201c3079f77db69fb552acd56067661f8c2f534a718/markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce", size = 12020, upload-time = "2025-09-27T18:36:31.971Z" }, - { url = "https://files.pythonhosted.org/packages/1e/2c/799f4742efc39633a1b54a92eec4082e4f815314869865d876824c257c1e/markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d", size = 24332, upload-time = "2025-09-27T18:36:32.813Z" }, - { url = "https://files.pythonhosted.org/packages/3c/2e/8d0c2ab90a8c1d9a24f0399058ab8519a3279d1bd4289511d74e909f060e/markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d", size = 22947, upload-time = "2025-09-27T18:36:33.86Z" }, - { url = "https://files.pythonhosted.org/packages/2c/54/887f3092a85238093a0b2154bd629c89444f395618842e8b0c41783898ea/markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a", size = 21962, upload-time = "2025-09-27T18:36:35.099Z" }, - { url = "https://files.pythonhosted.org/packages/c9/2f/336b8c7b6f4a4d95e91119dc8521402461b74a485558d8f238a68312f11c/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b", size = 23760, upload-time = "2025-09-27T18:36:36.001Z" }, - { url = "https://files.pythonhosted.org/packages/32/43/67935f2b7e4982ffb50a4d169b724d74b62a3964bc1a9a527f5ac4f1ee2b/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f", size = 21529, upload-time = "2025-09-27T18:36:36.906Z" }, - { url = "https://files.pythonhosted.org/packages/89/e0/4486f11e51bbba8b0c041098859e869e304d1c261e59244baa3d295d47b7/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b", size = 23015, upload-time = "2025-09-27T18:36:37.868Z" }, - { url = "https://files.pythonhosted.org/packages/2f/e1/78ee7a023dac597a5825441ebd17170785a9dab23de95d2c7508ade94e0e/markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d", size = 14540, upload-time = "2025-09-27T18:36:38.761Z" }, - { url = "https://files.pythonhosted.org/packages/aa/5b/bec5aa9bbbb2c946ca2733ef9c4ca91c91b6a24580193e891b5f7dbe8e1e/markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c", size = 15105, upload-time = "2025-09-27T18:36:39.701Z" }, - { url = "https://files.pythonhosted.org/packages/e5/f1/216fc1bbfd74011693a4fd837e7026152e89c4bcf3e77b6692fba9923123/markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f", size = 13906, upload-time = "2025-09-27T18:36:40.689Z" }, - { url = "https://files.pythonhosted.org/packages/38/2f/907b9c7bbba283e68f20259574b13d005c121a0fa4c175f9bed27c4597ff/markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795", size = 11622, upload-time = "2025-09-27T18:36:41.777Z" }, - { url = "https://files.pythonhosted.org/packages/9c/d9/5f7756922cdd676869eca1c4e3c0cd0df60ed30199ffd775e319089cb3ed/markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219", size = 12029, upload-time = "2025-09-27T18:36:43.257Z" }, - { url = "https://files.pythonhosted.org/packages/00/07/575a68c754943058c78f30db02ee03a64b3c638586fba6a6dd56830b30a3/markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6", size = 24374, upload-time = "2025-09-27T18:36:44.508Z" }, - { url = "https://files.pythonhosted.org/packages/a9/21/9b05698b46f218fc0e118e1f8168395c65c8a2c750ae2bab54fc4bd4e0e8/markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676", size = 22980, upload-time = "2025-09-27T18:36:45.385Z" }, - { url = "https://files.pythonhosted.org/packages/7f/71/544260864f893f18b6827315b988c146b559391e6e7e8f7252839b1b846a/markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9", size = 21990, upload-time = "2025-09-27T18:36:46.916Z" }, - { url = "https://files.pythonhosted.org/packages/c2/28/b50fc2f74d1ad761af2f5dcce7492648b983d00a65b8c0e0cb457c82ebbe/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1", size = 23784, upload-time = "2025-09-27T18:36:47.884Z" }, - { url = "https://files.pythonhosted.org/packages/ed/76/104b2aa106a208da8b17a2fb72e033a5a9d7073c68f7e508b94916ed47a9/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc", size = 21588, upload-time = "2025-09-27T18:36:48.82Z" }, - { url = "https://files.pythonhosted.org/packages/b5/99/16a5eb2d140087ebd97180d95249b00a03aa87e29cc224056274f2e45fd6/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12", size = 23041, upload-time = "2025-09-27T18:36:49.797Z" }, - { url = "https://files.pythonhosted.org/packages/19/bc/e7140ed90c5d61d77cea142eed9f9c303f4c4806f60a1044c13e3f1471d0/markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed", size = 14543, upload-time = "2025-09-27T18:36:51.584Z" }, - { url = "https://files.pythonhosted.org/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5", size = 15113, upload-time = "2025-09-27T18:36:52.537Z" }, - { url = "https://files.pythonhosted.org/packages/f0/3a/fa34a0f7cfef23cf9500d68cb7c32dd64ffd58a12b09225fb03dd37d5b80/markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485", size = 13911, upload-time = "2025-09-27T18:36:53.513Z" }, - { url = "https://files.pythonhosted.org/packages/e4/d7/e05cd7efe43a88a17a37b3ae96e79a19e846f3f456fe79c57ca61356ef01/markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73", size = 11658, upload-time = "2025-09-27T18:36:54.819Z" }, - { url = "https://files.pythonhosted.org/packages/99/9e/e412117548182ce2148bdeacdda3bb494260c0b0184360fe0d56389b523b/markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37", size = 12066, upload-time = "2025-09-27T18:36:55.714Z" }, - { url = "https://files.pythonhosted.org/packages/bc/e6/fa0ffcda717ef64a5108eaa7b4f5ed28d56122c9a6d70ab8b72f9f715c80/markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19", size = 25639, upload-time = "2025-09-27T18:36:56.908Z" }, - { url = "https://files.pythonhosted.org/packages/96/ec/2102e881fe9d25fc16cb4b25d5f5cde50970967ffa5dddafdb771237062d/markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025", size = 23569, upload-time = "2025-09-27T18:36:57.913Z" }, - { url = "https://files.pythonhosted.org/packages/4b/30/6f2fce1f1f205fc9323255b216ca8a235b15860c34b6798f810f05828e32/markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6", size = 23284, upload-time = "2025-09-27T18:36:58.833Z" }, - { url = "https://files.pythonhosted.org/packages/58/47/4a0ccea4ab9f5dcb6f79c0236d954acb382202721e704223a8aafa38b5c8/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f", size = 24801, upload-time = "2025-09-27T18:36:59.739Z" }, - { url = "https://files.pythonhosted.org/packages/6a/70/3780e9b72180b6fecb83a4814d84c3bf4b4ae4bf0b19c27196104149734c/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb", size = 22769, upload-time = "2025-09-27T18:37:00.719Z" }, - { url = "https://files.pythonhosted.org/packages/98/c5/c03c7f4125180fc215220c035beac6b9cb684bc7a067c84fc69414d315f5/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009", size = 23642, upload-time = "2025-09-27T18:37:01.673Z" }, - { url = "https://files.pythonhosted.org/packages/80/d6/2d1b89f6ca4bff1036499b1e29a1d02d282259f3681540e16563f27ebc23/markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354", size = 14612, upload-time = "2025-09-27T18:37:02.639Z" }, - { url = "https://files.pythonhosted.org/packages/2b/98/e48a4bfba0a0ffcf9925fe2d69240bfaa19c6f7507b8cd09c70684a53c1e/markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218", size = 15200, upload-time = "2025-09-27T18:37:03.582Z" }, - { url = "https://files.pythonhosted.org/packages/0e/72/e3cc540f351f316e9ed0f092757459afbc595824ca724cbc5a5d4263713f/markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287", size = 13973, upload-time = "2025-09-27T18:37:04.929Z" }, +wheels = [ + {url = "https://files.pythonhosted.org/packages/5a/72/147da192e38635ada20e0a2e1a51cf8823d2119ce8883f7053879c2199b5/markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e", size = 11615, upload-time = "2025-09-27T18:36:30.854Z"}, + {url = "https://files.pythonhosted.org/packages/9a/81/7e4e08678a1f98521201c3079f77db69fb552acd56067661f8c2f534a718/markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce", size = 12020, upload-time = "2025-09-27T18:36:31.971Z"}, + {url = "https://files.pythonhosted.org/packages/1e/2c/799f4742efc39633a1b54a92eec4082e4f815314869865d876824c257c1e/markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d", size = 24332, upload-time = "2025-09-27T18:36:32.813Z"}, + {url = "https://files.pythonhosted.org/packages/3c/2e/8d0c2ab90a8c1d9a24f0399058ab8519a3279d1bd4289511d74e909f060e/markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d", size = 22947, upload-time = "2025-09-27T18:36:33.86Z"}, + {url = "https://files.pythonhosted.org/packages/2c/54/887f3092a85238093a0b2154bd629c89444f395618842e8b0c41783898ea/markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a", size = 21962, upload-time = "2025-09-27T18:36:35.099Z"}, + {url = "https://files.pythonhosted.org/packages/c9/2f/336b8c7b6f4a4d95e91119dc8521402461b74a485558d8f238a68312f11c/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b", size = 23760, upload-time = "2025-09-27T18:36:36.001Z"}, + {url = "https://files.pythonhosted.org/packages/32/43/67935f2b7e4982ffb50a4d169b724d74b62a3964bc1a9a527f5ac4f1ee2b/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f", size = 21529, upload-time = "2025-09-27T18:36:36.906Z"}, + {url = "https://files.pythonhosted.org/packages/89/e0/4486f11e51bbba8b0c041098859e869e304d1c261e59244baa3d295d47b7/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b", size = 23015, upload-time = "2025-09-27T18:36:37.868Z"}, + {url = "https://files.pythonhosted.org/packages/2f/e1/78ee7a023dac597a5825441ebd17170785a9dab23de95d2c7508ade94e0e/markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d", size = 14540, upload-time = "2025-09-27T18:36:38.761Z"}, + {url = "https://files.pythonhosted.org/packages/aa/5b/bec5aa9bbbb2c946ca2733ef9c4ca91c91b6a24580193e891b5f7dbe8e1e/markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c", size = 15105, upload-time = "2025-09-27T18:36:39.701Z"}, + {url = "https://files.pythonhosted.org/packages/e5/f1/216fc1bbfd74011693a4fd837e7026152e89c4bcf3e77b6692fba9923123/markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f", size = 13906, upload-time = "2025-09-27T18:36:40.689Z"}, + {url = "https://files.pythonhosted.org/packages/38/2f/907b9c7bbba283e68f20259574b13d005c121a0fa4c175f9bed27c4597ff/markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795", size = 11622, upload-time = "2025-09-27T18:36:41.777Z"}, + {url = "https://files.pythonhosted.org/packages/9c/d9/5f7756922cdd676869eca1c4e3c0cd0df60ed30199ffd775e319089cb3ed/markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219", size = 12029, upload-time = "2025-09-27T18:36:43.257Z"}, + {url = "https://files.pythonhosted.org/packages/00/07/575a68c754943058c78f30db02ee03a64b3c638586fba6a6dd56830b30a3/markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6", size = 24374, upload-time = "2025-09-27T18:36:44.508Z"}, + {url = "https://files.pythonhosted.org/packages/a9/21/9b05698b46f218fc0e118e1f8168395c65c8a2c750ae2bab54fc4bd4e0e8/markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676", size = 22980, upload-time = "2025-09-27T18:36:45.385Z"}, + {url = "https://files.pythonhosted.org/packages/7f/71/544260864f893f18b6827315b988c146b559391e6e7e8f7252839b1b846a/markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9", size = 21990, upload-time = "2025-09-27T18:36:46.916Z"}, + {url = "https://files.pythonhosted.org/packages/c2/28/b50fc2f74d1ad761af2f5dcce7492648b983d00a65b8c0e0cb457c82ebbe/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1", size = 23784, upload-time = "2025-09-27T18:36:47.884Z"}, + {url = "https://files.pythonhosted.org/packages/ed/76/104b2aa106a208da8b17a2fb72e033a5a9d7073c68f7e508b94916ed47a9/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc", size = 21588, upload-time = "2025-09-27T18:36:48.82Z"}, + {url = "https://files.pythonhosted.org/packages/b5/99/16a5eb2d140087ebd97180d95249b00a03aa87e29cc224056274f2e45fd6/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12", size = 23041, upload-time = "2025-09-27T18:36:49.797Z"}, + {url = "https://files.pythonhosted.org/packages/19/bc/e7140ed90c5d61d77cea142eed9f9c303f4c4806f60a1044c13e3f1471d0/markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed", size = 14543, upload-time = "2025-09-27T18:36:51.584Z"}, + {url = "https://files.pythonhosted.org/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5", size = 15113, upload-time = "2025-09-27T18:36:52.537Z"}, + {url = "https://files.pythonhosted.org/packages/f0/3a/fa34a0f7cfef23cf9500d68cb7c32dd64ffd58a12b09225fb03dd37d5b80/markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485", size = 13911, upload-time = "2025-09-27T18:36:53.513Z"}, + {url = "https://files.pythonhosted.org/packages/e4/d7/e05cd7efe43a88a17a37b3ae96e79a19e846f3f456fe79c57ca61356ef01/markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73", size = 11658, upload-time = "2025-09-27T18:36:54.819Z"}, + {url = "https://files.pythonhosted.org/packages/99/9e/e412117548182ce2148bdeacdda3bb494260c0b0184360fe0d56389b523b/markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37", size = 12066, upload-time = "2025-09-27T18:36:55.714Z"}, + {url = "https://files.pythonhosted.org/packages/bc/e6/fa0ffcda717ef64a5108eaa7b4f5ed28d56122c9a6d70ab8b72f9f715c80/markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19", size = 25639, upload-time = "2025-09-27T18:36:56.908Z"}, + {url = "https://files.pythonhosted.org/packages/96/ec/2102e881fe9d25fc16cb4b25d5f5cde50970967ffa5dddafdb771237062d/markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025", size = 23569, upload-time = "2025-09-27T18:36:57.913Z"}, + {url = "https://files.pythonhosted.org/packages/4b/30/6f2fce1f1f205fc9323255b216ca8a235b15860c34b6798f810f05828e32/markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6", size = 23284, upload-time = "2025-09-27T18:36:58.833Z"}, + {url = "https://files.pythonhosted.org/packages/58/47/4a0ccea4ab9f5dcb6f79c0236d954acb382202721e704223a8aafa38b5c8/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f", size = 24801, upload-time = "2025-09-27T18:36:59.739Z"}, + {url = "https://files.pythonhosted.org/packages/6a/70/3780e9b72180b6fecb83a4814d84c3bf4b4ae4bf0b19c27196104149734c/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb", size = 22769, upload-time = "2025-09-27T18:37:00.719Z"}, + {url = "https://files.pythonhosted.org/packages/98/c5/c03c7f4125180fc215220c035beac6b9cb684bc7a067c84fc69414d315f5/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009", size = 23642, upload-time = "2025-09-27T18:37:01.673Z"}, + {url = "https://files.pythonhosted.org/packages/80/d6/2d1b89f6ca4bff1036499b1e29a1d02d282259f3681540e16563f27ebc23/markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354", size = 14612, upload-time = "2025-09-27T18:37:02.639Z"}, + {url = "https://files.pythonhosted.org/packages/2b/98/e48a4bfba0a0ffcf9925fe2d69240bfaa19c6f7507b8cd09c70684a53c1e/markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218", size = 15200, upload-time = "2025-09-27T18:37:03.582Z"}, + {url = "https://files.pythonhosted.org/packages/0e/72/e3cc540f351f316e9ed0f092757459afbc595824ca724cbc5a5d4263713f/markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287", size = 13973, upload-time = "2025-09-27T18:37:04.929Z"} ] [[package]] -name = "mdit-py-plugins" -version = "0.5.0" -source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "markdown-it-py" }, + {name = "markdown-it-py"} ] -sdist = { url = "https://files.pythonhosted.org/packages/b2/fd/a756d36c0bfba5f6e39a1cdbdbfdd448dc02692467d83816dff4592a1ebc/mdit_py_plugins-0.5.0.tar.gz", hash = "sha256:f4918cb50119f50446560513a8e311d574ff6aaed72606ddae6d35716fe809c6", size = 44655, upload-time = "2025-08-11T07:25:49.083Z" } +name = "mdit-py-plugins" +sdist = {url = "https://files.pythonhosted.org/packages/b2/fd/a756d36c0bfba5f6e39a1cdbdbfdd448dc02692467d83816dff4592a1ebc/mdit_py_plugins-0.5.0.tar.gz", hash = "sha256:f4918cb50119f50446560513a8e311d574ff6aaed72606ddae6d35716fe809c6", size = 44655, upload-time = "2025-08-11T07:25:49.083Z"} +source = {registry = "https://pypi.org/simple"} +version = "0.5.0" wheels = [ - { url = "https://files.pythonhosted.org/packages/fb/86/dd6e5db36df29e76c7a7699123569a4a18c1623ce68d826ed96c62643cae/mdit_py_plugins-0.5.0-py3-none-any.whl", hash = "sha256:07a08422fc1936a5d26d146759e9155ea466e842f5ab2f7d2266dd084c8dab1f", size = 57205, upload-time = "2025-08-11T07:25:47.597Z" }, + {url = "https://files.pythonhosted.org/packages/fb/86/dd6e5db36df29e76c7a7699123569a4a18c1623ce68d826ed96c62643cae/mdit_py_plugins-0.5.0-py3-none-any.whl", hash = "sha256:07a08422fc1936a5d26d146759e9155ea466e842f5ab2f7d2266dd084c8dab1f", size = 57205, upload-time = "2025-08-11T07:25:47.597Z"} ] [[package]] name = "mdurl" +sdist = {url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z"} +source = {registry = "https://pypi.org/simple"} version = "0.1.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, + {url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z"} ] [[package]] -name = "mypy" -version = "1.19.1" -source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "librt", marker = "platform_python_implementation != 'PyPy'" }, - { name = "mypy-extensions" }, - { name = "pathspec" }, - { name = "typing-extensions" }, + {name = "librt", marker = "platform_python_implementation != 'PyPy'"}, + {name = "mypy-extensions"}, + {name = "pathspec"}, + {name = "typing-extensions"} ] -sdist = { url = "https://files.pythonhosted.org/packages/f5/db/4efed9504bc01309ab9c2da7e352cc223569f05478012b5d9ece38fd44d2/mypy-1.19.1.tar.gz", hash = "sha256:19d88bb05303fe63f71dd2c6270daca27cb9401c4ca8255fe50d1d920e0eb9ba", size = 3582404, upload-time = "2025-12-15T05:03:48.42Z" } +name = "mypy" +sdist = {url = "https://files.pythonhosted.org/packages/f5/db/4efed9504bc01309ab9c2da7e352cc223569f05478012b5d9ece38fd44d2/mypy-1.19.1.tar.gz", hash = "sha256:19d88bb05303fe63f71dd2c6270daca27cb9401c4ca8255fe50d1d920e0eb9ba", size = 3582404, upload-time = "2025-12-15T05:03:48.42Z"} +source = {registry = "https://pypi.org/simple"} +version = "1.19.1" wheels = [ - { url = "https://files.pythonhosted.org/packages/06/8a/19bfae96f6615aa8a0604915512e0289b1fad33d5909bf7244f02935d33a/mypy-1.19.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a8174a03289288c1f6c46d55cef02379b478bfbc8e358e02047487cad44c6ca1", size = 13206053, upload-time = "2025-12-15T05:03:46.622Z" }, - { url = "https://files.pythonhosted.org/packages/a5/34/3e63879ab041602154ba2a9f99817bb0c85c4df19a23a1443c8986e4d565/mypy-1.19.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ffcebe56eb09ff0c0885e750036a095e23793ba6c2e894e7e63f6d89ad51f22e", size = 12219134, upload-time = "2025-12-15T05:03:24.367Z" }, - { url = "https://files.pythonhosted.org/packages/89/cc/2db6f0e95366b630364e09845672dbee0cbf0bbe753a204b29a944967cd9/mypy-1.19.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b64d987153888790bcdb03a6473d321820597ab8dd9243b27a92153c4fa50fd2", size = 12731616, upload-time = "2025-12-15T05:02:44.725Z" }, - { url = "https://files.pythonhosted.org/packages/00/be/dd56c1fd4807bc1eba1cf18b2a850d0de7bacb55e158755eb79f77c41f8e/mypy-1.19.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c35d298c2c4bba75feb2195655dfea8124d855dfd7343bf8b8c055421eaf0cf8", size = 13620847, upload-time = "2025-12-15T05:03:39.633Z" }, - { url = "https://files.pythonhosted.org/packages/6d/42/332951aae42b79329f743bf1da088cd75d8d4d9acc18fbcbd84f26c1af4e/mypy-1.19.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:34c81968774648ab5ac09c29a375fdede03ba253f8f8287847bd480782f73a6a", size = 13834976, upload-time = "2025-12-15T05:03:08.786Z" }, - { url = "https://files.pythonhosted.org/packages/6f/63/e7493e5f90e1e085c562bb06e2eb32cae27c5057b9653348d38b47daaecc/mypy-1.19.1-cp312-cp312-win_amd64.whl", hash = "sha256:b10e7c2cd7870ba4ad9b2d8a6102eb5ffc1f16ca35e3de6bfa390c1113029d13", size = 10118104, upload-time = "2025-12-15T05:03:10.834Z" }, - { url = "https://files.pythonhosted.org/packages/de/9f/a6abae693f7a0c697dbb435aac52e958dc8da44e92e08ba88d2e42326176/mypy-1.19.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e3157c7594ff2ef1634ee058aafc56a82db665c9438fd41b390f3bde1ab12250", size = 13201927, upload-time = "2025-12-15T05:02:29.138Z" }, - { url = "https://files.pythonhosted.org/packages/9a/a4/45c35ccf6e1c65afc23a069f50e2c66f46bd3798cbe0d680c12d12935caa/mypy-1.19.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bdb12f69bcc02700c2b47e070238f42cb87f18c0bc1fc4cdb4fb2bc5fd7a3b8b", size = 12206730, upload-time = "2025-12-15T05:03:01.325Z" }, - { url = "https://files.pythonhosted.org/packages/05/bb/cdcf89678e26b187650512620eec8368fded4cfd99cfcb431e4cdfd19dec/mypy-1.19.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f859fb09d9583a985be9a493d5cfc5515b56b08f7447759a0c5deaf68d80506e", size = 12724581, upload-time = "2025-12-15T05:03:20.087Z" }, - { url = "https://files.pythonhosted.org/packages/d1/32/dd260d52babf67bad8e6770f8e1102021877ce0edea106e72df5626bb0ec/mypy-1.19.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c9a6538e0415310aad77cb94004ca6482330fece18036b5f360b62c45814c4ef", size = 13616252, upload-time = "2025-12-15T05:02:49.036Z" }, - { url = "https://files.pythonhosted.org/packages/71/d0/5e60a9d2e3bd48432ae2b454b7ef2b62a960ab51292b1eda2a95edd78198/mypy-1.19.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:da4869fc5e7f62a88f3fe0b5c919d1d9f7ea3cef92d3689de2823fd27e40aa75", size = 13840848, upload-time = "2025-12-15T05:02:55.95Z" }, - { url = "https://files.pythonhosted.org/packages/98/76/d32051fa65ecf6cc8c6610956473abdc9b4c43301107476ac03559507843/mypy-1.19.1-cp313-cp313-win_amd64.whl", hash = "sha256:016f2246209095e8eda7538944daa1d60e1e8134d98983b9fc1e92c1fc0cb8dd", size = 10135510, upload-time = "2025-12-15T05:02:58.438Z" }, - { url = "https://files.pythonhosted.org/packages/8d/f4/4ce9a05ce5ded1de3ec1c1d96cf9f9504a04e54ce0ed55cfa38619a32b8d/mypy-1.19.1-py3-none-any.whl", hash = "sha256:f1235f5ea01b7db5468d53ece6aaddf1ad0b88d9e7462b86ef96fe04995d7247", size = 2471239, upload-time = "2025-12-15T05:03:07.248Z" }, + {url = "https://files.pythonhosted.org/packages/06/8a/19bfae96f6615aa8a0604915512e0289b1fad33d5909bf7244f02935d33a/mypy-1.19.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a8174a03289288c1f6c46d55cef02379b478bfbc8e358e02047487cad44c6ca1", size = 13206053, upload-time = "2025-12-15T05:03:46.622Z"}, + {url = "https://files.pythonhosted.org/packages/a5/34/3e63879ab041602154ba2a9f99817bb0c85c4df19a23a1443c8986e4d565/mypy-1.19.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ffcebe56eb09ff0c0885e750036a095e23793ba6c2e894e7e63f6d89ad51f22e", size = 12219134, upload-time = "2025-12-15T05:03:24.367Z"}, + {url = "https://files.pythonhosted.org/packages/89/cc/2db6f0e95366b630364e09845672dbee0cbf0bbe753a204b29a944967cd9/mypy-1.19.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b64d987153888790bcdb03a6473d321820597ab8dd9243b27a92153c4fa50fd2", size = 12731616, upload-time = "2025-12-15T05:02:44.725Z"}, + {url = "https://files.pythonhosted.org/packages/00/be/dd56c1fd4807bc1eba1cf18b2a850d0de7bacb55e158755eb79f77c41f8e/mypy-1.19.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c35d298c2c4bba75feb2195655dfea8124d855dfd7343bf8b8c055421eaf0cf8", size = 13620847, upload-time = "2025-12-15T05:03:39.633Z"}, + {url = "https://files.pythonhosted.org/packages/6d/42/332951aae42b79329f743bf1da088cd75d8d4d9acc18fbcbd84f26c1af4e/mypy-1.19.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:34c81968774648ab5ac09c29a375fdede03ba253f8f8287847bd480782f73a6a", size = 13834976, upload-time = "2025-12-15T05:03:08.786Z"}, + {url = "https://files.pythonhosted.org/packages/6f/63/e7493e5f90e1e085c562bb06e2eb32cae27c5057b9653348d38b47daaecc/mypy-1.19.1-cp312-cp312-win_amd64.whl", hash = "sha256:b10e7c2cd7870ba4ad9b2d8a6102eb5ffc1f16ca35e3de6bfa390c1113029d13", size = 10118104, upload-time = "2025-12-15T05:03:10.834Z"}, + {url = "https://files.pythonhosted.org/packages/de/9f/a6abae693f7a0c697dbb435aac52e958dc8da44e92e08ba88d2e42326176/mypy-1.19.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e3157c7594ff2ef1634ee058aafc56a82db665c9438fd41b390f3bde1ab12250", size = 13201927, upload-time = "2025-12-15T05:02:29.138Z"}, + {url = "https://files.pythonhosted.org/packages/9a/a4/45c35ccf6e1c65afc23a069f50e2c66f46bd3798cbe0d680c12d12935caa/mypy-1.19.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bdb12f69bcc02700c2b47e070238f42cb87f18c0bc1fc4cdb4fb2bc5fd7a3b8b", size = 12206730, upload-time = "2025-12-15T05:03:01.325Z"}, + {url = "https://files.pythonhosted.org/packages/05/bb/cdcf89678e26b187650512620eec8368fded4cfd99cfcb431e4cdfd19dec/mypy-1.19.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f859fb09d9583a985be9a493d5cfc5515b56b08f7447759a0c5deaf68d80506e", size = 12724581, upload-time = "2025-12-15T05:03:20.087Z"}, + {url = "https://files.pythonhosted.org/packages/d1/32/dd260d52babf67bad8e6770f8e1102021877ce0edea106e72df5626bb0ec/mypy-1.19.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c9a6538e0415310aad77cb94004ca6482330fece18036b5f360b62c45814c4ef", size = 13616252, upload-time = "2025-12-15T05:02:49.036Z"}, + {url = "https://files.pythonhosted.org/packages/71/d0/5e60a9d2e3bd48432ae2b454b7ef2b62a960ab51292b1eda2a95edd78198/mypy-1.19.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:da4869fc5e7f62a88f3fe0b5c919d1d9f7ea3cef92d3689de2823fd27e40aa75", size = 13840848, upload-time = "2025-12-15T05:02:55.95Z"}, + {url = "https://files.pythonhosted.org/packages/98/76/d32051fa65ecf6cc8c6610956473abdc9b4c43301107476ac03559507843/mypy-1.19.1-cp313-cp313-win_amd64.whl", hash = "sha256:016f2246209095e8eda7538944daa1d60e1e8134d98983b9fc1e92c1fc0cb8dd", size = 10135510, upload-time = "2025-12-15T05:02:58.438Z"}, + {url = "https://files.pythonhosted.org/packages/8d/f4/4ce9a05ce5ded1de3ec1c1d96cf9f9504a04e54ce0ed55cfa38619a32b8d/mypy-1.19.1-py3-none-any.whl", hash = "sha256:f1235f5ea01b7db5468d53ece6aaddf1ad0b88d9e7462b86ef96fe04995d7247", size = 2471239, upload-time = "2025-12-15T05:03:07.248Z"} ] [[package]] name = "mypy-extensions" +sdist = {url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343, upload-time = "2025-04-22T14:54:24.164Z"} +source = {registry = "https://pypi.org/simple"} version = "1.1.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343, upload-time = "2025-04-22T14:54:24.164Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" }, + {url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z"} ] [[package]] -name = "myst-parser" -version = "5.0.0" -source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "docutils" }, - { name = "jinja2" }, - { name = "markdown-it-py" }, - { name = "mdit-py-plugins" }, - { name = "pyyaml" }, - { name = "sphinx" }, + {name = "docutils"}, + {name = "jinja2"}, + {name = "markdown-it-py"}, + {name = "mdit-py-plugins"}, + {name = "pyyaml"}, + {name = "sphinx"} ] -sdist = { url = "https://files.pythonhosted.org/packages/33/fa/7b45eef11b7971f0beb29d27b7bfe0d747d063aa29e170d9edd004733c8a/myst_parser-5.0.0.tar.gz", hash = "sha256:f6f231452c56e8baa662cc352c548158f6a16fcbd6e3800fc594978002b94f3a", size = 98535, upload-time = "2026-01-15T09:08:18.036Z" } +name = "myst-parser" +sdist = {url = "https://files.pythonhosted.org/packages/33/fa/7b45eef11b7971f0beb29d27b7bfe0d747d063aa29e170d9edd004733c8a/myst_parser-5.0.0.tar.gz", hash = "sha256:f6f231452c56e8baa662cc352c548158f6a16fcbd6e3800fc594978002b94f3a", size = 98535, upload-time = "2026-01-15T09:08:18.036Z"} +source = {registry = "https://pypi.org/simple"} +version = "5.0.0" wheels = [ - { url = "https://files.pythonhosted.org/packages/d3/ac/686789b9145413f1a61878c407210e41bfdb097976864e0913078b24098c/myst_parser-5.0.0-py3-none-any.whl", hash = "sha256:ab31e516024918296e169139072b81592336f2fef55b8986aa31c9f04b5f7211", size = 84533, upload-time = "2026-01-15T09:08:16.788Z" }, + {url = "https://files.pythonhosted.org/packages/d3/ac/686789b9145413f1a61878c407210e41bfdb097976864e0913078b24098c/myst_parser-5.0.0-py3-none-any.whl", hash = "sha256:ab31e516024918296e169139072b81592336f2fef55b8986aa31c9f04b5f7211", size = 84533, upload-time = "2026-01-15T09:08:16.788Z"} ] [[package]] name = "nodeenv" +sdist = {url = "https://files.pythonhosted.org/packages/24/bf/d1bda4f6168e0b2e9e5958945e01910052158313224ada5ce1fb2e1113b8/nodeenv-1.10.0.tar.gz", hash = "sha256:996c191ad80897d076bdfba80a41994c2b47c68e224c542b48feba42ba00f8bb", size = 55611, upload-time = "2025-12-20T14:08:54.006Z"} +source = {registry = "https://pypi.org/simple"} version = "1.10.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/24/bf/d1bda4f6168e0b2e9e5958945e01910052158313224ada5ce1fb2e1113b8/nodeenv-1.10.0.tar.gz", hash = "sha256:996c191ad80897d076bdfba80a41994c2b47c68e224c542b48feba42ba00f8bb", size = 55611, upload-time = "2025-12-20T14:08:54.006Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl", hash = "sha256:5bb13e3eed2923615535339b3c620e76779af4cb4c6a90deccc9e36b274d3827", size = 23438, upload-time = "2025-12-20T14:08:52.782Z" }, + {url = "https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl", hash = "sha256:5bb13e3eed2923615535339b3c620e76779af4cb4c6a90deccc9e36b274d3827", size = 23438, upload-time = "2025-12-20T14:08:52.782Z"} ] [[package]] -name = "numba" -version = "0.64.0" -source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "llvmlite" }, - { name = "numpy" }, + {name = "llvmlite"}, + {name = "numpy"} ] -sdist = { url = "https://files.pythonhosted.org/packages/23/c9/a0fb41787d01d621046138da30f6c2100d80857bf34b3390dd68040f27a3/numba-0.64.0.tar.gz", hash = "sha256:95e7300af648baa3308127b1955b52ce6d11889d16e8cfe637b4f85d2fca52b1", size = 2765679, upload-time = "2026-02-18T18:41:20.974Z" } +name = "numba" +sdist = {url = "https://files.pythonhosted.org/packages/23/c9/a0fb41787d01d621046138da30f6c2100d80857bf34b3390dd68040f27a3/numba-0.64.0.tar.gz", hash = "sha256:95e7300af648baa3308127b1955b52ce6d11889d16e8cfe637b4f85d2fca52b1", size = 2765679, upload-time = "2026-02-18T18:41:20.974Z"} +source = {registry = "https://pypi.org/simple"} +version = "0.64.0" wheels = [ - { url = "https://files.pythonhosted.org/packages/70/a6/9fc52cb4f0d5e6d8b5f4d81615bc01012e3cf24e1052a60f17a68deb8092/numba-0.64.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:69440a8e8bc1a81028446f06b363e28635aa67bd51b1e498023f03b812e0ce68", size = 2683418, upload-time = "2026-02-18T18:40:59.886Z" }, - { url = "https://files.pythonhosted.org/packages/9b/89/1a74ea99b180b7a5587b0301ed1b183a2937c4b4b67f7994689b5d36fc34/numba-0.64.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f13721011f693ba558b8dd4e4db7f2640462bba1b855bdc804be45bbeb55031a", size = 3804087, upload-time = "2026-02-18T18:41:01.699Z" }, - { url = "https://files.pythonhosted.org/packages/91/e1/583c647404b15f807410510fec1eb9b80cb8474165940b7749f026f21cbc/numba-0.64.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e0b180b1133f2b5d8b3f09d96b6d7a9e51a7da5dda3c09e998b5bcfac85d222c", size = 3504309, upload-time = "2026-02-18T18:41:03.252Z" }, - { url = "https://files.pythonhosted.org/packages/85/23/0fce5789b8a5035e7ace21216a468143f3144e02013252116616c58339aa/numba-0.64.0-cp312-cp312-win_amd64.whl", hash = "sha256:e63dc94023b47894849b8b106db28ccb98b49d5498b98878fac1a38f83ac007a", size = 2752740, upload-time = "2026-02-18T18:41:05.097Z" }, - { url = "https://files.pythonhosted.org/packages/52/80/2734de90f9300a6e2503b35ee50d9599926b90cbb7ac54f9e40074cd07f1/numba-0.64.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:3bab2c872194dcd985f1153b70782ec0fbbe348fffef340264eacd3a76d59fd6", size = 2683392, upload-time = "2026-02-18T18:41:06.563Z" }, - { url = "https://files.pythonhosted.org/packages/42/e8/14b5853ebefd5b37723ef365c5318a30ce0702d39057eaa8d7d76392859d/numba-0.64.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:703a246c60832cad231d2e73c1182f25bf3cc8b699759ec8fe58a2dbc689a70c", size = 3812245, upload-time = "2026-02-18T18:41:07.963Z" }, - { url = "https://files.pythonhosted.org/packages/8a/a2/f60dc6c96d19b7185144265a5fbf01c14993d37ff4cd324b09d0212aa7ce/numba-0.64.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7e2e49a7900ee971d32af7609adc0cfe6aa7477c6f6cccdf6d8138538cf7756f", size = 3511328, upload-time = "2026-02-18T18:41:09.504Z" }, - { url = "https://files.pythonhosted.org/packages/9c/2a/fe7003ea7e7237ee7014f8eaeeb7b0d228a2db22572ca85bab2648cf52cb/numba-0.64.0-cp313-cp313-win_amd64.whl", hash = "sha256:396f43c3f77e78d7ec84cdfc6b04969c78f8f169351b3c4db814b97e7acf4245", size = 2752668, upload-time = "2026-02-18T18:41:11.455Z" }, + {url = "https://files.pythonhosted.org/packages/70/a6/9fc52cb4f0d5e6d8b5f4d81615bc01012e3cf24e1052a60f17a68deb8092/numba-0.64.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:69440a8e8bc1a81028446f06b363e28635aa67bd51b1e498023f03b812e0ce68", size = 2683418, upload-time = "2026-02-18T18:40:59.886Z"}, + {url = "https://files.pythonhosted.org/packages/9b/89/1a74ea99b180b7a5587b0301ed1b183a2937c4b4b67f7994689b5d36fc34/numba-0.64.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f13721011f693ba558b8dd4e4db7f2640462bba1b855bdc804be45bbeb55031a", size = 3804087, upload-time = "2026-02-18T18:41:01.699Z"}, + {url = "https://files.pythonhosted.org/packages/91/e1/583c647404b15f807410510fec1eb9b80cb8474165940b7749f026f21cbc/numba-0.64.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e0b180b1133f2b5d8b3f09d96b6d7a9e51a7da5dda3c09e998b5bcfac85d222c", size = 3504309, upload-time = "2026-02-18T18:41:03.252Z"}, + {url = "https://files.pythonhosted.org/packages/85/23/0fce5789b8a5035e7ace21216a468143f3144e02013252116616c58339aa/numba-0.64.0-cp312-cp312-win_amd64.whl", hash = "sha256:e63dc94023b47894849b8b106db28ccb98b49d5498b98878fac1a38f83ac007a", size = 2752740, upload-time = "2026-02-18T18:41:05.097Z"}, + {url = "https://files.pythonhosted.org/packages/52/80/2734de90f9300a6e2503b35ee50d9599926b90cbb7ac54f9e40074cd07f1/numba-0.64.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:3bab2c872194dcd985f1153b70782ec0fbbe348fffef340264eacd3a76d59fd6", size = 2683392, upload-time = "2026-02-18T18:41:06.563Z"}, + {url = "https://files.pythonhosted.org/packages/42/e8/14b5853ebefd5b37723ef365c5318a30ce0702d39057eaa8d7d76392859d/numba-0.64.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:703a246c60832cad231d2e73c1182f25bf3cc8b699759ec8fe58a2dbc689a70c", size = 3812245, upload-time = "2026-02-18T18:41:07.963Z"}, + {url = "https://files.pythonhosted.org/packages/8a/a2/f60dc6c96d19b7185144265a5fbf01c14993d37ff4cd324b09d0212aa7ce/numba-0.64.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7e2e49a7900ee971d32af7609adc0cfe6aa7477c6f6cccdf6d8138538cf7756f", size = 3511328, upload-time = "2026-02-18T18:41:09.504Z"}, + {url = "https://files.pythonhosted.org/packages/9c/2a/fe7003ea7e7237ee7014f8eaeeb7b0d228a2db22572ca85bab2648cf52cb/numba-0.64.0-cp313-cp313-win_amd64.whl", hash = "sha256:396f43c3f77e78d7ec84cdfc6b04969c78f8f169351b3c4db814b97e7acf4245", size = 2752668, upload-time = "2026-02-18T18:41:11.455Z"} ] [[package]] name = "numpy" +sdist = {url = "https://files.pythonhosted.org/packages/57/fd/0005efbd0af48e55eb3c7208af93f2862d4b1a56cd78e84309a2d959208d/numpy-2.4.2.tar.gz", hash = "sha256:659a6107e31a83c4e33f763942275fd278b21d095094044eb35569e86a21ddae", size = 20723651, upload-time = "2026-01-31T23:13:10.135Z"} +source = {registry = "https://pypi.org/simple"} version = "2.4.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/57/fd/0005efbd0af48e55eb3c7208af93f2862d4b1a56cd78e84309a2d959208d/numpy-2.4.2.tar.gz", hash = "sha256:659a6107e31a83c4e33f763942275fd278b21d095094044eb35569e86a21ddae", size = 20723651, upload-time = "2026-01-31T23:13:10.135Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/51/6e/6f394c9c77668153e14d4da83bcc247beb5952f6ead7699a1a2992613bea/numpy-2.4.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:21982668592194c609de53ba4933a7471880ccbaadcc52352694a59ecc860b3a", size = 16667963, upload-time = "2026-01-31T23:10:52.147Z" }, - { url = "https://files.pythonhosted.org/packages/1f/f8/55483431f2b2fd015ae6ed4fe62288823ce908437ed49db5a03d15151678/numpy-2.4.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40397bda92382fcec844066efb11f13e1c9a3e2a8e8f318fb72ed8b6db9f60f1", size = 14693571, upload-time = "2026-01-31T23:10:54.789Z" }, - { url = "https://files.pythonhosted.org/packages/2f/20/18026832b1845cdc82248208dd929ca14c9d8f2bac391f67440707fff27c/numpy-2.4.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:b3a24467af63c67829bfaa61eecf18d5432d4f11992688537be59ecd6ad32f5e", size = 5203469, upload-time = "2026-01-31T23:10:57.343Z" }, - { url = "https://files.pythonhosted.org/packages/7d/33/2eb97c8a77daaba34eaa3fa7241a14ac5f51c46a6bd5911361b644c4a1e2/numpy-2.4.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:805cc8de9fd6e7a22da5aed858e0ab16be5a4db6c873dde1d7451c541553aa27", size = 6550820, upload-time = "2026-01-31T23:10:59.429Z" }, - { url = "https://files.pythonhosted.org/packages/b1/91/b97fdfd12dc75b02c44e26c6638241cc004d4079a0321a69c62f51470c4c/numpy-2.4.2-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6d82351358ffbcdcd7b686b90742a9b86632d6c1c051016484fa0b326a0a1548", size = 15663067, upload-time = "2026-01-31T23:11:01.291Z" }, - { url = "https://files.pythonhosted.org/packages/f5/c6/a18e59f3f0b8071cc85cbc8d80cd02d68aa9710170b2553a117203d46936/numpy-2.4.2-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e35d3e0144137d9fdae62912e869136164534d64a169f86438bc9561b6ad49f", size = 16619782, upload-time = "2026-01-31T23:11:03.669Z" }, - { url = "https://files.pythonhosted.org/packages/b7/83/9751502164601a79e18847309f5ceec0b1446d7b6aa12305759b72cf98b2/numpy-2.4.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:adb6ed2ad29b9e15321d167d152ee909ec73395901b70936f029c3bc6d7f4460", size = 17013128, upload-time = "2026-01-31T23:11:05.913Z" }, - { url = "https://files.pythonhosted.org/packages/61/c4/c4066322256ec740acc1c8923a10047818691d2f8aec254798f3dd90f5f2/numpy-2.4.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8906e71fd8afcb76580404e2a950caef2685df3d2a57fe82a86ac8d33cc007ba", size = 18345324, upload-time = "2026-01-31T23:11:08.248Z" }, - { url = "https://files.pythonhosted.org/packages/ab/af/6157aa6da728fa4525a755bfad486ae7e3f76d4c1864138003eb84328497/numpy-2.4.2-cp312-cp312-win32.whl", hash = "sha256:ec055f6dae239a6299cace477b479cca2fc125c5675482daf1dd886933a1076f", size = 5960282, upload-time = "2026-01-31T23:11:10.497Z" }, - { url = "https://files.pythonhosted.org/packages/92/0f/7ceaaeaacb40567071e94dbf2c9480c0ae453d5bb4f52bea3892c39dc83c/numpy-2.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:209fae046e62d0ce6435fcfe3b1a10537e858249b3d9b05829e2a05218296a85", size = 12314210, upload-time = "2026-01-31T23:11:12.176Z" }, - { url = "https://files.pythonhosted.org/packages/2f/a3/56c5c604fae6dd40fa2ed3040d005fca97e91bd320d232ac9931d77ba13c/numpy-2.4.2-cp312-cp312-win_arm64.whl", hash = "sha256:fbde1b0c6e81d56f5dccd95dd4a711d9b95df1ae4009a60887e56b27e8d903fa", size = 10220171, upload-time = "2026-01-31T23:11:14.684Z" }, - { url = "https://files.pythonhosted.org/packages/a1/22/815b9fe25d1d7ae7d492152adbc7226d3eff731dffc38fe970589fcaaa38/numpy-2.4.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:25f2059807faea4b077a2b6837391b5d830864b3543627f381821c646f31a63c", size = 16663696, upload-time = "2026-01-31T23:11:17.516Z" }, - { url = "https://files.pythonhosted.org/packages/09/f0/817d03a03f93ba9c6c8993de509277d84e69f9453601915e4a69554102a1/numpy-2.4.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bd3a7a9f5847d2fb8c2c6d1c862fa109c31a9abeca1a3c2bd5a64572955b2979", size = 14688322, upload-time = "2026-01-31T23:11:19.883Z" }, - { url = "https://files.pythonhosted.org/packages/da/b4/f805ab79293c728b9a99438775ce51885fd4f31b76178767cfc718701a39/numpy-2.4.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:8e4549f8a3c6d13d55041925e912bfd834285ef1dd64d6bc7d542583355e2e98", size = 5198157, upload-time = "2026-01-31T23:11:22.375Z" }, - { url = "https://files.pythonhosted.org/packages/74/09/826e4289844eccdcd64aac27d13b0fd3f32039915dd5b9ba01baae1f436c/numpy-2.4.2-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:aea4f66ff44dfddf8c2cffd66ba6538c5ec67d389285292fe428cb2c738c8aef", size = 6546330, upload-time = "2026-01-31T23:11:23.958Z" }, - { url = "https://files.pythonhosted.org/packages/19/fb/cbfdbfa3057a10aea5422c558ac57538e6acc87ec1669e666d32ac198da7/numpy-2.4.2-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c3cd545784805de05aafe1dde61752ea49a359ccba9760c1e5d1c88a93bbf2b7", size = 15660968, upload-time = "2026-01-31T23:11:25.713Z" }, - { url = "https://files.pythonhosted.org/packages/04/dc/46066ce18d01645541f0186877377b9371b8fa8017fa8262002b4ef22612/numpy-2.4.2-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d0d9b7c93578baafcbc5f0b83eaf17b79d345c6f36917ba0c67f45226911d499", size = 16607311, upload-time = "2026-01-31T23:11:28.117Z" }, - { url = "https://files.pythonhosted.org/packages/14/d9/4b5adfc39a43fa6bf918c6d544bc60c05236cc2f6339847fc5b35e6cb5b0/numpy-2.4.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f74f0f7779cc7ae07d1810aab8ac6b1464c3eafb9e283a40da7309d5e6e48fbb", size = 17012850, upload-time = "2026-01-31T23:11:30.888Z" }, - { url = "https://files.pythonhosted.org/packages/b7/20/adb6e6adde6d0130046e6fdfb7675cc62bc2f6b7b02239a09eb58435753d/numpy-2.4.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c7ac672d699bf36275c035e16b65539931347d68b70667d28984c9fb34e07fa7", size = 18334210, upload-time = "2026-01-31T23:11:33.214Z" }, - { url = "https://files.pythonhosted.org/packages/78/0e/0a73b3dff26803a8c02baa76398015ea2a5434d9b8265a7898a6028c1591/numpy-2.4.2-cp313-cp313-win32.whl", hash = "sha256:8e9afaeb0beff068b4d9cd20d322ba0ee1cecfb0b08db145e4ab4dd44a6b5110", size = 5958199, upload-time = "2026-01-31T23:11:35.385Z" }, - { url = "https://files.pythonhosted.org/packages/43/bc/6352f343522fcb2c04dbaf94cb30cca6fd32c1a750c06ad6231b4293708c/numpy-2.4.2-cp313-cp313-win_amd64.whl", hash = "sha256:7df2de1e4fba69a51c06c28f5a3de36731eb9639feb8e1cf7e4a7b0daf4cf622", size = 12310848, upload-time = "2026-01-31T23:11:38.001Z" }, - { url = "https://files.pythonhosted.org/packages/6e/8d/6da186483e308da5da1cc6918ce913dcfe14ffde98e710bfeff2a6158d4e/numpy-2.4.2-cp313-cp313-win_arm64.whl", hash = "sha256:0fece1d1f0a89c16b03442eae5c56dc0be0c7883b5d388e0c03f53019a4bfd71", size = 10221082, upload-time = "2026-01-31T23:11:40.392Z" }, - { url = "https://files.pythonhosted.org/packages/25/a1/9510aa43555b44781968935c7548a8926274f815de42ad3997e9e83680dd/numpy-2.4.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5633c0da313330fd20c484c78cdd3f9b175b55e1a766c4a174230c6b70ad8262", size = 14815866, upload-time = "2026-01-31T23:11:42.495Z" }, - { url = "https://files.pythonhosted.org/packages/36/30/6bbb5e76631a5ae46e7923dd16ca9d3f1c93cfa8d4ed79a129814a9d8db3/numpy-2.4.2-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:d9f64d786b3b1dd742c946c42d15b07497ed14af1a1f3ce840cce27daa0ce913", size = 5325631, upload-time = "2026-01-31T23:11:44.7Z" }, - { url = "https://files.pythonhosted.org/packages/46/00/3a490938800c1923b567b3a15cd17896e68052e2145d8662aaf3e1ffc58f/numpy-2.4.2-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:b21041e8cb6a1eb5312dd1d2f80a94d91efffb7a06b70597d44f1bd2dfc315ab", size = 6646254, upload-time = "2026-01-31T23:11:46.341Z" }, - { url = "https://files.pythonhosted.org/packages/d3/e9/fac0890149898a9b609caa5af7455a948b544746e4b8fe7c212c8edd71f8/numpy-2.4.2-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:00ab83c56211a1d7c07c25e3217ea6695e50a3e2f255053686b081dc0b091a82", size = 15720138, upload-time = "2026-01-31T23:11:48.082Z" }, - { url = "https://files.pythonhosted.org/packages/ea/5c/08887c54e68e1e28df53709f1893ce92932cc6f01f7c3d4dc952f61ffd4e/numpy-2.4.2-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2fb882da679409066b4603579619341c6d6898fc83a8995199d5249f986e8e8f", size = 16655398, upload-time = "2026-01-31T23:11:50.293Z" }, - { url = "https://files.pythonhosted.org/packages/4d/89/253db0fa0e66e9129c745e4ef25631dc37d5f1314dad2b53e907b8538e6d/numpy-2.4.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:66cb9422236317f9d44b67b4d18f44efe6e9c7f8794ac0462978513359461554", size = 17079064, upload-time = "2026-01-31T23:11:52.927Z" }, - { url = "https://files.pythonhosted.org/packages/2a/d5/cbade46ce97c59c6c3da525e8d95b7abe8a42974a1dc5c1d489c10433e88/numpy-2.4.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:0f01dcf33e73d80bd8dc0f20a71303abbafa26a19e23f6b68d1aa9990af90257", size = 18379680, upload-time = "2026-01-31T23:11:55.22Z" }, - { url = "https://files.pythonhosted.org/packages/40/62/48f99ae172a4b63d981babe683685030e8a3df4f246c893ea5c6ef99f018/numpy-2.4.2-cp313-cp313t-win32.whl", hash = "sha256:52b913ec40ff7ae845687b0b34d8d93b60cb66dcee06996dd5c99f2fc9328657", size = 6082433, upload-time = "2026-01-31T23:11:58.096Z" }, - { url = "https://files.pythonhosted.org/packages/07/38/e054a61cfe48ad9f1ed0d188e78b7e26859d0b60ef21cd9de4897cdb5326/numpy-2.4.2-cp313-cp313t-win_amd64.whl", hash = "sha256:5eea80d908b2c1f91486eb95b3fb6fab187e569ec9752ab7d9333d2e66bf2d6b", size = 12451181, upload-time = "2026-01-31T23:11:59.782Z" }, - { url = "https://files.pythonhosted.org/packages/6e/a4/a05c3a6418575e185dd84d0b9680b6bb2e2dc3e4202f036b7b4e22d6e9dc/numpy-2.4.2-cp313-cp313t-win_arm64.whl", hash = "sha256:fd49860271d52127d61197bb50b64f58454e9f578cb4b2c001a6de8b1f50b0b1", size = 10290756, upload-time = "2026-01-31T23:12:02.438Z" }, +wheels = [ + {url = "https://files.pythonhosted.org/packages/51/6e/6f394c9c77668153e14d4da83bcc247beb5952f6ead7699a1a2992613bea/numpy-2.4.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:21982668592194c609de53ba4933a7471880ccbaadcc52352694a59ecc860b3a", size = 16667963, upload-time = "2026-01-31T23:10:52.147Z"}, + {url = "https://files.pythonhosted.org/packages/1f/f8/55483431f2b2fd015ae6ed4fe62288823ce908437ed49db5a03d15151678/numpy-2.4.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40397bda92382fcec844066efb11f13e1c9a3e2a8e8f318fb72ed8b6db9f60f1", size = 14693571, upload-time = "2026-01-31T23:10:54.789Z"}, + {url = "https://files.pythonhosted.org/packages/2f/20/18026832b1845cdc82248208dd929ca14c9d8f2bac391f67440707fff27c/numpy-2.4.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:b3a24467af63c67829bfaa61eecf18d5432d4f11992688537be59ecd6ad32f5e", size = 5203469, upload-time = "2026-01-31T23:10:57.343Z"}, + {url = "https://files.pythonhosted.org/packages/7d/33/2eb97c8a77daaba34eaa3fa7241a14ac5f51c46a6bd5911361b644c4a1e2/numpy-2.4.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:805cc8de9fd6e7a22da5aed858e0ab16be5a4db6c873dde1d7451c541553aa27", size = 6550820, upload-time = "2026-01-31T23:10:59.429Z"}, + {url = "https://files.pythonhosted.org/packages/b1/91/b97fdfd12dc75b02c44e26c6638241cc004d4079a0321a69c62f51470c4c/numpy-2.4.2-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6d82351358ffbcdcd7b686b90742a9b86632d6c1c051016484fa0b326a0a1548", size = 15663067, upload-time = "2026-01-31T23:11:01.291Z"}, + {url = "https://files.pythonhosted.org/packages/f5/c6/a18e59f3f0b8071cc85cbc8d80cd02d68aa9710170b2553a117203d46936/numpy-2.4.2-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e35d3e0144137d9fdae62912e869136164534d64a169f86438bc9561b6ad49f", size = 16619782, upload-time = "2026-01-31T23:11:03.669Z"}, + {url = "https://files.pythonhosted.org/packages/b7/83/9751502164601a79e18847309f5ceec0b1446d7b6aa12305759b72cf98b2/numpy-2.4.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:adb6ed2ad29b9e15321d167d152ee909ec73395901b70936f029c3bc6d7f4460", size = 17013128, upload-time = "2026-01-31T23:11:05.913Z"}, + {url = "https://files.pythonhosted.org/packages/61/c4/c4066322256ec740acc1c8923a10047818691d2f8aec254798f3dd90f5f2/numpy-2.4.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8906e71fd8afcb76580404e2a950caef2685df3d2a57fe82a86ac8d33cc007ba", size = 18345324, upload-time = "2026-01-31T23:11:08.248Z"}, + {url = "https://files.pythonhosted.org/packages/ab/af/6157aa6da728fa4525a755bfad486ae7e3f76d4c1864138003eb84328497/numpy-2.4.2-cp312-cp312-win32.whl", hash = "sha256:ec055f6dae239a6299cace477b479cca2fc125c5675482daf1dd886933a1076f", size = 5960282, upload-time = "2026-01-31T23:11:10.497Z"}, + {url = "https://files.pythonhosted.org/packages/92/0f/7ceaaeaacb40567071e94dbf2c9480c0ae453d5bb4f52bea3892c39dc83c/numpy-2.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:209fae046e62d0ce6435fcfe3b1a10537e858249b3d9b05829e2a05218296a85", size = 12314210, upload-time = "2026-01-31T23:11:12.176Z"}, + {url = "https://files.pythonhosted.org/packages/2f/a3/56c5c604fae6dd40fa2ed3040d005fca97e91bd320d232ac9931d77ba13c/numpy-2.4.2-cp312-cp312-win_arm64.whl", hash = "sha256:fbde1b0c6e81d56f5dccd95dd4a711d9b95df1ae4009a60887e56b27e8d903fa", size = 10220171, upload-time = "2026-01-31T23:11:14.684Z"}, + {url = "https://files.pythonhosted.org/packages/a1/22/815b9fe25d1d7ae7d492152adbc7226d3eff731dffc38fe970589fcaaa38/numpy-2.4.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:25f2059807faea4b077a2b6837391b5d830864b3543627f381821c646f31a63c", size = 16663696, upload-time = "2026-01-31T23:11:17.516Z"}, + {url = "https://files.pythonhosted.org/packages/09/f0/817d03a03f93ba9c6c8993de509277d84e69f9453601915e4a69554102a1/numpy-2.4.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bd3a7a9f5847d2fb8c2c6d1c862fa109c31a9abeca1a3c2bd5a64572955b2979", size = 14688322, upload-time = "2026-01-31T23:11:19.883Z"}, + {url = "https://files.pythonhosted.org/packages/da/b4/f805ab79293c728b9a99438775ce51885fd4f31b76178767cfc718701a39/numpy-2.4.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:8e4549f8a3c6d13d55041925e912bfd834285ef1dd64d6bc7d542583355e2e98", size = 5198157, upload-time = "2026-01-31T23:11:22.375Z"}, + {url = "https://files.pythonhosted.org/packages/74/09/826e4289844eccdcd64aac27d13b0fd3f32039915dd5b9ba01baae1f436c/numpy-2.4.2-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:aea4f66ff44dfddf8c2cffd66ba6538c5ec67d389285292fe428cb2c738c8aef", size = 6546330, upload-time = "2026-01-31T23:11:23.958Z"}, + {url = "https://files.pythonhosted.org/packages/19/fb/cbfdbfa3057a10aea5422c558ac57538e6acc87ec1669e666d32ac198da7/numpy-2.4.2-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c3cd545784805de05aafe1dde61752ea49a359ccba9760c1e5d1c88a93bbf2b7", size = 15660968, upload-time = "2026-01-31T23:11:25.713Z"}, + {url = "https://files.pythonhosted.org/packages/04/dc/46066ce18d01645541f0186877377b9371b8fa8017fa8262002b4ef22612/numpy-2.4.2-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d0d9b7c93578baafcbc5f0b83eaf17b79d345c6f36917ba0c67f45226911d499", size = 16607311, upload-time = "2026-01-31T23:11:28.117Z"}, + {url = "https://files.pythonhosted.org/packages/14/d9/4b5adfc39a43fa6bf918c6d544bc60c05236cc2f6339847fc5b35e6cb5b0/numpy-2.4.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f74f0f7779cc7ae07d1810aab8ac6b1464c3eafb9e283a40da7309d5e6e48fbb", size = 17012850, upload-time = "2026-01-31T23:11:30.888Z"}, + {url = "https://files.pythonhosted.org/packages/b7/20/adb6e6adde6d0130046e6fdfb7675cc62bc2f6b7b02239a09eb58435753d/numpy-2.4.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c7ac672d699bf36275c035e16b65539931347d68b70667d28984c9fb34e07fa7", size = 18334210, upload-time = "2026-01-31T23:11:33.214Z"}, + {url = "https://files.pythonhosted.org/packages/78/0e/0a73b3dff26803a8c02baa76398015ea2a5434d9b8265a7898a6028c1591/numpy-2.4.2-cp313-cp313-win32.whl", hash = "sha256:8e9afaeb0beff068b4d9cd20d322ba0ee1cecfb0b08db145e4ab4dd44a6b5110", size = 5958199, upload-time = "2026-01-31T23:11:35.385Z"}, + {url = "https://files.pythonhosted.org/packages/43/bc/6352f343522fcb2c04dbaf94cb30cca6fd32c1a750c06ad6231b4293708c/numpy-2.4.2-cp313-cp313-win_amd64.whl", hash = "sha256:7df2de1e4fba69a51c06c28f5a3de36731eb9639feb8e1cf7e4a7b0daf4cf622", size = 12310848, upload-time = "2026-01-31T23:11:38.001Z"}, + {url = "https://files.pythonhosted.org/packages/6e/8d/6da186483e308da5da1cc6918ce913dcfe14ffde98e710bfeff2a6158d4e/numpy-2.4.2-cp313-cp313-win_arm64.whl", hash = "sha256:0fece1d1f0a89c16b03442eae5c56dc0be0c7883b5d388e0c03f53019a4bfd71", size = 10221082, upload-time = "2026-01-31T23:11:40.392Z"}, + {url = "https://files.pythonhosted.org/packages/25/a1/9510aa43555b44781968935c7548a8926274f815de42ad3997e9e83680dd/numpy-2.4.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5633c0da313330fd20c484c78cdd3f9b175b55e1a766c4a174230c6b70ad8262", size = 14815866, upload-time = "2026-01-31T23:11:42.495Z"}, + {url = "https://files.pythonhosted.org/packages/36/30/6bbb5e76631a5ae46e7923dd16ca9d3f1c93cfa8d4ed79a129814a9d8db3/numpy-2.4.2-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:d9f64d786b3b1dd742c946c42d15b07497ed14af1a1f3ce840cce27daa0ce913", size = 5325631, upload-time = "2026-01-31T23:11:44.7Z"}, + {url = "https://files.pythonhosted.org/packages/46/00/3a490938800c1923b567b3a15cd17896e68052e2145d8662aaf3e1ffc58f/numpy-2.4.2-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:b21041e8cb6a1eb5312dd1d2f80a94d91efffb7a06b70597d44f1bd2dfc315ab", size = 6646254, upload-time = "2026-01-31T23:11:46.341Z"}, + {url = "https://files.pythonhosted.org/packages/d3/e9/fac0890149898a9b609caa5af7455a948b544746e4b8fe7c212c8edd71f8/numpy-2.4.2-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:00ab83c56211a1d7c07c25e3217ea6695e50a3e2f255053686b081dc0b091a82", size = 15720138, upload-time = "2026-01-31T23:11:48.082Z"}, + {url = "https://files.pythonhosted.org/packages/ea/5c/08887c54e68e1e28df53709f1893ce92932cc6f01f7c3d4dc952f61ffd4e/numpy-2.4.2-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2fb882da679409066b4603579619341c6d6898fc83a8995199d5249f986e8e8f", size = 16655398, upload-time = "2026-01-31T23:11:50.293Z"}, + {url = "https://files.pythonhosted.org/packages/4d/89/253db0fa0e66e9129c745e4ef25631dc37d5f1314dad2b53e907b8538e6d/numpy-2.4.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:66cb9422236317f9d44b67b4d18f44efe6e9c7f8794ac0462978513359461554", size = 17079064, upload-time = "2026-01-31T23:11:52.927Z"}, + {url = "https://files.pythonhosted.org/packages/2a/d5/cbade46ce97c59c6c3da525e8d95b7abe8a42974a1dc5c1d489c10433e88/numpy-2.4.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:0f01dcf33e73d80bd8dc0f20a71303abbafa26a19e23f6b68d1aa9990af90257", size = 18379680, upload-time = "2026-01-31T23:11:55.22Z"}, + {url = "https://files.pythonhosted.org/packages/40/62/48f99ae172a4b63d981babe683685030e8a3df4f246c893ea5c6ef99f018/numpy-2.4.2-cp313-cp313t-win32.whl", hash = "sha256:52b913ec40ff7ae845687b0b34d8d93b60cb66dcee06996dd5c99f2fc9328657", size = 6082433, upload-time = "2026-01-31T23:11:58.096Z"}, + {url = "https://files.pythonhosted.org/packages/07/38/e054a61cfe48ad9f1ed0d188e78b7e26859d0b60ef21cd9de4897cdb5326/numpy-2.4.2-cp313-cp313t-win_amd64.whl", hash = "sha256:5eea80d908b2c1f91486eb95b3fb6fab187e569ec9752ab7d9333d2e66bf2d6b", size = 12451181, upload-time = "2026-01-31T23:11:59.782Z"}, + {url = "https://files.pythonhosted.org/packages/6e/a4/a05c3a6418575e185dd84d0b9680b6bb2e2dc3e4202f036b7b4e22d6e9dc/numpy-2.4.2-cp313-cp313t-win_arm64.whl", hash = "sha256:fd49860271d52127d61197bb50b64f58454e9f578cb4b2c001a6de8b1f50b0b1", size = 10290756, upload-time = "2026-01-31T23:12:02.438Z"} ] [[package]] -name = "openptv-python" -source = { editable = "." } dependencies = [ - { name = "numba" }, - { name = "numpy" }, - { name = "pyyaml" }, - { name = "scipy" }, + {name = "numba"}, + {name = "numpy"}, + {name = "pyyaml"}, + {name = "scipy"} +] +name = "openptv-python" +source = {editable = "."} + +[package.metadata] +provides-extras = ["docs", "native", "test", "dev"] +requires-dist = [ + {name = "mypy", marker = "extra == 'dev'", specifier = ">=1.19.1"}, + {name = "myst-parser", marker = "extra == 'dev'", specifier = ">=5.0.0"}, + {name = "myst-parser", marker = "extra == 'docs'", specifier = ">=5.0.0"}, + {name = "numba", specifier = ">=0.64.0"}, + {name = "numpy", specifier = ">=2.4.2"}, + {name = "optv", marker = "extra == 'dev'", specifier = ">=0.3.2"}, + {name = "optv", marker = "extra == 'native'", specifier = ">=0.3.2"}, + {name = "pre-commit", marker = "extra == 'dev'", specifier = ">=4.5.1"}, + {name = "pydata-sphinx-theme", marker = "extra == 'dev'", specifier = ">=0.16.1"}, + {name = "pydata-sphinx-theme", marker = "extra == 'docs'", specifier = ">=0.16.1"}, + {name = "pytest", marker = "extra == 'dev'", specifier = ">=9.0.2"}, + {name = "pytest", marker = "extra == 'test'", specifier = ">=9.0.2"}, + {name = "pytest-cov", marker = "extra == 'dev'", specifier = ">=7.0.0"}, + {name = "pytest-cov", marker = "extra == 'test'", specifier = ">=7.0.0"}, + {name = "pyyaml", specifier = ">=6.0.3"}, + {name = "scipy", specifier = ">=1.17.1"}, + {name = "sphinx", marker = "extra == 'dev'", specifier = ">=9.1.0"}, + {name = "sphinx", marker = "extra == 'docs'", specifier = ">=9.1.0"}, + {name = "sphinx-autoapi", marker = "extra == 'dev'", specifier = ">=3.7.0"}, + {name = "sphinx-autoapi", marker = "extra == 'docs'", specifier = ">=3.7.0"}, + {name = "types-pyyaml", marker = "extra == 'dev'", specifier = ">=6.0.12.20250915"} ] [package.optional-dependencies] dev = [ - { name = "mypy" }, - { name = "myst-parser" }, - { name = "optv" }, - { name = "pre-commit" }, - { name = "pydata-sphinx-theme" }, - { name = "pytest" }, - { name = "pytest-cov" }, - { name = "sphinx" }, - { name = "sphinx-autoapi" }, - { name = "types-pyyaml" }, + {name = "mypy"}, + {name = "myst-parser"}, + {name = "optv"}, + {name = "pre-commit"}, + {name = "pydata-sphinx-theme"}, + {name = "pytest"}, + {name = "pytest-cov"}, + {name = "sphinx"}, + {name = "sphinx-autoapi"}, + {name = "types-pyyaml"} ] docs = [ - { name = "myst-parser" }, - { name = "pydata-sphinx-theme" }, - { name = "sphinx" }, - { name = "sphinx-autoapi" }, + {name = "myst-parser"}, + {name = "pydata-sphinx-theme"}, + {name = "sphinx"}, + {name = "sphinx-autoapi"} ] native = [ - { name = "optv" }, + {name = "optv"} ] test = [ - { name = "pytest" }, - { name = "pytest-cov" }, -] - -[package.metadata] -requires-dist = [ - { name = "mypy", marker = "extra == 'dev'", specifier = ">=1.19.1" }, - { name = "myst-parser", marker = "extra == 'dev'", specifier = ">=5.0.0" }, - { name = "myst-parser", marker = "extra == 'docs'", specifier = ">=5.0.0" }, - { name = "numba", specifier = ">=0.64.0" }, - { name = "numpy", specifier = ">=2.4.2" }, - { name = "optv", marker = "extra == 'dev'", specifier = ">=0.3.2" }, - { name = "optv", marker = "extra == 'native'", specifier = ">=0.3.2" }, - { name = "pre-commit", marker = "extra == 'dev'", specifier = ">=4.5.1" }, - { name = "pydata-sphinx-theme", marker = "extra == 'dev'", specifier = ">=0.16.1" }, - { name = "pydata-sphinx-theme", marker = "extra == 'docs'", specifier = ">=0.16.1" }, - { name = "pytest", marker = "extra == 'dev'", specifier = ">=9.0.2" }, - { name = "pytest", marker = "extra == 'test'", specifier = ">=9.0.2" }, - { name = "pytest-cov", marker = "extra == 'dev'", specifier = ">=7.0.0" }, - { name = "pytest-cov", marker = "extra == 'test'", specifier = ">=7.0.0" }, - { name = "pyyaml", specifier = ">=6.0.3" }, - { name = "scipy", specifier = ">=1.17.1" }, - { name = "sphinx", marker = "extra == 'dev'", specifier = ">=9.1.0" }, - { name = "sphinx", marker = "extra == 'docs'", specifier = ">=9.1.0" }, - { name = "sphinx-autoapi", marker = "extra == 'dev'", specifier = ">=3.7.0" }, - { name = "sphinx-autoapi", marker = "extra == 'docs'", specifier = ">=3.7.0" }, - { name = "types-pyyaml", marker = "extra == 'dev'", specifier = ">=6.0.12.20250915" }, + {name = "pytest"}, + {name = "pytest-cov"} ] -provides-extras = ["docs", "native", "test", "dev"] [[package]] -name = "optv" -version = "0.3.2" -source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "numpy" }, - { name = "pyyaml" }, + {name = "numpy"}, + {name = "pyyaml"} ] +name = "optv" +source = {registry = "https://pypi.org/simple"} +version = "0.3.2" wheels = [ - { url = "https://files.pythonhosted.org/packages/93/29/ac2ab83e885a53ae2d4b6537b26c9029ce663618a56eb8e9ab10a0c53330/optv-0.3.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:9cf47f3756a95ce4aad6895ccd7a8ca9fdc51d513900fb68761a16450289f84e", size = 2347739, upload-time = "2026-03-01T23:03:35.73Z" }, - { url = "https://files.pythonhosted.org/packages/07/0a/cca02bf1a478450f1ba2a3e44db4a917b56e15c6eb6274d195ed217c0950/optv-0.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4404e00d0bf0d21dfeaa1595ac453fe66ffed26d889e70f6727630280895916b", size = 2262784, upload-time = "2026-03-01T23:03:39.472Z" }, - { url = "https://files.pythonhosted.org/packages/90/f6/47953d940fc82f78a629e59afe0251f936c49452065715349faf2803cb14/optv-0.3.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9df069358f9283560ba707e23c87f2071ad2c1815c201f3891ed95e3216eb457", size = 6179852, upload-time = "2026-03-01T23:03:47.464Z" }, - { url = "https://files.pythonhosted.org/packages/28/3b/1c38b9b6b9d19e432cd1dbba8c2bd658fd0b852692cd1c9574a67b3bc58d/optv-0.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:555c6a06ab28e1ae93d9c3d21ec89a9a2d6bb573ebf09efd1606553ccf13eeb1", size = 12215450, upload-time = "2026-03-01T23:03:59.265Z" }, - { url = "https://files.pythonhosted.org/packages/c0/6b/6afeed9e26263fd3a6f1cded81efc031c43985aee42bea7686dbc554f7cc/optv-0.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:e36e3093abdee39a35680e4a2304e5fdcddbb392096741326b7632a13c65b991", size = 1731896, upload-time = "2026-03-01T23:04:03.206Z" }, - { url = "https://files.pythonhosted.org/packages/c6/40/9b8e9edda07f9f5f9d2469cf26edc8ee674a02069145f5d21b8a9b789ad0/optv-0.3.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4c05949ea6c15dcc8fcdb36ca4e935aff273ac81d80405930e7a2b64ff2a710e", size = 2342514, upload-time = "2026-03-01T23:04:06.56Z" }, - { url = "https://files.pythonhosted.org/packages/b4/76/30921ef229074276aebfdd18f0603d7a14461c3cf74a9964678bbf82d80a/optv-0.3.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a549e36df4d3f1c3f30b7a667e6595f52da50bcb3727f57de045a652ab9a1194", size = 2257415, upload-time = "2026-03-01T23:04:10.218Z" }, - { url = "https://files.pythonhosted.org/packages/ae/e1/bbeb1dd50759ec6c88d074570a1790741fb373fde4c66915980e192eb51c/optv-0.3.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3fdc240569f2804c6d74951a27e0b7f168e85de2a9e4ab5b09a290efc8d70417", size = 6070665, upload-time = "2026-03-01T23:04:17.879Z" }, - { url = "https://files.pythonhosted.org/packages/f3/3a/76b420f2c7937fb4b5231a05dacc45eb66c46d88515b56b96701e89b2708/optv-0.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:80529429fed3f984cd400e77fcef986b7409322b0b14fdd6b8aad47fde69cef4", size = 5968060, upload-time = "2026-03-01T23:04:25.215Z" }, - { url = "https://files.pythonhosted.org/packages/31/d3/475e6f830eca6f886f5d0d74e542d059978d6dabfc16ddd61f004ea727d8/optv-0.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:c863518e8c91e018887f24d0cf99ca1720f8f2155fe92b9b46fa794e97e43259", size = 1728112, upload-time = "2026-03-01T23:04:28.062Z" }, + {url = "https://files.pythonhosted.org/packages/93/29/ac2ab83e885a53ae2d4b6537b26c9029ce663618a56eb8e9ab10a0c53330/optv-0.3.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:9cf47f3756a95ce4aad6895ccd7a8ca9fdc51d513900fb68761a16450289f84e", size = 2347739, upload-time = "2026-03-01T23:03:35.73Z"}, + {url = "https://files.pythonhosted.org/packages/07/0a/cca02bf1a478450f1ba2a3e44db4a917b56e15c6eb6274d195ed217c0950/optv-0.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4404e00d0bf0d21dfeaa1595ac453fe66ffed26d889e70f6727630280895916b", size = 2262784, upload-time = "2026-03-01T23:03:39.472Z"}, + {url = "https://files.pythonhosted.org/packages/90/f6/47953d940fc82f78a629e59afe0251f936c49452065715349faf2803cb14/optv-0.3.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9df069358f9283560ba707e23c87f2071ad2c1815c201f3891ed95e3216eb457", size = 6179852, upload-time = "2026-03-01T23:03:47.464Z"}, + {url = "https://files.pythonhosted.org/packages/28/3b/1c38b9b6b9d19e432cd1dbba8c2bd658fd0b852692cd1c9574a67b3bc58d/optv-0.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:555c6a06ab28e1ae93d9c3d21ec89a9a2d6bb573ebf09efd1606553ccf13eeb1", size = 12215450, upload-time = "2026-03-01T23:03:59.265Z"}, + {url = "https://files.pythonhosted.org/packages/c0/6b/6afeed9e26263fd3a6f1cded81efc031c43985aee42bea7686dbc554f7cc/optv-0.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:e36e3093abdee39a35680e4a2304e5fdcddbb392096741326b7632a13c65b991", size = 1731896, upload-time = "2026-03-01T23:04:03.206Z"}, + {url = "https://files.pythonhosted.org/packages/c6/40/9b8e9edda07f9f5f9d2469cf26edc8ee674a02069145f5d21b8a9b789ad0/optv-0.3.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4c05949ea6c15dcc8fcdb36ca4e935aff273ac81d80405930e7a2b64ff2a710e", size = 2342514, upload-time = "2026-03-01T23:04:06.56Z"}, + {url = "https://files.pythonhosted.org/packages/b4/76/30921ef229074276aebfdd18f0603d7a14461c3cf74a9964678bbf82d80a/optv-0.3.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a549e36df4d3f1c3f30b7a667e6595f52da50bcb3727f57de045a652ab9a1194", size = 2257415, upload-time = "2026-03-01T23:04:10.218Z"}, + {url = "https://files.pythonhosted.org/packages/ae/e1/bbeb1dd50759ec6c88d074570a1790741fb373fde4c66915980e192eb51c/optv-0.3.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3fdc240569f2804c6d74951a27e0b7f168e85de2a9e4ab5b09a290efc8d70417", size = 6070665, upload-time = "2026-03-01T23:04:17.879Z"}, + {url = "https://files.pythonhosted.org/packages/f3/3a/76b420f2c7937fb4b5231a05dacc45eb66c46d88515b56b96701e89b2708/optv-0.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:80529429fed3f984cd400e77fcef986b7409322b0b14fdd6b8aad47fde69cef4", size = 5968060, upload-time = "2026-03-01T23:04:25.215Z"}, + {url = "https://files.pythonhosted.org/packages/31/d3/475e6f830eca6f886f5d0d74e542d059978d6dabfc16ddd61f004ea727d8/optv-0.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:c863518e8c91e018887f24d0cf99ca1720f8f2155fe92b9b46fa794e97e43259", size = 1728112, upload-time = "2026-03-01T23:04:28.062Z"} ] [[package]] name = "packaging" +sdist = {url = "https://files.pythonhosted.org/packages/65/ee/299d360cdc32edc7d2cf530f3accf79c4fca01e96ffc950d8a52213bd8e4/packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4", size = 143416, upload-time = "2026-01-21T20:50:39.064Z"} +source = {registry = "https://pypi.org/simple"} version = "26.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/65/ee/299d360cdc32edc7d2cf530f3accf79c4fca01e96ffc950d8a52213bd8e4/packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4", size = 143416, upload-time = "2026-01-21T20:50:39.064Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529", size = 74366, upload-time = "2026-01-21T20:50:37.788Z" }, + {url = "https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529", size = 74366, upload-time = "2026-01-21T20:50:37.788Z"} ] [[package]] name = "pathspec" +sdist = {url = "https://files.pythonhosted.org/packages/fa/36/e27608899f9b8d4dff0617b2d9ab17ca5608956ca44461ac14ac48b44015/pathspec-1.0.4.tar.gz", hash = "sha256:0210e2ae8a21a9137c0d470578cb0e595af87edaa6ebf12ff176f14a02e0e645", size = 131200, upload-time = "2026-01-27T03:59:46.938Z"} +source = {registry = "https://pypi.org/simple"} version = "1.0.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/fa/36/e27608899f9b8d4dff0617b2d9ab17ca5608956ca44461ac14ac48b44015/pathspec-1.0.4.tar.gz", hash = "sha256:0210e2ae8a21a9137c0d470578cb0e595af87edaa6ebf12ff176f14a02e0e645", size = 131200, upload-time = "2026-01-27T03:59:46.938Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ef/3c/2c197d226f9ea224a9ab8d197933f9da0ae0aac5b6e0f884e2b8d9c8e9f7/pathspec-1.0.4-py3-none-any.whl", hash = "sha256:fb6ae2fd4e7c921a165808a552060e722767cfa526f99ca5156ed2ce45a5c723", size = 55206, upload-time = "2026-01-27T03:59:45.137Z" }, + {url = "https://files.pythonhosted.org/packages/ef/3c/2c197d226f9ea224a9ab8d197933f9da0ae0aac5b6e0f884e2b8d9c8e9f7/pathspec-1.0.4-py3-none-any.whl", hash = "sha256:fb6ae2fd4e7c921a165808a552060e722767cfa526f99ca5156ed2ce45a5c723", size = 55206, upload-time = "2026-01-27T03:59:45.137Z"} ] [[package]] name = "platformdirs" +sdist = {url = "https://files.pythonhosted.org/packages/19/56/8d4c30c8a1d07013911a8fdbd8f89440ef9f08d07a1b50ab8ca8be5a20f9/platformdirs-4.9.4.tar.gz", hash = "sha256:1ec356301b7dc906d83f371c8f487070e99d3ccf9e501686456394622a01a934", size = 28737, upload-time = "2026-03-05T18:34:13.271Z"} +source = {registry = "https://pypi.org/simple"} version = "4.9.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/19/56/8d4c30c8a1d07013911a8fdbd8f89440ef9f08d07a1b50ab8ca8be5a20f9/platformdirs-4.9.4.tar.gz", hash = "sha256:1ec356301b7dc906d83f371c8f487070e99d3ccf9e501686456394622a01a934", size = 28737, upload-time = "2026-03-05T18:34:13.271Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/63/d7/97f7e3a6abb67d8080dd406fd4df842c2be0efaf712d1c899c32a075027c/platformdirs-4.9.4-py3-none-any.whl", hash = "sha256:68a9a4619a666ea6439f2ff250c12a853cd1cbd5158d258bd824a7df6be2f868", size = 21216, upload-time = "2026-03-05T18:34:12.172Z" }, + {url = "https://files.pythonhosted.org/packages/63/d7/97f7e3a6abb67d8080dd406fd4df842c2be0efaf712d1c899c32a075027c/platformdirs-4.9.4-py3-none-any.whl", hash = "sha256:68a9a4619a666ea6439f2ff250c12a853cd1cbd5158d258bd824a7df6be2f868", size = 21216, upload-time = "2026-03-05T18:34:12.172Z"} ] [[package]] name = "pluggy" +sdist = {url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z"} +source = {registry = "https://pypi.org/simple"} version = "1.6.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, + {url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z"} ] [[package]] -name = "pre-commit" -version = "4.5.1" -source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "cfgv" }, - { name = "identify" }, - { name = "nodeenv" }, - { name = "pyyaml" }, - { name = "virtualenv" }, + {name = "cfgv"}, + {name = "identify"}, + {name = "nodeenv"}, + {name = "pyyaml"}, + {name = "virtualenv"} ] -sdist = { url = "https://files.pythonhosted.org/packages/40/f1/6d86a29246dfd2e9b6237f0b5823717f60cad94d47ddc26afa916d21f525/pre_commit-4.5.1.tar.gz", hash = "sha256:eb545fcff725875197837263e977ea257a402056661f09dae08e4b149b030a61", size = 198232, upload-time = "2025-12-16T21:14:33.552Z" } +name = "pre-commit" +sdist = {url = "https://files.pythonhosted.org/packages/40/f1/6d86a29246dfd2e9b6237f0b5823717f60cad94d47ddc26afa916d21f525/pre_commit-4.5.1.tar.gz", hash = "sha256:eb545fcff725875197837263e977ea257a402056661f09dae08e4b149b030a61", size = 198232, upload-time = "2025-12-16T21:14:33.552Z"} +source = {registry = "https://pypi.org/simple"} +version = "4.5.1" wheels = [ - { url = "https://files.pythonhosted.org/packages/5d/19/fd3ef348460c80af7bb4669ea7926651d1f95c23ff2df18b9d24bab4f3fa/pre_commit-4.5.1-py2.py3-none-any.whl", hash = "sha256:3b3afd891e97337708c1674210f8eba659b52a38ea5f822ff142d10786221f77", size = 226437, upload-time = "2025-12-16T21:14:32.409Z" }, + {url = "https://files.pythonhosted.org/packages/5d/19/fd3ef348460c80af7bb4669ea7926651d1f95c23ff2df18b9d24bab4f3fa/pre_commit-4.5.1-py2.py3-none-any.whl", hash = "sha256:3b3afd891e97337708c1674210f8eba659b52a38ea5f822ff142d10786221f77", size = 226437, upload-time = "2025-12-16T21:14:32.409Z"} ] [[package]] -name = "pydata-sphinx-theme" -version = "0.16.1" -source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "accessible-pygments" }, - { name = "babel" }, - { name = "beautifulsoup4" }, - { name = "docutils" }, - { name = "pygments" }, - { name = "sphinx" }, - { name = "typing-extensions" }, + {name = "accessible-pygments"}, + {name = "babel"}, + {name = "beautifulsoup4"}, + {name = "docutils"}, + {name = "pygments"}, + {name = "sphinx"}, + {name = "typing-extensions"} ] -sdist = { url = "https://files.pythonhosted.org/packages/00/20/bb50f9de3a6de69e6abd6b087b52fa2418a0418b19597601605f855ad044/pydata_sphinx_theme-0.16.1.tar.gz", hash = "sha256:a08b7f0b7f70387219dc659bff0893a7554d5eb39b59d3b8ef37b8401b7642d7", size = 2412693, upload-time = "2024-12-17T10:53:39.537Z" } +name = "pydata-sphinx-theme" +sdist = {url = "https://files.pythonhosted.org/packages/00/20/bb50f9de3a6de69e6abd6b087b52fa2418a0418b19597601605f855ad044/pydata_sphinx_theme-0.16.1.tar.gz", hash = "sha256:a08b7f0b7f70387219dc659bff0893a7554d5eb39b59d3b8ef37b8401b7642d7", size = 2412693, upload-time = "2024-12-17T10:53:39.537Z"} +source = {registry = "https://pypi.org/simple"} +version = "0.16.1" wheels = [ - { url = "https://files.pythonhosted.org/packages/e2/0d/8ba33fa83a7dcde13eb3c1c2a0c1cc29950a048bfed6d9b0d8b6bd710b4c/pydata_sphinx_theme-0.16.1-py3-none-any.whl", hash = "sha256:225331e8ac4b32682c18fcac5a57a6f717c4e632cea5dd0e247b55155faeccde", size = 6723264, upload-time = "2024-12-17T10:53:35.645Z" }, + {url = "https://files.pythonhosted.org/packages/e2/0d/8ba33fa83a7dcde13eb3c1c2a0c1cc29950a048bfed6d9b0d8b6bd710b4c/pydata_sphinx_theme-0.16.1-py3-none-any.whl", hash = "sha256:225331e8ac4b32682c18fcac5a57a6f717c4e632cea5dd0e247b55155faeccde", size = 6723264, upload-time = "2024-12-17T10:53:35.645Z"} ] [[package]] name = "pygments" +sdist = {url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z"} +source = {registry = "https://pypi.org/simple"} version = "2.19.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, + {url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z"} ] [[package]] -name = "pytest" -version = "9.0.2" -source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, - { name = "iniconfig" }, - { name = "packaging" }, - { name = "pluggy" }, - { name = "pygments" }, + {name = "colorama", marker = "sys_platform == 'win32'"}, + {name = "iniconfig"}, + {name = "packaging"}, + {name = "pluggy"}, + {name = "pygments"} ] -sdist = { url = "https://files.pythonhosted.org/packages/d1/db/7ef3487e0fb0049ddb5ce41d3a49c235bf9ad299b6a25d5780a89f19230f/pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11", size = 1568901, upload-time = "2025-12-06T21:30:51.014Z" } +name = "pytest" +sdist = {url = "https://files.pythonhosted.org/packages/d1/db/7ef3487e0fb0049ddb5ce41d3a49c235bf9ad299b6a25d5780a89f19230f/pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11", size = 1568901, upload-time = "2025-12-06T21:30:51.014Z"} +source = {registry = "https://pypi.org/simple"} +version = "9.0.2" wheels = [ - { url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801, upload-time = "2025-12-06T21:30:49.154Z" }, + {url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801, upload-time = "2025-12-06T21:30:49.154Z"} ] [[package]] -name = "pytest-cov" -version = "7.0.0" -source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "coverage" }, - { name = "pluggy" }, - { name = "pytest" }, + {name = "coverage"}, + {name = "pluggy"}, + {name = "pytest"} ] -sdist = { url = "https://files.pythonhosted.org/packages/5e/f7/c933acc76f5208b3b00089573cf6a2bc26dc80a8aece8f52bb7d6b1855ca/pytest_cov-7.0.0.tar.gz", hash = "sha256:33c97eda2e049a0c5298e91f519302a1334c26ac65c1a483d6206fd458361af1", size = 54328, upload-time = "2025-09-09T10:57:02.113Z" } +name = "pytest-cov" +sdist = {url = "https://files.pythonhosted.org/packages/5e/f7/c933acc76f5208b3b00089573cf6a2bc26dc80a8aece8f52bb7d6b1855ca/pytest_cov-7.0.0.tar.gz", hash = "sha256:33c97eda2e049a0c5298e91f519302a1334c26ac65c1a483d6206fd458361af1", size = 54328, upload-time = "2025-09-09T10:57:02.113Z"} +source = {registry = "https://pypi.org/simple"} +version = "7.0.0" wheels = [ - { url = "https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl", hash = "sha256:3b8e9558b16cc1479da72058bdecf8073661c7f57f7d3c5f22a1c23507f2d861", size = 22424, upload-time = "2025-09-09T10:57:00.695Z" }, + {url = "https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl", hash = "sha256:3b8e9558b16cc1479da72058bdecf8073661c7f57f7d3c5f22a1c23507f2d861", size = 22424, upload-time = "2025-09-09T10:57:00.695Z"} ] [[package]] -name = "python-discovery" -version = "1.1.1" -source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "filelock" }, - { name = "platformdirs" }, + {name = "filelock"}, + {name = "platformdirs"} ] -sdist = { url = "https://files.pythonhosted.org/packages/ec/67/09765eacf4e44413c4f8943ba5a317fcb9c7b447c3b8b0b7fce7e3090b0b/python_discovery-1.1.1.tar.gz", hash = "sha256:584c08b141c5b7029f206b4e8b78b1a1764b22121e21519b89dec56936e95b0a", size = 56016, upload-time = "2026-03-07T00:00:56.354Z" } +name = "python-discovery" +sdist = {url = "https://files.pythonhosted.org/packages/ec/67/09765eacf4e44413c4f8943ba5a317fcb9c7b447c3b8b0b7fce7e3090b0b/python_discovery-1.1.1.tar.gz", hash = "sha256:584c08b141c5b7029f206b4e8b78b1a1764b22121e21519b89dec56936e95b0a", size = 56016, upload-time = "2026-03-07T00:00:56.354Z"} +source = {registry = "https://pypi.org/simple"} +version = "1.1.1" wheels = [ - { url = "https://files.pythonhosted.org/packages/75/0f/2bf7e3b5a4a65f623cb820feb5793e243fad58ae561015ee15a6152f67a2/python_discovery-1.1.1-py3-none-any.whl", hash = "sha256:69f11073fa2392251e405d4e847d60ffffd25fd762a0dc4d1a7d6b9c3f79f1a3", size = 30732, upload-time = "2026-03-07T00:00:55.143Z" }, + {url = "https://files.pythonhosted.org/packages/75/0f/2bf7e3b5a4a65f623cb820feb5793e243fad58ae561015ee15a6152f67a2/python_discovery-1.1.1-py3-none-any.whl", hash = "sha256:69f11073fa2392251e405d4e847d60ffffd25fd762a0dc4d1a7d6b9c3f79f1a3", size = 30732, upload-time = "2026-03-07T00:00:55.143Z"} ] [[package]] name = "pyyaml" +sdist = {url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z"} +source = {registry = "https://pypi.org/simple"} version = "6.0.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" }, - { url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" }, - { url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" }, - { url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z" }, - { url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z" }, - { url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z" }, - { url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z" }, - { url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z" }, - { url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z" }, - { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" }, - { url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" }, - { url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" }, - { url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" }, - { url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" }, - { url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" }, - { url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" }, - { url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" }, - { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" }, - { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" }, - { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" }, +wheels = [ + {url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z"}, + {url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z"}, + {url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z"}, + {url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z"}, + {url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z"}, + {url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z"}, + {url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z"}, + {url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z"}, + {url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z"}, + {url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z"}, + {url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z"}, + {url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z"}, + {url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z"}, + {url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z"}, + {url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z"}, + {url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z"}, + {url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z"}, + {url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z"}, + {url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z"}, + {url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z"} ] [[package]] -name = "requests" -version = "2.32.5" -source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "certifi" }, - { name = "charset-normalizer" }, - { name = "idna" }, - { name = "urllib3" }, + {name = "certifi"}, + {name = "charset-normalizer"}, + {name = "idna"}, + {name = "urllib3"} ] -sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" } +name = "requests" +sdist = {url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z"} +source = {registry = "https://pypi.org/simple"} +version = "2.32.5" wheels = [ - { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" }, + {url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z"} ] [[package]] name = "roman-numerals" +sdist = {url = "https://files.pythonhosted.org/packages/ae/f9/41dc953bbeb056c17d5f7a519f50fdf010bd0553be2d630bc69d1e022703/roman_numerals-4.1.0.tar.gz", hash = "sha256:1af8b147eb1405d5839e78aeb93131690495fe9da5c91856cb33ad55a7f1e5b2", size = 9077, upload-time = "2025-12-17T18:25:34.381Z"} +source = {registry = "https://pypi.org/simple"} version = "4.1.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ae/f9/41dc953bbeb056c17d5f7a519f50fdf010bd0553be2d630bc69d1e022703/roman_numerals-4.1.0.tar.gz", hash = "sha256:1af8b147eb1405d5839e78aeb93131690495fe9da5c91856cb33ad55a7f1e5b2", size = 9077, upload-time = "2025-12-17T18:25:34.381Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/04/54/6f679c435d28e0a568d8e8a7c0a93a09010818634c3c3907fc98d8983770/roman_numerals-4.1.0-py3-none-any.whl", hash = "sha256:647ba99caddc2cc1e55a51e4360689115551bf4476d90e8162cf8c345fe233c7", size = 7676, upload-time = "2025-12-17T18:25:33.098Z" }, + {url = "https://files.pythonhosted.org/packages/04/54/6f679c435d28e0a568d8e8a7c0a93a09010818634c3c3907fc98d8983770/roman_numerals-4.1.0-py3-none-any.whl", hash = "sha256:647ba99caddc2cc1e55a51e4360689115551bf4476d90e8162cf8c345fe233c7", size = 7676, upload-time = "2025-12-17T18:25:33.098Z"} ] [[package]] +dependencies = [ + {name = "numpy"} +] name = "scipy" +sdist = {url = "https://files.pythonhosted.org/packages/7a/97/5a3609c4f8d58b039179648e62dd220f89864f56f7357f5d4f45c29eb2cc/scipy-1.17.1.tar.gz", hash = "sha256:95d8e012d8cb8816c226aef832200b1d45109ed4464303e997c5b13122b297c0", size = 30573822, upload-time = "2026-02-23T00:26:24.851Z"} +source = {registry = "https://pypi.org/simple"} version = "1.17.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "numpy" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/7a/97/5a3609c4f8d58b039179648e62dd220f89864f56f7357f5d4f45c29eb2cc/scipy-1.17.1.tar.gz", hash = "sha256:95d8e012d8cb8816c226aef832200b1d45109ed4464303e997c5b13122b297c0", size = 30573822, upload-time = "2026-02-23T00:26:24.851Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/35/48/b992b488d6f299dbe3f11a20b24d3dda3d46f1a635ede1c46b5b17a7b163/scipy-1.17.1-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:35c3a56d2ef83efc372eaec584314bd0ef2e2f0d2adb21c55e6ad5b344c0dcb8", size = 31610954, upload-time = "2026-02-23T00:17:49.855Z" }, - { url = "https://files.pythonhosted.org/packages/b2/02/cf107b01494c19dc100f1d0b7ac3cc08666e96ba2d64db7626066cee895e/scipy-1.17.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:fcb310ddb270a06114bb64bbe53c94926b943f5b7f0842194d585c65eb4edd76", size = 28172662, upload-time = "2026-02-23T00:18:01.64Z" }, - { url = "https://files.pythonhosted.org/packages/cf/a9/599c28631bad314d219cf9ffd40e985b24d603fc8a2f4ccc5ae8419a535b/scipy-1.17.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:cc90d2e9c7e5c7f1a482c9875007c095c3194b1cfedca3c2f3291cdc2bc7c086", size = 20344366, upload-time = "2026-02-23T00:18:12.015Z" }, - { url = "https://files.pythonhosted.org/packages/35/f5/906eda513271c8deb5af284e5ef0206d17a96239af79f9fa0aebfe0e36b4/scipy-1.17.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:c80be5ede8f3f8eded4eff73cc99a25c388ce98e555b17d31da05287015ffa5b", size = 22704017, upload-time = "2026-02-23T00:18:21.502Z" }, - { url = "https://files.pythonhosted.org/packages/da/34/16f10e3042d2f1d6b66e0428308ab52224b6a23049cb2f5c1756f713815f/scipy-1.17.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e19ebea31758fac5893a2ac360fedd00116cbb7628e650842a6691ba7ca28a21", size = 32927842, upload-time = "2026-02-23T00:18:35.367Z" }, - { url = "https://files.pythonhosted.org/packages/01/8e/1e35281b8ab6d5d72ebe9911edcdffa3f36b04ed9d51dec6dd140396e220/scipy-1.17.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:02ae3b274fde71c5e92ac4d54bc06c42d80e399fec704383dcd99b301df37458", size = 35235890, upload-time = "2026-02-23T00:18:49.188Z" }, - { url = "https://files.pythonhosted.org/packages/c5/5c/9d7f4c88bea6e0d5a4f1bc0506a53a00e9fcb198de372bfe4d3652cef482/scipy-1.17.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8a604bae87c6195d8b1045eddece0514d041604b14f2727bbc2b3020172045eb", size = 35003557, upload-time = "2026-02-23T00:18:54.74Z" }, - { url = "https://files.pythonhosted.org/packages/65/94/7698add8f276dbab7a9de9fb6b0e02fc13ee61d51c7c3f85ac28b65e1239/scipy-1.17.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f590cd684941912d10becc07325a3eeb77886fe981415660d9265c4c418d0bea", size = 37625856, upload-time = "2026-02-23T00:19:00.307Z" }, - { url = "https://files.pythonhosted.org/packages/a2/84/dc08d77fbf3d87d3ee27f6a0c6dcce1de5829a64f2eae85a0ecc1f0daa73/scipy-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:41b71f4a3a4cab9d366cd9065b288efc4d4f3c0b37a91a8e0947fb5bd7f31d87", size = 36549682, upload-time = "2026-02-23T00:19:07.67Z" }, - { url = "https://files.pythonhosted.org/packages/bc/98/fe9ae9ffb3b54b62559f52dedaebe204b408db8109a8c66fdd04869e6424/scipy-1.17.1-cp312-cp312-win_arm64.whl", hash = "sha256:f4115102802df98b2b0db3cce5cb9b92572633a1197c77b7553e5203f284a5b3", size = 24547340, upload-time = "2026-02-23T00:19:12.024Z" }, - { url = "https://files.pythonhosted.org/packages/76/27/07ee1b57b65e92645f219b37148a7e7928b82e2b5dbeccecb4dff7c64f0b/scipy-1.17.1-cp313-cp313-macosx_10_14_x86_64.whl", hash = "sha256:5e3c5c011904115f88a39308379c17f91546f77c1667cea98739fe0fccea804c", size = 31590199, upload-time = "2026-02-23T00:19:17.192Z" }, - { url = "https://files.pythonhosted.org/packages/ec/ae/db19f8ab842e9b724bf5dbb7db29302a91f1e55bc4d04b1025d6d605a2c5/scipy-1.17.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:6fac755ca3d2c3edcb22f479fceaa241704111414831ddd3bc6056e18516892f", size = 28154001, upload-time = "2026-02-23T00:19:22.241Z" }, - { url = "https://files.pythonhosted.org/packages/5b/58/3ce96251560107b381cbd6e8413c483bbb1228a6b919fa8652b0d4090e7f/scipy-1.17.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:7ff200bf9d24f2e4d5dc6ee8c3ac64d739d3a89e2326ba68aaf6c4a2b838fd7d", size = 20325719, upload-time = "2026-02-23T00:19:26.329Z" }, - { url = "https://files.pythonhosted.org/packages/b2/83/15087d945e0e4d48ce2377498abf5ad171ae013232ae31d06f336e64c999/scipy-1.17.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:4b400bdc6f79fa02a4d86640310dde87a21fba0c979efff5248908c6f15fad1b", size = 22683595, upload-time = "2026-02-23T00:19:30.304Z" }, - { url = "https://files.pythonhosted.org/packages/b4/e0/e58fbde4a1a594c8be8114eb4aac1a55bcd6587047efc18a61eb1f5c0d30/scipy-1.17.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2b64ca7d4aee0102a97f3ba22124052b4bd2152522355073580bf4845e2550b6", size = 32896429, upload-time = "2026-02-23T00:19:35.536Z" }, - { url = "https://files.pythonhosted.org/packages/f5/5f/f17563f28ff03c7b6799c50d01d5d856a1d55f2676f537ca8d28c7f627cd/scipy-1.17.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:581b2264fc0aa555f3f435a5944da7504ea3a065d7029ad60e7c3d1ae09c5464", size = 35203952, upload-time = "2026-02-23T00:19:42.259Z" }, - { url = "https://files.pythonhosted.org/packages/8d/a5/9afd17de24f657fdfe4df9a3f1ea049b39aef7c06000c13db1530d81ccca/scipy-1.17.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:beeda3d4ae615106d7094f7e7cef6218392e4465cc95d25f900bebabfded0950", size = 34979063, upload-time = "2026-02-23T00:19:47.547Z" }, - { url = "https://files.pythonhosted.org/packages/8b/13/88b1d2384b424bf7c924f2038c1c409f8d88bb2a8d49d097861dd64a57b2/scipy-1.17.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6609bc224e9568f65064cfa72edc0f24ee6655b47575954ec6339534b2798369", size = 37598449, upload-time = "2026-02-23T00:19:53.238Z" }, - { url = "https://files.pythonhosted.org/packages/35/e5/d6d0e51fc888f692a35134336866341c08655d92614f492c6860dc45bb2c/scipy-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:37425bc9175607b0268f493d79a292c39f9d001a357bebb6b88fdfaff13f6448", size = 36510943, upload-time = "2026-02-23T00:20:50.89Z" }, - { url = "https://files.pythonhosted.org/packages/2a/fd/3be73c564e2a01e690e19cc618811540ba5354c67c8680dce3281123fb79/scipy-1.17.1-cp313-cp313-win_arm64.whl", hash = "sha256:5cf36e801231b6a2059bf354720274b7558746f3b1a4efb43fcf557ccd484a87", size = 24545621, upload-time = "2026-02-23T00:20:55.871Z" }, - { url = "https://files.pythonhosted.org/packages/6f/6b/17787db8b8114933a66f9dcc479a8272e4b4da75fe03b0c282f7b0ade8cd/scipy-1.17.1-cp313-cp313t-macosx_10_14_x86_64.whl", hash = "sha256:d59c30000a16d8edc7e64152e30220bfbd724c9bbb08368c054e24c651314f0a", size = 31936708, upload-time = "2026-02-23T00:19:58.694Z" }, - { url = "https://files.pythonhosted.org/packages/38/2e/524405c2b6392765ab1e2b722a41d5da33dc5c7b7278184a8ad29b6cb206/scipy-1.17.1-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:010f4333c96c9bb1a4516269e33cb5917b08ef2166d5556ca2fd9f082a9e6ea0", size = 28570135, upload-time = "2026-02-23T00:20:03.934Z" }, - { url = "https://files.pythonhosted.org/packages/fd/c3/5bd7199f4ea8556c0c8e39f04ccb014ac37d1468e6cfa6a95c6b3562b76e/scipy-1.17.1-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:2ceb2d3e01c5f1d83c4189737a42d9cb2fc38a6eeed225e7515eef71ad301dce", size = 20741977, upload-time = "2026-02-23T00:20:07.935Z" }, - { url = "https://files.pythonhosted.org/packages/d9/b8/8ccd9b766ad14c78386599708eb745f6b44f08400a5fd0ade7cf89b6fc93/scipy-1.17.1-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:844e165636711ef41f80b4103ed234181646b98a53c8f05da12ca5ca289134f6", size = 23029601, upload-time = "2026-02-23T00:20:12.161Z" }, - { url = "https://files.pythonhosted.org/packages/6d/a0/3cb6f4d2fb3e17428ad2880333cac878909ad1a89f678527b5328b93c1d4/scipy-1.17.1-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:158dd96d2207e21c966063e1635b1063cd7787b627b6f07305315dd73d9c679e", size = 33019667, upload-time = "2026-02-23T00:20:17.208Z" }, - { url = "https://files.pythonhosted.org/packages/f3/c3/2d834a5ac7bf3a0c806ad1508efc02dda3c8c61472a56132d7894c312dea/scipy-1.17.1-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:74cbb80d93260fe2ffa334efa24cb8f2f0f622a9b9febf8b483c0b865bfb3475", size = 35264159, upload-time = "2026-02-23T00:20:23.087Z" }, - { url = "https://files.pythonhosted.org/packages/4d/77/d3ed4becfdbd217c52062fafe35a72388d1bd82c2d0ba5ca19d6fcc93e11/scipy-1.17.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:dbc12c9f3d185f5c737d801da555fb74b3dcfa1a50b66a1a93e09190f41fab50", size = 35102771, upload-time = "2026-02-23T00:20:28.636Z" }, - { url = "https://files.pythonhosted.org/packages/bd/12/d19da97efde68ca1ee5538bb261d5d2c062f0c055575128f11a2730e3ac1/scipy-1.17.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:94055a11dfebe37c656e70317e1996dc197e1a15bbcc351bcdd4610e128fe1ca", size = 37665910, upload-time = "2026-02-23T00:20:34.743Z" }, - { url = "https://files.pythonhosted.org/packages/06/1c/1172a88d507a4baaf72c5a09bb6c018fe2ae0ab622e5830b703a46cc9e44/scipy-1.17.1-cp313-cp313t-win_amd64.whl", hash = "sha256:e30bdeaa5deed6bc27b4cc490823cd0347d7dae09119b8803ae576ea0ce52e4c", size = 36562980, upload-time = "2026-02-23T00:20:40.575Z" }, - { url = "https://files.pythonhosted.org/packages/70/b0/eb757336e5a76dfa7911f63252e3b7d1de00935d7705cf772db5b45ec238/scipy-1.17.1-cp313-cp313t-win_arm64.whl", hash = "sha256:a720477885a9d2411f94a93d16f9d89bad0f28ca23c3f8daa521e2dcc3f44d49", size = 24856543, upload-time = "2026-02-23T00:20:45.313Z" }, +wheels = [ + {url = "https://files.pythonhosted.org/packages/35/48/b992b488d6f299dbe3f11a20b24d3dda3d46f1a635ede1c46b5b17a7b163/scipy-1.17.1-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:35c3a56d2ef83efc372eaec584314bd0ef2e2f0d2adb21c55e6ad5b344c0dcb8", size = 31610954, upload-time = "2026-02-23T00:17:49.855Z"}, + {url = "https://files.pythonhosted.org/packages/b2/02/cf107b01494c19dc100f1d0b7ac3cc08666e96ba2d64db7626066cee895e/scipy-1.17.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:fcb310ddb270a06114bb64bbe53c94926b943f5b7f0842194d585c65eb4edd76", size = 28172662, upload-time = "2026-02-23T00:18:01.64Z"}, + {url = "https://files.pythonhosted.org/packages/cf/a9/599c28631bad314d219cf9ffd40e985b24d603fc8a2f4ccc5ae8419a535b/scipy-1.17.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:cc90d2e9c7e5c7f1a482c9875007c095c3194b1cfedca3c2f3291cdc2bc7c086", size = 20344366, upload-time = "2026-02-23T00:18:12.015Z"}, + {url = "https://files.pythonhosted.org/packages/35/f5/906eda513271c8deb5af284e5ef0206d17a96239af79f9fa0aebfe0e36b4/scipy-1.17.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:c80be5ede8f3f8eded4eff73cc99a25c388ce98e555b17d31da05287015ffa5b", size = 22704017, upload-time = "2026-02-23T00:18:21.502Z"}, + {url = "https://files.pythonhosted.org/packages/da/34/16f10e3042d2f1d6b66e0428308ab52224b6a23049cb2f5c1756f713815f/scipy-1.17.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e19ebea31758fac5893a2ac360fedd00116cbb7628e650842a6691ba7ca28a21", size = 32927842, upload-time = "2026-02-23T00:18:35.367Z"}, + {url = "https://files.pythonhosted.org/packages/01/8e/1e35281b8ab6d5d72ebe9911edcdffa3f36b04ed9d51dec6dd140396e220/scipy-1.17.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:02ae3b274fde71c5e92ac4d54bc06c42d80e399fec704383dcd99b301df37458", size = 35235890, upload-time = "2026-02-23T00:18:49.188Z"}, + {url = "https://files.pythonhosted.org/packages/c5/5c/9d7f4c88bea6e0d5a4f1bc0506a53a00e9fcb198de372bfe4d3652cef482/scipy-1.17.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8a604bae87c6195d8b1045eddece0514d041604b14f2727bbc2b3020172045eb", size = 35003557, upload-time = "2026-02-23T00:18:54.74Z"}, + {url = "https://files.pythonhosted.org/packages/65/94/7698add8f276dbab7a9de9fb6b0e02fc13ee61d51c7c3f85ac28b65e1239/scipy-1.17.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f590cd684941912d10becc07325a3eeb77886fe981415660d9265c4c418d0bea", size = 37625856, upload-time = "2026-02-23T00:19:00.307Z"}, + {url = "https://files.pythonhosted.org/packages/a2/84/dc08d77fbf3d87d3ee27f6a0c6dcce1de5829a64f2eae85a0ecc1f0daa73/scipy-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:41b71f4a3a4cab9d366cd9065b288efc4d4f3c0b37a91a8e0947fb5bd7f31d87", size = 36549682, upload-time = "2026-02-23T00:19:07.67Z"}, + {url = "https://files.pythonhosted.org/packages/bc/98/fe9ae9ffb3b54b62559f52dedaebe204b408db8109a8c66fdd04869e6424/scipy-1.17.1-cp312-cp312-win_arm64.whl", hash = "sha256:f4115102802df98b2b0db3cce5cb9b92572633a1197c77b7553e5203f284a5b3", size = 24547340, upload-time = "2026-02-23T00:19:12.024Z"}, + {url = "https://files.pythonhosted.org/packages/76/27/07ee1b57b65e92645f219b37148a7e7928b82e2b5dbeccecb4dff7c64f0b/scipy-1.17.1-cp313-cp313-macosx_10_14_x86_64.whl", hash = "sha256:5e3c5c011904115f88a39308379c17f91546f77c1667cea98739fe0fccea804c", size = 31590199, upload-time = "2026-02-23T00:19:17.192Z"}, + {url = "https://files.pythonhosted.org/packages/ec/ae/db19f8ab842e9b724bf5dbb7db29302a91f1e55bc4d04b1025d6d605a2c5/scipy-1.17.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:6fac755ca3d2c3edcb22f479fceaa241704111414831ddd3bc6056e18516892f", size = 28154001, upload-time = "2026-02-23T00:19:22.241Z"}, + {url = "https://files.pythonhosted.org/packages/5b/58/3ce96251560107b381cbd6e8413c483bbb1228a6b919fa8652b0d4090e7f/scipy-1.17.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:7ff200bf9d24f2e4d5dc6ee8c3ac64d739d3a89e2326ba68aaf6c4a2b838fd7d", size = 20325719, upload-time = "2026-02-23T00:19:26.329Z"}, + {url = "https://files.pythonhosted.org/packages/b2/83/15087d945e0e4d48ce2377498abf5ad171ae013232ae31d06f336e64c999/scipy-1.17.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:4b400bdc6f79fa02a4d86640310dde87a21fba0c979efff5248908c6f15fad1b", size = 22683595, upload-time = "2026-02-23T00:19:30.304Z"}, + {url = "https://files.pythonhosted.org/packages/b4/e0/e58fbde4a1a594c8be8114eb4aac1a55bcd6587047efc18a61eb1f5c0d30/scipy-1.17.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2b64ca7d4aee0102a97f3ba22124052b4bd2152522355073580bf4845e2550b6", size = 32896429, upload-time = "2026-02-23T00:19:35.536Z"}, + {url = "https://files.pythonhosted.org/packages/f5/5f/f17563f28ff03c7b6799c50d01d5d856a1d55f2676f537ca8d28c7f627cd/scipy-1.17.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:581b2264fc0aa555f3f435a5944da7504ea3a065d7029ad60e7c3d1ae09c5464", size = 35203952, upload-time = "2026-02-23T00:19:42.259Z"}, + {url = "https://files.pythonhosted.org/packages/8d/a5/9afd17de24f657fdfe4df9a3f1ea049b39aef7c06000c13db1530d81ccca/scipy-1.17.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:beeda3d4ae615106d7094f7e7cef6218392e4465cc95d25f900bebabfded0950", size = 34979063, upload-time = "2026-02-23T00:19:47.547Z"}, + {url = "https://files.pythonhosted.org/packages/8b/13/88b1d2384b424bf7c924f2038c1c409f8d88bb2a8d49d097861dd64a57b2/scipy-1.17.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6609bc224e9568f65064cfa72edc0f24ee6655b47575954ec6339534b2798369", size = 37598449, upload-time = "2026-02-23T00:19:53.238Z"}, + {url = "https://files.pythonhosted.org/packages/35/e5/d6d0e51fc888f692a35134336866341c08655d92614f492c6860dc45bb2c/scipy-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:37425bc9175607b0268f493d79a292c39f9d001a357bebb6b88fdfaff13f6448", size = 36510943, upload-time = "2026-02-23T00:20:50.89Z"}, + {url = "https://files.pythonhosted.org/packages/2a/fd/3be73c564e2a01e690e19cc618811540ba5354c67c8680dce3281123fb79/scipy-1.17.1-cp313-cp313-win_arm64.whl", hash = "sha256:5cf36e801231b6a2059bf354720274b7558746f3b1a4efb43fcf557ccd484a87", size = 24545621, upload-time = "2026-02-23T00:20:55.871Z"}, + {url = "https://files.pythonhosted.org/packages/6f/6b/17787db8b8114933a66f9dcc479a8272e4b4da75fe03b0c282f7b0ade8cd/scipy-1.17.1-cp313-cp313t-macosx_10_14_x86_64.whl", hash = "sha256:d59c30000a16d8edc7e64152e30220bfbd724c9bbb08368c054e24c651314f0a", size = 31936708, upload-time = "2026-02-23T00:19:58.694Z"}, + {url = "https://files.pythonhosted.org/packages/38/2e/524405c2b6392765ab1e2b722a41d5da33dc5c7b7278184a8ad29b6cb206/scipy-1.17.1-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:010f4333c96c9bb1a4516269e33cb5917b08ef2166d5556ca2fd9f082a9e6ea0", size = 28570135, upload-time = "2026-02-23T00:20:03.934Z"}, + {url = "https://files.pythonhosted.org/packages/fd/c3/5bd7199f4ea8556c0c8e39f04ccb014ac37d1468e6cfa6a95c6b3562b76e/scipy-1.17.1-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:2ceb2d3e01c5f1d83c4189737a42d9cb2fc38a6eeed225e7515eef71ad301dce", size = 20741977, upload-time = "2026-02-23T00:20:07.935Z"}, + {url = "https://files.pythonhosted.org/packages/d9/b8/8ccd9b766ad14c78386599708eb745f6b44f08400a5fd0ade7cf89b6fc93/scipy-1.17.1-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:844e165636711ef41f80b4103ed234181646b98a53c8f05da12ca5ca289134f6", size = 23029601, upload-time = "2026-02-23T00:20:12.161Z"}, + {url = "https://files.pythonhosted.org/packages/6d/a0/3cb6f4d2fb3e17428ad2880333cac878909ad1a89f678527b5328b93c1d4/scipy-1.17.1-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:158dd96d2207e21c966063e1635b1063cd7787b627b6f07305315dd73d9c679e", size = 33019667, upload-time = "2026-02-23T00:20:17.208Z"}, + {url = "https://files.pythonhosted.org/packages/f3/c3/2d834a5ac7bf3a0c806ad1508efc02dda3c8c61472a56132d7894c312dea/scipy-1.17.1-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:74cbb80d93260fe2ffa334efa24cb8f2f0f622a9b9febf8b483c0b865bfb3475", size = 35264159, upload-time = "2026-02-23T00:20:23.087Z"}, + {url = "https://files.pythonhosted.org/packages/4d/77/d3ed4becfdbd217c52062fafe35a72388d1bd82c2d0ba5ca19d6fcc93e11/scipy-1.17.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:dbc12c9f3d185f5c737d801da555fb74b3dcfa1a50b66a1a93e09190f41fab50", size = 35102771, upload-time = "2026-02-23T00:20:28.636Z"}, + {url = "https://files.pythonhosted.org/packages/bd/12/d19da97efde68ca1ee5538bb261d5d2c062f0c055575128f11a2730e3ac1/scipy-1.17.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:94055a11dfebe37c656e70317e1996dc197e1a15bbcc351bcdd4610e128fe1ca", size = 37665910, upload-time = "2026-02-23T00:20:34.743Z"}, + {url = "https://files.pythonhosted.org/packages/06/1c/1172a88d507a4baaf72c5a09bb6c018fe2ae0ab622e5830b703a46cc9e44/scipy-1.17.1-cp313-cp313t-win_amd64.whl", hash = "sha256:e30bdeaa5deed6bc27b4cc490823cd0347d7dae09119b8803ae576ea0ce52e4c", size = 36562980, upload-time = "2026-02-23T00:20:40.575Z"}, + {url = "https://files.pythonhosted.org/packages/70/b0/eb757336e5a76dfa7911f63252e3b7d1de00935d7705cf772db5b45ec238/scipy-1.17.1-cp313-cp313t-win_arm64.whl", hash = "sha256:a720477885a9d2411f94a93d16f9d89bad0f28ca23c3f8daa521e2dcc3f44d49", size = 24856543, upload-time = "2026-02-23T00:20:45.313Z"} ] [[package]] name = "snowballstemmer" +sdist = {url = "https://files.pythonhosted.org/packages/75/a7/9810d872919697c9d01295633f5d574fb416d47e535f258272ca1f01f447/snowballstemmer-3.0.1.tar.gz", hash = "sha256:6d5eeeec8e9f84d4d56b847692bacf79bc2c8e90c7f80ca4444ff8b6f2e52895", size = 105575, upload-time = "2025-05-09T16:34:51.843Z"} +source = {registry = "https://pypi.org/simple"} version = "3.0.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/75/a7/9810d872919697c9d01295633f5d574fb416d47e535f258272ca1f01f447/snowballstemmer-3.0.1.tar.gz", hash = "sha256:6d5eeeec8e9f84d4d56b847692bacf79bc2c8e90c7f80ca4444ff8b6f2e52895", size = 105575, upload-time = "2025-05-09T16:34:51.843Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c8/78/3565d011c61f5a43488987ee32b6f3f656e7f107ac2782dd57bdd7d91d9a/snowballstemmer-3.0.1-py3-none-any.whl", hash = "sha256:6cd7b3897da8d6c9ffb968a6781fa6532dce9c3618a4b127d920dab764a19064", size = 103274, upload-time = "2025-05-09T16:34:50.371Z" }, + {url = "https://files.pythonhosted.org/packages/c8/78/3565d011c61f5a43488987ee32b6f3f656e7f107ac2782dd57bdd7d91d9a/snowballstemmer-3.0.1-py3-none-any.whl", hash = "sha256:6cd7b3897da8d6c9ffb968a6781fa6532dce9c3618a4b127d920dab764a19064", size = 103274, upload-time = "2025-05-09T16:34:50.371Z"} ] [[package]] name = "soupsieve" +sdist = {url = "https://files.pythonhosted.org/packages/7b/ae/2d9c981590ed9999a0d91755b47fc74f74de286b0f5cee14c9269041e6c4/soupsieve-2.8.3.tar.gz", hash = "sha256:3267f1eeea4251fb42728b6dfb746edc9acaffc4a45b27e19450b676586e8349", size = 118627, upload-time = "2026-01-20T04:27:02.457Z"} +source = {registry = "https://pypi.org/simple"} version = "2.8.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7b/ae/2d9c981590ed9999a0d91755b47fc74f74de286b0f5cee14c9269041e6c4/soupsieve-2.8.3.tar.gz", hash = "sha256:3267f1eeea4251fb42728b6dfb746edc9acaffc4a45b27e19450b676586e8349", size = 118627, upload-time = "2026-01-20T04:27:02.457Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/46/2c/1462b1d0a634697ae9e55b3cecdcb64788e8b7d63f54d923fcd0bb140aed/soupsieve-2.8.3-py3-none-any.whl", hash = "sha256:ed64f2ba4eebeab06cc4962affce381647455978ffc1e36bb79a545b91f45a95", size = 37016, upload-time = "2026-01-20T04:27:01.012Z" }, + {url = "https://files.pythonhosted.org/packages/46/2c/1462b1d0a634697ae9e55b3cecdcb64788e8b7d63f54d923fcd0bb140aed/soupsieve-2.8.3-py3-none-any.whl", hash = "sha256:ed64f2ba4eebeab06cc4962affce381647455978ffc1e36bb79a545b91f45a95", size = 37016, upload-time = "2026-01-20T04:27:01.012Z"} ] [[package]] +dependencies = [ + {name = "alabaster"}, + {name = "babel"}, + {name = "colorama", marker = "sys_platform == 'win32'"}, + {name = "docutils"}, + {name = "imagesize"}, + {name = "jinja2"}, + {name = "packaging"}, + {name = "pygments"}, + {name = "requests"}, + {name = "roman-numerals"}, + {name = "snowballstemmer"}, + {name = "sphinxcontrib-applehelp"}, + {name = "sphinxcontrib-devhelp"}, + {name = "sphinxcontrib-htmlhelp"}, + {name = "sphinxcontrib-jsmath"}, + {name = "sphinxcontrib-qthelp"}, + {name = "sphinxcontrib-serializinghtml"} +] name = "sphinx" +sdist = {url = "https://files.pythonhosted.org/packages/cd/bd/f08eb0f4eed5c83f1ba2a3bd18f7745a2b1525fad70660a1c00224ec468a/sphinx-9.1.0.tar.gz", hash = "sha256:7741722357dd75f8190766926071fed3bdc211c74dd2d7d4df5404da95930ddb", size = 8718324, upload-time = "2025-12-31T15:09:27.646Z"} +source = {registry = "https://pypi.org/simple"} version = "9.1.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "alabaster" }, - { name = "babel" }, - { name = "colorama", marker = "sys_platform == 'win32'" }, - { name = "docutils" }, - { name = "imagesize" }, - { name = "jinja2" }, - { name = "packaging" }, - { name = "pygments" }, - { name = "requests" }, - { name = "roman-numerals" }, - { name = "snowballstemmer" }, - { name = "sphinxcontrib-applehelp" }, - { name = "sphinxcontrib-devhelp" }, - { name = "sphinxcontrib-htmlhelp" }, - { name = "sphinxcontrib-jsmath" }, - { name = "sphinxcontrib-qthelp" }, - { name = "sphinxcontrib-serializinghtml" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/cd/bd/f08eb0f4eed5c83f1ba2a3bd18f7745a2b1525fad70660a1c00224ec468a/sphinx-9.1.0.tar.gz", hash = "sha256:7741722357dd75f8190766926071fed3bdc211c74dd2d7d4df5404da95930ddb", size = 8718324, upload-time = "2025-12-31T15:09:27.646Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/73/f7/b1884cb3188ab181fc81fa00c266699dab600f927a964df02ec3d5d1916a/sphinx-9.1.0-py3-none-any.whl", hash = "sha256:c84fdd4e782504495fe4f2c0b3413d6c2bf388589bb352d439b2a3bb99991978", size = 3921742, upload-time = "2025-12-31T15:09:25.561Z" }, +wheels = [ + {url = "https://files.pythonhosted.org/packages/73/f7/b1884cb3188ab181fc81fa00c266699dab600f927a964df02ec3d5d1916a/sphinx-9.1.0-py3-none-any.whl", hash = "sha256:c84fdd4e782504495fe4f2c0b3413d6c2bf388589bb352d439b2a3bb99991978", size = 3921742, upload-time = "2025-12-31T15:09:25.561Z"} ] [[package]] -name = "sphinx-autoapi" -version = "3.7.0" -source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "astroid" }, - { name = "jinja2" }, - { name = "pyyaml" }, - { name = "sphinx" }, + {name = "astroid"}, + {name = "jinja2"}, + {name = "pyyaml"}, + {name = "sphinx"} ] -sdist = { url = "https://files.pythonhosted.org/packages/47/22/7ea4b660e98ffa271dbec5847b64738127955746d887f9596940279d30bf/sphinx_autoapi-3.7.0.tar.gz", hash = "sha256:5c8a934d788f00d11b8c8092536c7373592cce986820de8dd7daf6dfd79afd91", size = 58136, upload-time = "2026-02-11T05:24:29.86Z" } +name = "sphinx-autoapi" +sdist = {url = "https://files.pythonhosted.org/packages/47/22/7ea4b660e98ffa271dbec5847b64738127955746d887f9596940279d30bf/sphinx_autoapi-3.7.0.tar.gz", hash = "sha256:5c8a934d788f00d11b8c8092536c7373592cce986820de8dd7daf6dfd79afd91", size = 58136, upload-time = "2026-02-11T05:24:29.86Z"} +source = {registry = "https://pypi.org/simple"} +version = "3.7.0" wheels = [ - { url = "https://files.pythonhosted.org/packages/d8/11/9825443890d3ef6b6a0db938054e7c806e9911509e0315fe984d00a090c1/sphinx_autoapi-3.7.0-py3-none-any.whl", hash = "sha256:5ea37271b50de08538cf6e33044ef4fb6e981ddb693060ec84f297005014fdfd", size = 36021, upload-time = "2026-02-11T05:24:28.657Z" }, + {url = "https://files.pythonhosted.org/packages/d8/11/9825443890d3ef6b6a0db938054e7c806e9911509e0315fe984d00a090c1/sphinx_autoapi-3.7.0-py3-none-any.whl", hash = "sha256:5ea37271b50de08538cf6e33044ef4fb6e981ddb693060ec84f297005014fdfd", size = 36021, upload-time = "2026-02-11T05:24:28.657Z"} ] [[package]] name = "sphinxcontrib-applehelp" +sdist = {url = "https://files.pythonhosted.org/packages/ba/6e/b837e84a1a704953c62ef8776d45c3e8d759876b4a84fe14eba2859106fe/sphinxcontrib_applehelp-2.0.0.tar.gz", hash = "sha256:2f29ef331735ce958efa4734873f084941970894c6090408b079c61b2e1c06d1", size = 20053, upload-time = "2024-07-29T01:09:00.465Z"} +source = {registry = "https://pypi.org/simple"} version = "2.0.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ba/6e/b837e84a1a704953c62ef8776d45c3e8d759876b4a84fe14eba2859106fe/sphinxcontrib_applehelp-2.0.0.tar.gz", hash = "sha256:2f29ef331735ce958efa4734873f084941970894c6090408b079c61b2e1c06d1", size = 20053, upload-time = "2024-07-29T01:09:00.465Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5d/85/9ebeae2f76e9e77b952f4b274c27238156eae7979c5421fba91a28f4970d/sphinxcontrib_applehelp-2.0.0-py3-none-any.whl", hash = "sha256:4cd3f0ec4ac5dd9c17ec65e9ab272c9b867ea77425228e68ecf08d6b28ddbdb5", size = 119300, upload-time = "2024-07-29T01:08:58.99Z" }, + {url = "https://files.pythonhosted.org/packages/5d/85/9ebeae2f76e9e77b952f4b274c27238156eae7979c5421fba91a28f4970d/sphinxcontrib_applehelp-2.0.0-py3-none-any.whl", hash = "sha256:4cd3f0ec4ac5dd9c17ec65e9ab272c9b867ea77425228e68ecf08d6b28ddbdb5", size = 119300, upload-time = "2024-07-29T01:08:58.99Z"} ] [[package]] name = "sphinxcontrib-devhelp" +sdist = {url = "https://files.pythonhosted.org/packages/f6/d2/5beee64d3e4e747f316bae86b55943f51e82bb86ecd325883ef65741e7da/sphinxcontrib_devhelp-2.0.0.tar.gz", hash = "sha256:411f5d96d445d1d73bb5d52133377b4248ec79db5c793ce7dbe59e074b4dd1ad", size = 12967, upload-time = "2024-07-29T01:09:23.417Z"} +source = {registry = "https://pypi.org/simple"} version = "2.0.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f6/d2/5beee64d3e4e747f316bae86b55943f51e82bb86ecd325883ef65741e7da/sphinxcontrib_devhelp-2.0.0.tar.gz", hash = "sha256:411f5d96d445d1d73bb5d52133377b4248ec79db5c793ce7dbe59e074b4dd1ad", size = 12967, upload-time = "2024-07-29T01:09:23.417Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/35/7a/987e583882f985fe4d7323774889ec58049171828b58c2217e7f79cdf44e/sphinxcontrib_devhelp-2.0.0-py3-none-any.whl", hash = "sha256:aefb8b83854e4b0998877524d1029fd3e6879210422ee3780459e28a1f03a8a2", size = 82530, upload-time = "2024-07-29T01:09:21.945Z" }, + {url = "https://files.pythonhosted.org/packages/35/7a/987e583882f985fe4d7323774889ec58049171828b58c2217e7f79cdf44e/sphinxcontrib_devhelp-2.0.0-py3-none-any.whl", hash = "sha256:aefb8b83854e4b0998877524d1029fd3e6879210422ee3780459e28a1f03a8a2", size = 82530, upload-time = "2024-07-29T01:09:21.945Z"} ] [[package]] name = "sphinxcontrib-htmlhelp" +sdist = {url = "https://files.pythonhosted.org/packages/43/93/983afd9aa001e5201eab16b5a444ed5b9b0a7a010541e0ddfbbfd0b2470c/sphinxcontrib_htmlhelp-2.1.0.tar.gz", hash = "sha256:c9e2916ace8aad64cc13a0d233ee22317f2b9025b9cf3295249fa985cc7082e9", size = 22617, upload-time = "2024-07-29T01:09:37.889Z"} +source = {registry = "https://pypi.org/simple"} version = "2.1.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/43/93/983afd9aa001e5201eab16b5a444ed5b9b0a7a010541e0ddfbbfd0b2470c/sphinxcontrib_htmlhelp-2.1.0.tar.gz", hash = "sha256:c9e2916ace8aad64cc13a0d233ee22317f2b9025b9cf3295249fa985cc7082e9", size = 22617, upload-time = "2024-07-29T01:09:37.889Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0a/7b/18a8c0bcec9182c05a0b3ec2a776bba4ead82750a55ff798e8d406dae604/sphinxcontrib_htmlhelp-2.1.0-py3-none-any.whl", hash = "sha256:166759820b47002d22914d64a075ce08f4c46818e17cfc9470a9786b759b19f8", size = 98705, upload-time = "2024-07-29T01:09:36.407Z" }, + {url = "https://files.pythonhosted.org/packages/0a/7b/18a8c0bcec9182c05a0b3ec2a776bba4ead82750a55ff798e8d406dae604/sphinxcontrib_htmlhelp-2.1.0-py3-none-any.whl", hash = "sha256:166759820b47002d22914d64a075ce08f4c46818e17cfc9470a9786b759b19f8", size = 98705, upload-time = "2024-07-29T01:09:36.407Z"} ] [[package]] name = "sphinxcontrib-jsmath" +sdist = {url = "https://files.pythonhosted.org/packages/b2/e8/9ed3830aeed71f17c026a07a5097edcf44b692850ef215b161b8ad875729/sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8", size = 5787, upload-time = "2019-01-21T16:10:16.347Z"} +source = {registry = "https://pypi.org/simple"} version = "1.0.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b2/e8/9ed3830aeed71f17c026a07a5097edcf44b692850ef215b161b8ad875729/sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8", size = 5787, upload-time = "2019-01-21T16:10:16.347Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c2/42/4c8646762ee83602e3fb3fbe774c2fac12f317deb0b5dbeeedd2d3ba4b77/sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178", size = 5071, upload-time = "2019-01-21T16:10:14.333Z" }, + {url = "https://files.pythonhosted.org/packages/c2/42/4c8646762ee83602e3fb3fbe774c2fac12f317deb0b5dbeeedd2d3ba4b77/sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178", size = 5071, upload-time = "2019-01-21T16:10:14.333Z"} ] [[package]] name = "sphinxcontrib-qthelp" +sdist = {url = "https://files.pythonhosted.org/packages/68/bc/9104308fc285eb3e0b31b67688235db556cd5b0ef31d96f30e45f2e51cae/sphinxcontrib_qthelp-2.0.0.tar.gz", hash = "sha256:4fe7d0ac8fc171045be623aba3e2a8f613f8682731f9153bb2e40ece16b9bbab", size = 17165, upload-time = "2024-07-29T01:09:56.435Z"} +source = {registry = "https://pypi.org/simple"} version = "2.0.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/68/bc/9104308fc285eb3e0b31b67688235db556cd5b0ef31d96f30e45f2e51cae/sphinxcontrib_qthelp-2.0.0.tar.gz", hash = "sha256:4fe7d0ac8fc171045be623aba3e2a8f613f8682731f9153bb2e40ece16b9bbab", size = 17165, upload-time = "2024-07-29T01:09:56.435Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/27/83/859ecdd180cacc13b1f7e857abf8582a64552ea7a061057a6c716e790fce/sphinxcontrib_qthelp-2.0.0-py3-none-any.whl", hash = "sha256:b18a828cdba941ccd6ee8445dbe72ffa3ef8cbe7505d8cd1fa0d42d3f2d5f3eb", size = 88743, upload-time = "2024-07-29T01:09:54.885Z" }, + {url = "https://files.pythonhosted.org/packages/27/83/859ecdd180cacc13b1f7e857abf8582a64552ea7a061057a6c716e790fce/sphinxcontrib_qthelp-2.0.0-py3-none-any.whl", hash = "sha256:b18a828cdba941ccd6ee8445dbe72ffa3ef8cbe7505d8cd1fa0d42d3f2d5f3eb", size = 88743, upload-time = "2024-07-29T01:09:54.885Z"} ] [[package]] name = "sphinxcontrib-serializinghtml" +sdist = {url = "https://files.pythonhosted.org/packages/3b/44/6716b257b0aa6bfd51a1b31665d1c205fb12cb5ad56de752dfa15657de2f/sphinxcontrib_serializinghtml-2.0.0.tar.gz", hash = "sha256:e9d912827f872c029017a53f0ef2180b327c3f7fd23c87229f7a8e8b70031d4d", size = 16080, upload-time = "2024-07-29T01:10:09.332Z"} +source = {registry = "https://pypi.org/simple"} version = "2.0.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/3b/44/6716b257b0aa6bfd51a1b31665d1c205fb12cb5ad56de752dfa15657de2f/sphinxcontrib_serializinghtml-2.0.0.tar.gz", hash = "sha256:e9d912827f872c029017a53f0ef2180b327c3f7fd23c87229f7a8e8b70031d4d", size = 16080, upload-time = "2024-07-29T01:10:09.332Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/52/a7/d2782e4e3f77c8450f727ba74a8f12756d5ba823d81b941f1b04da9d033a/sphinxcontrib_serializinghtml-2.0.0-py3-none-any.whl", hash = "sha256:6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331", size = 92072, upload-time = "2024-07-29T01:10:08.203Z" }, + {url = "https://files.pythonhosted.org/packages/52/a7/d2782e4e3f77c8450f727ba74a8f12756d5ba823d81b941f1b04da9d033a/sphinxcontrib_serializinghtml-2.0.0-py3-none-any.whl", hash = "sha256:6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331", size = 92072, upload-time = "2024-07-29T01:10:08.203Z"} ] [[package]] name = "types-pyyaml" +sdist = {url = "https://files.pythonhosted.org/packages/7e/69/3c51b36d04da19b92f9e815be12753125bd8bc247ba0470a982e6979e71c/types_pyyaml-6.0.12.20250915.tar.gz", hash = "sha256:0f8b54a528c303f0e6f7165687dd33fafa81c807fcac23f632b63aa624ced1d3", size = 17522, upload-time = "2025-09-15T03:01:00.728Z"} +source = {registry = "https://pypi.org/simple"} version = "6.0.12.20250915" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7e/69/3c51b36d04da19b92f9e815be12753125bd8bc247ba0470a982e6979e71c/types_pyyaml-6.0.12.20250915.tar.gz", hash = "sha256:0f8b54a528c303f0e6f7165687dd33fafa81c807fcac23f632b63aa624ced1d3", size = 17522, upload-time = "2025-09-15T03:01:00.728Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/bd/e0/1eed384f02555dde685fff1a1ac805c1c7dcb6dd019c916fe659b1c1f9ec/types_pyyaml-6.0.12.20250915-py3-none-any.whl", hash = "sha256:e7d4d9e064e89a3b3cae120b4990cd370874d2bf12fa5f46c97018dd5d3c9ab6", size = 20338, upload-time = "2025-09-15T03:00:59.218Z" }, + {url = "https://files.pythonhosted.org/packages/bd/e0/1eed384f02555dde685fff1a1ac805c1c7dcb6dd019c916fe659b1c1f9ec/types_pyyaml-6.0.12.20250915-py3-none-any.whl", hash = "sha256:e7d4d9e064e89a3b3cae120b4990cd370874d2bf12fa5f46c97018dd5d3c9ab6", size = 20338, upload-time = "2025-09-15T03:00:59.218Z"} ] [[package]] name = "typing-extensions" +sdist = {url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z"} +source = {registry = "https://pypi.org/simple"} version = "4.15.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, + {url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z"} ] [[package]] name = "urllib3" +sdist = {url = "https://files.pythonhosted.org/packages/c7/24/5f1b3bdffd70275f6661c76461e25f024d5a38a46f04aaca912426a2b1d3/urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed", size = 435556, upload-time = "2026-01-07T16:24:43.925Z"} +source = {registry = "https://pypi.org/simple"} version = "2.6.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c7/24/5f1b3bdffd70275f6661c76461e25f024d5a38a46f04aaca912426a2b1d3/urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed", size = 435556, upload-time = "2026-01-07T16:24:43.925Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4", size = 131584, upload-time = "2026-01-07T16:24:42.685Z" }, + {url = "https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4", size = 131584, upload-time = "2026-01-07T16:24:42.685Z"} ] [[package]] -name = "virtualenv" -version = "21.1.0" -source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "distlib" }, - { name = "filelock" }, - { name = "platformdirs" }, - { name = "python-discovery" }, + {name = "distlib"}, + {name = "filelock"}, + {name = "platformdirs"}, + {name = "python-discovery"} ] -sdist = { url = "https://files.pythonhosted.org/packages/2f/c9/18d4b36606d6091844daa3bd93cf7dc78e6f5da21d9f21d06c221104b684/virtualenv-21.1.0.tar.gz", hash = "sha256:1990a0188c8f16b6b9cf65c9183049007375b26aad415514d377ccacf1e4fb44", size = 5840471, upload-time = "2026-02-27T08:49:29.702Z" } +name = "virtualenv" +sdist = {url = "https://files.pythonhosted.org/packages/2f/c9/18d4b36606d6091844daa3bd93cf7dc78e6f5da21d9f21d06c221104b684/virtualenv-21.1.0.tar.gz", hash = "sha256:1990a0188c8f16b6b9cf65c9183049007375b26aad415514d377ccacf1e4fb44", size = 5840471, upload-time = "2026-02-27T08:49:29.702Z"} +source = {registry = "https://pypi.org/simple"} +version = "21.1.0" wheels = [ - { url = "https://files.pythonhosted.org/packages/78/55/896b06bf93a49bec0f4ae2a6f1ed12bd05c8860744ac3a70eda041064e4d/virtualenv-21.1.0-py3-none-any.whl", hash = "sha256:164f5e14c5587d170cf98e60378eb91ea35bf037be313811905d3a24ea33cc07", size = 5825072, upload-time = "2026-02-27T08:49:27.516Z" }, + {url = "https://files.pythonhosted.org/packages/78/55/896b06bf93a49bec0f4ae2a6f1ed12bd05c8860744ac3a70eda041064e4d/virtualenv-21.1.0-py3-none-any.whl", hash = "sha256:164f5e14c5587d170cf98e60378eb91ea35bf037be313811905d3a24ea33cc07", size = 5825072, upload-time = "2026-02-27T08:49:27.516Z"} ] From b7b9d2c0b8ef164ef4f68324b64f8c1930c6316e Mon Sep 17 00:00:00 2001 From: Alex Liberzon Date: Sun, 15 Mar 2026 10:57:51 +0200 Subject: [PATCH 24/27] hooks --- .pre-commit-config.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 64c2474..a7a6829 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -33,6 +33,7 @@ repos: args: [--autofix, --preserve-quotes] - id: pretty-format-toml args: [--autofix] + exclude: ^uv\.lock$ - repo: https://github.com/gitleaks/gitleaks rev: v8.30.0 hooks: From be368ec462036f8757402de2fa6746917721033b Mon Sep 17 00:00:00 2001 From: Alex Liberzon Date: Sun, 15 Mar 2026 10:59:18 +0200 Subject: [PATCH 25/27] fix hard coded path --- tests/test_tracking_ground_truth.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/test_tracking_ground_truth.py b/tests/test_tracking_ground_truth.py index 026954b..ec24e74 100644 --- a/tests/test_tracking_ground_truth.py +++ b/tests/test_tracking_ground_truth.py @@ -335,7 +335,9 @@ def _write_synthetic_tracking_fixture( add_distractor: bool = True, ) -> list[Calibration]: """Write a clean deterministic tracking fixture into a temporary workspace.""" - source = Path("/home/user/Documents/GitHub/openptv-python/tests/testing_fodder") + source = Path(__file__).resolve().parent / "testing_fodder" + if not source.exists(): + raise FileNotFoundError(f"Missing test fixture directory: {source}") shutil.copytree(source, workdir) os.chdir(workdir) From 8ca86896a187b0be60a121642fb7cf58f961bd6f Mon Sep 17 00:00:00 2001 From: Alex Liberzon Date: Sun, 15 Mar 2026 10:59:43 +0200 Subject: [PATCH 26/27] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- openptv_python/image_processing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openptv_python/image_processing.py b/openptv_python/image_processing.py index 082e785..83156d2 100644 --- a/openptv_python/image_processing.py +++ b/openptv_python/image_processing.py @@ -132,7 +132,7 @@ def fast_box_blur( col = imx - filt_span left = row_start + imx - n right = left + 1 - while col < row_start + imx - row_start and m > 0 and right < row_start + imx: + while col < imx and m > 0 and right < row_start + imx: accum -= int(src_flat[left]) + int(src_flat[right]) row_accum[row_start + col] = accum * n // m left += 2 From 523efa72d60a9588e13e06267c3e65e1018e4771 Mon Sep 17 00:00:00 2001 From: Alex Liberzon Date: Sun, 15 Mar 2026 12:34:52 +0200 Subject: [PATCH 27/27] mypy --- openptv_python/track.py | 2 +- tests/test_tracking_ground_truth.py | 25 +++++++++++++++++++------ 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/openptv_python/track.py b/openptv_python/track.py index f9e2697..2a81377 100644 --- a/openptv_python/track.py +++ b/openptv_python/track.py @@ -253,7 +253,7 @@ def angle_acc(start: np.ndarray, pred: np.ndarray, cand: np.ndarray) -> np.ndarr acc = np.linalg.norm(v0 - v1) if np.all(v0 == -v1): - angle = 200 + angle = 200.0 elif np.all(v0 == v1): angle = 0 else: diff --git a/tests/test_tracking_ground_truth.py b/tests/test_tracking_ground_truth.py index ec24e74..39784eb 100644 --- a/tests/test_tracking_ground_truth.py +++ b/tests/test_tracking_ground_truth.py @@ -7,6 +7,7 @@ from contextlib import ExitStack, redirect_stdout from dataclasses import dataclass from pathlib import Path +from typing import cast from unittest.mock import patch import numpy as np @@ -186,7 +187,8 @@ def _make_bounds(*, dv_limit: float, dacc: float, dangle: float) -> dict[str, fl def _rounded_position(pos: np.ndarray) -> tuple[float, float, float]: - return tuple(round(float(value), 5) for value in pos) + values = tuple(round(float(value), 5) for value in pos) + return cast(tuple[float, float, float], values) def _snapshot_graph(snapshot): @@ -359,7 +361,7 @@ def _write_synthetic_tracking_fixture( for frame_index, frame_num in enumerate(FRAMES): frame = Frame(3, 16) - cam_targets = [[] for _ in range(3)] + cam_targets: list[list[Target]] = [[] for _ in range(3)] correspond_indices = { track_id: [CORRES_NONE] * 4 for track_id, positions in tracks.items() @@ -427,7 +429,9 @@ def _write_synthetic_tracking_fixture( correspond_indices[track_id], dtype=np.int32, ) - frame.path_info[row_index].x = tracks[track_id][frame_index] + position = tracks[track_id][frame_index] + assert position is not None + frame.path_info[row_index].x = position write_path_frame( frame.correspond, @@ -446,7 +450,10 @@ def _snapshot_tracking_outputs() -> dict[ int, list[tuple[int, int, tuple[int, int, int, int], tuple[float, float, float]]] ]: """Read the written tracking outputs into a stable frame-by-frame snapshot.""" - snapshots = {} + snapshots: dict[ + int, + list[tuple[int, int, tuple[int, int, int, int], tuple[float, float, float]]], + ] = {} for frame in FRAMES: correspond, path_info = read_path_frame( "res/particles", @@ -458,8 +465,14 @@ def _snapshot_tracking_outputs() -> dict[ ( int(path.prev_frame), int(path.next_frame), - tuple(int(value) for value in correspond[idx].p), - tuple(round(float(value), 5) for value in path.x), + cast( + tuple[int, int, int, int], + tuple(int(value) for value in correspond[idx].p), + ), + cast( + tuple[float, float, float], + tuple(round(float(value), 5) for value in path.x), + ), ) for idx, path in enumerate(path_info) ]