diff --git a/Deep-SAD-PyTorch/uv.lock b/Deep-SAD-PyTorch/uv.lock new file mode 100644 index 0000000..ad8ca75 --- /dev/null +++ b/Deep-SAD-PyTorch/uv.lock @@ -0,0 +1,998 @@ +version = 1 +revision = 2 +requires-python = ">=3.12" +resolution-markers = [ + "python_full_version >= '3.13'", + "python_full_version < '3.13'", +] + +[[package]] +name = "aggdraw" +version = "1.3.19" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/03/f1/031b1205f1f580c41566a509166f7097cbff13adffd0c1080b0b3e0ea4ae/aggdraw-1.3.19.tar.gz", hash = "sha256:e78a23b29fb66a079832bae5604f082bfa4ff9d5d469c77506a67253d7fee7db", size = 260414, upload-time = "2024-09-11T16:59:53.445Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/46/93/aeab2f4832c10975eacf051296d6f674951e597a92ff64d7d397159b9ef5/aggdraw-1.3.19-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:e68afe82d0bbd111b2d6d7f41ac8cf13a010cd65ad095117c2e9fd8252710a73", size = 479651, upload-time = "2024-09-11T16:59:31.408Z" }, + { url = "https://files.pythonhosted.org/packages/1d/99/2e7bbcca4be5e00656555041245f293cda360c2524d5ddf40f3a9bbfdf7b/aggdraw-1.3.19-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f3ff2595490b3760658a28afdca5111ed787bd1fccdab5c81d0ea57e0fe5a8c", size = 975835, upload-time = "2024-09-11T16:59:33.54Z" }, + { url = "https://files.pythonhosted.org/packages/20/81/b845347ef6d399dafa7ac3e8dfe5993d71d2c73d52a1c21a429bb7e459ea/aggdraw-1.3.19-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56bbcc8d4f64e60ed815419837b9d2ba579169d92c4fe19e4caa3679aaedb6aa", size = 1000009, upload-time = "2024-09-11T16:59:35.31Z" }, + { url = "https://files.pythonhosted.org/packages/e9/64/0b8fef4d67d928043f9a24a7f65223d3734ea10c24ff0ca3bacbd888b4c1/aggdraw-1.3.19-cp312-cp312-win_amd64.whl", hash = "sha256:6a39bdf2f1043ca4148a926e35feaa1d4d592c25c8b225d8d0ad945415bab7cc", size = 45060, upload-time = "2024-09-11T16:59:37.055Z" }, + { url = "https://files.pythonhosted.org/packages/3c/9c/05ed3a30b8b6f40da118cf4ffa52599d33ecb49e0f4b10d3f0c9e4d6fbea/aggdraw-1.3.19-cp312-cp312-win_arm64.whl", hash = "sha256:21f951129b3014b3cf678b250ba5e4345d93243602b4062804e6111319d0eaf3", size = 34196, upload-time = "2024-09-11T16:59:38.045Z" }, + { url = "https://files.pythonhosted.org/packages/e9/75/eb67b18d131cf08b68db7bd2cb971e58280a3a6c216f662f92751e11bd77/aggdraw-1.3.19-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:f7cb8141620ec4cb3be55601115126d9a1b084ef5bdea0bb510007efa42e8cfe", size = 479637, upload-time = "2024-09-11T16:59:39.617Z" }, + { url = "https://files.pythonhosted.org/packages/76/ed/dd15ed34166139f0fc1ae8956b6aa4657b38e209e6bd35f1ee4617690c15/aggdraw-1.3.19-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aecacc4f6d13b8343ac07624576bab4dadd4528f0c47f54b2fae0656fc913670", size = 975787, upload-time = "2024-09-11T16:59:41.145Z" }, + { url = "https://files.pythonhosted.org/packages/83/46/c5b9094701787d11e40c7bfced2c081fb59e9afc740722bb0d71f5e0335d/aggdraw-1.3.19-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:981210dbcbb649d80763a8ae32b845ff517103718c6bb0a95bae5b9d5d1c8cd2", size = 999932, upload-time = "2024-09-11T16:59:42.576Z" }, + { url = "https://files.pythonhosted.org/packages/fa/11/a0d24b499e4f5800817e4760ad0be3c51713a458b1030df0e5192fae5d57/aggdraw-1.3.19-cp313-cp313-win_amd64.whl", hash = "sha256:41e09faf469cae11339f5d1b0680dba7ec02502efebc10086f1a142a8a39afb8", size = 45067, upload-time = "2024-09-11T16:59:43.789Z" }, + { url = "https://files.pythonhosted.org/packages/bf/1f/fe05b249ee116ba5c659b6e312ddc2a22dc1490f8869c2478404be220b23/aggdraw-1.3.19-cp313-cp313-win_arm64.whl", hash = "sha256:e5552ac136225692a774d481a9baa0736c68739f64b7a7ec9f4cc5f69c8a789a", size = 34185, upload-time = "2024-09-11T16:59:44.859Z" }, +] + +[[package]] +name = "click" +version = "8.2.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/60/6c/8ca2efa64cf75a977a0d7fac081354553ebe483345c734fb6b6515d96bbc/click-8.2.1.tar.gz", hash = "sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202", size = 286342, upload-time = "2025-05-20T23:19:49.832Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/85/32/10bb5764d90a8eee674e9dc6f4db6a0ab47c8c4d0d83c27f7c39ac415a4d/click-8.2.1-py3-none-any.whl", hash = "sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b", size = 102215, upload-time = "2025-05-20T23:19:47.796Z" }, +] + +[[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 = "contourpy" +version = "1.3.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/54/eb9bfc647b19f2009dd5c7f5ec51c4e6ca831725f1aea7a993034f483147/contourpy-1.3.2.tar.gz", hash = "sha256:b6945942715a034c671b7fc54f9588126b0b8bf23db2696e3ca8328f3ff0ab54", size = 13466130, upload-time = "2025-04-15T17:47:53.79Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/34/f7/44785876384eff370c251d58fd65f6ad7f39adce4a093c934d4a67a7c6b6/contourpy-1.3.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4caf2bcd2969402bf77edc4cb6034c7dd7c0803213b3523f111eb7460a51b8d2", size = 271580, upload-time = "2025-04-15T17:37:03.105Z" }, + { url = "https://files.pythonhosted.org/packages/93/3b/0004767622a9826ea3d95f0e9d98cd8729015768075d61f9fea8eeca42a8/contourpy-1.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:82199cb78276249796419fe36b7386bd8d2cc3f28b3bc19fe2454fe2e26c4c15", size = 255530, upload-time = "2025-04-15T17:37:07.026Z" }, + { url = "https://files.pythonhosted.org/packages/e7/bb/7bd49e1f4fa805772d9fd130e0d375554ebc771ed7172f48dfcd4ca61549/contourpy-1.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:106fab697af11456fcba3e352ad50effe493a90f893fca6c2ca5c033820cea92", size = 307688, upload-time = "2025-04-15T17:37:11.481Z" }, + { url = "https://files.pythonhosted.org/packages/fc/97/e1d5dbbfa170725ef78357a9a0edc996b09ae4af170927ba8ce977e60a5f/contourpy-1.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d14f12932a8d620e307f715857107b1d1845cc44fdb5da2bc8e850f5ceba9f87", size = 347331, upload-time = "2025-04-15T17:37:18.212Z" }, + { url = "https://files.pythonhosted.org/packages/6f/66/e69e6e904f5ecf6901be3dd16e7e54d41b6ec6ae3405a535286d4418ffb4/contourpy-1.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:532fd26e715560721bb0d5fc7610fce279b3699b018600ab999d1be895b09415", size = 318963, upload-time = "2025-04-15T17:37:22.76Z" }, + { url = "https://files.pythonhosted.org/packages/a8/32/b8a1c8965e4f72482ff2d1ac2cd670ce0b542f203c8e1d34e7c3e6925da7/contourpy-1.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f26b383144cf2d2c29f01a1e8170f50dacf0eac02d64139dcd709a8ac4eb3cfe", size = 323681, upload-time = "2025-04-15T17:37:33.001Z" }, + { url = "https://files.pythonhosted.org/packages/30/c6/12a7e6811d08757c7162a541ca4c5c6a34c0f4e98ef2b338791093518e40/contourpy-1.3.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c49f73e61f1f774650a55d221803b101d966ca0c5a2d6d5e4320ec3997489441", size = 1308674, upload-time = "2025-04-15T17:37:48.64Z" }, + { url = "https://files.pythonhosted.org/packages/2a/8a/bebe5a3f68b484d3a2b8ffaf84704b3e343ef1addea528132ef148e22b3b/contourpy-1.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3d80b2c0300583228ac98d0a927a1ba6a2ba6b8a742463c564f1d419ee5b211e", size = 1380480, upload-time = "2025-04-15T17:38:06.7Z" }, + { url = "https://files.pythonhosted.org/packages/34/db/fcd325f19b5978fb509a7d55e06d99f5f856294c1991097534360b307cf1/contourpy-1.3.2-cp312-cp312-win32.whl", hash = "sha256:90df94c89a91b7362e1142cbee7568f86514412ab8a2c0d0fca72d7e91b62912", size = 178489, upload-time = "2025-04-15T17:38:10.338Z" }, + { url = "https://files.pythonhosted.org/packages/01/c8/fadd0b92ffa7b5eb5949bf340a63a4a496a6930a6c37a7ba0f12acb076d6/contourpy-1.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:8c942a01d9163e2e5cfb05cb66110121b8d07ad438a17f9e766317bcb62abf73", size = 223042, upload-time = "2025-04-15T17:38:14.239Z" }, + { url = "https://files.pythonhosted.org/packages/2e/61/5673f7e364b31e4e7ef6f61a4b5121c5f170f941895912f773d95270f3a2/contourpy-1.3.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:de39db2604ae755316cb5967728f4bea92685884b1e767b7c24e983ef5f771cb", size = 271630, upload-time = "2025-04-15T17:38:19.142Z" }, + { url = "https://files.pythonhosted.org/packages/ff/66/a40badddd1223822c95798c55292844b7e871e50f6bfd9f158cb25e0bd39/contourpy-1.3.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3f9e896f447c5c8618f1edb2bafa9a4030f22a575ec418ad70611450720b5b08", size = 255670, upload-time = "2025-04-15T17:38:23.688Z" }, + { url = "https://files.pythonhosted.org/packages/1e/c7/cf9fdee8200805c9bc3b148f49cb9482a4e3ea2719e772602a425c9b09f8/contourpy-1.3.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71e2bd4a1c4188f5c2b8d274da78faab884b59df20df63c34f74aa1813c4427c", size = 306694, upload-time = "2025-04-15T17:38:28.238Z" }, + { url = "https://files.pythonhosted.org/packages/dd/e7/ccb9bec80e1ba121efbffad7f38021021cda5be87532ec16fd96533bb2e0/contourpy-1.3.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de425af81b6cea33101ae95ece1f696af39446db9682a0b56daaa48cfc29f38f", size = 345986, upload-time = "2025-04-15T17:38:33.502Z" }, + { url = "https://files.pythonhosted.org/packages/dc/49/ca13bb2da90391fa4219fdb23b078d6065ada886658ac7818e5441448b78/contourpy-1.3.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:977e98a0e0480d3fe292246417239d2d45435904afd6d7332d8455981c408b85", size = 318060, upload-time = "2025-04-15T17:38:38.672Z" }, + { url = "https://files.pythonhosted.org/packages/c8/65/5245ce8c548a8422236c13ffcdcdada6a2a812c361e9e0c70548bb40b661/contourpy-1.3.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:434f0adf84911c924519d2b08fc10491dd282b20bdd3fa8f60fd816ea0b48841", size = 322747, upload-time = "2025-04-15T17:38:43.712Z" }, + { url = "https://files.pythonhosted.org/packages/72/30/669b8eb48e0a01c660ead3752a25b44fdb2e5ebc13a55782f639170772f9/contourpy-1.3.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c66c4906cdbc50e9cba65978823e6e00b45682eb09adbb78c9775b74eb222422", size = 1308895, upload-time = "2025-04-15T17:39:00.224Z" }, + { url = "https://files.pythonhosted.org/packages/05/5a/b569f4250decee6e8d54498be7bdf29021a4c256e77fe8138c8319ef8eb3/contourpy-1.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8b7fc0cd78ba2f4695fd0a6ad81a19e7e3ab825c31b577f384aa9d7817dc3bef", size = 1379098, upload-time = "2025-04-15T17:43:29.649Z" }, + { url = "https://files.pythonhosted.org/packages/19/ba/b227c3886d120e60e41b28740ac3617b2f2b971b9f601c835661194579f1/contourpy-1.3.2-cp313-cp313-win32.whl", hash = "sha256:15ce6ab60957ca74cff444fe66d9045c1fd3e92c8936894ebd1f3eef2fff075f", size = 178535, upload-time = "2025-04-15T17:44:44.532Z" }, + { url = "https://files.pythonhosted.org/packages/12/6e/2fed56cd47ca739b43e892707ae9a13790a486a3173be063681ca67d2262/contourpy-1.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:e1578f7eafce927b168752ed7e22646dad6cd9bca673c60bff55889fa236ebf9", size = 223096, upload-time = "2025-04-15T17:44:48.194Z" }, + { url = "https://files.pythonhosted.org/packages/54/4c/e76fe2a03014a7c767d79ea35c86a747e9325537a8b7627e0e5b3ba266b4/contourpy-1.3.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0475b1f6604896bc7c53bb070e355e9321e1bc0d381735421a2d2068ec56531f", size = 285090, upload-time = "2025-04-15T17:43:34.084Z" }, + { url = "https://files.pythonhosted.org/packages/7b/e2/5aba47debd55d668e00baf9651b721e7733975dc9fc27264a62b0dd26eb8/contourpy-1.3.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:c85bb486e9be652314bb5b9e2e3b0d1b2e643d5eec4992c0fbe8ac71775da739", size = 268643, upload-time = "2025-04-15T17:43:38.626Z" }, + { url = "https://files.pythonhosted.org/packages/a1/37/cd45f1f051fe6230f751cc5cdd2728bb3a203f5619510ef11e732109593c/contourpy-1.3.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:745b57db7758f3ffc05a10254edd3182a2a83402a89c00957a8e8a22f5582823", size = 310443, upload-time = "2025-04-15T17:43:44.522Z" }, + { url = "https://files.pythonhosted.org/packages/8b/a2/36ea6140c306c9ff6dd38e3bcec80b3b018474ef4d17eb68ceecd26675f4/contourpy-1.3.2-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:970e9173dbd7eba9b4e01aab19215a48ee5dd3f43cef736eebde064a171f89a5", size = 349865, upload-time = "2025-04-15T17:43:49.545Z" }, + { url = "https://files.pythonhosted.org/packages/95/b7/2fc76bc539693180488f7b6cc518da7acbbb9e3b931fd9280504128bf956/contourpy-1.3.2-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c6c4639a9c22230276b7bffb6a850dfc8258a2521305e1faefe804d006b2e532", size = 321162, upload-time = "2025-04-15T17:43:54.203Z" }, + { url = "https://files.pythonhosted.org/packages/f4/10/76d4f778458b0aa83f96e59d65ece72a060bacb20cfbee46cf6cd5ceba41/contourpy-1.3.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc829960f34ba36aad4302e78eabf3ef16a3a100863f0d4eeddf30e8a485a03b", size = 327355, upload-time = "2025-04-15T17:44:01.025Z" }, + { url = "https://files.pythonhosted.org/packages/43/a3/10cf483ea683f9f8ab096c24bad3cce20e0d1dd9a4baa0e2093c1c962d9d/contourpy-1.3.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:d32530b534e986374fc19eaa77fcb87e8a99e5431499949b828312bdcd20ac52", size = 1307935, upload-time = "2025-04-15T17:44:17.322Z" }, + { url = "https://files.pythonhosted.org/packages/78/73/69dd9a024444489e22d86108e7b913f3528f56cfc312b5c5727a44188471/contourpy-1.3.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:e298e7e70cf4eb179cc1077be1c725b5fd131ebc81181bf0c03525c8abc297fd", size = 1372168, upload-time = "2025-04-15T17:44:33.43Z" }, + { url = "https://files.pythonhosted.org/packages/0f/1b/96d586ccf1b1a9d2004dd519b25fbf104a11589abfd05484ff12199cca21/contourpy-1.3.2-cp313-cp313t-win32.whl", hash = "sha256:d0e589ae0d55204991450bb5c23f571c64fe43adaa53f93fc902a84c96f52fe1", size = 189550, upload-time = "2025-04-15T17:44:37.092Z" }, + { url = "https://files.pythonhosted.org/packages/b0/e6/6000d0094e8a5e32ad62591c8609e269febb6e4db83a1c75ff8868b42731/contourpy-1.3.2-cp313-cp313t-win_amd64.whl", hash = "sha256:78e9253c3de756b3f6a5174d024c4835acd59eb3f8e2ca13e775dbffe1558f69", size = 238214, upload-time = "2025-04-15T17:44:40.827Z" }, +] + +[[package]] +name = "cvxopt" +version = "1.3.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f5/12/8467d16008ab7577259d32f1e59c4d84edda22b7729ab4a1a0dfd5f0550b/cvxopt-1.3.2.tar.gz", hash = "sha256:3461fa42c1b2240ba4da1d985ca73503914157fc4c77417327ed6d7d85acdbe6", size = 4108454, upload-time = "2023-08-09T14:31:17.514Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/10/dc/1c21715e1267ca29f562e4450426d1ff8a7ffcc3e670100cec332a105b95/cvxopt-1.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:25adbeb0efd50d7ea4f07e5f5bd390a3c807df907f03efb86b018807c2c8cfbe", size = 13836586, upload-time = "2023-12-22T12:05:40.049Z" }, + { url = "https://files.pythonhosted.org/packages/cd/c8/a04048143d0329ccd36403951746c1a6b5f1fc56c479e5a0a77efb2064b2/cvxopt-1.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c10e27cb7a27b55f17e0df30c6b85e98c9672a7bdb7000a7509560eee7679137", size = 12765513, upload-time = "2023-12-22T12:05:43.608Z" }, + { url = "https://files.pythonhosted.org/packages/c7/17/ee82c745c5bda340a4dd812652c42fb71efd45f663554a10c3ec45f230df/cvxopt-1.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e8bcf71a5016aeb24e597dc099564e8de809e0bc5d6af21e26422586aea26718", size = 17870231, upload-time = "2023-12-22T14:05:33.01Z" }, + { url = "https://files.pythonhosted.org/packages/c6/f9/467c3f4682f3dbfbd7ff67f2307ed746a86b6dcc6b0b62cf1eeaebbd9d74/cvxopt-1.3.2-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:a581e6c87a06371210184f64353055ff7c917d49363901ae0c527da139095082", size = 13846494, upload-time = "2024-10-21T20:50:42.583Z" }, + { url = "https://files.pythonhosted.org/packages/41/8e/c3869928250e12ad9264da388bc70150a9de039e233b815a6a3bd2b8b8ae/cvxopt-1.3.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be7800ac4556d8920aaf8e4e2d89348aafd5d585642aabf9eeecb09a2659fbca", size = 9529949, upload-time = "2024-10-21T20:50:45.165Z" }, + { url = "https://files.pythonhosted.org/packages/9f/ad/edce467c24529c536fc9de787546a1c8eca293009383a872b6f638d22eae/cvxopt-1.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:a92ebfc5df77fea57544f8ad2102bfc45af0e77ac4dfe98ed1b9628e8bba77c3", size = 12845277, upload-time = "2023-12-22T12:05:47.516Z" }, + { url = "https://files.pythonhosted.org/packages/3e/c5/3e70e50c4c478acd3fefe3ea51b7e42ad661ce5a265a72b3dba175ce10fc/cvxopt-1.3.2-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:2f9135eea23c9b781574e0cadc5738cf5651a8fd8de822b6de1260411523bfd1", size = 16873224, upload-time = "2024-10-21T20:48:37.221Z" }, + { url = "https://files.pythonhosted.org/packages/61/96/e42b9ec38e1bbe9bf85a5fc9cc7feb173de5a874889735072b49a7d4d8d0/cvxopt-1.3.2-cp313-cp313-macosx_15_0_arm64.whl", hash = "sha256:d7921768712db156e6ec92ac21f7ce52069feb1fb994868d0ca795498111fbac", size = 12424739, upload-time = "2024-10-21T20:48:40.325Z" }, + { url = "https://files.pythonhosted.org/packages/32/08/2c621ad782e9ff7f921c2244c6b4bcbc72ca756cb33021295c288123c465/cvxopt-1.3.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0af63db45ba559e3e15180fbec140d8a4ff612d8f21d989181a4e8479fa3b8b6", size = 17869707, upload-time = "2024-10-21T20:48:42.881Z" }, + { url = "https://files.pythonhosted.org/packages/62/60/583a1ef8e2e259bdd1bf32fccd4ea15aef4aad5854746ec59cbb2462eb92/cvxopt-1.3.2-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:8fe178ac780a8bccf425a08004d853eae43b3ddcf7617521fb35c63550077b17", size = 13846614, upload-time = "2024-10-21T20:48:46.26Z" }, + { url = "https://files.pythonhosted.org/packages/e4/2b/d8721b046a3c8bff494490a01ef1eeacf1f970f0d1274448856ccbe0475c/cvxopt-1.3.2-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:a47a95d7848e6fe768b55910bac8bb114c5f1f355f5a6590196d5e9bdf775d2f", size = 21277032, upload-time = "2024-10-21T20:48:49.48Z" }, + { url = "https://files.pythonhosted.org/packages/6a/19/b1e1c16895a36cc504bf7a940e88431b82b18ca10cbce81072860b9e3d60/cvxopt-1.3.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e863238d64a4b4443b8be53a08f6b94eda6ec1727038c330da02014f7c19e1be", size = 9530674, upload-time = "2024-10-21T20:48:51.948Z" }, + { url = "https://files.pythonhosted.org/packages/42/cc/ac0705749f96cc52f8d30c9c06e54dc8d4c04ef9c2d21aeed1ae2ee63dab/cvxopt-1.3.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4c56965415afd8a493cc4af3587960751f8780057ca3de8c6be97217156e4633", size = 13725340, upload-time = "2024-10-21T20:48:55.074Z" }, + { url = "https://files.pythonhosted.org/packages/76/f2/7e3c3f51e8e6b325bf00bfc37036f1f58bd9a5c29bbd88fb2eef2ebc0ac2/cvxopt-1.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:85c3b52c1353b294c597b169cc901f5274d8bb8776908ccad66fec7a14b69519", size = 16226402, upload-time = "2024-10-21T20:48:57.616Z" }, + { url = "https://files.pythonhosted.org/packages/b9/55/90b40b489a235a9f35a532eb77cec81782e466779d9a531ffda6b2f99410/cvxopt-1.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:0a0987966009ad383de0918e61255d34ed9ebc783565bcb15470d4155010b6bf", size = 12845323, upload-time = "2024-10-21T20:49:00.581Z" }, +] + +[[package]] +name = "cycler" +version = "0.12.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a9/95/a3dbbb5028f35eafb79008e7522a75244477d2838f38cbb722248dabc2a8/cycler-0.12.1.tar.gz", hash = "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c", size = 7615, upload-time = "2023-10-07T05:32:18.335Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30", size = 8321, upload-time = "2023-10-07T05:32:16.783Z" }, +] + +[[package]] +name = "deep-sad-pytorch" +version = "0.1.0" +source = { virtual = "." } +dependencies = [ + { name = "click" }, + { name = "cvxopt" }, + { name = "cycler" }, + { name = "joblib" }, + { name = "kiwisolver" }, + { name = "matplotlib" }, + { name = "numpy" }, + { name = "onnx" }, + { name = "onnxscript" }, + { name = "pandas" }, + { name = "pillow" }, + { name = "pyparsing" }, + { name = "python-dateutil" }, + { name = "pytz" }, + { name = "scikit-learn" }, + { name = "scipy" }, + { name = "seaborn" }, + { name = "six" }, + { name = "torch-receptive-field" }, + { name = "torchscan" }, + { name = "visualtorch" }, +] + +[package.metadata] +requires-dist = [ + { name = "click", specifier = ">=8.2.1" }, + { name = "cvxopt", specifier = ">=1.3.2" }, + { name = "cycler", specifier = ">=0.12.1" }, + { name = "joblib", specifier = ">=1.5.1" }, + { name = "kiwisolver", specifier = ">=1.4.8" }, + { name = "matplotlib", specifier = ">=3.10.3" }, + { name = "numpy", specifier = ">=2.3.1" }, + { name = "onnx", specifier = ">=1.18.0" }, + { name = "onnxscript", specifier = ">=0.3.2" }, + { name = "pandas", specifier = ">=2.3.0" }, + { name = "pillow", specifier = ">=11.2.1" }, + { name = "pyparsing", specifier = ">=3.2.3" }, + { name = "python-dateutil", specifier = ">=2.9.0.post0" }, + { name = "pytz", specifier = ">=2025.2" }, + { name = "scikit-learn", specifier = ">=1.7.0" }, + { name = "scipy", specifier = ">=1.16.0" }, + { name = "seaborn", specifier = ">=0.13.2" }, + { name = "six", specifier = ">=1.17.0" }, + { name = "torch-receptive-field", git = "https://github.com/Fangyh09/pytorch-receptive-field.git" }, + { name = "torchscan", specifier = ">=0.1.1" }, + { name = "visualtorch", specifier = ">=0.2.4" }, +] + +[[package]] +name = "filelock" +version = "3.18.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0a/10/c23352565a6544bdc5353e0b15fc1c563352101f30e24bf500207a54df9a/filelock-3.18.0.tar.gz", hash = "sha256:adbc88eabb99d2fec8c9c1b229b171f18afa655400173ddc653d5d01501fb9f2", size = 18075, upload-time = "2025-03-14T07:11:40.47Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4d/36/2a115987e2d8c300a974597416d9de88f2444426de9571f4b59b2cca3acc/filelock-3.18.0-py3-none-any.whl", hash = "sha256:c401f4f8377c4464e6db25fff06205fd89bdd83b65eb0488ed1b160f780e21de", size = 16215, upload-time = "2025-03-14T07:11:39.145Z" }, +] + +[[package]] +name = "fonttools" +version = "4.58.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2e/5a/1124b2c8cb3a8015faf552e92714040bcdbc145dfa29928891b02d147a18/fonttools-4.58.4.tar.gz", hash = "sha256:928a8009b9884ed3aae17724b960987575155ca23c6f0b8146e400cc9e0d44ba", size = 3525026, upload-time = "2025-06-13T17:25:15.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/3c/1d1792bfe91ef46f22a3d23b4deb514c325e73c17d4f196b385b5e2faf1c/fonttools-4.58.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:462211c0f37a278494e74267a994f6be9a2023d0557aaa9ecbcbfce0f403b5a6", size = 2754082, upload-time = "2025-06-13T17:24:24.862Z" }, + { url = "https://files.pythonhosted.org/packages/2a/1f/2b261689c901a1c3bc57a6690b0b9fc21a9a93a8b0c83aae911d3149f34e/fonttools-4.58.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0c7a12fb6f769165547f00fcaa8d0df9517603ae7e04b625e5acb8639809b82d", size = 2321677, upload-time = "2025-06-13T17:24:26.815Z" }, + { url = "https://files.pythonhosted.org/packages/fe/6b/4607add1755a1e6581ae1fc0c9a640648e0d9cdd6591cc2d581c2e07b8c3/fonttools-4.58.4-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2d42c63020a922154add0a326388a60a55504629edc3274bc273cd3806b4659f", size = 4896354, upload-time = "2025-06-13T17:24:28.428Z" }, + { url = "https://files.pythonhosted.org/packages/cd/95/34b4f483643d0cb11a1f830b72c03fdd18dbd3792d77a2eb2e130a96fada/fonttools-4.58.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8f2b4e6fd45edc6805f5f2c355590b092ffc7e10a945bd6a569fc66c1d2ae7aa", size = 4941633, upload-time = "2025-06-13T17:24:30.568Z" }, + { url = "https://files.pythonhosted.org/packages/81/ac/9bafbdb7694059c960de523e643fa5a61dd2f698f3f72c0ca18ae99257c7/fonttools-4.58.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f155b927f6efb1213a79334e4cb9904d1e18973376ffc17a0d7cd43d31981f1e", size = 4886170, upload-time = "2025-06-13T17:24:32.724Z" }, + { url = "https://files.pythonhosted.org/packages/ae/44/a3a3b70d5709405f7525bb7cb497b4e46151e0c02e3c8a0e40e5e9fe030b/fonttools-4.58.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e38f687d5de97c7fb7da3e58169fb5ba349e464e141f83c3c2e2beb91d317816", size = 5037851, upload-time = "2025-06-13T17:24:35.034Z" }, + { url = "https://files.pythonhosted.org/packages/21/cb/e8923d197c78969454eb876a4a55a07b59c9c4c46598f02b02411dc3b45c/fonttools-4.58.4-cp312-cp312-win32.whl", hash = "sha256:636c073b4da9db053aa683db99580cac0f7c213a953b678f69acbca3443c12cc", size = 2187428, upload-time = "2025-06-13T17:24:36.996Z" }, + { url = "https://files.pythonhosted.org/packages/46/e6/fe50183b1a0e1018e7487ee740fa8bb127b9f5075a41e20d017201e8ab14/fonttools-4.58.4-cp312-cp312-win_amd64.whl", hash = "sha256:82e8470535743409b30913ba2822e20077acf9ea70acec40b10fcf5671dceb58", size = 2236649, upload-time = "2025-06-13T17:24:38.985Z" }, + { url = "https://files.pythonhosted.org/packages/d4/4f/c05cab5fc1a4293e6bc535c6cb272607155a0517700f5418a4165b7f9ec8/fonttools-4.58.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:5f4a64846495c543796fa59b90b7a7a9dff6839bd852741ab35a71994d685c6d", size = 2745197, upload-time = "2025-06-13T17:24:40.645Z" }, + { url = "https://files.pythonhosted.org/packages/3e/d3/49211b1f96ae49308f4f78ca7664742377a6867f00f704cdb31b57e4b432/fonttools-4.58.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e80661793a5d4d7ad132a2aa1eae2e160fbdbb50831a0edf37c7c63b2ed36574", size = 2317272, upload-time = "2025-06-13T17:24:43.428Z" }, + { url = "https://files.pythonhosted.org/packages/b2/11/c9972e46a6abd752a40a46960e431c795ad1f306775fc1f9e8c3081a1274/fonttools-4.58.4-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fe5807fc64e4ba5130f1974c045a6e8d795f3b7fb6debfa511d1773290dbb76b", size = 4877184, upload-time = "2025-06-13T17:24:45.527Z" }, + { url = "https://files.pythonhosted.org/packages/ea/24/5017c01c9ef8df572cc9eaf9f12be83ad8ed722ff6dc67991d3d752956e4/fonttools-4.58.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b610b9bef841cb8f4b50472494158b1e347d15cad56eac414c722eda695a6cfd", size = 4939445, upload-time = "2025-06-13T17:24:47.647Z" }, + { url = "https://files.pythonhosted.org/packages/79/b0/538cc4d0284b5a8826b4abed93a69db52e358525d4b55c47c8cef3669767/fonttools-4.58.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2daa7f0e213c38f05f054eb5e1730bd0424aebddbeac094489ea1585807dd187", size = 4878800, upload-time = "2025-06-13T17:24:49.766Z" }, + { url = "https://files.pythonhosted.org/packages/5a/9b/a891446b7a8250e65bffceb248508587958a94db467ffd33972723ab86c9/fonttools-4.58.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:66cccb6c0b944496b7f26450e9a66e997739c513ffaac728d24930df2fd9d35b", size = 5021259, upload-time = "2025-06-13T17:24:51.754Z" }, + { url = "https://files.pythonhosted.org/packages/17/b2/c4d2872cff3ace3ddd1388bf15b76a1d8d5313f0a61f234e9aed287e674d/fonttools-4.58.4-cp313-cp313-win32.whl", hash = "sha256:94d2aebb5ca59a5107825520fde596e344652c1f18170ef01dacbe48fa60c889", size = 2185824, upload-time = "2025-06-13T17:24:54.324Z" }, + { url = "https://files.pythonhosted.org/packages/98/57/cddf8bcc911d4f47dfca1956c1e3aeeb9f7c9b8e88b2a312fe8c22714e0b/fonttools-4.58.4-cp313-cp313-win_amd64.whl", hash = "sha256:b554bd6e80bba582fd326ddab296e563c20c64dca816d5e30489760e0c41529f", size = 2236382, upload-time = "2025-06-13T17:24:56.291Z" }, + { url = "https://files.pythonhosted.org/packages/0b/2f/c536b5b9bb3c071e91d536a4d11f969e911dbb6b227939f4c5b0bca090df/fonttools-4.58.4-py3-none-any.whl", hash = "sha256:a10ce13a13f26cbb9f37512a4346bb437ad7e002ff6fa966a7ce7ff5ac3528bd", size = 1114660, upload-time = "2025-06-13T17:25:13.321Z" }, +] + +[[package]] +name = "fsspec" +version = "2025.5.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/00/f7/27f15d41f0ed38e8fcc488584b57e902b331da7f7c6dcda53721b15838fc/fsspec-2025.5.1.tar.gz", hash = "sha256:2e55e47a540b91843b755e83ded97c6e897fa0942b11490113f09e9c443c2475", size = 303033, upload-time = "2025-05-24T12:03:23.792Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bb/61/78c7b3851add1481b048b5fdc29067397a1784e2910592bc81bb3f608635/fsspec-2025.5.1-py3-none-any.whl", hash = "sha256:24d3a2e663d5fc735ab256263c4075f374a174c3410c0b25e5bd1970bceaa462", size = 199052, upload-time = "2025-05-24T12:03:21.66Z" }, +] + +[[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 = "joblib" +version = "1.5.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/dc/fe/0f5a938c54105553436dbff7a61dc4fed4b1b2c98852f8833beaf4d5968f/joblib-1.5.1.tar.gz", hash = "sha256:f4f86e351f39fe3d0d32a9f2c3d8af1ee4cec285aafcb27003dda5205576b444", size = 330475, upload-time = "2025-05-23T12:04:37.097Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7d/4f/1195bbac8e0c2acc5f740661631d8d750dc38d4a32b23ee5df3cde6f4e0d/joblib-1.5.1-py3-none-any.whl", hash = "sha256:4719a31f054c7d766948dcd83e9613686b27114f190f717cec7eaa2084f8a74a", size = 307746, upload-time = "2025-05-23T12:04:35.124Z" }, +] + +[[package]] +name = "kiwisolver" +version = "1.4.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/82/59/7c91426a8ac292e1cdd53a63b6d9439abd573c875c3f92c146767dd33faf/kiwisolver-1.4.8.tar.gz", hash = "sha256:23d5f023bdc8c7e54eb65f03ca5d5bb25b601eac4d7f1a042888a1f45237987e", size = 97538, upload-time = "2024-12-24T18:30:51.519Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fc/aa/cea685c4ab647f349c3bc92d2daf7ae34c8e8cf405a6dcd3a497f58a2ac3/kiwisolver-1.4.8-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d6af5e8815fd02997cb6ad9bbed0ee1e60014438ee1a5c2444c96f87b8843502", size = 124152, upload-time = "2024-12-24T18:29:16.85Z" }, + { url = "https://files.pythonhosted.org/packages/c5/0b/8db6d2e2452d60d5ebc4ce4b204feeb16176a851fd42462f66ade6808084/kiwisolver-1.4.8-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:bade438f86e21d91e0cf5dd7c0ed00cda0f77c8c1616bd83f9fc157fa6760d31", size = 66555, upload-time = "2024-12-24T18:29:19.146Z" }, + { url = "https://files.pythonhosted.org/packages/60/26/d6a0db6785dd35d3ba5bf2b2df0aedc5af089962c6eb2cbf67a15b81369e/kiwisolver-1.4.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b83dc6769ddbc57613280118fb4ce3cd08899cc3369f7d0e0fab518a7cf37fdb", size = 65067, upload-time = "2024-12-24T18:29:20.096Z" }, + { url = "https://files.pythonhosted.org/packages/c9/ed/1d97f7e3561e09757a196231edccc1bcf59d55ddccefa2afc9c615abd8e0/kiwisolver-1.4.8-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:111793b232842991be367ed828076b03d96202c19221b5ebab421ce8bcad016f", size = 1378443, upload-time = "2024-12-24T18:29:22.843Z" }, + { url = "https://files.pythonhosted.org/packages/29/61/39d30b99954e6b46f760e6289c12fede2ab96a254c443639052d1b573fbc/kiwisolver-1.4.8-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:257af1622860e51b1a9d0ce387bf5c2c4f36a90594cb9514f55b074bcc787cfc", size = 1472728, upload-time = "2024-12-24T18:29:24.463Z" }, + { url = "https://files.pythonhosted.org/packages/0c/3e/804163b932f7603ef256e4a715e5843a9600802bb23a68b4e08c8c0ff61d/kiwisolver-1.4.8-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:69b5637c3f316cab1ec1c9a12b8c5f4750a4c4b71af9157645bf32830e39c03a", size = 1478388, upload-time = "2024-12-24T18:29:25.776Z" }, + { url = "https://files.pythonhosted.org/packages/8a/9e/60eaa75169a154700be74f875a4d9961b11ba048bef315fbe89cb6999056/kiwisolver-1.4.8-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:782bb86f245ec18009890e7cb8d13a5ef54dcf2ebe18ed65f795e635a96a1c6a", size = 1413849, upload-time = "2024-12-24T18:29:27.202Z" }, + { url = "https://files.pythonhosted.org/packages/bc/b3/9458adb9472e61a998c8c4d95cfdfec91c73c53a375b30b1428310f923e4/kiwisolver-1.4.8-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc978a80a0db3a66d25767b03688f1147a69e6237175c0f4ffffaaedf744055a", size = 1475533, upload-time = "2024-12-24T18:29:28.638Z" }, + { url = "https://files.pythonhosted.org/packages/e4/7a/0a42d9571e35798de80aef4bb43a9b672aa7f8e58643d7bd1950398ffb0a/kiwisolver-1.4.8-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:36dbbfd34838500a31f52c9786990d00150860e46cd5041386f217101350f0d3", size = 2268898, upload-time = "2024-12-24T18:29:30.368Z" }, + { url = "https://files.pythonhosted.org/packages/d9/07/1255dc8d80271400126ed8db35a1795b1a2c098ac3a72645075d06fe5c5d/kiwisolver-1.4.8-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:eaa973f1e05131de5ff3569bbba7f5fd07ea0595d3870ed4a526d486fe57fa1b", size = 2425605, upload-time = "2024-12-24T18:29:33.151Z" }, + { url = "https://files.pythonhosted.org/packages/84/df/5a3b4cf13780ef6f6942df67b138b03b7e79e9f1f08f57c49957d5867f6e/kiwisolver-1.4.8-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:a66f60f8d0c87ab7f59b6fb80e642ebb29fec354a4dfad687ca4092ae69d04f4", size = 2375801, upload-time = "2024-12-24T18:29:34.584Z" }, + { url = "https://files.pythonhosted.org/packages/8f/10/2348d068e8b0f635c8c86892788dac7a6b5c0cb12356620ab575775aad89/kiwisolver-1.4.8-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:858416b7fb777a53f0c59ca08190ce24e9abbd3cffa18886a5781b8e3e26f65d", size = 2520077, upload-time = "2024-12-24T18:29:36.138Z" }, + { url = "https://files.pythonhosted.org/packages/32/d8/014b89fee5d4dce157d814303b0fce4d31385a2af4c41fed194b173b81ac/kiwisolver-1.4.8-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:085940635c62697391baafaaeabdf3dd7a6c3643577dde337f4d66eba021b2b8", size = 2338410, upload-time = "2024-12-24T18:29:39.991Z" }, + { url = "https://files.pythonhosted.org/packages/bd/72/dfff0cc97f2a0776e1c9eb5bef1ddfd45f46246c6533b0191887a427bca5/kiwisolver-1.4.8-cp312-cp312-win_amd64.whl", hash = "sha256:01c3d31902c7db5fb6182832713d3b4122ad9317c2c5877d0539227d96bb2e50", size = 71853, upload-time = "2024-12-24T18:29:42.006Z" }, + { url = "https://files.pythonhosted.org/packages/dc/85/220d13d914485c0948a00f0b9eb419efaf6da81b7d72e88ce2391f7aed8d/kiwisolver-1.4.8-cp312-cp312-win_arm64.whl", hash = "sha256:a3c44cb68861de93f0c4a8175fbaa691f0aa22550c331fefef02b618a9dcb476", size = 65424, upload-time = "2024-12-24T18:29:44.38Z" }, + { url = "https://files.pythonhosted.org/packages/79/b3/e62464a652f4f8cd9006e13d07abad844a47df1e6537f73ddfbf1bc997ec/kiwisolver-1.4.8-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:1c8ceb754339793c24aee1c9fb2485b5b1f5bb1c2c214ff13368431e51fc9a09", size = 124156, upload-time = "2024-12-24T18:29:45.368Z" }, + { url = "https://files.pythonhosted.org/packages/8d/2d/f13d06998b546a2ad4f48607a146e045bbe48030774de29f90bdc573df15/kiwisolver-1.4.8-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:54a62808ac74b5e55a04a408cda6156f986cefbcf0ada13572696b507cc92fa1", size = 66555, upload-time = "2024-12-24T18:29:46.37Z" }, + { url = "https://files.pythonhosted.org/packages/59/e3/b8bd14b0a54998a9fd1e8da591c60998dc003618cb19a3f94cb233ec1511/kiwisolver-1.4.8-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:68269e60ee4929893aad82666821aaacbd455284124817af45c11e50a4b42e3c", size = 65071, upload-time = "2024-12-24T18:29:47.333Z" }, + { url = "https://files.pythonhosted.org/packages/f0/1c/6c86f6d85ffe4d0ce04228d976f00674f1df5dc893bf2dd4f1928748f187/kiwisolver-1.4.8-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:34d142fba9c464bc3bbfeff15c96eab0e7310343d6aefb62a79d51421fcc5f1b", size = 1378053, upload-time = "2024-12-24T18:29:49.636Z" }, + { url = "https://files.pythonhosted.org/packages/4e/b9/1c6e9f6dcb103ac5cf87cb695845f5fa71379021500153566d8a8a9fc291/kiwisolver-1.4.8-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ddc373e0eef45b59197de815b1b28ef89ae3955e7722cc9710fb91cd77b7f47", size = 1472278, upload-time = "2024-12-24T18:29:51.164Z" }, + { url = "https://files.pythonhosted.org/packages/ee/81/aca1eb176de671f8bda479b11acdc42c132b61a2ac861c883907dde6debb/kiwisolver-1.4.8-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:77e6f57a20b9bd4e1e2cedda4d0b986ebd0216236f0106e55c28aea3d3d69b16", size = 1478139, upload-time = "2024-12-24T18:29:52.594Z" }, + { url = "https://files.pythonhosted.org/packages/49/f4/e081522473671c97b2687d380e9e4c26f748a86363ce5af48b4a28e48d06/kiwisolver-1.4.8-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:08e77738ed7538f036cd1170cbed942ef749137b1311fa2bbe2a7fda2f6bf3cc", size = 1413517, upload-time = "2024-12-24T18:29:53.941Z" }, + { url = "https://files.pythonhosted.org/packages/8f/e9/6a7d025d8da8c4931522922cd706105aa32b3291d1add8c5427cdcd66e63/kiwisolver-1.4.8-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a5ce1e481a74b44dd5e92ff03ea0cb371ae7a0268318e202be06c8f04f4f1246", size = 1474952, upload-time = "2024-12-24T18:29:56.523Z" }, + { url = "https://files.pythonhosted.org/packages/82/13/13fa685ae167bee5d94b415991c4fc7bb0a1b6ebea6e753a87044b209678/kiwisolver-1.4.8-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:fc2ace710ba7c1dfd1a3b42530b62b9ceed115f19a1656adefce7b1782a37794", size = 2269132, upload-time = "2024-12-24T18:29:57.989Z" }, + { url = "https://files.pythonhosted.org/packages/ef/92/bb7c9395489b99a6cb41d502d3686bac692586db2045adc19e45ee64ed23/kiwisolver-1.4.8-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:3452046c37c7692bd52b0e752b87954ef86ee2224e624ef7ce6cb21e8c41cc1b", size = 2425997, upload-time = "2024-12-24T18:29:59.393Z" }, + { url = "https://files.pythonhosted.org/packages/ed/12/87f0e9271e2b63d35d0d8524954145837dd1a6c15b62a2d8c1ebe0f182b4/kiwisolver-1.4.8-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:7e9a60b50fe8b2ec6f448fe8d81b07e40141bfced7f896309df271a0b92f80f3", size = 2376060, upload-time = "2024-12-24T18:30:01.338Z" }, + { url = "https://files.pythonhosted.org/packages/02/6e/c8af39288edbce8bf0fa35dee427b082758a4b71e9c91ef18fa667782138/kiwisolver-1.4.8-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:918139571133f366e8362fa4a297aeba86c7816b7ecf0bc79168080e2bd79957", size = 2520471, upload-time = "2024-12-24T18:30:04.574Z" }, + { url = "https://files.pythonhosted.org/packages/13/78/df381bc7b26e535c91469f77f16adcd073beb3e2dd25042efd064af82323/kiwisolver-1.4.8-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e063ef9f89885a1d68dd8b2e18f5ead48653176d10a0e324e3b0030e3a69adeb", size = 2338793, upload-time = "2024-12-24T18:30:06.25Z" }, + { url = "https://files.pythonhosted.org/packages/d0/dc/c1abe38c37c071d0fc71c9a474fd0b9ede05d42f5a458d584619cfd2371a/kiwisolver-1.4.8-cp313-cp313-win_amd64.whl", hash = "sha256:a17b7c4f5b2c51bb68ed379defd608a03954a1845dfed7cc0117f1cc8a9b7fd2", size = 71855, upload-time = "2024-12-24T18:30:07.535Z" }, + { url = "https://files.pythonhosted.org/packages/a0/b6/21529d595b126ac298fdd90b705d87d4c5693de60023e0efcb4f387ed99e/kiwisolver-1.4.8-cp313-cp313-win_arm64.whl", hash = "sha256:3cd3bc628b25f74aedc6d374d5babf0166a92ff1317f46267f12d2ed54bc1d30", size = 65430, upload-time = "2024-12-24T18:30:08.504Z" }, + { url = "https://files.pythonhosted.org/packages/34/bd/b89380b7298e3af9b39f49334e3e2a4af0e04819789f04b43d560516c0c8/kiwisolver-1.4.8-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:370fd2df41660ed4e26b8c9d6bbcad668fbe2560462cba151a721d49e5b6628c", size = 126294, upload-time = "2024-12-24T18:30:09.508Z" }, + { url = "https://files.pythonhosted.org/packages/83/41/5857dc72e5e4148eaac5aa76e0703e594e4465f8ab7ec0fc60e3a9bb8fea/kiwisolver-1.4.8-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:84a2f830d42707de1d191b9490ac186bf7997a9495d4e9072210a1296345f7dc", size = 67736, upload-time = "2024-12-24T18:30:11.039Z" }, + { url = "https://files.pythonhosted.org/packages/e1/d1/be059b8db56ac270489fb0b3297fd1e53d195ba76e9bbb30e5401fa6b759/kiwisolver-1.4.8-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:7a3ad337add5148cf51ce0b55642dc551c0b9d6248458a757f98796ca7348712", size = 66194, upload-time = "2024-12-24T18:30:14.886Z" }, + { url = "https://files.pythonhosted.org/packages/e1/83/4b73975f149819eb7dcf9299ed467eba068ecb16439a98990dcb12e63fdd/kiwisolver-1.4.8-cp313-cp313t-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7506488470f41169b86d8c9aeff587293f530a23a23a49d6bc64dab66bedc71e", size = 1465942, upload-time = "2024-12-24T18:30:18.927Z" }, + { url = "https://files.pythonhosted.org/packages/c7/2c/30a5cdde5102958e602c07466bce058b9d7cb48734aa7a4327261ac8e002/kiwisolver-1.4.8-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f0121b07b356a22fb0414cec4666bbe36fd6d0d759db3d37228f496ed67c880", size = 1595341, upload-time = "2024-12-24T18:30:22.102Z" }, + { url = "https://files.pythonhosted.org/packages/ff/9b/1e71db1c000385aa069704f5990574b8244cce854ecd83119c19e83c9586/kiwisolver-1.4.8-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d6d6bd87df62c27d4185de7c511c6248040afae67028a8a22012b010bc7ad062", size = 1598455, upload-time = "2024-12-24T18:30:24.947Z" }, + { url = "https://files.pythonhosted.org/packages/85/92/c8fec52ddf06231b31cbb779af77e99b8253cd96bd135250b9498144c78b/kiwisolver-1.4.8-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:291331973c64bb9cce50bbe871fb2e675c4331dab4f31abe89f175ad7679a4d7", size = 1522138, upload-time = "2024-12-24T18:30:26.286Z" }, + { url = "https://files.pythonhosted.org/packages/0b/51/9eb7e2cd07a15d8bdd976f6190c0164f92ce1904e5c0c79198c4972926b7/kiwisolver-1.4.8-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:893f5525bb92d3d735878ec00f781b2de998333659507d29ea4466208df37bed", size = 1582857, upload-time = "2024-12-24T18:30:28.86Z" }, + { url = "https://files.pythonhosted.org/packages/0f/95/c5a00387a5405e68ba32cc64af65ce881a39b98d73cc394b24143bebc5b8/kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b47a465040146981dc9db8647981b8cb96366fbc8d452b031e4f8fdffec3f26d", size = 2293129, upload-time = "2024-12-24T18:30:30.34Z" }, + { url = "https://files.pythonhosted.org/packages/44/83/eeb7af7d706b8347548313fa3a3a15931f404533cc54fe01f39e830dd231/kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:99cea8b9dd34ff80c521aef46a1dddb0dcc0283cf18bde6d756f1e6f31772165", size = 2421538, upload-time = "2024-12-24T18:30:33.334Z" }, + { url = "https://files.pythonhosted.org/packages/05/f9/27e94c1b3eb29e6933b6986ffc5fa1177d2cd1f0c8efc5f02c91c9ac61de/kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:151dffc4865e5fe6dafce5480fab84f950d14566c480c08a53c663a0020504b6", size = 2390661, upload-time = "2024-12-24T18:30:34.939Z" }, + { url = "https://files.pythonhosted.org/packages/d9/d4/3c9735faa36ac591a4afcc2980d2691000506050b7a7e80bcfe44048daa7/kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:577facaa411c10421314598b50413aa1ebcf5126f704f1e5d72d7e4e9f020d90", size = 2546710, upload-time = "2024-12-24T18:30:37.281Z" }, + { url = "https://files.pythonhosted.org/packages/4c/fa/be89a49c640930180657482a74970cdcf6f7072c8d2471e1babe17a222dc/kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:be4816dc51c8a471749d664161b434912eee82f2ea66bd7628bd14583a833e85", size = 2349213, upload-time = "2024-12-24T18:30:40.019Z" }, +] + +[[package]] +name = "markupsafe" +version = "3.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537, upload-time = "2024-10-18T15:21:54.129Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/22/09/d1f21434c97fc42f09d290cbb6350d44eb12f09cc62c9476effdb33a18aa/MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf", size = 14274, upload-time = "2024-10-18T15:21:13.777Z" }, + { url = "https://files.pythonhosted.org/packages/6b/b0/18f76bba336fa5aecf79d45dcd6c806c280ec44538b3c13671d49099fdd0/MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225", size = 12348, upload-time = "2024-10-18T15:21:14.822Z" }, + { url = "https://files.pythonhosted.org/packages/e0/25/dd5c0f6ac1311e9b40f4af06c78efde0f3b5cbf02502f8ef9501294c425b/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028", size = 24149, upload-time = "2024-10-18T15:21:15.642Z" }, + { url = "https://files.pythonhosted.org/packages/f3/f0/89e7aadfb3749d0f52234a0c8c7867877876e0a20b60e2188e9850794c17/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8", size = 23118, upload-time = "2024-10-18T15:21:17.133Z" }, + { url = "https://files.pythonhosted.org/packages/d5/da/f2eeb64c723f5e3777bc081da884b414671982008c47dcc1873d81f625b6/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c", size = 22993, upload-time = "2024-10-18T15:21:18.064Z" }, + { url = "https://files.pythonhosted.org/packages/da/0e/1f32af846df486dce7c227fe0f2398dc7e2e51d4a370508281f3c1c5cddc/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557", size = 24178, upload-time = "2024-10-18T15:21:18.859Z" }, + { url = "https://files.pythonhosted.org/packages/c4/f6/bb3ca0532de8086cbff5f06d137064c8410d10779c4c127e0e47d17c0b71/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22", size = 23319, upload-time = "2024-10-18T15:21:19.671Z" }, + { url = "https://files.pythonhosted.org/packages/a2/82/8be4c96ffee03c5b4a034e60a31294daf481e12c7c43ab8e34a1453ee48b/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48", size = 23352, upload-time = "2024-10-18T15:21:20.971Z" }, + { url = "https://files.pythonhosted.org/packages/51/ae/97827349d3fcffee7e184bdf7f41cd6b88d9919c80f0263ba7acd1bbcb18/MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30", size = 15097, upload-time = "2024-10-18T15:21:22.646Z" }, + { url = "https://files.pythonhosted.org/packages/c1/80/a61f99dc3a936413c3ee4e1eecac96c0da5ed07ad56fd975f1a9da5bc630/MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87", size = 15601, upload-time = "2024-10-18T15:21:23.499Z" }, + { url = "https://files.pythonhosted.org/packages/83/0e/67eb10a7ecc77a0c2bbe2b0235765b98d164d81600746914bebada795e97/MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd", size = 14274, upload-time = "2024-10-18T15:21:24.577Z" }, + { url = "https://files.pythonhosted.org/packages/2b/6d/9409f3684d3335375d04e5f05744dfe7e9f120062c9857df4ab490a1031a/MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430", size = 12352, upload-time = "2024-10-18T15:21:25.382Z" }, + { url = "https://files.pythonhosted.org/packages/d2/f5/6eadfcd3885ea85fe2a7c128315cc1bb7241e1987443d78c8fe712d03091/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094", size = 24122, upload-time = "2024-10-18T15:21:26.199Z" }, + { url = "https://files.pythonhosted.org/packages/0c/91/96cf928db8236f1bfab6ce15ad070dfdd02ed88261c2afafd4b43575e9e9/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396", size = 23085, upload-time = "2024-10-18T15:21:27.029Z" }, + { url = "https://files.pythonhosted.org/packages/c2/cf/c9d56af24d56ea04daae7ac0940232d31d5a8354f2b457c6d856b2057d69/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79", size = 22978, upload-time = "2024-10-18T15:21:27.846Z" }, + { url = "https://files.pythonhosted.org/packages/2a/9f/8619835cd6a711d6272d62abb78c033bda638fdc54c4e7f4272cf1c0962b/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a", size = 24208, upload-time = "2024-10-18T15:21:28.744Z" }, + { url = "https://files.pythonhosted.org/packages/f9/bf/176950a1792b2cd2102b8ffeb5133e1ed984547b75db47c25a67d3359f77/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca", size = 23357, upload-time = "2024-10-18T15:21:29.545Z" }, + { url = "https://files.pythonhosted.org/packages/ce/4f/9a02c1d335caabe5c4efb90e1b6e8ee944aa245c1aaaab8e8a618987d816/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c", size = 23344, upload-time = "2024-10-18T15:21:30.366Z" }, + { url = "https://files.pythonhosted.org/packages/ee/55/c271b57db36f748f0e04a759ace9f8f759ccf22b4960c270c78a394f58be/MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1", size = 15101, upload-time = "2024-10-18T15:21:31.207Z" }, + { url = "https://files.pythonhosted.org/packages/29/88/07df22d2dd4df40aba9f3e402e6dc1b8ee86297dddbad4872bd5e7b0094f/MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f", size = 15603, upload-time = "2024-10-18T15:21:32.032Z" }, + { url = "https://files.pythonhosted.org/packages/62/6a/8b89d24db2d32d433dffcd6a8779159da109842434f1dd2f6e71f32f738c/MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c", size = 14510, upload-time = "2024-10-18T15:21:33.625Z" }, + { url = "https://files.pythonhosted.org/packages/7a/06/a10f955f70a2e5a9bf78d11a161029d278eeacbd35ef806c3fd17b13060d/MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb", size = 12486, upload-time = "2024-10-18T15:21:34.611Z" }, + { url = "https://files.pythonhosted.org/packages/34/cf/65d4a571869a1a9078198ca28f39fba5fbb910f952f9dbc5220afff9f5e6/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c", size = 25480, upload-time = "2024-10-18T15:21:35.398Z" }, + { url = "https://files.pythonhosted.org/packages/0c/e3/90e9651924c430b885468b56b3d597cabf6d72be4b24a0acd1fa0e12af67/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d", size = 23914, upload-time = "2024-10-18T15:21:36.231Z" }, + { url = "https://files.pythonhosted.org/packages/66/8c/6c7cf61f95d63bb866db39085150df1f2a5bd3335298f14a66b48e92659c/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe", size = 23796, upload-time = "2024-10-18T15:21:37.073Z" }, + { url = "https://files.pythonhosted.org/packages/bb/35/cbe9238ec3f47ac9a7c8b3df7a808e7cb50fe149dc7039f5f454b3fba218/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5", size = 25473, upload-time = "2024-10-18T15:21:37.932Z" }, + { url = "https://files.pythonhosted.org/packages/e6/32/7621a4382488aa283cc05e8984a9c219abad3bca087be9ec77e89939ded9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a", size = 24114, upload-time = "2024-10-18T15:21:39.799Z" }, + { url = "https://files.pythonhosted.org/packages/0d/80/0985960e4b89922cb5a0bac0ed39c5b96cbc1a536a99f30e8c220a996ed9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9", size = 24098, upload-time = "2024-10-18T15:21:40.813Z" }, + { url = "https://files.pythonhosted.org/packages/82/78/fedb03c7d5380df2427038ec8d973587e90561b2d90cd472ce9254cf348b/MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6", size = 15208, upload-time = "2024-10-18T15:21:41.814Z" }, + { url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739, upload-time = "2024-10-18T15:21:42.784Z" }, +] + +[[package]] +name = "matplotlib" +version = "3.10.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "contourpy" }, + { name = "cycler" }, + { name = "fonttools" }, + { name = "kiwisolver" }, + { name = "numpy" }, + { name = "packaging" }, + { name = "pillow" }, + { name = "pyparsing" }, + { name = "python-dateutil" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/26/91/d49359a21893183ed2a5b6c76bec40e0b1dcbf8ca148f864d134897cfc75/matplotlib-3.10.3.tar.gz", hash = "sha256:2f82d2c5bb7ae93aaaa4cd42aca65d76ce6376f83304fa3a630b569aca274df0", size = 34799811, upload-time = "2025-05-08T19:10:54.39Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/eb/43/6b80eb47d1071f234ef0c96ca370c2ca621f91c12045f1401b5c9b28a639/matplotlib-3.10.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0ab1affc11d1f495ab9e6362b8174a25afc19c081ba5b0775ef00533a4236eea", size = 8179689, upload-time = "2025-05-08T19:10:07.602Z" }, + { url = "https://files.pythonhosted.org/packages/0f/70/d61a591958325c357204870b5e7b164f93f2a8cca1dc6ce940f563909a13/matplotlib-3.10.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2a818d8bdcafa7ed2eed74487fdb071c09c1ae24152d403952adad11fa3c65b4", size = 8050466, upload-time = "2025-05-08T19:10:09.383Z" }, + { url = "https://files.pythonhosted.org/packages/e7/75/70c9d2306203148cc7902a961240c5927dd8728afedf35e6a77e105a2985/matplotlib-3.10.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:748ebc3470c253e770b17d8b0557f0aa85cf8c63fd52f1a61af5b27ec0b7ffee", size = 8456252, upload-time = "2025-05-08T19:10:11.958Z" }, + { url = "https://files.pythonhosted.org/packages/c4/91/ba0ae1ff4b3f30972ad01cd4a8029e70a0ec3b8ea5be04764b128b66f763/matplotlib-3.10.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ed70453fd99733293ace1aec568255bc51c6361cb0da94fa5ebf0649fdb2150a", size = 8601321, upload-time = "2025-05-08T19:10:14.47Z" }, + { url = "https://files.pythonhosted.org/packages/d2/88/d636041eb54a84b889e11872d91f7cbf036b3b0e194a70fa064eb8b04f7a/matplotlib-3.10.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dbed9917b44070e55640bd13419de83b4c918e52d97561544814ba463811cbc7", size = 9406972, upload-time = "2025-05-08T19:10:16.569Z" }, + { url = "https://files.pythonhosted.org/packages/b1/79/0d1c165eac44405a86478082e225fce87874f7198300bbebc55faaf6d28d/matplotlib-3.10.3-cp312-cp312-win_amd64.whl", hash = "sha256:cf37d8c6ef1a48829443e8ba5227b44236d7fcaf7647caa3178a4ff9f7a5be05", size = 8067954, upload-time = "2025-05-08T19:10:18.663Z" }, + { url = "https://files.pythonhosted.org/packages/3b/c1/23cfb566a74c696a3b338d8955c549900d18fe2b898b6e94d682ca21e7c2/matplotlib-3.10.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9f2efccc8dcf2b86fc4ee849eea5dcaecedd0773b30f47980dc0cbeabf26ec84", size = 8180318, upload-time = "2025-05-08T19:10:20.426Z" }, + { url = "https://files.pythonhosted.org/packages/6c/0c/02f1c3b66b30da9ee343c343acbb6251bef5b01d34fad732446eaadcd108/matplotlib-3.10.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3ddbba06a6c126e3301c3d272a99dcbe7f6c24c14024e80307ff03791a5f294e", size = 8051132, upload-time = "2025-05-08T19:10:22.569Z" }, + { url = "https://files.pythonhosted.org/packages/b4/ab/8db1a5ac9b3a7352fb914133001dae889f9fcecb3146541be46bed41339c/matplotlib-3.10.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:748302b33ae9326995b238f606e9ed840bf5886ebafcb233775d946aa8107a15", size = 8457633, upload-time = "2025-05-08T19:10:24.749Z" }, + { url = "https://files.pythonhosted.org/packages/f5/64/41c4367bcaecbc03ef0d2a3ecee58a7065d0a36ae1aa817fe573a2da66d4/matplotlib-3.10.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a80fcccbef63302c0efd78042ea3c2436104c5b1a4d3ae20f864593696364ac7", size = 8601031, upload-time = "2025-05-08T19:10:27.03Z" }, + { url = "https://files.pythonhosted.org/packages/12/6f/6cc79e9e5ab89d13ed64da28898e40fe5b105a9ab9c98f83abd24e46d7d7/matplotlib-3.10.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:55e46cbfe1f8586adb34f7587c3e4f7dedc59d5226719faf6cb54fc24f2fd52d", size = 9406988, upload-time = "2025-05-08T19:10:29.056Z" }, + { url = "https://files.pythonhosted.org/packages/b1/0f/eed564407bd4d935ffabf561ed31099ed609e19287409a27b6d336848653/matplotlib-3.10.3-cp313-cp313-win_amd64.whl", hash = "sha256:151d89cb8d33cb23345cd12490c76fd5d18a56581a16d950b48c6ff19bb2ab93", size = 8068034, upload-time = "2025-05-08T19:10:31.221Z" }, + { url = "https://files.pythonhosted.org/packages/3e/e5/2f14791ff69b12b09e9975e1d116d9578ac684460860ce542c2588cb7a1c/matplotlib-3.10.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:c26dd9834e74d164d06433dc7be5d75a1e9890b926b3e57e74fa446e1a62c3e2", size = 8218223, upload-time = "2025-05-08T19:10:33.114Z" }, + { url = "https://files.pythonhosted.org/packages/5c/08/30a94afd828b6e02d0a52cae4a29d6e9ccfcf4c8b56cc28b021d3588873e/matplotlib-3.10.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:24853dad5b8c84c8c2390fc31ce4858b6df504156893292ce8092d190ef8151d", size = 8094985, upload-time = "2025-05-08T19:10:35.337Z" }, + { url = "https://files.pythonhosted.org/packages/89/44/f3bc6b53066c889d7a1a3ea8094c13af6a667c5ca6220ec60ecceec2dabe/matplotlib-3.10.3-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68f7878214d369d7d4215e2a9075fef743be38fa401d32e6020bab2dfabaa566", size = 8483109, upload-time = "2025-05-08T19:10:37.611Z" }, + { url = "https://files.pythonhosted.org/packages/ba/c7/473bc559beec08ebee9f86ca77a844b65747e1a6c2691e8c92e40b9f42a8/matplotlib-3.10.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6929fc618cb6db9cb75086f73b3219bbb25920cb24cee2ea7a12b04971a4158", size = 8618082, upload-time = "2025-05-08T19:10:39.892Z" }, + { url = "https://files.pythonhosted.org/packages/d8/e9/6ce8edd264c8819e37bbed8172e0ccdc7107fe86999b76ab5752276357a4/matplotlib-3.10.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6c7818292a5cc372a2dc4c795e5c356942eb8350b98ef913f7fda51fe175ac5d", size = 9413699, upload-time = "2025-05-08T19:10:42.376Z" }, + { url = "https://files.pythonhosted.org/packages/1b/92/9a45c91089c3cf690b5badd4be81e392ff086ccca8a1d4e3a08463d8a966/matplotlib-3.10.3-cp313-cp313t-win_amd64.whl", hash = "sha256:4f23ffe95c5667ef8a2b56eea9b53db7f43910fa4a2d5472ae0f72b64deab4d5", size = 8139044, upload-time = "2025-05-08T19:10:44.551Z" }, +] + +[[package]] +name = "ml-dtypes" +version = "0.5.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/78/a7/aad060393123cfb383956dca68402aff3db1e1caffd5764887ed5153f41b/ml_dtypes-0.5.3.tar.gz", hash = "sha256:95ce33057ba4d05df50b1f3cfefab22e351868a843b3b15a46c65836283670c9", size = 692316, upload-time = "2025-07-29T18:39:19.454Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0d/eb/bc07c88a6ab002b4635e44585d80fa0b350603f11a2097c9d1bfacc03357/ml_dtypes-0.5.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:156418abeeda48ea4797db6776db3c5bdab9ac7be197c1233771e0880c304057", size = 663864, upload-time = "2025-07-29T18:38:33.777Z" }, + { url = "https://files.pythonhosted.org/packages/cf/89/11af9b0f21b99e6386b6581ab40fb38d03225f9de5f55cf52097047e2826/ml_dtypes-0.5.3-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1db60c154989af253f6c4a34e8a540c2c9dce4d770784d426945e09908fbb177", size = 4951313, upload-time = "2025-07-29T18:38:36.45Z" }, + { url = "https://files.pythonhosted.org/packages/d8/a9/b98b86426c24900b0c754aad006dce2863df7ce0bb2bcc2c02f9cc7e8489/ml_dtypes-0.5.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1b255acada256d1fa8c35ed07b5f6d18bc21d1556f842fbc2d5718aea2cd9e55", size = 4928805, upload-time = "2025-07-29T18:38:38.29Z" }, + { url = "https://files.pythonhosted.org/packages/50/c1/85e6be4fc09c6175f36fb05a45917837f30af9a5146a5151cb3a3f0f9e09/ml_dtypes-0.5.3-cp312-cp312-win_amd64.whl", hash = "sha256:da65e5fd3eea434ccb8984c3624bc234ddcc0d9f4c81864af611aaebcc08a50e", size = 208182, upload-time = "2025-07-29T18:38:39.72Z" }, + { url = "https://files.pythonhosted.org/packages/9e/17/cf5326d6867be057f232d0610de1458f70a8ce7b6290e4b4a277ea62b4cd/ml_dtypes-0.5.3-cp312-cp312-win_arm64.whl", hash = "sha256:8bb9cd1ce63096567f5f42851f5843b5a0ea11511e50039a7649619abfb4ba6d", size = 161560, upload-time = "2025-07-29T18:38:41.072Z" }, + { url = "https://files.pythonhosted.org/packages/2d/87/1bcc98a66de7b2455dfb292f271452cac9edc4e870796e0d87033524d790/ml_dtypes-0.5.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:5103856a225465371fe119f2fef737402b705b810bd95ad5f348e6e1a6ae21af", size = 663781, upload-time = "2025-07-29T18:38:42.984Z" }, + { url = "https://files.pythonhosted.org/packages/fd/2c/bd2a79ba7c759ee192b5601b675b180a3fd6ccf48ffa27fe1782d280f1a7/ml_dtypes-0.5.3-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4cae435a68861660af81fa3c5af16b70ca11a17275c5b662d9c6f58294e0f113", size = 4956217, upload-time = "2025-07-29T18:38:44.65Z" }, + { url = "https://files.pythonhosted.org/packages/14/f3/091ba84e5395d7fe5b30c081a44dec881cd84b408db1763ee50768b2ab63/ml_dtypes-0.5.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6936283b56d74fbec431ca57ce58a90a908fdbd14d4e2d22eea6d72bb208a7b7", size = 4933109, upload-time = "2025-07-29T18:38:46.405Z" }, + { url = "https://files.pythonhosted.org/packages/bc/24/054036dbe32c43295382c90a1363241684c4d6aaa1ecc3df26bd0c8d5053/ml_dtypes-0.5.3-cp313-cp313-win_amd64.whl", hash = "sha256:d0f730a17cf4f343b2c7ad50cee3bd19e969e793d2be6ed911f43086460096e4", size = 208187, upload-time = "2025-07-29T18:38:48.24Z" }, + { url = "https://files.pythonhosted.org/packages/a6/3d/7dc3ec6794a4a9004c765e0c341e32355840b698f73fd2daff46f128afc1/ml_dtypes-0.5.3-cp313-cp313-win_arm64.whl", hash = "sha256:2db74788fc01914a3c7f7da0763427280adfc9cd377e9604b6b64eb8097284bd", size = 161559, upload-time = "2025-07-29T18:38:50.493Z" }, + { url = "https://files.pythonhosted.org/packages/12/91/e6c7a0d67a152b9330445f9f0cf8ae6eee9b83f990b8c57fe74631e42a90/ml_dtypes-0.5.3-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:93c36a08a6d158db44f2eb9ce3258e53f24a9a4a695325a689494f0fdbc71770", size = 689321, upload-time = "2025-07-29T18:38:52.03Z" }, + { url = "https://files.pythonhosted.org/packages/9e/6c/b7b94b84a104a5be1883305b87d4c6bd6ae781504474b4cca067cb2340ec/ml_dtypes-0.5.3-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0e44a3761f64bc009d71ddb6d6c71008ba21b53ab6ee588dadab65e2fa79eafc", size = 5274495, upload-time = "2025-07-29T18:38:53.797Z" }, + { url = "https://files.pythonhosted.org/packages/5b/38/6266604dffb43378055394ea110570cf261a49876fc48f548dfe876f34cc/ml_dtypes-0.5.3-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bdf40d2aaabd3913dec11840f0d0ebb1b93134f99af6a0a4fd88ffe924928ab4", size = 5285422, upload-time = "2025-07-29T18:38:56.603Z" }, + { url = "https://files.pythonhosted.org/packages/7c/88/8612ff177d043a474b9408f0382605d881eeb4125ba89d4d4b3286573a83/ml_dtypes-0.5.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:aec640bd94c4c85c0d11e2733bd13cbb10438fb004852996ec0efbc6cacdaf70", size = 661182, upload-time = "2025-07-29T18:38:58.414Z" }, + { url = "https://files.pythonhosted.org/packages/6f/2b/0569a5e88b29240d373e835107c94ae9256fb2191d3156b43b2601859eff/ml_dtypes-0.5.3-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bda32ce212baa724e03c68771e5c69f39e584ea426bfe1a701cb01508ffc7035", size = 4956187, upload-time = "2025-07-29T18:39:00.611Z" }, + { url = "https://files.pythonhosted.org/packages/51/66/273c2a06ae44562b104b61e6b14444da00061fd87652506579d7eb2c40b1/ml_dtypes-0.5.3-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c205cac07d24a29840c163d6469f61069ce4b065518519216297fc2f261f8db9", size = 4930911, upload-time = "2025-07-29T18:39:02.405Z" }, + { url = "https://files.pythonhosted.org/packages/93/ab/606be3e87dc0821bd360c8c1ee46108025c31a4f96942b63907bb441b87d/ml_dtypes-0.5.3-cp314-cp314-win_amd64.whl", hash = "sha256:cd7c0bb22d4ff86d65ad61b5dd246812e8993fbc95b558553624c33e8b6903ea", size = 216664, upload-time = "2025-07-29T18:39:03.927Z" }, + { url = "https://files.pythonhosted.org/packages/30/a2/e900690ca47d01dffffd66375c5de8c4f8ced0f1ef809ccd3b25b3e6b8fa/ml_dtypes-0.5.3-cp314-cp314-win_arm64.whl", hash = "sha256:9d55ea7f7baf2aed61bf1872116cefc9d0c3693b45cae3916897ee27ef4b835e", size = 160203, upload-time = "2025-07-29T18:39:05.671Z" }, + { url = "https://files.pythonhosted.org/packages/53/21/783dfb51f40d2660afeb9bccf3612b99f6a803d980d2a09132b0f9d216ab/ml_dtypes-0.5.3-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:e12e29764a0e66a7a31e9b8bf1de5cc0423ea72979f45909acd4292de834ccd3", size = 689324, upload-time = "2025-07-29T18:39:07.567Z" }, + { url = "https://files.pythonhosted.org/packages/09/f7/a82d249c711abf411ac027b7163f285487f5e615c3e0716c61033ce996ab/ml_dtypes-0.5.3-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:19f6c3a4f635c2fc9e2aa7d91416bd7a3d649b48350c51f7f715a09370a90d93", size = 5275917, upload-time = "2025-07-29T18:39:09.339Z" }, + { url = "https://files.pythonhosted.org/packages/7f/3c/541c4b30815ab90ebfbb51df15d0b4254f2f9f1e2b4907ab229300d5e6f2/ml_dtypes-0.5.3-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5ab039ffb40f3dc0aeeeba84fd6c3452781b5e15bef72e2d10bcb33e4bbffc39", size = 5285284, upload-time = "2025-07-29T18:39:11.532Z" }, +] + +[[package]] +name = "mpmath" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e0/47/dd32fa426cc72114383ac549964eecb20ecfd886d1e5ccf5340b55b02f57/mpmath-1.3.0.tar.gz", hash = "sha256:7a28eb2a9774d00c7bc92411c19a89209d5da7c4c9a9e227be8330a23a25b91f", size = 508106, upload-time = "2023-03-07T16:47:11.061Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl", hash = "sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c", size = 536198, upload-time = "2023-03-07T16:47:09.197Z" }, +] + +[[package]] +name = "networkx" +version = "3.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6c/4f/ccdb8ad3a38e583f214547fd2f7ff1fc160c43a75af88e6aec213404b96a/networkx-3.5.tar.gz", hash = "sha256:d4c6f9cf81f52d69230866796b82afbccdec3db7ae4fbd1b65ea750feed50037", size = 2471065, upload-time = "2025-05-29T11:35:07.804Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/eb/8d/776adee7bbf76365fdd7f2552710282c79a4ead5d2a46408c9043a2b70ba/networkx-3.5-py3-none-any.whl", hash = "sha256:0030d386a9a06dee3565298b4a734b68589749a544acbb6c412dc9e2489ec6ec", size = 2034406, upload-time = "2025-05-29T11:35:04.961Z" }, +] + +[[package]] +name = "numpy" +version = "2.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2e/19/d7c972dfe90a353dbd3efbbe1d14a5951de80c99c9dc1b93cd998d51dc0f/numpy-2.3.1.tar.gz", hash = "sha256:1ec9ae20a4226da374362cca3c62cd753faf2f951440b0e3b98e93c235441d2b", size = 20390372, upload-time = "2025-06-21T12:28:33.469Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c6/56/71ad5022e2f63cfe0ca93559403d0edef14aea70a841d640bd13cdba578e/numpy-2.3.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2959d8f268f3d8ee402b04a9ec4bb7604555aeacf78b360dc4ec27f1d508177d", size = 20896664, upload-time = "2025-06-21T12:15:30.845Z" }, + { url = "https://files.pythonhosted.org/packages/25/65/2db52ba049813670f7f987cc5db6dac9be7cd95e923cc6832b3d32d87cef/numpy-2.3.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:762e0c0c6b56bdedfef9a8e1d4538556438288c4276901ea008ae44091954e29", size = 14131078, upload-time = "2025-06-21T12:15:52.23Z" }, + { url = "https://files.pythonhosted.org/packages/57/dd/28fa3c17b0e751047ac928c1e1b6990238faad76e9b147e585b573d9d1bd/numpy-2.3.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:867ef172a0976aaa1f1d1b63cf2090de8b636a7674607d514505fb7276ab08fc", size = 5112554, upload-time = "2025-06-21T12:16:01.434Z" }, + { url = "https://files.pythonhosted.org/packages/c9/fc/84ea0cba8e760c4644b708b6819d91784c290288c27aca916115e3311d17/numpy-2.3.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:4e602e1b8682c2b833af89ba641ad4176053aaa50f5cacda1a27004352dde943", size = 6646560, upload-time = "2025-06-21T12:16:11.895Z" }, + { url = "https://files.pythonhosted.org/packages/61/b2/512b0c2ddec985ad1e496b0bd853eeb572315c0f07cd6997473ced8f15e2/numpy-2.3.1-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:8e333040d069eba1652fb08962ec5b76af7f2c7bce1df7e1418c8055cf776f25", size = 14260638, upload-time = "2025-06-21T12:16:32.611Z" }, + { url = "https://files.pythonhosted.org/packages/6e/45/c51cb248e679a6c6ab14b7a8e3ead3f4a3fe7425fc7a6f98b3f147bec532/numpy-2.3.1-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:e7cbf5a5eafd8d230a3ce356d892512185230e4781a361229bd902ff403bc660", size = 16632729, upload-time = "2025-06-21T12:16:57.439Z" }, + { url = "https://files.pythonhosted.org/packages/e4/ff/feb4be2e5c09a3da161b412019caf47183099cbea1132fd98061808c2df2/numpy-2.3.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5f1b8f26d1086835f442286c1d9b64bb3974b0b1e41bb105358fd07d20872952", size = 15565330, upload-time = "2025-06-21T12:17:20.638Z" }, + { url = "https://files.pythonhosted.org/packages/bc/6d/ceafe87587101e9ab0d370e4f6e5f3f3a85b9a697f2318738e5e7e176ce3/numpy-2.3.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ee8340cb48c9b7a5899d1149eece41ca535513a9698098edbade2a8e7a84da77", size = 18361734, upload-time = "2025-06-21T12:17:47.938Z" }, + { url = "https://files.pythonhosted.org/packages/2b/19/0fb49a3ea088be691f040c9bf1817e4669a339d6e98579f91859b902c636/numpy-2.3.1-cp312-cp312-win32.whl", hash = "sha256:e772dda20a6002ef7061713dc1e2585bc1b534e7909b2030b5a46dae8ff077ab", size = 6320411, upload-time = "2025-06-21T12:17:58.475Z" }, + { url = "https://files.pythonhosted.org/packages/b1/3e/e28f4c1dd9e042eb57a3eb652f200225e311b608632bc727ae378623d4f8/numpy-2.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:cfecc7822543abdea6de08758091da655ea2210b8ffa1faf116b940693d3df76", size = 12734973, upload-time = "2025-06-21T12:18:17.601Z" }, + { url = "https://files.pythonhosted.org/packages/04/a8/8a5e9079dc722acf53522b8f8842e79541ea81835e9b5483388701421073/numpy-2.3.1-cp312-cp312-win_arm64.whl", hash = "sha256:7be91b2239af2658653c5bb6f1b8bccafaf08226a258caf78ce44710a0160d30", size = 10191491, upload-time = "2025-06-21T12:18:33.585Z" }, + { url = "https://files.pythonhosted.org/packages/d4/bd/35ad97006d8abff8631293f8ea6adf07b0108ce6fec68da3c3fcca1197f2/numpy-2.3.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:25a1992b0a3fdcdaec9f552ef10d8103186f5397ab45e2d25f8ac51b1a6b97e8", size = 20889381, upload-time = "2025-06-21T12:19:04.103Z" }, + { url = "https://files.pythonhosted.org/packages/f1/4f/df5923874d8095b6062495b39729178eef4a922119cee32a12ee1bd4664c/numpy-2.3.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7dea630156d39b02a63c18f508f85010230409db5b2927ba59c8ba4ab3e8272e", size = 14152726, upload-time = "2025-06-21T12:19:25.599Z" }, + { url = "https://files.pythonhosted.org/packages/8c/0f/a1f269b125806212a876f7efb049b06c6f8772cf0121139f97774cd95626/numpy-2.3.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:bada6058dd886061f10ea15f230ccf7dfff40572e99fef440a4a857c8728c9c0", size = 5105145, upload-time = "2025-06-21T12:19:34.782Z" }, + { url = "https://files.pythonhosted.org/packages/6d/63/a7f7fd5f375b0361682f6ffbf686787e82b7bbd561268e4f30afad2bb3c0/numpy-2.3.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:a894f3816eb17b29e4783e5873f92faf55b710c2519e5c351767c51f79d8526d", size = 6639409, upload-time = "2025-06-21T12:19:45.228Z" }, + { url = "https://files.pythonhosted.org/packages/bf/0d/1854a4121af895aab383f4aa233748f1df4671ef331d898e32426756a8a6/numpy-2.3.1-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:18703df6c4a4fee55fd3d6e5a253d01c5d33a295409b03fda0c86b3ca2ff41a1", size = 14257630, upload-time = "2025-06-21T12:20:06.544Z" }, + { url = "https://files.pythonhosted.org/packages/50/30/af1b277b443f2fb08acf1c55ce9d68ee540043f158630d62cef012750f9f/numpy-2.3.1-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:5902660491bd7a48b2ec16c23ccb9124b8abfd9583c5fdfa123fe6b421e03de1", size = 16627546, upload-time = "2025-06-21T12:20:31.002Z" }, + { url = "https://files.pythonhosted.org/packages/6e/ec/3b68220c277e463095342d254c61be8144c31208db18d3fd8ef02712bcd6/numpy-2.3.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:36890eb9e9d2081137bd78d29050ba63b8dab95dff7912eadf1185e80074b2a0", size = 15562538, upload-time = "2025-06-21T12:20:54.322Z" }, + { url = "https://files.pythonhosted.org/packages/77/2b/4014f2bcc4404484021c74d4c5ee8eb3de7e3f7ac75f06672f8dcf85140a/numpy-2.3.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a780033466159c2270531e2b8ac063704592a0bc62ec4a1b991c7c40705eb0e8", size = 18360327, upload-time = "2025-06-21T12:21:21.053Z" }, + { url = "https://files.pythonhosted.org/packages/40/8d/2ddd6c9b30fcf920837b8672f6c65590c7d92e43084c25fc65edc22e93ca/numpy-2.3.1-cp313-cp313-win32.whl", hash = "sha256:39bff12c076812595c3a306f22bfe49919c5513aa1e0e70fac756a0be7c2a2b8", size = 6312330, upload-time = "2025-06-21T12:25:07.447Z" }, + { url = "https://files.pythonhosted.org/packages/dd/c8/beaba449925988d415efccb45bf977ff8327a02f655090627318f6398c7b/numpy-2.3.1-cp313-cp313-win_amd64.whl", hash = "sha256:8d5ee6eec45f08ce507a6570e06f2f879b374a552087a4179ea7838edbcbfa42", size = 12731565, upload-time = "2025-06-21T12:25:26.444Z" }, + { url = "https://files.pythonhosted.org/packages/0b/c3/5c0c575d7ec78c1126998071f58facfc124006635da75b090805e642c62e/numpy-2.3.1-cp313-cp313-win_arm64.whl", hash = "sha256:0c4d9e0a8368db90f93bd192bfa771ace63137c3488d198ee21dfb8e7771916e", size = 10190262, upload-time = "2025-06-21T12:25:42.196Z" }, + { url = "https://files.pythonhosted.org/packages/ea/19/a029cd335cf72f79d2644dcfc22d90f09caa86265cbbde3b5702ccef6890/numpy-2.3.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:b0b5397374f32ec0649dd98c652a1798192042e715df918c20672c62fb52d4b8", size = 20987593, upload-time = "2025-06-21T12:21:51.664Z" }, + { url = "https://files.pythonhosted.org/packages/25/91/8ea8894406209107d9ce19b66314194675d31761fe2cb3c84fe2eeae2f37/numpy-2.3.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:c5bdf2015ccfcee8253fb8be695516ac4457c743473a43290fd36eba6a1777eb", size = 14300523, upload-time = "2025-06-21T12:22:13.583Z" }, + { url = "https://files.pythonhosted.org/packages/a6/7f/06187b0066eefc9e7ce77d5f2ddb4e314a55220ad62dd0bfc9f2c44bac14/numpy-2.3.1-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:d70f20df7f08b90a2062c1f07737dd340adccf2068d0f1b9b3d56e2038979fee", size = 5227993, upload-time = "2025-06-21T12:22:22.53Z" }, + { url = "https://files.pythonhosted.org/packages/e8/ec/a926c293c605fa75e9cfb09f1e4840098ed46d2edaa6e2152ee35dc01ed3/numpy-2.3.1-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:2fb86b7e58f9ac50e1e9dd1290154107e47d1eef23a0ae9145ded06ea606f992", size = 6736652, upload-time = "2025-06-21T12:22:33.629Z" }, + { url = "https://files.pythonhosted.org/packages/e3/62/d68e52fb6fde5586650d4c0ce0b05ff3a48ad4df4ffd1b8866479d1d671d/numpy-2.3.1-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:23ab05b2d241f76cb883ce8b9a93a680752fbfcbd51c50eff0b88b979e471d8c", size = 14331561, upload-time = "2025-06-21T12:22:55.056Z" }, + { url = "https://files.pythonhosted.org/packages/fc/ec/b74d3f2430960044bdad6900d9f5edc2dc0fb8bf5a0be0f65287bf2cbe27/numpy-2.3.1-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:ce2ce9e5de4703a673e705183f64fd5da5bf36e7beddcb63a25ee2286e71ca48", size = 16693349, upload-time = "2025-06-21T12:23:20.53Z" }, + { url = "https://files.pythonhosted.org/packages/0d/15/def96774b9d7eb198ddadfcbd20281b20ebb510580419197e225f5c55c3e/numpy-2.3.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c4913079974eeb5c16ccfd2b1f09354b8fed7e0d6f2cab933104a09a6419b1ee", size = 15642053, upload-time = "2025-06-21T12:23:43.697Z" }, + { url = "https://files.pythonhosted.org/packages/2b/57/c3203974762a759540c6ae71d0ea2341c1fa41d84e4971a8e76d7141678a/numpy-2.3.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:010ce9b4f00d5c036053ca684c77441f2f2c934fd23bee058b4d6f196efd8280", size = 18434184, upload-time = "2025-06-21T12:24:10.708Z" }, + { url = "https://files.pythonhosted.org/packages/22/8a/ccdf201457ed8ac6245187850aff4ca56a79edbea4829f4e9f14d46fa9a5/numpy-2.3.1-cp313-cp313t-win32.whl", hash = "sha256:6269b9edfe32912584ec496d91b00b6d34282ca1d07eb10e82dfc780907d6c2e", size = 6440678, upload-time = "2025-06-21T12:24:21.596Z" }, + { url = "https://files.pythonhosted.org/packages/f1/7e/7f431d8bd8eb7e03d79294aed238b1b0b174b3148570d03a8a8a8f6a0da9/numpy-2.3.1-cp313-cp313t-win_amd64.whl", hash = "sha256:2a809637460e88a113e186e87f228d74ae2852a2e0c44de275263376f17b5bdc", size = 12870697, upload-time = "2025-06-21T12:24:40.644Z" }, + { url = "https://files.pythonhosted.org/packages/d4/ca/af82bf0fad4c3e573c6930ed743b5308492ff19917c7caaf2f9b6f9e2e98/numpy-2.3.1-cp313-cp313t-win_arm64.whl", hash = "sha256:eccb9a159db9aed60800187bc47a6d3451553f0e1b08b068d8b277ddfbb9b244", size = 10260376, upload-time = "2025-06-21T12:24:56.884Z" }, +] + +[[package]] +name = "nvidia-cublas-cu12" +version = "12.6.4.1" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/af/eb/ff4b8c503fa1f1796679dce648854d58751982426e4e4b37d6fce49d259c/nvidia_cublas_cu12-12.6.4.1-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:08ed2686e9875d01b58e3cb379c6896df8e76c75e0d4a7f7dace3d7b6d9ef8eb", size = 393138322, upload-time = "2024-11-20T17:40:25.65Z" }, +] + +[[package]] +name = "nvidia-cuda-cupti-cu12" +version = "12.6.80" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/49/60/7b6497946d74bcf1de852a21824d63baad12cd417db4195fc1bfe59db953/nvidia_cuda_cupti_cu12-12.6.80-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6768bad6cab4f19e8292125e5f1ac8aa7d1718704012a0e3272a6f61c4bce132", size = 8917980, upload-time = "2024-11-20T17:36:04.019Z" }, + { url = "https://files.pythonhosted.org/packages/a5/24/120ee57b218d9952c379d1e026c4479c9ece9997a4fb46303611ee48f038/nvidia_cuda_cupti_cu12-12.6.80-py3-none-manylinux2014_x86_64.whl", hash = "sha256:a3eff6cdfcc6a4c35db968a06fcadb061cbc7d6dde548609a941ff8701b98b73", size = 8917972, upload-time = "2024-10-01T16:58:06.036Z" }, +] + +[[package]] +name = "nvidia-cuda-nvrtc-cu12" +version = "12.6.77" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/75/2e/46030320b5a80661e88039f59060d1790298b4718944a65a7f2aeda3d9e9/nvidia_cuda_nvrtc_cu12-12.6.77-py3-none-manylinux2014_x86_64.whl", hash = "sha256:35b0cc6ee3a9636d5409133e79273ce1f3fd087abb0532d2d2e8fff1fe9efc53", size = 23650380, upload-time = "2024-10-01T17:00:14.643Z" }, +] + +[[package]] +name = "nvidia-cuda-runtime-cu12" +version = "12.6.77" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e1/23/e717c5ac26d26cf39a27fbc076240fad2e3b817e5889d671b67f4f9f49c5/nvidia_cuda_runtime_cu12-12.6.77-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ba3b56a4f896141e25e19ab287cd71e52a6a0f4b29d0d31609f60e3b4d5219b7", size = 897690, upload-time = "2024-11-20T17:35:30.697Z" }, + { url = "https://files.pythonhosted.org/packages/f0/62/65c05e161eeddbafeca24dc461f47de550d9fa8a7e04eb213e32b55cfd99/nvidia_cuda_runtime_cu12-12.6.77-py3-none-manylinux2014_x86_64.whl", hash = "sha256:a84d15d5e1da416dd4774cb42edf5e954a3e60cc945698dc1d5be02321c44dc8", size = 897678, upload-time = "2024-10-01T16:57:33.821Z" }, +] + +[[package]] +name = "nvidia-cudnn-cu12" +version = "9.5.1.17" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nvidia-cublas-cu12" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/78/4535c9c7f859a64781e43c969a3a7e84c54634e319a996d43ef32ce46f83/nvidia_cudnn_cu12-9.5.1.17-py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:30ac3869f6db17d170e0e556dd6cc5eee02647abc31ca856634d5a40f82c15b2", size = 570988386, upload-time = "2024-10-25T19:54:26.39Z" }, +] + +[[package]] +name = "nvidia-cufft-cu12" +version = "11.3.0.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nvidia-nvjitlink-cu12" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/8f/16/73727675941ab8e6ffd86ca3a4b7b47065edcca7a997920b831f8147c99d/nvidia_cufft_cu12-11.3.0.4-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ccba62eb9cef5559abd5e0d54ceed2d9934030f51163df018532142a8ec533e5", size = 200221632, upload-time = "2024-11-20T17:41:32.357Z" }, + { url = "https://files.pythonhosted.org/packages/60/de/99ec247a07ea40c969d904fc14f3a356b3e2a704121675b75c366b694ee1/nvidia_cufft_cu12-11.3.0.4-py3-none-manylinux2014_x86_64.whl", hash = "sha256:768160ac89f6f7b459bee747e8d175dbf53619cfe74b2a5636264163138013ca", size = 200221622, upload-time = "2024-10-01T17:03:58.79Z" }, +] + +[[package]] +name = "nvidia-cufile-cu12" +version = "1.11.1.6" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b2/66/cc9876340ac68ae71b15c743ddb13f8b30d5244af344ec8322b449e35426/nvidia_cufile_cu12-1.11.1.6-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:cc23469d1c7e52ce6c1d55253273d32c565dd22068647f3aa59b3c6b005bf159", size = 1142103, upload-time = "2024-11-20T17:42:11.83Z" }, +] + +[[package]] +name = "nvidia-curand-cu12" +version = "10.3.7.77" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/73/1b/44a01c4e70933637c93e6e1a8063d1e998b50213a6b65ac5a9169c47e98e/nvidia_curand_cu12-10.3.7.77-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a42cd1344297f70b9e39a1e4f467a4e1c10f1da54ff7a85c12197f6c652c8bdf", size = 56279010, upload-time = "2024-11-20T17:42:50.958Z" }, + { url = "https://files.pythonhosted.org/packages/4a/aa/2c7ff0b5ee02eaef890c0ce7d4f74bc30901871c5e45dee1ae6d0083cd80/nvidia_curand_cu12-10.3.7.77-py3-none-manylinux2014_x86_64.whl", hash = "sha256:99f1a32f1ac2bd134897fc7a203f779303261268a65762a623bf30cc9fe79117", size = 56279000, upload-time = "2024-10-01T17:04:45.274Z" }, +] + +[[package]] +name = "nvidia-cusolver-cu12" +version = "11.7.1.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nvidia-cublas-cu12" }, + { name = "nvidia-cusparse-cu12" }, + { name = "nvidia-nvjitlink-cu12" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/f0/6e/c2cf12c9ff8b872e92b4a5740701e51ff17689c4d726fca91875b07f655d/nvidia_cusolver_cu12-11.7.1.2-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e9e49843a7707e42022babb9bcfa33c29857a93b88020c4e4434656a655b698c", size = 158229790, upload-time = "2024-11-20T17:43:43.211Z" }, + { url = "https://files.pythonhosted.org/packages/9f/81/baba53585da791d043c10084cf9553e074548408e04ae884cfe9193bd484/nvidia_cusolver_cu12-11.7.1.2-py3-none-manylinux2014_x86_64.whl", hash = "sha256:6cf28f17f64107a0c4d7802be5ff5537b2130bfc112f25d5a30df227058ca0e6", size = 158229780, upload-time = "2024-10-01T17:05:39.875Z" }, +] + +[[package]] +name = "nvidia-cusparse-cu12" +version = "12.5.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nvidia-nvjitlink-cu12" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/06/1e/b8b7c2f4099a37b96af5c9bb158632ea9e5d9d27d7391d7eb8fc45236674/nvidia_cusparse_cu12-12.5.4.2-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7556d9eca156e18184b94947ade0fba5bb47d69cec46bf8660fd2c71a4b48b73", size = 216561367, upload-time = "2024-11-20T17:44:54.824Z" }, + { url = "https://files.pythonhosted.org/packages/43/ac/64c4316ba163e8217a99680c7605f779accffc6a4bcd0c778c12948d3707/nvidia_cusparse_cu12-12.5.4.2-py3-none-manylinux2014_x86_64.whl", hash = "sha256:23749a6571191a215cb74d1cdbff4a86e7b19f1200c071b3fcf844a5bea23a2f", size = 216561357, upload-time = "2024-10-01T17:06:29.861Z" }, +] + +[[package]] +name = "nvidia-cusparselt-cu12" +version = "0.6.3" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3b/9a/72ef35b399b0e183bc2e8f6f558036922d453c4d8237dab26c666a04244b/nvidia_cusparselt_cu12-0.6.3-py3-none-manylinux2014_x86_64.whl", hash = "sha256:e5c8a26c36445dd2e6812f1177978a24e2d37cacce7e090f297a688d1ec44f46", size = 156785796, upload-time = "2024-10-15T21:29:17.709Z" }, +] + +[[package]] +name = "nvidia-nccl-cu12" +version = "2.26.2" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/67/ca/f42388aed0fddd64ade7493dbba36e1f534d4e6fdbdd355c6a90030ae028/nvidia_nccl_cu12-2.26.2-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:694cf3879a206553cc9d7dbda76b13efaf610fdb70a50cba303de1b0d1530ac6", size = 201319755, upload-time = "2025-03-13T00:29:55.296Z" }, +] + +[[package]] +name = "nvidia-nvjitlink-cu12" +version = "12.6.85" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9d/d7/c5383e47c7e9bf1c99d5bd2a8c935af2b6d705ad831a7ec5c97db4d82f4f/nvidia_nvjitlink_cu12-12.6.85-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl", hash = "sha256:eedc36df9e88b682efe4309aa16b5b4e78c2407eac59e8c10a6a47535164369a", size = 19744971, upload-time = "2024-11-20T17:46:53.366Z" }, +] + +[[package]] +name = "nvidia-nvtx-cu12" +version = "12.6.77" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/56/9a/fff8376f8e3d084cd1530e1ef7b879bb7d6d265620c95c1b322725c694f4/nvidia_nvtx_cu12-12.6.77-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b90bed3df379fa79afbd21be8e04a0314336b8ae16768b58f2d34cb1d04cd7d2", size = 89276, upload-time = "2024-11-20T17:38:27.621Z" }, + { url = "https://files.pythonhosted.org/packages/9e/4e/0d0c945463719429b7bd21dece907ad0bde437a2ff12b9b12fee94722ab0/nvidia_nvtx_cu12-12.6.77-py3-none-manylinux2014_x86_64.whl", hash = "sha256:6574241a3ec5fdc9334353ab8c479fe75841dbe8f4532a8fc97ce63503330ba1", size = 89265, upload-time = "2024-10-01T17:00:38.172Z" }, +] + +[[package]] +name = "onnx" +version = "1.18.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, + { name = "protobuf" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3d/60/e56e8ec44ed34006e6d4a73c92a04d9eea6163cc12440e35045aec069175/onnx-1.18.0.tar.gz", hash = "sha256:3d8dbf9e996629131ba3aa1afd1d8239b660d1f830c6688dd7e03157cccd6b9c", size = 12563009, upload-time = "2025-05-12T22:03:09.626Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/fe/16228aca685392a7114625b89aae98b2dc4058a47f0f467a376745efe8d0/onnx-1.18.0-cp312-cp312-macosx_12_0_universal2.whl", hash = "sha256:521bac578448667cbb37c50bf05b53c301243ede8233029555239930996a625b", size = 18285770, upload-time = "2025-05-12T22:02:26.116Z" }, + { url = "https://files.pythonhosted.org/packages/1e/77/ba50a903a9b5e6f9be0fa50f59eb2fca4a26ee653375408fbc72c3acbf9f/onnx-1.18.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4da451bf1c5ae381f32d430004a89f0405bc57a8471b0bddb6325a5b334aa40", size = 17421291, upload-time = "2025-05-12T22:02:29.645Z" }, + { url = "https://files.pythonhosted.org/packages/11/23/25ec2ba723ac62b99e8fed6d7b59094dadb15e38d4c007331cc9ae3dfa5f/onnx-1.18.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:99afac90b4cdb1471432203c3c1f74e16549c526df27056d39f41a9a47cfb4af", size = 17584084, upload-time = "2025-05-12T22:02:32.789Z" }, + { url = "https://files.pythonhosted.org/packages/6a/4d/2c253a36070fb43f340ff1d2c450df6a9ef50b938adcd105693fee43c4ee/onnx-1.18.0-cp312-cp312-win32.whl", hash = "sha256:ee159b41a3ae58d9c7341cf432fc74b96aaf50bd7bb1160029f657b40dc69715", size = 15734892, upload-time = "2025-05-12T22:02:35.527Z" }, + { url = "https://files.pythonhosted.org/packages/e8/92/048ba8fafe6b2b9a268ec2fb80def7e66c0b32ab2cae74de886981f05a27/onnx-1.18.0-cp312-cp312-win_amd64.whl", hash = "sha256:102c04edc76b16e9dfeda5a64c1fccd7d3d2913b1544750c01d38f1ac3c04e05", size = 15850336, upload-time = "2025-05-12T22:02:38.545Z" }, + { url = "https://files.pythonhosted.org/packages/a1/66/bbc4ffedd44165dcc407a51ea4c592802a5391ce3dc94aa5045350f64635/onnx-1.18.0-cp312-cp312-win_arm64.whl", hash = "sha256:911b37d724a5d97396f3c2ef9ea25361c55cbc9aa18d75b12a52b620b67145af", size = 15823802, upload-time = "2025-05-12T22:02:42.037Z" }, + { url = "https://files.pythonhosted.org/packages/45/da/9fb8824513fae836239276870bfcc433fa2298d34ed282c3a47d3962561b/onnx-1.18.0-cp313-cp313-macosx_12_0_universal2.whl", hash = "sha256:030d9f5f878c5f4c0ff70a4545b90d7812cd6bfe511de2f3e469d3669c8cff95", size = 18285906, upload-time = "2025-05-12T22:02:45.01Z" }, + { url = "https://files.pythonhosted.org/packages/05/e8/762b5fb5ed1a2b8e9a4bc5e668c82723b1b789c23b74e6b5a3356731ae4e/onnx-1.18.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8521544987d713941ee1e591520044d35e702f73dc87e91e6d4b15a064ae813d", size = 17421486, upload-time = "2025-05-12T22:02:48.467Z" }, + { url = "https://files.pythonhosted.org/packages/12/bb/471da68df0364f22296456c7f6becebe0a3da1ba435cdb371099f516da6e/onnx-1.18.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3c137eecf6bc618c2f9398bcc381474b55c817237992b169dfe728e169549e8f", size = 17583581, upload-time = "2025-05-12T22:02:51.784Z" }, + { url = "https://files.pythonhosted.org/packages/76/0d/01a95edc2cef6ad916e04e8e1267a9286f15b55c90cce5d3cdeb359d75d6/onnx-1.18.0-cp313-cp313-win32.whl", hash = "sha256:6c093ffc593e07f7e33862824eab9225f86aa189c048dd43ffde207d7041a55f", size = 15734621, upload-time = "2025-05-12T22:02:54.62Z" }, + { url = "https://files.pythonhosted.org/packages/64/95/253451a751be32b6173a648b68f407188009afa45cd6388780c330ff5d5d/onnx-1.18.0-cp313-cp313-win_amd64.whl", hash = "sha256:230b0fb615e5b798dc4a3718999ec1828360bc71274abd14f915135eab0255f1", size = 15850472, upload-time = "2025-05-12T22:02:57.54Z" }, + { url = "https://files.pythonhosted.org/packages/0a/b1/6fd41b026836df480a21687076e0f559bc3ceeac90f2be8c64b4a7a1f332/onnx-1.18.0-cp313-cp313-win_arm64.whl", hash = "sha256:6f91930c1a284135db0f891695a263fc876466bf2afbd2215834ac08f600cfca", size = 15823808, upload-time = "2025-05-12T22:03:00.305Z" }, + { url = "https://files.pythonhosted.org/packages/70/f3/499e53dd41fa7302f914dd18543da01e0786a58b9a9d347497231192001f/onnx-1.18.0-cp313-cp313t-macosx_12_0_universal2.whl", hash = "sha256:2f4d37b0b5c96a873887652d1cbf3f3c70821b8c66302d84b0f0d89dd6e47653", size = 18316526, upload-time = "2025-05-12T22:03:03.691Z" }, + { url = "https://files.pythonhosted.org/packages/84/dd/6abe5d7bd23f5ed3ade8352abf30dff1c7a9e97fc1b0a17b5d7c726e98a9/onnx-1.18.0-cp313-cp313t-win_amd64.whl", hash = "sha256:a69afd0baa372162948b52c13f3aa2730123381edf926d7ef3f68ca7cec6d0d0", size = 15865055, upload-time = "2025-05-12T22:03:06.663Z" }, +] + +[[package]] +name = "onnx-ir" +version = "0.1.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ml-dtypes" }, + { name = "numpy" }, + { name = "onnx" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2a/2a/92caa778bf1bcaa5dce502d3f9ae9c026a3e8b002f464db3bcd26a32b839/onnx_ir-0.1.5.tar.gz", hash = "sha256:0182e679ea7125d7d3a4600c10df69576a67224ab6ef0cdbf20425c3ef5c8d8d", size = 106648, upload-time = "2025-08-08T17:06:18.391Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/16/80/4810ea8f8e5394995b65f13676b3d9dbbcce36ce4c6bd5b62ea8252cc22e/onnx_ir-0.1.5-py3-none-any.whl", hash = "sha256:f7e58098e470d796bc22e2732f09422c6f5f7432f6b81ef3d1e1921c97fcd3fe", size = 120678, upload-time = "2025-08-08T17:06:17.164Z" }, +] + +[[package]] +name = "onnxscript" +version = "0.3.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ml-dtypes" }, + { name = "numpy" }, + { name = "onnx" }, + { name = "onnx-ir" }, + { name = "packaging" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fd/8e/1bb4821b4bd1e52c8be6a9edb3fd787875660caa3b0816fcb3e1eafb3568/onnxscript-0.3.2.tar.gz", hash = "sha256:60820d4c3e39f8ea7d945dd4f96fa5dd23c3c2e111022512477c52c9e78eb3d4", size = 575968, upload-time = "2025-07-11T20:04:19.683Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e4/88/008f82aca27fd6240d12d3084d24852f586cef2ccf2fb07612185fbb71f6/onnxscript-0.3.2-py3-none-any.whl", hash = "sha256:220bbecccae228a285bd385159c47a09f003c623c9636afd56cc91df50706a88", size = 667405, upload-time = "2025-07-11T20:04:22.036Z" }, +] + +[[package]] +name = "packaging" +version = "25.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, +] + +[[package]] +name = "pandas" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, + { name = "python-dateutil" }, + { name = "pytz" }, + { name = "tzdata" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/72/51/48f713c4c728d7c55ef7444ba5ea027c26998d96d1a40953b346438602fc/pandas-2.3.0.tar.gz", hash = "sha256:34600ab34ebf1131a7613a260a61dbe8b62c188ec0ea4c296da7c9a06b004133", size = 4484490, upload-time = "2025-06-05T03:27:54.133Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/94/46/24192607058dd607dbfacdd060a2370f6afb19c2ccb617406469b9aeb8e7/pandas-2.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2eb4728a18dcd2908c7fccf74a982e241b467d178724545a48d0caf534b38ebf", size = 11573865, upload-time = "2025-06-05T03:26:46.774Z" }, + { url = "https://files.pythonhosted.org/packages/9f/cc/ae8ea3b800757a70c9fdccc68b67dc0280a6e814efcf74e4211fd5dea1ca/pandas-2.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b9d8c3187be7479ea5c3d30c32a5d73d62a621166675063b2edd21bc47614027", size = 10702154, upload-time = "2025-06-05T16:50:14.439Z" }, + { url = "https://files.pythonhosted.org/packages/d8/ba/a7883d7aab3d24c6540a2768f679e7414582cc389876d469b40ec749d78b/pandas-2.3.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9ff730713d4c4f2f1c860e36c005c7cefc1c7c80c21c0688fd605aa43c9fcf09", size = 11262180, upload-time = "2025-06-05T16:50:17.453Z" }, + { url = "https://files.pythonhosted.org/packages/01/a5/931fc3ad333d9d87b10107d948d757d67ebcfc33b1988d5faccc39c6845c/pandas-2.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba24af48643b12ffe49b27065d3babd52702d95ab70f50e1b34f71ca703e2c0d", size = 11991493, upload-time = "2025-06-05T03:26:51.813Z" }, + { url = "https://files.pythonhosted.org/packages/d7/bf/0213986830a92d44d55153c1d69b509431a972eb73f204242988c4e66e86/pandas-2.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:404d681c698e3c8a40a61d0cd9412cc7364ab9a9cc6e144ae2992e11a2e77a20", size = 12470733, upload-time = "2025-06-06T00:00:18.651Z" }, + { url = "https://files.pythonhosted.org/packages/a4/0e/21eb48a3a34a7d4bac982afc2c4eb5ab09f2d988bdf29d92ba9ae8e90a79/pandas-2.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6021910b086b3ca756755e86ddc64e0ddafd5e58e076c72cb1585162e5ad259b", size = 13212406, upload-time = "2025-06-05T03:26:55.992Z" }, + { url = "https://files.pythonhosted.org/packages/1f/d9/74017c4eec7a28892d8d6e31ae9de3baef71f5a5286e74e6b7aad7f8c837/pandas-2.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:094e271a15b579650ebf4c5155c05dcd2a14fd4fdd72cf4854b2f7ad31ea30be", size = 10976199, upload-time = "2025-06-05T03:26:59.594Z" }, + { url = "https://files.pythonhosted.org/packages/d3/57/5cb75a56a4842bbd0511c3d1c79186d8315b82dac802118322b2de1194fe/pandas-2.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2c7e2fc25f89a49a11599ec1e76821322439d90820108309bf42130d2f36c983", size = 11518913, upload-time = "2025-06-05T03:27:02.757Z" }, + { url = "https://files.pythonhosted.org/packages/05/01/0c8785610e465e4948a01a059562176e4c8088aa257e2e074db868f86d4e/pandas-2.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c6da97aeb6a6d233fb6b17986234cc723b396b50a3c6804776351994f2a658fd", size = 10655249, upload-time = "2025-06-05T16:50:20.17Z" }, + { url = "https://files.pythonhosted.org/packages/e8/6a/47fd7517cd8abe72a58706aab2b99e9438360d36dcdb052cf917b7bf3bdc/pandas-2.3.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb32dc743b52467d488e7a7c8039b821da2826a9ba4f85b89ea95274f863280f", size = 11328359, upload-time = "2025-06-05T03:27:06.431Z" }, + { url = "https://files.pythonhosted.org/packages/2a/b3/463bfe819ed60fb7e7ddffb4ae2ee04b887b3444feee6c19437b8f834837/pandas-2.3.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:213cd63c43263dbb522c1f8a7c9d072e25900f6975596f883f4bebd77295d4f3", size = 12024789, upload-time = "2025-06-05T03:27:09.875Z" }, + { url = "https://files.pythonhosted.org/packages/04/0c/e0704ccdb0ac40aeb3434d1c641c43d05f75c92e67525df39575ace35468/pandas-2.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1d2b33e68d0ce64e26a4acc2e72d747292084f4e8db4c847c6f5f6cbe56ed6d8", size = 12480734, upload-time = "2025-06-06T00:00:22.246Z" }, + { url = "https://files.pythonhosted.org/packages/e9/df/815d6583967001153bb27f5cf075653d69d51ad887ebbf4cfe1173a1ac58/pandas-2.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:430a63bae10b5086995db1b02694996336e5a8ac9a96b4200572b413dfdfccb9", size = 13223381, upload-time = "2025-06-05T03:27:15.641Z" }, + { url = "https://files.pythonhosted.org/packages/79/88/ca5973ed07b7f484c493e941dbff990861ca55291ff7ac67c815ce347395/pandas-2.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:4930255e28ff5545e2ca404637bcc56f031893142773b3468dc021c6c32a1390", size = 10970135, upload-time = "2025-06-05T03:27:24.131Z" }, + { url = "https://files.pythonhosted.org/packages/24/fb/0994c14d1f7909ce83f0b1fb27958135513c4f3f2528bde216180aa73bfc/pandas-2.3.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:f925f1ef673b4bd0271b1809b72b3270384f2b7d9d14a189b12b7fc02574d575", size = 12141356, upload-time = "2025-06-05T03:27:34.547Z" }, + { url = "https://files.pythonhosted.org/packages/9d/a2/9b903e5962134497ac4f8a96f862ee3081cb2506f69f8e4778ce3d9c9d82/pandas-2.3.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e78ad363ddb873a631e92a3c063ade1ecfb34cae71e9a2be6ad100f875ac1042", size = 11474674, upload-time = "2025-06-05T03:27:39.448Z" }, + { url = "https://files.pythonhosted.org/packages/81/3a/3806d041bce032f8de44380f866059437fb79e36d6b22c82c187e65f765b/pandas-2.3.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:951805d146922aed8357e4cc5671b8b0b9be1027f0619cea132a9f3f65f2f09c", size = 11439876, upload-time = "2025-06-05T03:27:43.652Z" }, + { url = "https://files.pythonhosted.org/packages/15/aa/3fc3181d12b95da71f5c2537c3e3b3af6ab3a8c392ab41ebb766e0929bc6/pandas-2.3.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a881bc1309f3fce34696d07b00f13335c41f5f5a8770a33b09ebe23261cfc67", size = 11966182, upload-time = "2025-06-05T03:27:47.652Z" }, + { url = "https://files.pythonhosted.org/packages/37/e7/e12f2d9b0a2c4a2cc86e2aabff7ccfd24f03e597d770abfa2acd313ee46b/pandas-2.3.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:e1991bbb96f4050b09b5f811253c4f3cf05ee89a589379aa36cd623f21a31d6f", size = 12547686, upload-time = "2025-06-06T00:00:26.142Z" }, + { url = "https://files.pythonhosted.org/packages/39/c2/646d2e93e0af70f4e5359d870a63584dacbc324b54d73e6b3267920ff117/pandas-2.3.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:bb3be958022198531eb7ec2008cfc78c5b1eed51af8600c6c5d9160d89d8d249", size = 13231847, upload-time = "2025-06-05T03:27:51.465Z" }, +] + +[[package]] +name = "pillow" +version = "11.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/af/cb/bb5c01fcd2a69335b86c22142b2bccfc3464087efb7fd382eee5ffc7fdf7/pillow-11.2.1.tar.gz", hash = "sha256:a64dd61998416367b7ef979b73d3a85853ba9bec4c2925f74e588879a58716b6", size = 47026707, upload-time = "2025-04-12T17:50:03.289Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/40/052610b15a1b8961f52537cc8326ca6a881408bc2bdad0d852edeb6ed33b/pillow-11.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:78afba22027b4accef10dbd5eed84425930ba41b3ea0a86fa8d20baaf19d807f", size = 3190185, upload-time = "2025-04-12T17:48:00.417Z" }, + { url = "https://files.pythonhosted.org/packages/e5/7e/b86dbd35a5f938632093dc40d1682874c33dcfe832558fc80ca56bfcb774/pillow-11.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:78092232a4ab376a35d68c4e6d5e00dfd73454bd12b230420025fbe178ee3b0b", size = 3030306, upload-time = "2025-04-12T17:48:02.391Z" }, + { url = "https://files.pythonhosted.org/packages/a4/5c/467a161f9ed53e5eab51a42923c33051bf8d1a2af4626ac04f5166e58e0c/pillow-11.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25a5f306095c6780c52e6bbb6109624b95c5b18e40aab1c3041da3e9e0cd3e2d", size = 4416121, upload-time = "2025-04-12T17:48:04.554Z" }, + { url = "https://files.pythonhosted.org/packages/62/73/972b7742e38ae0e2ac76ab137ca6005dcf877480da0d9d61d93b613065b4/pillow-11.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c7b29dbd4281923a2bfe562acb734cee96bbb129e96e6972d315ed9f232bef4", size = 4501707, upload-time = "2025-04-12T17:48:06.831Z" }, + { url = "https://files.pythonhosted.org/packages/e4/3a/427e4cb0b9e177efbc1a84798ed20498c4f233abde003c06d2650a6d60cb/pillow-11.2.1-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:3e645b020f3209a0181a418bffe7b4a93171eef6c4ef6cc20980b30bebf17b7d", size = 4522921, upload-time = "2025-04-12T17:48:09.229Z" }, + { url = "https://files.pythonhosted.org/packages/fe/7c/d8b1330458e4d2f3f45d9508796d7caf0c0d3764c00c823d10f6f1a3b76d/pillow-11.2.1-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:b2dbea1012ccb784a65349f57bbc93730b96e85b42e9bf7b01ef40443db720b4", size = 4612523, upload-time = "2025-04-12T17:48:11.631Z" }, + { url = "https://files.pythonhosted.org/packages/b3/2f/65738384e0b1acf451de5a573d8153fe84103772d139e1e0bdf1596be2ea/pillow-11.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:da3104c57bbd72948d75f6a9389e6727d2ab6333c3617f0a89d72d4940aa0443", size = 4587836, upload-time = "2025-04-12T17:48:13.592Z" }, + { url = "https://files.pythonhosted.org/packages/6a/c5/e795c9f2ddf3debb2dedd0df889f2fe4b053308bb59a3cc02a0cd144d641/pillow-11.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:598174aef4589af795f66f9caab87ba4ff860ce08cd5bb447c6fc553ffee603c", size = 4669390, upload-time = "2025-04-12T17:48:15.938Z" }, + { url = "https://files.pythonhosted.org/packages/96/ae/ca0099a3995976a9fce2f423166f7bff9b12244afdc7520f6ed38911539a/pillow-11.2.1-cp312-cp312-win32.whl", hash = "sha256:1d535df14716e7f8776b9e7fee118576d65572b4aad3ed639be9e4fa88a1cad3", size = 2332309, upload-time = "2025-04-12T17:48:17.885Z" }, + { url = "https://files.pythonhosted.org/packages/7c/18/24bff2ad716257fc03da964c5e8f05d9790a779a8895d6566e493ccf0189/pillow-11.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:14e33b28bf17c7a38eede290f77db7c664e4eb01f7869e37fa98a5aa95978941", size = 2676768, upload-time = "2025-04-12T17:48:19.655Z" }, + { url = "https://files.pythonhosted.org/packages/da/bb/e8d656c9543276517ee40184aaa39dcb41e683bca121022f9323ae11b39d/pillow-11.2.1-cp312-cp312-win_arm64.whl", hash = "sha256:21e1470ac9e5739ff880c211fc3af01e3ae505859392bf65458c224d0bf283eb", size = 2415087, upload-time = "2025-04-12T17:48:21.991Z" }, + { url = "https://files.pythonhosted.org/packages/36/9c/447528ee3776e7ab8897fe33697a7ff3f0475bb490c5ac1456a03dc57956/pillow-11.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:fdec757fea0b793056419bca3e9932eb2b0ceec90ef4813ea4c1e072c389eb28", size = 3190098, upload-time = "2025-04-12T17:48:23.915Z" }, + { url = "https://files.pythonhosted.org/packages/b5/09/29d5cd052f7566a63e5b506fac9c60526e9ecc553825551333e1e18a4858/pillow-11.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b0e130705d568e2f43a17bcbe74d90958e8a16263868a12c3e0d9c8162690830", size = 3030166, upload-time = "2025-04-12T17:48:25.738Z" }, + { url = "https://files.pythonhosted.org/packages/71/5d/446ee132ad35e7600652133f9c2840b4799bbd8e4adba881284860da0a36/pillow-11.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bdb5e09068332578214cadd9c05e3d64d99e0e87591be22a324bdbc18925be0", size = 4408674, upload-time = "2025-04-12T17:48:27.908Z" }, + { url = "https://files.pythonhosted.org/packages/69/5f/cbe509c0ddf91cc3a03bbacf40e5c2339c4912d16458fcb797bb47bcb269/pillow-11.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d189ba1bebfbc0c0e529159631ec72bb9e9bc041f01ec6d3233d6d82eb823bc1", size = 4496005, upload-time = "2025-04-12T17:48:29.888Z" }, + { url = "https://files.pythonhosted.org/packages/f9/b3/dd4338d8fb8a5f312021f2977fb8198a1184893f9b00b02b75d565c33b51/pillow-11.2.1-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:191955c55d8a712fab8934a42bfefbf99dd0b5875078240943f913bb66d46d9f", size = 4518707, upload-time = "2025-04-12T17:48:31.874Z" }, + { url = "https://files.pythonhosted.org/packages/13/eb/2552ecebc0b887f539111c2cd241f538b8ff5891b8903dfe672e997529be/pillow-11.2.1-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:ad275964d52e2243430472fc5d2c2334b4fc3ff9c16cb0a19254e25efa03a155", size = 4610008, upload-time = "2025-04-12T17:48:34.422Z" }, + { url = "https://files.pythonhosted.org/packages/72/d1/924ce51bea494cb6e7959522d69d7b1c7e74f6821d84c63c3dc430cbbf3b/pillow-11.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:750f96efe0597382660d8b53e90dd1dd44568a8edb51cb7f9d5d918b80d4de14", size = 4585420, upload-time = "2025-04-12T17:48:37.641Z" }, + { url = "https://files.pythonhosted.org/packages/43/ab/8f81312d255d713b99ca37479a4cb4b0f48195e530cdc1611990eb8fd04b/pillow-11.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fe15238d3798788d00716637b3d4e7bb6bde18b26e5d08335a96e88564a36b6b", size = 4667655, upload-time = "2025-04-12T17:48:39.652Z" }, + { url = "https://files.pythonhosted.org/packages/94/86/8f2e9d2dc3d308dfd137a07fe1cc478df0a23d42a6c4093b087e738e4827/pillow-11.2.1-cp313-cp313-win32.whl", hash = "sha256:3fe735ced9a607fee4f481423a9c36701a39719252a9bb251679635f99d0f7d2", size = 2332329, upload-time = "2025-04-12T17:48:41.765Z" }, + { url = "https://files.pythonhosted.org/packages/6d/ec/1179083b8d6067a613e4d595359b5fdea65d0a3b7ad623fee906e1b3c4d2/pillow-11.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:74ee3d7ecb3f3c05459ba95eed5efa28d6092d751ce9bf20e3e253a4e497e691", size = 2676388, upload-time = "2025-04-12T17:48:43.625Z" }, + { url = "https://files.pythonhosted.org/packages/23/f1/2fc1e1e294de897df39fa8622d829b8828ddad938b0eaea256d65b84dd72/pillow-11.2.1-cp313-cp313-win_arm64.whl", hash = "sha256:5119225c622403afb4b44bad4c1ca6c1f98eed79db8d3bc6e4e160fc6339d66c", size = 2414950, upload-time = "2025-04-12T17:48:45.475Z" }, + { url = "https://files.pythonhosted.org/packages/c4/3e/c328c48b3f0ead7bab765a84b4977acb29f101d10e4ef57a5e3400447c03/pillow-11.2.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:8ce2e8411c7aaef53e6bb29fe98f28cd4fbd9a1d9be2eeea434331aac0536b22", size = 3192759, upload-time = "2025-04-12T17:48:47.866Z" }, + { url = "https://files.pythonhosted.org/packages/18/0e/1c68532d833fc8b9f404d3a642991441d9058eccd5606eab31617f29b6d4/pillow-11.2.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:9ee66787e095127116d91dea2143db65c7bb1e232f617aa5957c0d9d2a3f23a7", size = 3033284, upload-time = "2025-04-12T17:48:50.189Z" }, + { url = "https://files.pythonhosted.org/packages/b7/cb/6faf3fb1e7705fd2db74e070f3bf6f88693601b0ed8e81049a8266de4754/pillow-11.2.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9622e3b6c1d8b551b6e6f21873bdcc55762b4b2126633014cea1803368a9aa16", size = 4445826, upload-time = "2025-04-12T17:48:52.346Z" }, + { url = "https://files.pythonhosted.org/packages/07/94/8be03d50b70ca47fb434a358919d6a8d6580f282bbb7af7e4aa40103461d/pillow-11.2.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63b5dff3a68f371ea06025a1a6966c9a1e1ee452fc8020c2cd0ea41b83e9037b", size = 4527329, upload-time = "2025-04-12T17:48:54.403Z" }, + { url = "https://files.pythonhosted.org/packages/fd/a4/bfe78777076dc405e3bd2080bc32da5ab3945b5a25dc5d8acaa9de64a162/pillow-11.2.1-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:31df6e2d3d8fc99f993fd253e97fae451a8db2e7207acf97859732273e108406", size = 4549049, upload-time = "2025-04-12T17:48:56.383Z" }, + { url = "https://files.pythonhosted.org/packages/65/4d/eaf9068dc687c24979e977ce5677e253624bd8b616b286f543f0c1b91662/pillow-11.2.1-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:062b7a42d672c45a70fa1f8b43d1d38ff76b63421cbbe7f88146b39e8a558d91", size = 4635408, upload-time = "2025-04-12T17:48:58.782Z" }, + { url = "https://files.pythonhosted.org/packages/1d/26/0fd443365d9c63bc79feb219f97d935cd4b93af28353cba78d8e77b61719/pillow-11.2.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4eb92eca2711ef8be42fd3f67533765d9fd043b8c80db204f16c8ea62ee1a751", size = 4614863, upload-time = "2025-04-12T17:49:00.709Z" }, + { url = "https://files.pythonhosted.org/packages/49/65/dca4d2506be482c2c6641cacdba5c602bc76d8ceb618fd37de855653a419/pillow-11.2.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f91ebf30830a48c825590aede79376cb40f110b387c17ee9bd59932c961044f9", size = 4692938, upload-time = "2025-04-12T17:49:02.946Z" }, + { url = "https://files.pythonhosted.org/packages/b3/92/1ca0c3f09233bd7decf8f7105a1c4e3162fb9142128c74adad0fb361b7eb/pillow-11.2.1-cp313-cp313t-win32.whl", hash = "sha256:e0b55f27f584ed623221cfe995c912c61606be8513bfa0e07d2c674b4516d9dd", size = 2335774, upload-time = "2025-04-12T17:49:04.889Z" }, + { url = "https://files.pythonhosted.org/packages/a5/ac/77525347cb43b83ae905ffe257bbe2cc6fd23acb9796639a1f56aa59d191/pillow-11.2.1-cp313-cp313t-win_amd64.whl", hash = "sha256:36d6b82164c39ce5482f649b437382c0fb2395eabc1e2b1702a6deb8ad647d6e", size = 2681895, upload-time = "2025-04-12T17:49:06.635Z" }, + { url = "https://files.pythonhosted.org/packages/67/32/32dc030cfa91ca0fc52baebbba2e009bb001122a1daa8b6a79ad830b38d3/pillow-11.2.1-cp313-cp313t-win_arm64.whl", hash = "sha256:225c832a13326e34f212d2072982bb1adb210e0cc0b153e688743018c94a2681", size = 2417234, upload-time = "2025-04-12T17:49:08.399Z" }, +] + +[[package]] +name = "protobuf" +version = "6.31.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/52/f3/b9655a711b32c19720253f6f06326faf90580834e2e83f840472d752bc8b/protobuf-6.31.1.tar.gz", hash = "sha256:d8cac4c982f0b957a4dc73a80e2ea24fab08e679c0de9deb835f4a12d69aca9a", size = 441797, upload-time = "2025-05-28T19:25:54.947Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f3/6f/6ab8e4bf962fd5570d3deaa2d5c38f0a363f57b4501047b5ebeb83ab1125/protobuf-6.31.1-cp310-abi3-win32.whl", hash = "sha256:7fa17d5a29c2e04b7d90e5e32388b8bfd0e7107cd8e616feef7ed3fa6bdab5c9", size = 423603, upload-time = "2025-05-28T19:25:41.198Z" }, + { url = "https://files.pythonhosted.org/packages/44/3a/b15c4347dd4bf3a1b0ee882f384623e2063bb5cf9fa9d57990a4f7df2fb6/protobuf-6.31.1-cp310-abi3-win_amd64.whl", hash = "sha256:426f59d2964864a1a366254fa703b8632dcec0790d8862d30034d8245e1cd447", size = 435283, upload-time = "2025-05-28T19:25:44.275Z" }, + { url = "https://files.pythonhosted.org/packages/6a/c9/b9689a2a250264a84e66c46d8862ba788ee7a641cdca39bccf64f59284b7/protobuf-6.31.1-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:6f1227473dc43d44ed644425268eb7c2e488ae245d51c6866d19fe158e207402", size = 425604, upload-time = "2025-05-28T19:25:45.702Z" }, + { url = "https://files.pythonhosted.org/packages/76/a1/7a5a94032c83375e4fe7e7f56e3976ea6ac90c5e85fac8576409e25c39c3/protobuf-6.31.1-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:a40fc12b84c154884d7d4c4ebd675d5b3b5283e155f324049ae396b95ddebc39", size = 322115, upload-time = "2025-05-28T19:25:47.128Z" }, + { url = "https://files.pythonhosted.org/packages/fa/b1/b59d405d64d31999244643d88c45c8241c58f17cc887e73bcb90602327f8/protobuf-6.31.1-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:4ee898bf66f7a8b0bd21bce523814e6fbd8c6add948045ce958b73af7e8878c6", size = 321070, upload-time = "2025-05-28T19:25:50.036Z" }, + { url = "https://files.pythonhosted.org/packages/f7/af/ab3c51ab7507a7325e98ffe691d9495ee3d3aa5f589afad65ec920d39821/protobuf-6.31.1-py3-none-any.whl", hash = "sha256:720a6c7e6b77288b85063569baae8536671b39f15cc22037ec7045658d80489e", size = 168724, upload-time = "2025-05-28T19:25:53.926Z" }, +] + +[[package]] +name = "pyparsing" +version = "3.2.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bb/22/f1129e69d94ffff626bdb5c835506b3a5b4f3d070f17ea295e12c2c6f60f/pyparsing-3.2.3.tar.gz", hash = "sha256:b9c13f1ab8b3b542f72e28f634bad4de758ab3ce4546e4301970ad6fa77c38be", size = 1088608, upload-time = "2025-03-25T05:01:28.114Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/05/e7/df2285f3d08fee213f2d041540fa4fc9ca6c2d44cf36d3a035bf2a8d2bcc/pyparsing-3.2.3-py3-none-any.whl", hash = "sha256:a749938e02d6fd0b59b356ca504a24982314bb090c383e3cf201c95ef7e2bfcf", size = 111120, upload-time = "2025-03-25T05:01:24.908Z" }, +] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, +] + +[[package]] +name = "pytz" +version = "2025.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f8/bf/abbd3cdfb8fbc7fb3d4d38d320f2441b1e7cbe29be4f23797b4a2b5d8aac/pytz-2025.2.tar.gz", hash = "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3", size = 320884, upload-time = "2025-03-25T02:25:00.538Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00", size = 509225, upload-time = "2025-03-25T02:24:58.468Z" }, +] + +[[package]] +name = "scikit-learn" +version = "1.7.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "joblib" }, + { name = "numpy" }, + { name = "scipy" }, + { name = "threadpoolctl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/3b/29fa87e76b1d7b3b77cc1fcbe82e6e6b8cd704410705b008822de530277c/scikit_learn-1.7.0.tar.gz", hash = "sha256:c01e869b15aec88e2cdb73d27f15bdbe03bce8e2fb43afbe77c45d399e73a5a3", size = 7178217, upload-time = "2025-06-05T22:02:46.703Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/70/3a/bffab14e974a665a3ee2d79766e7389572ffcaad941a246931c824afcdb2/scikit_learn-1.7.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c2c7243d34aaede0efca7a5a96d67fddaebb4ad7e14a70991b9abee9dc5c0379", size = 11646758, upload-time = "2025-06-05T22:02:09.51Z" }, + { url = "https://files.pythonhosted.org/packages/58/d8/f3249232fa79a70cb40595282813e61453c1e76da3e1a44b77a63dd8d0cb/scikit_learn-1.7.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:9f39f6a811bf3f15177b66c82cbe0d7b1ebad9f190737dcdef77cfca1ea3c19c", size = 10673971, upload-time = "2025-06-05T22:02:12.217Z" }, + { url = "https://files.pythonhosted.org/packages/67/93/eb14c50533bea2f77758abe7d60a10057e5f2e2cdcf0a75a14c6bc19c734/scikit_learn-1.7.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63017a5f9a74963d24aac7590287149a8d0f1a0799bbe7173c0d8ba1523293c0", size = 11818428, upload-time = "2025-06-05T22:02:14.947Z" }, + { url = "https://files.pythonhosted.org/packages/08/17/804cc13b22a8663564bb0b55fb89e661a577e4e88a61a39740d58b909efe/scikit_learn-1.7.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b2f8a0b1e73e9a08b7cc498bb2aeab36cdc1f571f8ab2b35c6e5d1c7115d97d", size = 12505887, upload-time = "2025-06-05T22:02:17.824Z" }, + { url = "https://files.pythonhosted.org/packages/68/c7/4e956281a077f4835458c3f9656c666300282d5199039f26d9de1dabd9be/scikit_learn-1.7.0-cp312-cp312-win_amd64.whl", hash = "sha256:34cc8d9d010d29fb2b7cbcd5ccc24ffdd80515f65fe9f1e4894ace36b267ce19", size = 10668129, upload-time = "2025-06-05T22:02:20.536Z" }, + { url = "https://files.pythonhosted.org/packages/9a/c3/a85dcccdaf1e807e6f067fa95788a6485b0491d9ea44fd4c812050d04f45/scikit_learn-1.7.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5b7974f1f32bc586c90145df51130e02267e4b7e77cab76165c76cf43faca0d9", size = 11559841, upload-time = "2025-06-05T22:02:23.308Z" }, + { url = "https://files.pythonhosted.org/packages/d8/57/eea0de1562cc52d3196eae51a68c5736a31949a465f0b6bb3579b2d80282/scikit_learn-1.7.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:014e07a23fe02e65f9392898143c542a50b6001dbe89cb867e19688e468d049b", size = 10616463, upload-time = "2025-06-05T22:02:26.068Z" }, + { url = "https://files.pythonhosted.org/packages/10/a4/39717ca669296dfc3a62928393168da88ac9d8cbec88b6321ffa62c6776f/scikit_learn-1.7.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e7e7ced20582d3a5516fb6f405fd1d254e1f5ce712bfef2589f51326af6346e8", size = 11766512, upload-time = "2025-06-05T22:02:28.689Z" }, + { url = "https://files.pythonhosted.org/packages/d5/cd/a19722241d5f7b51e08351e1e82453e0057aeb7621b17805f31fcb57bb6c/scikit_learn-1.7.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1babf2511e6ffd695da7a983b4e4d6de45dce39577b26b721610711081850906", size = 12461075, upload-time = "2025-06-05T22:02:31.233Z" }, + { url = "https://files.pythonhosted.org/packages/f3/bc/282514272815c827a9acacbe5b99f4f1a4bc5961053719d319480aee0812/scikit_learn-1.7.0-cp313-cp313-win_amd64.whl", hash = "sha256:5abd2acff939d5bd4701283f009b01496832d50ddafa83c90125a4e41c33e314", size = 10652517, upload-time = "2025-06-05T22:02:34.139Z" }, + { url = "https://files.pythonhosted.org/packages/ea/78/7357d12b2e4c6674175f9a09a3ba10498cde8340e622715bcc71e532981d/scikit_learn-1.7.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:e39d95a929b112047c25b775035c8c234c5ca67e681ce60d12413afb501129f7", size = 12111822, upload-time = "2025-06-05T22:02:36.904Z" }, + { url = "https://files.pythonhosted.org/packages/d0/0c/9c3715393343f04232f9d81fe540eb3831d0b4ec351135a145855295110f/scikit_learn-1.7.0-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:0521cb460426c56fee7e07f9365b0f45ec8ca7b2d696534ac98bfb85e7ae4775", size = 11325286, upload-time = "2025-06-05T22:02:39.739Z" }, + { url = "https://files.pythonhosted.org/packages/64/e0/42282ad3dd70b7c1a5f65c412ac3841f6543502a8d6263cae7b466612dc9/scikit_learn-1.7.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:317ca9f83acbde2883bd6bb27116a741bfcb371369706b4f9973cf30e9a03b0d", size = 12380865, upload-time = "2025-06-05T22:02:42.137Z" }, + { url = "https://files.pythonhosted.org/packages/4e/d0/3ef4ab2c6be4aa910445cd09c5ef0b44512e3de2cfb2112a88bb647d2cf7/scikit_learn-1.7.0-cp313-cp313t-win_amd64.whl", hash = "sha256:126c09740a6f016e815ab985b21e3a0656835414521c81fc1a8da78b679bdb75", size = 11549609, upload-time = "2025-06-05T22:02:44.483Z" }, +] + +[[package]] +name = "scipy" +version = "1.16.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/81/18/b06a83f0c5ee8cddbde5e3f3d0bb9b702abfa5136ef6d4620ff67df7eee5/scipy-1.16.0.tar.gz", hash = "sha256:b5ef54021e832869c8cfb03bc3bf20366cbcd426e02a58e8a58d7584dfbb8f62", size = 30581216, upload-time = "2025-06-22T16:27:55.782Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/01/c0/c943bc8d2bbd28123ad0f4f1eef62525fa1723e84d136b32965dcb6bad3a/scipy-1.16.0-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:7eb6bd33cef4afb9fa5f1fb25df8feeb1e52d94f21a44f1d17805b41b1da3180", size = 36459071, upload-time = "2025-06-22T16:19:06.605Z" }, + { url = "https://files.pythonhosted.org/packages/99/0d/270e2e9f1a4db6ffbf84c9a0b648499842046e4e0d9b2275d150711b3aba/scipy-1.16.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:1dbc8fdba23e4d80394ddfab7a56808e3e6489176d559c6c71935b11a2d59db1", size = 28490500, upload-time = "2025-06-22T16:19:11.775Z" }, + { url = "https://files.pythonhosted.org/packages/1c/22/01d7ddb07cff937d4326198ec8d10831367a708c3da72dfd9b7ceaf13028/scipy-1.16.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:7dcf42c380e1e3737b343dec21095c9a9ad3f9cbe06f9c05830b44b1786c9e90", size = 20762345, upload-time = "2025-06-22T16:19:15.813Z" }, + { url = "https://files.pythonhosted.org/packages/34/7f/87fd69856569ccdd2a5873fe5d7b5bbf2ad9289d7311d6a3605ebde3a94b/scipy-1.16.0-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:26ec28675f4a9d41587266084c626b02899db373717d9312fa96ab17ca1ae94d", size = 23418563, upload-time = "2025-06-22T16:19:20.746Z" }, + { url = "https://files.pythonhosted.org/packages/f6/f1/e4f4324fef7f54160ab749efbab6a4bf43678a9eb2e9817ed71a0a2fd8de/scipy-1.16.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:952358b7e58bd3197cfbd2f2f2ba829f258404bdf5db59514b515a8fe7a36c52", size = 33203951, upload-time = "2025-06-22T16:19:25.813Z" }, + { url = "https://files.pythonhosted.org/packages/6d/f0/b6ac354a956384fd8abee2debbb624648125b298f2c4a7b4f0d6248048a5/scipy-1.16.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:03931b4e870c6fef5b5c0970d52c9f6ddd8c8d3e934a98f09308377eba6f3824", size = 35070225, upload-time = "2025-06-22T16:19:31.416Z" }, + { url = "https://files.pythonhosted.org/packages/e5/73/5cbe4a3fd4bc3e2d67ffad02c88b83edc88f381b73ab982f48f3df1a7790/scipy-1.16.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:512c4f4f85912767c351a0306824ccca6fd91307a9f4318efe8fdbd9d30562ef", size = 35389070, upload-time = "2025-06-22T16:19:37.387Z" }, + { url = "https://files.pythonhosted.org/packages/86/e8/a60da80ab9ed68b31ea5a9c6dfd3c2f199347429f229bf7f939a90d96383/scipy-1.16.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e69f798847e9add03d512eaf5081a9a5c9a98757d12e52e6186ed9681247a1ac", size = 37825287, upload-time = "2025-06-22T16:19:43.375Z" }, + { url = "https://files.pythonhosted.org/packages/ea/b5/29fece1a74c6a94247f8a6fb93f5b28b533338e9c34fdcc9cfe7a939a767/scipy-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:adf9b1999323ba335adc5d1dc7add4781cb5a4b0ef1e98b79768c05c796c4e49", size = 38431929, upload-time = "2025-06-22T16:19:49.385Z" }, + { url = "https://files.pythonhosted.org/packages/46/95/0746417bc24be0c2a7b7563946d61f670a3b491b76adede420e9d173841f/scipy-1.16.0-cp313-cp313-macosx_10_14_x86_64.whl", hash = "sha256:e9f414cbe9ca289a73e0cc92e33a6a791469b6619c240aa32ee18abdce8ab451", size = 36418162, upload-time = "2025-06-22T16:19:56.3Z" }, + { url = "https://files.pythonhosted.org/packages/19/5a/914355a74481b8e4bbccf67259bbde171348a3f160b67b4945fbc5f5c1e5/scipy-1.16.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:bbba55fb97ba3cdef9b1ee973f06b09d518c0c7c66a009c729c7d1592be1935e", size = 28465985, upload-time = "2025-06-22T16:20:01.238Z" }, + { url = "https://files.pythonhosted.org/packages/58/46/63477fc1246063855969cbefdcee8c648ba4b17f67370bd542ba56368d0b/scipy-1.16.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:58e0d4354eacb6004e7aa1cd350e5514bd0270acaa8d5b36c0627bb3bb486974", size = 20737961, upload-time = "2025-06-22T16:20:05.913Z" }, + { url = "https://files.pythonhosted.org/packages/93/86/0fbb5588b73555e40f9d3d6dde24ee6fac7d8e301a27f6f0cab9d8f66ff2/scipy-1.16.0-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:75b2094ec975c80efc273567436e16bb794660509c12c6a31eb5c195cbf4b6dc", size = 23377941, upload-time = "2025-06-22T16:20:10.668Z" }, + { url = "https://files.pythonhosted.org/packages/ca/80/a561f2bf4c2da89fa631b3cbf31d120e21ea95db71fd9ec00cb0247c7a93/scipy-1.16.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6b65d232157a380fdd11a560e7e21cde34fdb69d65c09cb87f6cc024ee376351", size = 33196703, upload-time = "2025-06-22T16:20:16.097Z" }, + { url = "https://files.pythonhosted.org/packages/11/6b/3443abcd0707d52e48eb315e33cc669a95e29fc102229919646f5a501171/scipy-1.16.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1d8747f7736accd39289943f7fe53a8333be7f15a82eea08e4afe47d79568c32", size = 35083410, upload-time = "2025-06-22T16:20:21.734Z" }, + { url = "https://files.pythonhosted.org/packages/20/ab/eb0fc00e1e48961f1bd69b7ad7e7266896fe5bad4ead91b5fc6b3561bba4/scipy-1.16.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:eb9f147a1b8529bb7fec2a85cf4cf42bdfadf9e83535c309a11fdae598c88e8b", size = 35387829, upload-time = "2025-06-22T16:20:27.548Z" }, + { url = "https://files.pythonhosted.org/packages/57/9e/d6fc64e41fad5d481c029ee5a49eefc17f0b8071d636a02ceee44d4a0de2/scipy-1.16.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d2b83c37edbfa837a8923d19c749c1935ad3d41cf196006a24ed44dba2ec4358", size = 37841356, upload-time = "2025-06-22T16:20:35.112Z" }, + { url = "https://files.pythonhosted.org/packages/7c/a7/4c94bbe91f12126b8bf6709b2471900577b7373a4fd1f431f28ba6f81115/scipy-1.16.0-cp313-cp313-win_amd64.whl", hash = "sha256:79a3c13d43c95aa80b87328a46031cf52508cf5f4df2767602c984ed1d3c6bbe", size = 38403710, upload-time = "2025-06-22T16:21:54.473Z" }, + { url = "https://files.pythonhosted.org/packages/47/20/965da8497f6226e8fa90ad3447b82ed0e28d942532e92dd8b91b43f100d4/scipy-1.16.0-cp313-cp313t-macosx_10_14_x86_64.whl", hash = "sha256:f91b87e1689f0370690e8470916fe1b2308e5b2061317ff76977c8f836452a47", size = 36813833, upload-time = "2025-06-22T16:20:43.925Z" }, + { url = "https://files.pythonhosted.org/packages/28/f4/197580c3dac2d234e948806e164601c2df6f0078ed9f5ad4a62685b7c331/scipy-1.16.0-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:88a6ca658fb94640079e7a50b2ad3b67e33ef0f40e70bdb7dc22017dae73ac08", size = 28974431, upload-time = "2025-06-22T16:20:51.302Z" }, + { url = "https://files.pythonhosted.org/packages/8a/fc/e18b8550048d9224426e76906694c60028dbdb65d28b1372b5503914b89d/scipy-1.16.0-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:ae902626972f1bd7e4e86f58fd72322d7f4ec7b0cfc17b15d4b7006efc385176", size = 21246454, upload-time = "2025-06-22T16:20:57.276Z" }, + { url = "https://files.pythonhosted.org/packages/8c/48/07b97d167e0d6a324bfd7484cd0c209cc27338b67e5deadae578cf48e809/scipy-1.16.0-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:8cb824c1fc75ef29893bc32b3ddd7b11cf9ab13c1127fe26413a05953b8c32ed", size = 23772979, upload-time = "2025-06-22T16:21:03.363Z" }, + { url = "https://files.pythonhosted.org/packages/4c/4f/9efbd3f70baf9582edf271db3002b7882c875ddd37dc97f0f675ad68679f/scipy-1.16.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:de2db7250ff6514366a9709c2cba35cb6d08498e961cba20d7cff98a7ee88938", size = 33341972, upload-time = "2025-06-22T16:21:11.14Z" }, + { url = "https://files.pythonhosted.org/packages/3f/dc/9e496a3c5dbe24e76ee24525155ab7f659c20180bab058ef2c5fa7d9119c/scipy-1.16.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e85800274edf4db8dd2e4e93034f92d1b05c9421220e7ded9988b16976f849c1", size = 35185476, upload-time = "2025-06-22T16:21:19.156Z" }, + { url = "https://files.pythonhosted.org/packages/ce/b3/21001cff985a122ba434c33f2c9d7d1dc3b669827e94f4fc4e1fe8b9dfd8/scipy-1.16.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4f720300a3024c237ace1cb11f9a84c38beb19616ba7c4cdcd771047a10a1706", size = 35570990, upload-time = "2025-06-22T16:21:27.797Z" }, + { url = "https://files.pythonhosted.org/packages/e5/d3/7ba42647d6709251cdf97043d0c107e0317e152fa2f76873b656b509ff55/scipy-1.16.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:aad603e9339ddb676409b104c48a027e9916ce0d2838830691f39552b38a352e", size = 37950262, upload-time = "2025-06-22T16:21:36.976Z" }, + { url = "https://files.pythonhosted.org/packages/eb/c4/231cac7a8385394ebbbb4f1ca662203e9d8c332825ab4f36ffc3ead09a42/scipy-1.16.0-cp313-cp313t-win_amd64.whl", hash = "sha256:f56296fefca67ba605fd74d12f7bd23636267731a72cb3947963e76b8c0a25db", size = 38515076, upload-time = "2025-06-22T16:21:45.694Z" }, +] + +[[package]] +name = "seaborn" +version = "0.13.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "matplotlib" }, + { name = "numpy" }, + { name = "pandas" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/86/59/a451d7420a77ab0b98f7affa3a1d78a313d2f7281a57afb1a34bae8ab412/seaborn-0.13.2.tar.gz", hash = "sha256:93e60a40988f4d65e9f4885df477e2fdaff6b73a9ded434c1ab356dd57eefff7", size = 1457696, upload-time = "2024-01-25T13:21:52.551Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/83/11/00d3c3dfc25ad54e731d91449895a79e4bf2384dc3ac01809010ba88f6d5/seaborn-0.13.2-py3-none-any.whl", hash = "sha256:636f8336facf092165e27924f223d3c62ca560b1f2bb5dff7ab7fad265361987", size = 294914, upload-time = "2024-01-25T13:21:49.598Z" }, +] + +[[package]] +name = "setuptools" +version = "80.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/18/5d/3bf57dcd21979b887f014ea83c24ae194cfcd12b9e0fda66b957c69d1fca/setuptools-80.9.0.tar.gz", hash = "sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c", size = 1319958, upload-time = "2025-05-27T00:56:51.443Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a3/dc/17031897dae0efacfea57dfd3a82fdd2a2aeb58e0ff71b77b87e44edc772/setuptools-80.9.0-py3-none-any.whl", hash = "sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922", size = 1201486, upload-time = "2025-05-27T00:56:49.664Z" }, +] + +[[package]] +name = "six" +version = "1.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, +] + +[[package]] +name = "sympy" +version = "1.14.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mpmath" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/83/d3/803453b36afefb7c2bb238361cd4ae6125a569b4db67cd9e79846ba2d68c/sympy-1.14.0.tar.gz", hash = "sha256:d3d3fe8df1e5a0b42f0e7bdf50541697dbe7d23746e894990c030e2b05e72517", size = 7793921, upload-time = "2025-04-27T18:05:01.611Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl", hash = "sha256:e091cc3e99d2141a0ba2847328f5479b05d94a6635cb96148ccb3f34671bd8f5", size = 6299353, upload-time = "2025-04-27T18:04:59.103Z" }, +] + +[[package]] +name = "threadpoolctl" +version = "3.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b7/4d/08c89e34946fce2aec4fbb45c9016efd5f4d7f24af8e5d93296e935631d8/threadpoolctl-3.6.0.tar.gz", hash = "sha256:8ab8b4aa3491d812b623328249fab5302a68d2d71745c8a4c719a2fcaba9f44e", size = 21274, upload-time = "2025-03-13T13:49:23.031Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/32/d5/f9a850d79b0851d1d4ef6456097579a9005b31fea68726a4ae5f2d82ddd9/threadpoolctl-3.6.0-py3-none-any.whl", hash = "sha256:43a0b8fd5a2928500110039e43a5eed8480b918967083ea48dc3ab9f13c4a7fb", size = 18638, upload-time = "2025-03-13T13:49:21.846Z" }, +] + +[[package]] +name = "torch" +version = "2.7.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "filelock" }, + { name = "fsspec" }, + { name = "jinja2" }, + { name = "networkx" }, + { name = "nvidia-cublas-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-cuda-cupti-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-cuda-nvrtc-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-cuda-runtime-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-cudnn-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-cufft-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-cufile-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-curand-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-cusolver-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-cusparse-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-cusparselt-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-nccl-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-nvjitlink-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-nvtx-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "setuptools" }, + { name = "sympy" }, + { name = "triton", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "typing-extensions" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/87/93/fb505a5022a2e908d81fe9a5e0aa84c86c0d5f408173be71c6018836f34e/torch-2.7.1-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:27ea1e518df4c9de73af7e8a720770f3628e7f667280bce2be7a16292697e3fa", size = 98948276, upload-time = "2025-06-04T17:39:12.852Z" }, + { url = "https://files.pythonhosted.org/packages/56/7e/67c3fe2b8c33f40af06326a3d6ae7776b3e3a01daa8f71d125d78594d874/torch-2.7.1-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:c33360cfc2edd976c2633b3b66c769bdcbbf0e0b6550606d188431c81e7dd1fc", size = 821025792, upload-time = "2025-06-04T17:34:58.747Z" }, + { url = "https://files.pythonhosted.org/packages/a1/37/a37495502bc7a23bf34f89584fa5a78e25bae7b8da513bc1b8f97afb7009/torch-2.7.1-cp312-cp312-win_amd64.whl", hash = "sha256:d8bf6e1856ddd1807e79dc57e54d3335f2b62e6f316ed13ed3ecfe1fc1df3d8b", size = 216050349, upload-time = "2025-06-04T17:38:59.709Z" }, + { url = "https://files.pythonhosted.org/packages/3a/60/04b77281c730bb13460628e518c52721257814ac6c298acd25757f6a175c/torch-2.7.1-cp312-none-macosx_11_0_arm64.whl", hash = "sha256:787687087412c4bd68d315e39bc1223f08aae1d16a9e9771d95eabbb04ae98fb", size = 68645146, upload-time = "2025-06-04T17:38:52.97Z" }, + { url = "https://files.pythonhosted.org/packages/66/81/e48c9edb655ee8eb8c2a6026abdb6f8d2146abd1f150979ede807bb75dcb/torch-2.7.1-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:03563603d931e70722dce0e11999d53aa80a375a3d78e6b39b9f6805ea0a8d28", size = 98946649, upload-time = "2025-06-04T17:38:43.031Z" }, + { url = "https://files.pythonhosted.org/packages/3a/24/efe2f520d75274fc06b695c616415a1e8a1021d87a13c68ff9dce733d088/torch-2.7.1-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:d632f5417b6980f61404a125b999ca6ebd0b8b4bbdbb5fbbba44374ab619a412", size = 821033192, upload-time = "2025-06-04T17:38:09.146Z" }, + { url = "https://files.pythonhosted.org/packages/dd/d9/9c24d230333ff4e9b6807274f6f8d52a864210b52ec794c5def7925f4495/torch-2.7.1-cp313-cp313-win_amd64.whl", hash = "sha256:23660443e13995ee93e3d844786701ea4ca69f337027b05182f5ba053ce43b38", size = 216055668, upload-time = "2025-06-04T17:38:36.253Z" }, + { url = "https://files.pythonhosted.org/packages/95/bf/e086ee36ddcef9299f6e708d3b6c8487c1651787bb9ee2939eb2a7f74911/torch-2.7.1-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:0da4f4dba9f65d0d203794e619fe7ca3247a55ffdcbd17ae8fb83c8b2dc9b585", size = 68925988, upload-time = "2025-06-04T17:38:29.273Z" }, + { url = "https://files.pythonhosted.org/packages/69/6a/67090dcfe1cf9048448b31555af6efb149f7afa0a310a366adbdada32105/torch-2.7.1-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:e08d7e6f21a617fe38eeb46dd2213ded43f27c072e9165dc27300c9ef9570934", size = 99028857, upload-time = "2025-06-04T17:37:50.956Z" }, + { url = "https://files.pythonhosted.org/packages/90/1c/48b988870823d1cc381f15ec4e70ed3d65e043f43f919329b0045ae83529/torch-2.7.1-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:30207f672328a42df4f2174b8f426f354b2baa0b7cca3a0adb3d6ab5daf00dc8", size = 821098066, upload-time = "2025-06-04T17:37:33.939Z" }, + { url = "https://files.pythonhosted.org/packages/7b/eb/10050d61c9d5140c5dc04a89ed3257ef1a6b93e49dd91b95363d757071e0/torch-2.7.1-cp313-cp313t-win_amd64.whl", hash = "sha256:79042feca1c634aaf6603fe6feea8c6b30dfa140a6bbc0b973e2260c7e79a22e", size = 216336310, upload-time = "2025-06-04T17:36:09.862Z" }, + { url = "https://files.pythonhosted.org/packages/b1/29/beb45cdf5c4fc3ebe282bf5eafc8dfd925ead7299b3c97491900fe5ed844/torch-2.7.1-cp313-none-macosx_11_0_arm64.whl", hash = "sha256:988b0cbc4333618a1056d2ebad9eb10089637b659eb645434d0809d8d937b946", size = 68645708, upload-time = "2025-06-04T17:34:39.852Z" }, +] + +[[package]] +name = "torch-receptive-field" +version = "0.1.0" +source = { git = "https://github.com/Fangyh09/pytorch-receptive-field.git#0aeb7f80cd1dd8aa1ed8e6a6882f651dd7e6e877" } + +[[package]] +name = "torchscan" +version = "0.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "torch" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a9/b5/73bc396ae852a3446909421fb8f2943b223f4c83002f9cbca1910d29697e/torchscan-0.1.1.tar.gz", hash = "sha256:672261bc80c39fcb1839c70377a2d580aa233ecb13d34754086120f8e5a0d322", size = 21084, upload-time = "2020-08-03T22:25:38.266Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/30/8f/0f255453c3c44990cb879574ac023f7484acfb225d7f17468cbebc65bf01/torchscan-0.1.1-py3-none-any.whl", hash = "sha256:1084c42a978961aa2098acaefe684e45535749761b1542a44aa91cc18fd52fb9", size = 19005, upload-time = "2020-08-03T22:25:36.966Z" }, +] + +[[package]] +name = "triton" +version = "3.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "setuptools" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/24/5f/950fb373bf9c01ad4eb5a8cd5eaf32cdf9e238c02f9293557a2129b9c4ac/triton-3.3.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9999e83aba21e1a78c1f36f21bce621b77bcaa530277a50484a7cb4a822f6e43", size = 155669138, upload-time = "2025-05-29T23:39:51.771Z" }, + { url = "https://files.pythonhosted.org/packages/74/1f/dfb531f90a2d367d914adfee771babbd3f1a5b26c3f5fbc458dee21daa78/triton-3.3.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b89d846b5a4198317fec27a5d3a609ea96b6d557ff44b56c23176546023c4240", size = 155673035, upload-time = "2025-05-29T23:40:02.468Z" }, + { url = "https://files.pythonhosted.org/packages/28/71/bd20ffcb7a64c753dc2463489a61bf69d531f308e390ad06390268c4ea04/triton-3.3.1-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a3198adb9d78b77818a5388bff89fa72ff36f9da0bc689db2f0a651a67ce6a42", size = 155735832, upload-time = "2025-05-29T23:40:10.522Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.14.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d1/bc/51647cd02527e87d05cb083ccc402f93e441606ff1f01739a62c8ad09ba5/typing_extensions-4.14.0.tar.gz", hash = "sha256:8676b788e32f02ab42d9e7c61324048ae4c6d844a399eebace3d4979d75ceef4", size = 107423, upload-time = "2025-06-02T14:52:11.399Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/69/e0/552843e0d356fbb5256d21449fa957fa4eff3bbc135a74a691ee70c7c5da/typing_extensions-4.14.0-py3-none-any.whl", hash = "sha256:a1514509136dd0b477638fc68d6a91497af5076466ad0fa6c338e44e359944af", size = 43839, upload-time = "2025-06-02T14:52:10.026Z" }, +] + +[[package]] +name = "tzdata" +version = "2025.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/95/32/1a225d6164441be760d75c2c42e2780dc0873fe382da3e98a2e1e48361e5/tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9", size = 196380, upload-time = "2025-03-23T13:54:43.652Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8", size = 347839, upload-time = "2025-03-23T13:54:41.845Z" }, +] + +[[package]] +name = "visualtorch" +version = "0.2.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aggdraw" }, + { name = "numpy" }, + { name = "pillow" }, + { name = "torch" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2d/09/2c42a3d9c531ee794f235d6eb0a8e49d2b92adf7c6009932349913b2dfb1/visualtorch-0.2.4.tar.gz", hash = "sha256:662e32040533cbbf389cfd12e0a95799f0cf16612bac5eea149e6028bbb36812", size = 17507, upload-time = "2025-06-23T22:19:52.905Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3a/0f/504fcec35cc8eaf959364482e1dc2b061b279f49fbd0bab0d4df2742ddee/visualtorch-0.2.4-py3-none-any.whl", hash = "sha256:b84e9115204d3a4d1c2d054f85cdcc7ae0e154ece18340622e6ea832736c442c", size = 19965, upload-time = "2025-06-23T22:19:51.734Z" }, +] diff --git a/tools/ae_elbow_eval.py b/tools/ae_elbow_eval.py new file mode 100644 index 0000000..e220736 --- /dev/null +++ b/tools/ae_elbow_eval.py @@ -0,0 +1,174 @@ +# loads results from autoencoder training form a pickle file and evaluates results and visualizes them to find traiing elbow + +import pickle +import unittest +from pathlib import Path +from typing import Dict, List + +import matplotlib.pyplot as plt +import numpy as np + +results_folder = Path( + "/home/fedex/mt/projects/thesis-kowalczyk-jan/Deep-SAD-PyTorch/test/DeepSAD/subter_ae_elbow/" +) + +# Find all result files matching the pattern +result_files = sorted( + results_folder.glob("ae_elbow_results_subter_LeNet_dim_*_kfold.pkl") +) + +# Initialize data structures for both classes +dimensions = [] +normal_means = [] +normal_stds = [] +anomaly_means = [] +anomaly_stds = [] + +BATCH_SIZE = 256 # Add this constant at the top of the file + + +def calculate_batch_mean_loss(scores, batch_size=BATCH_SIZE): + """Calculate mean loss over batches similar to the original testing code.""" + n_samples = len(scores) + n_batches = (n_samples + batch_size - 1) // batch_size # ceiling division + + # Split scores into batches + batch_losses = [] + for i in range(0, n_samples, batch_size): + batch_scores = scores[i : i + batch_size] + batch_losses.append(np.mean(batch_scores)) + + return np.sum(batch_losses) / n_batches + + +def test_loss_calculation(results: Dict, batch_size: int = BATCH_SIZE) -> None: + """Test if our loss calculation matches the original implementation.""" + test = unittest.TestCase() + folds = results["ae_results"] + dim = results["dimension"] + + for fold_key in folds: + fold_data = folds[fold_key]["test"] + scores = np.array(fold_data["scores"]) + original_loss = fold_data["loss"] + calculated_loss = calculate_batch_mean_loss(scores) + + try: + test.assertAlmostEqual( + original_loss, + calculated_loss, + places=5, + msg=f"Loss mismatch for dim={dim}, {fold_key}", + ) + except AssertionError as e: + print(f"Warning: {str(e)}") + print(f"Original: {original_loss:.6f}, Calculated: {calculated_loss:.6f}") + raise + + +# Load and verify data +print("Verifying loss calculation implementation...") +for result_file in result_files: + with open(result_file, "rb") as f: + results = pickle.load(f) + test_loss_calculation(results) +print("Loss calculation verified successfully!") + +# Continue with actual data processing +for result_file in result_files: + with open(result_file, "rb") as f: + results = pickle.load(f) + dim = int(results["dimension"]) + folds = results["ae_results"] + + normal_fold_losses = [] + anomaly_fold_losses = [] + + for fold_key in folds: + fold_data = folds[fold_key]["test"] + scores = np.array(fold_data["scores"]) + labels = np.array(fold_data["labels_exp_based"]) + + # Calculate mean loss for normal and anomaly samples + normal_scores = scores[labels == 1] + anomaly_scores = scores[labels == -1] + + # Calculate losses using batch means + normal_fold_losses.append(calculate_batch_mean_loss(normal_scores)) + anomaly_fold_losses.append(calculate_batch_mean_loss(anomaly_scores)) + + dimensions.append(dim) + normal_means.append(np.mean(normal_fold_losses)) + normal_stds.append(np.std(normal_fold_losses)) + anomaly_means.append(np.mean(anomaly_fold_losses)) + anomaly_stds.append(np.std(anomaly_fold_losses)) + +# Sort by dimension +dims, n_means, n_stds, a_means, a_stds = zip( + *sorted(zip(dimensions, normal_means, normal_stds, anomaly_means, anomaly_stds)) +) + +# Calculate overall means and stds +means = [(n + a) / 2 for n, a in zip(n_means, a_means)] +stds = [(ns + as_) / 2 for ns, as_ in zip(n_stds, a_stds)] + + +def plot_loss_curve(dims, means, stds, title, color, output_path): + """Create and save a single loss curve plot. + + Args: + dims: List of latent dimensions + means: List of mean losses + stds: List of standard deviations + title: Plot title + color: Color for plot and fill + output_path: Where to save the plot + """ + plt.figure(figsize=(8, 5)) + plt.plot(dims, means, marker="o", color=color, label="Mean Test Loss") + plt.fill_between( + dims, + np.array(means) - np.array(stds), + np.array(means) + np.array(stds), + color=color, + alpha=0.2, + label="Std Dev", + ) + plt.xlabel("Latent Dimension") + plt.ylabel("Test Loss") + plt.title(title) + plt.legend() + plt.grid(True, alpha=0.3) + plt.xticks(dims) # Set x-ticks exactly at all data points + plt.tight_layout() + plt.savefig(output_path, dpi=150, bbox_inches="tight") + plt.close() + + +# Create the three plots +plot_loss_curve( + dims, + means, + stds, + "Overall Test Loss vs. Latent Dimension", + "blue", + results_folder / "ae_elbow_test_loss_overall.png", +) + +plot_loss_curve( + dims, + n_means, + n_stds, + "Normal Class Test Loss vs. Latent Dimension", + "green", + results_folder / "ae_elbow_test_loss_normal.png", +) + +plot_loss_curve( + dims, + a_means, + a_stds, + "Anomaly Class Test Loss vs. Latent Dimension", + "red", + results_folder / "ae_elbow_test_loss_anomaly.png", +) diff --git a/tools/calculate_conv_dimensions.py b/tools/calculate_conv_dimensions.py new file mode 100644 index 0000000..a22e1e7 --- /dev/null +++ b/tools/calculate_conv_dimensions.py @@ -0,0 +1,23 @@ +def calculate_conv_dimensions(W_in, H_in, K, S, P): + """ + Calculate the output dimensions of a convolutional layer. + + Parameters: + W_in (int): Width of the input image + H_in (int): Height of the input image + K (int): Size of the filter (assumed to be square) + S (int): Stride + P (int): Padding + + Returns: + (int, int): Width and height of the output activation map + """ + W_out = (W_in - K + (2 * P)) / S + 1 + H_out = (H_in - K + (2 * P)) / S + 1 + + return W_out, H_out + + +print(f"w, h = {calculate_conv_dimensions(W_in=2048, H_in=32, K=11, S=4, P=2)=}") +# w, h = calculate_conv_dimensions(W_in=2048, H_in=32, K=11, S=4, P=2) +# print(f"{calculate_conv_dimensions(W_in=w, H_in=h, K=11, S=4, P=2)=}") diff --git a/tools/calculate_rf_sizes.py b/tools/calculate_rf_sizes.py new file mode 100644 index 0000000..31b01bd --- /dev/null +++ b/tools/calculate_rf_sizes.py @@ -0,0 +1,102 @@ +def calculate_receptive_field_size(layers): + """ + Parameters + ---------- + layers : list of dict + Each dict must contain + ─ 'kernel_size' : (k_h, k_w) + ─ 'stride' : (s_h, s_w) + ─ 'dilation' : (d_h, d_w) # optional; defaults to (1, 1) + + Returns + ------- + (rf_h, rf_w) : tuple of ints + Receptive-field size in pixels for height and width. + """ + rf_h = rf_w = 1 # start with a 1×1 pixel + jump_h = jump_w = 1 # effective stride so far + + for layer in layers: + k_h, k_w = layer["kernel_size"] + s_h, s_w = layer["stride"] + d_h, d_w = layer.get("dilation", (1, 1)) # default = 1 + + # Dumoulin & Visin recurrence, now with dilation + rf_h += (k_h - 1) * d_h * jump_h + rf_w += (k_w - 1) * d_w * jump_w + + jump_h *= s_h + jump_w *= s_w + + return rf_h, rf_w + + +def calculate_angular_receptive_field( + rf_height: int, + rf_width: int, + vertical_res: int, + horizontal_res: int, + vertical_fov: float, + horizontal_fov: float, +) -> tuple[float, float]: + """Calculate the angular size of a receptive field in degrees. + + Args: + rf_height: Receptive field height in pixels + rf_width: Receptive field width in pixels + vertical_res: Vertical resolution of input in pixels + horizontal_res: Horizontal resolution of input in pixels + vertical_fov: Vertical field of view in degrees + horizontal_fov: Horizontal field of view in degrees + + Returns: + tuple[float, float]: Angular size (height, width) in degrees + """ + # Calculate degrees per pixel for each axis + vertical_deg_per_pixel = vertical_fov / vertical_res + horizontal_deg_per_pixel = horizontal_fov / horizontal_res + + # Calculate angular size of receptive field + rf_vertical_deg = rf_height * vertical_deg_per_pixel + rf_horizontal_deg = rf_width * horizontal_deg_per_pixel + + return rf_vertical_deg, rf_horizontal_deg + + +horizontal_resolution = 2048 +horizontal_fov = 360.0 +vertical_resolution = 32 +vertical_fov = 31.76 + +lenet_network_config = [ + {"name": "Conv1", "kernel_size": (5, 5), "stride": (1, 1), "dilation": (1, 1)}, + {"name": "MaxPool1", "kernel_size": (2, 2), "stride": (2, 2), "dilation": (1, 1)}, + {"name": "Conv2", "kernel_size": (5, 5), "stride": (1, 1), "dilation": (1, 1)}, + {"name": "MaxPool2", "kernel_size": (2, 2), "stride": (2, 2), "dilation": (1, 1)}, +] + +# Calculate and print results for LeNet +rf_h, rf_w = calculate_receptive_field_size(lenet_network_config) +rf_vert_deg, rf_horiz_deg = calculate_angular_receptive_field( + rf_h, rf_w, vertical_resolution, horizontal_resolution, vertical_fov, horizontal_fov +) +print(f"SubTer LeNet RF size: {rf_h} × {rf_w} pixels") +print(f"SubTer LeNet RF angular size: {rf_vert_deg:.2f}° × {rf_horiz_deg:.2f}°") + + +asym_network_config = [ + {"name": "Conv1", "kernel_size": (3, 17), "stride": (1, 1), "dilation": (1, 1)}, + {"name": "MaxPool1", "kernel_size": (2, 2), "stride": (2, 2), "dilation": (1, 1)}, + {"name": "Conv2", "kernel_size": (3, 17), "stride": (1, 1), "dilation": (1, 1)}, + {"name": "MaxPool2", "kernel_size": (2, 2), "stride": (2, 2), "dilation": (1, 1)}, +] + +# Calculate and print results for Asymmetric network +rf_h, rf_w = calculate_receptive_field_size(asym_network_config) +rf_vert_deg, rf_horiz_deg = calculate_angular_receptive_field( + rf_h, rf_w, vertical_resolution, horizontal_resolution, vertical_fov, horizontal_fov +) +print(f"SubTer LeNet (Asymmetric kernels) RF size: {rf_h} × {rf_w} pixels") +print( + f"SubTer LeNet (Asymmetric kernels) RF angular size: {rf_vert_deg:.2f}° × {rf_horiz_deg:.2f}°" +) diff --git a/tools/compare_projections.py b/tools/compare_projections.py new file mode 100644 index 0000000..1728416 --- /dev/null +++ b/tools/compare_projections.py @@ -0,0 +1,205 @@ +from pathlib import Path +from typing import Any, Dict, Optional, Tuple + +import numpy as np + +# Configuration +old_projections_path = Path("/home/fedex/mt/data/subter") +new_projections_path = Path("/home/fedex/mt/data/subter/new_projection") + + +def get_file_info(file_path: Path) -> Optional[dict]: + """Get detailed information about a .npy file.""" + if not file_path.exists(): + return None + try: + with file_path.open("rb") as f: + return { + "size": file_path.stat().st_size, + "format": np.lib.format.read_magic(f), + "header": np.lib.format.read_array_header_1_0(f), + } + except Exception as e: + print(f"Error reading file {file_path}: {e}") + return None + + +def get_array_info(arr: np.ndarray) -> dict: + """Get detailed information about a numpy array.""" + return { + "dtype": str(arr.dtype), + "itemsize": arr.itemsize, + "nbytes": arr.nbytes, + "byteorder": arr.dtype.byteorder, + "shape": arr.shape, + "nan_count": np.sum(np.isnan(arr)), + "inf_count": np.sum(np.isinf(arr)), + } + + +def compare_arrays(old_arr: np.ndarray, new_arr: np.ndarray) -> dict: + """Compare two numpy arrays and return detailed differences.""" + # Get basic array information + old_info = get_array_info(old_arr) + new_info = get_array_info(new_arr) + + differences = { + "old_info": old_info, + "new_info": new_info, + "shape_mismatch": old_arr.shape != new_arr.shape, + } + + if differences["shape_mismatch"]: + return differences + + # Compare values + both_nan = np.isnan(old_arr) & np.isnan(new_arr) + unequal = (old_arr != new_arr) & ~both_nan + mismatch_indices = np.where(unequal) + + differences.update( + { + "num_mismatches": mismatch_indices[0].size, + "mismatch_indices": mismatch_indices + if mismatch_indices[0].size > 0 + else None, + "mean_difference": np.mean(np.abs(old_arr - new_arr)), + "std_difference": np.std(old_arr - new_arr), + "new_zeros": np.sum(new_arr == 0), + } + ) + + # If there are mismatches, store some example values + if differences["num_mismatches"] > 0: + old_values = old_arr[mismatch_indices][:10] + new_values = new_arr[mismatch_indices][:10] + differences.update( + { + "example_old_values": old_values, + "example_new_values": new_values, + "all_new_mismatches_zero": np.all(new_arr[mismatch_indices] == 0), + } + ) + + return differences + + +def print_detailed_comparison(name: str, diff: dict): + """Print detailed comparison results for a single file.""" + print(f"\nDetailed comparison for: {name}") + print("=" * 80) + + # Storage information + old_info = diff["old_info"] + new_info = diff["new_info"] + print(f"Storage Information:") + print(f" Dtype: {old_info['dtype']} → {new_info['dtype']}") + print(f" Item size: {old_info['itemsize']} → {new_info['itemsize']} bytes") + print(f" Total bytes: {old_info['nbytes']} → {new_info['nbytes']}") + print(f" Byte ratio: {new_info['nbytes'] / old_info['nbytes']:.2f}") + + # Shape information + if diff["shape_mismatch"]: + print(f"Shape mismatch: {old_info['shape']} → {new_info['shape']}") + return + + # Value differences + if diff["num_mismatches"] > 0: + print(f"\nValue Differences:") + print(f" Number of mismatches: {diff['num_mismatches']}") + print(f" Mean difference: {diff['mean_difference']:.2e}") + print(f" Std difference: {diff['std_difference']:.2e}") + print(f" Example mismatches (old → new):") + for old_val, new_val in zip( + diff["example_old_values"], diff["example_new_values"] + ): + print(f" {old_val:.6e} → {new_val:.6e}") + if diff["all_new_mismatches_zero"]: + print(" Note: All mismatched values in new array are zero") + else: + print("\nNo value mismatches found.") + + +def summarize_differences(all_differences: Dict[str, dict]) -> str: + """Create a summary of all differences.""" + summary = [] + summary.append("\nSUMMARY OF ALL DIFFERENCES") + summary.append("=" * 80) + + total_files = len(all_differences) + files_with_mismatches = sum( + 1 for d in all_differences.values() if d["num_mismatches"] > 0 + ) + files_with_shape_mismatch = sum( + 1 for d in all_differences.values() if d["shape_mismatch"] + ) + + summary.append(f"Total files compared: {total_files}") + summary.append(f"Files with shape mismatches: {files_with_shape_mismatch}") + summary.append(f"Files with value mismatches: {files_with_mismatches}") + + if files_with_mismatches > 0: + summary.append("\nFiles with differences:") + for name, diff in all_differences.items(): + if diff["num_mismatches"] > 0: + summary.append(f" {name}:") + summary.append(f" Mismatches: {diff['num_mismatches']}") + summary.append(f" Mean difference: {diff['mean_difference']:.2e}") + if diff.get("all_new_mismatches_zero", False): + summary.append(" All mismatches are zeros in new file") + + return "\n".join(summary) + + +def main(): + # Get list of all .npy files + old_files = list(old_projections_path.glob("*.npy")) + new_files = list(new_projections_path.glob("*.npy")) + + # Check for missing files + old_names = {f.stem for f in old_files} + new_names = {f.stem for f in new_files} + if missing := (old_names - new_names): + print(f"Files missing in new directory: {missing}") + if missing := (new_names - old_names): + print(f"Files missing in old directory: {missing}") + + # Compare common files + all_differences = {} + for old_file in old_files: + if old_file.stem not in new_names: + continue + + print(f"\nComparing {old_file.stem}...") + new_file = new_projections_path / f"{old_file.stem}.npy" + + # Check file info before loading + old_info = get_file_info(old_file) + new_info = get_file_info(new_file) + + if not old_info or not new_info: + print(f"Skipping {old_file.stem} due to file reading errors") + continue + + try: + # Load arrays + old_arr = np.load(old_file) + new_arr = np.load(new_file) + + # Compare and print detailed results + differences = compare_arrays(old_arr, new_arr) + print_detailed_comparison(old_file.stem, differences) + all_differences[old_file.stem] = differences + except Exception as e: + print(f"Error processing {old_file.stem}: {e}") + continue + + # Print summary + if all_differences: + print(summarize_differences(all_differences)) + else: + print("\nNo valid comparisons were made.") + + +if __name__ == "__main__": + main() diff --git a/tools/elbow_structure.txt b/tools/elbow_structure.txt new file mode 100644 index 0000000..ee12ea5 --- /dev/null +++ b/tools/elbow_structure.txt @@ -0,0 +1,600 @@ + +=== Results for ae_elbow_results_subter_LeNet_dim_32_kfold.pkl === +dimension: + 32 +ae_results: + fold_0: + train: + time: + 47.35341715812683 + indices: + [ 6547 6096 17322 ... 14590 6764 6219] + labels_exp_based: + [1 1 1 ... 1 1 1] + labels_manual_based: + [1 1 1 ... 1 1 1] + semi_targets: + [1 1 1 ... 1 1 1] + file_ids: + [8 5 0 ... 1 9 1] + frame_ids: + [1260 569 2387 ... 3064 205 1096] + scores: + [0.06785126 0.13130851 0.10918648 ... 0.04708466 0.03133186 0.05559417] + loss: + 0.07233340218663216 + file_names: + 0: + 1_loop_closure_illuminated_2023-01-23.npy + 1: + 1_loop_closure_illuminated_two_LiDARs_2023-01-23.npy + 2: + 2_human_static_illuminated_2023-01-23-001.npy + 3: + 2_human_static_no_illumination_2023-01-23-001.npy + 4: + 2_human_walking_illuminated_2023-01-23-001.npy + 5: + 2_human_walking_no_illumination_2023-01-23-001.npy + 6: + 2_static_artifacts_illuminated_2023-01-23-001.npy + 7: + 2_static_artifacts_no_illumination_2023-01-23-001.npy + 8: + 2_static_no_artifacts_illuminated_2023-01-23-001.npy + 9: + 2_static_no_artifacts_no_illumination_2023-01-23-001.npy + 10: + 3_smoke_artifacts_human_static_2023-01-23-001.npy + 11: + 3_smoke_human_walking_2023-01-23.npy + 12: + 3_smoke_no_artifacts_2023-01-23.npy + 13: + 3_smoke_robot_stationary_static_excess_smoke_2023-01-23.npy + test: + time: + 3.2727890014648438 + indices: + [ 0 1 2 ... 5883 5884 5885] + labels_exp_based: + [ 1 1 1 ... -1 1 -1] + labels_manual_based: + [ 1 1 1 ... -1 1 -1] + semi_targets: + [ 1 1 1 ... -1 1 -1] + file_ids: + [ 0 1 3 ... 11 0 12] + frame_ids: + [3373 2260 81 ... 405 1342 335] + scores: + [0.28987885 0.06485476 0.03065375 ... 0.26596928 0.03807792 0.46061364] + loss: + 0.06829473586833995 + file_names: + 0: + 1_loop_closure_illuminated_2023-01-23.npy + 1: + 1_loop_closure_illuminated_two_LiDARs_2023-01-23.npy + 2: + 2_human_static_illuminated_2023-01-23-001.npy + 3: + 2_human_static_no_illumination_2023-01-23-001.npy + 4: + 2_human_walking_illuminated_2023-01-23-001.npy + 5: + 2_human_walking_no_illumination_2023-01-23-001.npy + 6: + 2_static_artifacts_illuminated_2023-01-23-001.npy + 7: + 2_static_artifacts_no_illumination_2023-01-23-001.npy + 8: + 2_static_no_artifacts_illuminated_2023-01-23-001.npy + 9: + 2_static_no_artifacts_no_illumination_2023-01-23-001.npy + 10: + 3_smoke_artifacts_human_static_2023-01-23-001.npy + 11: + 3_smoke_human_walking_2023-01-23.npy + 12: + 3_smoke_no_artifacts_2023-01-23.npy + 13: + 3_smoke_robot_stationary_static_excess_smoke_2023-01-23.npy + fold_1: + train: + time: + 46.584895849227905 + indices: + [14909 2898 15177 ... 3185 2297 2518] + labels_exp_based: + [ 1 1 1 ... 1 -1 1] + labels_manual_based: + [ 1 1 1 ... 1 -1 1] + semi_targets: + [ 1 1 1 ... 1 -1 1] + file_ids: + [ 5 6 1 ... 7 13 4] + frame_ids: + [ 936 606 3193 ... 989 86 844] + scores: + [0.10743871 0.17937626 0.3385203 ... 0.02518196 0.3909377 0.02823789] + loss: + 0.06587159360448519 + file_names: + 0: + 1_loop_closure_illuminated_2023-01-23.npy + 1: + 1_loop_closure_illuminated_two_LiDARs_2023-01-23.npy + 2: + 2_human_static_illuminated_2023-01-23-001.npy + 3: + 2_human_static_no_illumination_2023-01-23-001.npy + 4: + 2_human_walking_illuminated_2023-01-23-001.npy + 5: + 2_human_walking_no_illumination_2023-01-23-001.npy + 6: + 2_static_artifacts_illuminated_2023-01-23-001.npy + 7: + 2_static_artifacts_no_illumination_2023-01-23-001.npy + 8: + 2_static_no_artifacts_illuminated_2023-01-23-001.npy + 9: + 2_static_no_artifacts_no_illumination_2023-01-23-001.npy + 10: + 3_smoke_artifacts_human_static_2023-01-23-001.npy + 11: + 3_smoke_human_walking_2023-01-23.npy + 12: + 3_smoke_no_artifacts_2023-01-23.npy + 13: + 3_smoke_robot_stationary_static_excess_smoke_2023-01-23.npy + test: + time: + 3.254101037979126 + indices: + [ 5886 5887 5888 ... 11769 11770 11771] + labels_exp_based: + [ 1 1 -1 ... 1 1 1] + labels_manual_based: + [1 1 0 ... 1 1 1] + semi_targets: + [1 1 0 ... 1 1 1] + file_ids: + [ 0 1 12 ... 0 1 7] + frame_ids: + [2943 4 179 ... 348 663 423] + scores: + [0.02836892 0.03712069 0.13963991 ... 0.07210366 0.02482101 0.07186633] + loss: + 0.059809695119443146 + file_names: + 0: + 1_loop_closure_illuminated_2023-01-23.npy + 1: + 1_loop_closure_illuminated_two_LiDARs_2023-01-23.npy + 2: + 2_human_static_illuminated_2023-01-23-001.npy + 3: + 2_human_static_no_illumination_2023-01-23-001.npy + 4: + 2_human_walking_illuminated_2023-01-23-001.npy + 5: + 2_human_walking_no_illumination_2023-01-23-001.npy + 6: + 2_static_artifacts_illuminated_2023-01-23-001.npy + 7: + 2_static_artifacts_no_illumination_2023-01-23-001.npy + 8: + 2_static_no_artifacts_illuminated_2023-01-23-001.npy + 9: + 2_static_no_artifacts_no_illumination_2023-01-23-001.npy + 10: + 3_smoke_artifacts_human_static_2023-01-23-001.npy + 11: + 3_smoke_human_walking_2023-01-23.npy + 12: + 3_smoke_no_artifacts_2023-01-23.npy + 13: + 3_smoke_robot_stationary_static_excess_smoke_2023-01-23.npy + fold_2: + train: + time: + 47.0039427280426 + indices: + [ 4709 10566 1669 ... 4887 6760 1348] + labels_exp_based: + [1 1 1 ... 1 1 1] + labels_manual_based: + [1 1 1 ... 1 1 1] + semi_targets: + [1 1 1 ... 1 1 1] + file_ids: + [0 6 1 ... 8 5 0] + frame_ids: + [1701 990 2584 ... 1036 610 449] + scores: + [0.07715754 0.07710524 0.11718763 ... 0.03398419 0.07341428 0.03260035] + loss: + 0.06793700895375676 + file_names: + 0: + 1_loop_closure_illuminated_2023-01-23.npy + 1: + 1_loop_closure_illuminated_two_LiDARs_2023-01-23.npy + 2: + 2_human_static_illuminated_2023-01-23-001.npy + 3: + 2_human_static_no_illumination_2023-01-23-001.npy + 4: + 2_human_walking_illuminated_2023-01-23-001.npy + 5: + 2_human_walking_no_illumination_2023-01-23-001.npy + 6: + 2_static_artifacts_illuminated_2023-01-23-001.npy + 7: + 2_static_artifacts_no_illumination_2023-01-23-001.npy + 8: + 2_static_no_artifacts_illuminated_2023-01-23-001.npy + 9: + 2_static_no_artifacts_no_illumination_2023-01-23-001.npy + 10: + 3_smoke_artifacts_human_static_2023-01-23-001.npy + 11: + 3_smoke_human_walking_2023-01-23.npy + 12: + 3_smoke_no_artifacts_2023-01-23.npy + 13: + 3_smoke_robot_stationary_static_excess_smoke_2023-01-23.npy + test: + time: + 3.3094074726104736 + indices: + [11772 11773 11774 ... 17655 17656 17657] + labels_exp_based: + [1 1 1 ... 1 1 1] + labels_manual_based: + [1 1 1 ... 1 1 1] + semi_targets: + [1 1 1 ... 1 1 1] + file_ids: + [7 8 0 ... 4 5 0] + frame_ids: + [ 98 1000 873 ... 674 541 2732] + scores: + [0.033662 0.03786198 0.04839528 ... 0.07525856 0.07365932 0.0603102 ] + loss: + 0.06470830592772235 + file_names: + 0: + 1_loop_closure_illuminated_2023-01-23.npy + 1: + 1_loop_closure_illuminated_two_LiDARs_2023-01-23.npy + 2: + 2_human_static_illuminated_2023-01-23-001.npy + 3: + 2_human_static_no_illumination_2023-01-23-001.npy + 4: + 2_human_walking_illuminated_2023-01-23-001.npy + 5: + 2_human_walking_no_illumination_2023-01-23-001.npy + 6: + 2_static_artifacts_illuminated_2023-01-23-001.npy + 7: + 2_static_artifacts_no_illumination_2023-01-23-001.npy + 8: + 2_static_no_artifacts_illuminated_2023-01-23-001.npy + 9: + 2_static_no_artifacts_no_illumination_2023-01-23-001.npy + 10: + 3_smoke_artifacts_human_static_2023-01-23-001.npy + 11: + 3_smoke_human_walking_2023-01-23.npy + 12: + 3_smoke_no_artifacts_2023-01-23.npy + 13: + 3_smoke_robot_stationary_static_excess_smoke_2023-01-23.npy +k_fold: + True +k_fold_num: + 3 + +=== Results for ae_elbow_results_subter_LeNet_dim_64_kfold.pkl === +dimension: + 64 +ae_results: + fold_0: + train: + time: + 47.33047151565552 + indices: + [11876 12634 14187 ... 13309 8275 16184] + labels_exp_based: + [ 1 -1 1 ... 1 1 1] + labels_manual_based: + [1 0 1 ... 1 1 1] + semi_targets: + [1 0 1 ... 1 1 1] + file_ids: + [ 4 12 8 ... 0 7 4] + frame_ids: + [ 545 63 1159 ... 441 992 700] + scores: + [0.13897061 0.08483056 0.07720029 ... 0.02398726 0.02211376 0.05582099] + loss: + 0.05624731986059083 + file_names: + 0: + 1_loop_closure_illuminated_2023-01-23.npy + 1: + 1_loop_closure_illuminated_two_LiDARs_2023-01-23.npy + 2: + 2_human_static_illuminated_2023-01-23-001.npy + 3: + 2_human_static_no_illumination_2023-01-23-001.npy + 4: + 2_human_walking_illuminated_2023-01-23-001.npy + 5: + 2_human_walking_no_illumination_2023-01-23-001.npy + 6: + 2_static_artifacts_illuminated_2023-01-23-001.npy + 7: + 2_static_artifacts_no_illumination_2023-01-23-001.npy + 8: + 2_static_no_artifacts_illuminated_2023-01-23-001.npy + 9: + 2_static_no_artifacts_no_illumination_2023-01-23-001.npy + 10: + 3_smoke_artifacts_human_static_2023-01-23-001.npy + 11: + 3_smoke_human_walking_2023-01-23.npy + 12: + 3_smoke_no_artifacts_2023-01-23.npy + 13: + 3_smoke_robot_stationary_static_excess_smoke_2023-01-23.npy + test: + time: + 3.393460512161255 + indices: + [ 0 1 2 ... 5883 5884 5885] + labels_exp_based: + [ 1 1 1 ... -1 1 -1] + labels_manual_based: + [ 1 1 1 ... -1 1 -1] + semi_targets: + [ 1 1 1 ... -1 1 -1] + file_ids: + [ 0 1 3 ... 11 0 12] + frame_ids: + [3373 2260 81 ... 405 1342 335] + scores: + [0.19174853 0.04987323 0.0225831 ... 0.19634478 0.03153864 0.39142317] + loss: + 0.05257830946989681 + file_names: + 0: + 1_loop_closure_illuminated_2023-01-23.npy + 1: + 1_loop_closure_illuminated_two_LiDARs_2023-01-23.npy + 2: + 2_human_static_illuminated_2023-01-23-001.npy + 3: + 2_human_static_no_illumination_2023-01-23-001.npy + 4: + 2_human_walking_illuminated_2023-01-23-001.npy + 5: + 2_human_walking_no_illumination_2023-01-23-001.npy + 6: + 2_static_artifacts_illuminated_2023-01-23-001.npy + 7: + 2_static_artifacts_no_illumination_2023-01-23-001.npy + 8: + 2_static_no_artifacts_illuminated_2023-01-23-001.npy + 9: + 2_static_no_artifacts_no_illumination_2023-01-23-001.npy + 10: + 3_smoke_artifacts_human_static_2023-01-23-001.npy + 11: + 3_smoke_human_walking_2023-01-23.npy + 12: + 3_smoke_no_artifacts_2023-01-23.npy + 13: + 3_smoke_robot_stationary_static_excess_smoke_2023-01-23.npy + fold_1: + train: + time: + 47.514824867248535 + indices: + [13393 1405 12413 ... 14812 397 14801] + labels_exp_based: + [ 1 -1 1 ... -1 1 1] + labels_manual_based: + [ 1 0 1 ... -1 1 1] + semi_targets: + [ 1 0 1 ... -1 1 1] + file_ids: + [ 3 10 8 ... 10 9 7] + frame_ids: + [1059 161 236 ... 368 480 145] + scores: + [0.08272791 0.17799513 0.07856707 ... 0.16969317 0.09914576 0.04465985] + loss: + 0.08280573851532406 + file_names: + 0: + 1_loop_closure_illuminated_2023-01-23.npy + 1: + 1_loop_closure_illuminated_two_LiDARs_2023-01-23.npy + 2: + 2_human_static_illuminated_2023-01-23-001.npy + 3: + 2_human_static_no_illumination_2023-01-23-001.npy + 4: + 2_human_walking_illuminated_2023-01-23-001.npy + 5: + 2_human_walking_no_illumination_2023-01-23-001.npy + 6: + 2_static_artifacts_illuminated_2023-01-23-001.npy + 7: + 2_static_artifacts_no_illumination_2023-01-23-001.npy + 8: + 2_static_no_artifacts_illuminated_2023-01-23-001.npy + 9: + 2_static_no_artifacts_no_illumination_2023-01-23-001.npy + 10: + 3_smoke_artifacts_human_static_2023-01-23-001.npy + 11: + 3_smoke_human_walking_2023-01-23.npy + 12: + 3_smoke_no_artifacts_2023-01-23.npy + 13: + 3_smoke_robot_stationary_static_excess_smoke_2023-01-23.npy + test: + time: + 3.303018808364868 + indices: + [ 5886 5887 5888 ... 11769 11770 11771] + labels_exp_based: + [ 1 1 -1 ... 1 1 1] + labels_manual_based: + [1 1 0 ... 1 1 1] + semi_targets: + [1 1 0 ... 1 1 1] + file_ids: + [ 0 1 12 ... 0 1 7] + frame_ids: + [2943 4 179 ... 348 663 423] + scores: + [0.03737867 0.05766786 0.15279752 ... 0.07271019 0.04369381 0.10203677] + loss: + 0.07809325761121252 + file_names: + 0: + 1_loop_closure_illuminated_2023-01-23.npy + 1: + 1_loop_closure_illuminated_two_LiDARs_2023-01-23.npy + 2: + 2_human_static_illuminated_2023-01-23-001.npy + 3: + 2_human_static_no_illumination_2023-01-23-001.npy + 4: + 2_human_walking_illuminated_2023-01-23-001.npy + 5: + 2_human_walking_no_illumination_2023-01-23-001.npy + 6: + 2_static_artifacts_illuminated_2023-01-23-001.npy + 7: + 2_static_artifacts_no_illumination_2023-01-23-001.npy + 8: + 2_static_no_artifacts_illuminated_2023-01-23-001.npy + 9: + 2_static_no_artifacts_no_illumination_2023-01-23-001.npy + 10: + 3_smoke_artifacts_human_static_2023-01-23-001.npy + 11: + 3_smoke_human_walking_2023-01-23.npy + 12: + 3_smoke_no_artifacts_2023-01-23.npy + 13: + 3_smoke_robot_stationary_static_excess_smoke_2023-01-23.npy + fold_2: + train: + time: + 49.39735269546509 + indices: + [6159 8197 617 ... 5118 8505 8730] + labels_exp_based: + [1 1 1 ... 1 1 1] + labels_manual_based: + [1 1 1 ... 1 1 1] + semi_targets: + [1 1 1 ... 1 1 1] + file_ids: + [1 1 0 ... 6 5 0] + frame_ids: + [ 113 169 48 ... 741 231 2666] + scores: + [0.0654774 0.06249695 0.06091163 ... 0.01456711 0.0128163 0.0202495 ] + loss: + 0.03804728595746888 + file_names: + 0: + 1_loop_closure_illuminated_2023-01-23.npy + 1: + 1_loop_closure_illuminated_two_LiDARs_2023-01-23.npy + 2: + 2_human_static_illuminated_2023-01-23-001.npy + 3: + 2_human_static_no_illumination_2023-01-23-001.npy + 4: + 2_human_walking_illuminated_2023-01-23-001.npy + 5: + 2_human_walking_no_illumination_2023-01-23-001.npy + 6: + 2_static_artifacts_illuminated_2023-01-23-001.npy + 7: + 2_static_artifacts_no_illumination_2023-01-23-001.npy + 8: + 2_static_no_artifacts_illuminated_2023-01-23-001.npy + 9: + 2_static_no_artifacts_no_illumination_2023-01-23-001.npy + 10: + 3_smoke_artifacts_human_static_2023-01-23-001.npy + 11: + 3_smoke_human_walking_2023-01-23.npy + 12: + 3_smoke_no_artifacts_2023-01-23.npy + 13: + 3_smoke_robot_stationary_static_excess_smoke_2023-01-23.npy + test: + time: + 3.464538812637329 + indices: + [11772 11773 11774 ... 17655 17656 17657] + labels_exp_based: + [1 1 1 ... 1 1 1] + labels_manual_based: + [1 1 1 ... 1 1 1] + semi_targets: + [1 1 1 ... 1 1 1] + file_ids: + [7 8 0 ... 4 5 0] + frame_ids: + [ 98 1000 873 ... 674 541 2732] + scores: + [0.01374984 0.01219883 0.01543648 ... 0.01943305 0.01877283 0.02498569] + loss: + 0.03719014423372953 + file_names: + 0: + 1_loop_closure_illuminated_2023-01-23.npy + 1: + 1_loop_closure_illuminated_two_LiDARs_2023-01-23.npy + 2: + 2_human_static_illuminated_2023-01-23-001.npy + 3: + 2_human_static_no_illumination_2023-01-23-001.npy + 4: + 2_human_walking_illuminated_2023-01-23-001.npy + 5: + 2_human_walking_no_illumination_2023-01-23-001.npy + 6: + 2_static_artifacts_illuminated_2023-01-23-001.npy + 7: + 2_static_artifacts_no_illumination_2023-01-23-001.npy + 8: + 2_static_no_artifacts_illuminated_2023-01-23-001.npy + 9: + 2_static_no_artifacts_no_illumination_2023-01-23-001.npy + 10: + 3_smoke_artifacts_human_static_2023-01-23-001.npy + 11: + 3_smoke_human_walking_2023-01-23.npy + 12: + 3_smoke_no_artifacts_2023-01-23.npy + 13: + 3_smoke_robot_stationary_static_excess_smoke_2023-01-23.npy +k_fold: + True +k_fold_num: + 3 diff --git a/tools/gridsearch_new.py b/tools/gridsearch_new.py new file mode 100644 index 0000000..0b962da --- /dev/null +++ b/tools/gridsearch_new.py @@ -0,0 +1,295 @@ +#!/usr/bin/env python3 +""" +Grid-search launcher (2-GPU, fixed per-GPU batch sizes) + +• one job at a time per GPU +• watchdog heartbeats + error pushes + +""" + +import datetime +import itertools +import queue +import signal +import subprocess +import sys +import threading +import time +from os import environ +from pathlib import Path + +import GPUtil +import requests + +# ─────────────── Watchdog / host configuration ──────────────── +WATCHDOG_ROOT = "https://mt.the-k.at" +BASIC_AUTH = ("jan", "jruuI3GZ@9Rnt7") +HEARTBEAT_EVERY = 60 +HOSTNAME = "gpu-node-01" +OUT_FOLDER = Path("./grid_search") +DONE_FOLDER = OUT_FOLDER / "done" +FAILED_FOLDER = OUT_FOLDER / "failed" + +PING_URL, ERR_URL = f"{WATCHDOG_ROOT}/ping", f"{WATCHDOG_ROOT}/error" +HEADERS = {"Content-Type": "application/json"} + +# ───────────────────── Hyper-parameter grid ───────────────────── +PARAM_GRID = { + "network_name": ["subter_LeNet", "subter_efficient"], + "latent_space_dim": [32, 64, 128, 256, 512, 768, 1024], + "semi_label": [(0, 0), (50, 10), (500, 100)], # (normal, anomaly) +} + +# ────────────────────── FIXED PARAMETERS ────────────────────── +BATCH_SIZE = 128 # fixed batch size per GPU +EPOCHS_AE = 50 # autoencoder epochs +EPOCHS_DEEPSAD = 120 # Deep SAD epochs + +CMD_TEMPLATE = ( + "python src/main.py train subter {network_name} {exp_folder} " + "~/jan/data --k_fold True --k_fold_num 5 " + "--num_known_normal {num_known_normal} --num_known_outlier {num_known_anomaly} " + "--lr 0.0001 --n_epochs {deepsad_epochs} --lr_milestone 50 --batch_size {batch_size} " + "--weight_decay 0.5e-6 --latent_space_dim {latent_space_dim} " + "--pretrain True --ae_lr 0.0001 --ae_n_epochs {ae_epochs} --ae_batch_size {batch_size} " + "--ae_weight_decay 0.5e-3 --normal_class 0 --known_outlier_class 1 " + "--n_known_outlier_classes 1 --seed 3 --device cuda:0" +) + +# ──────────────── global bookkeeping / queues ────────────────── +STOP_EVT = threading.Event() +JOB_TABLE = [] # [{ …, status }] + + +# ─────────────────────── helper functions ────────────────────── +def post_json(url, payload): + try: + requests.post( + url, json=payload, headers=HEADERS, auth=BASIC_AUTH, timeout=5 + ).raise_for_status() + except Exception as exc: + print(f"[warn] POST {url} failed: {exc}", file=sys.stderr) + + +def gpu_stats(): + stats = [] + for g in GPUtil.getGPUs(): + stats.append( + { + "id": g.id, + "util": int(g.load * 100), + "mem": {"used": int(g.memoryUsed), "total": int(g.memoryTotal)}, + } + ) + return stats + + +def summarise_jobs(): + out = {"pending": 0, "running": 0, "ok": 0, "fail": 0} + for j in JOB_TABLE: + out[j["status"]] += 1 + return out + + +def heartbeater(): + while not STOP_EVT.wait(HEARTBEAT_EVERY): + post_json( + PING_URL, {"host": HOSTNAME, "gpu": gpu_stats(), "status": summarise_jobs()} + ) + + +# ───────────────────────── worker logic ──────────────────────── +def worker(device: str, q: "queue.Queue[dict]"): + while True: + job = q.get() + if job is None: + break # sentinel for shutdown + wait_until_allowed_hours() + run_job(job, device) + q.task_done() + + +def exp_folder_name(params): + num_norm, num_anom = params["semi_label"] + return ( + f"{params['network_name']}_" + f"latent{params['latent_space_dim']}_" + f"n{num_norm}_a{num_anom}" + ) + + +def move_exp_folder(exp_folder: Path, target_folder: Path): + """Move the experiment folder to the target folder.""" + destination_folder = target_folder / exp_folder.name + if destination_folder.exists(): + destination_folder.unlink() + target_folder.mkdir(parents=True, exist_ok=True) + exp_folder.rename(target_folder / exp_folder.name) + + +def already_done_set(): + if not DONE_FOLDER.exists(): + return set() + return set([f.name for f in DONE_FOLDER.iterdir() if f.is_dir()]) + + +def run_job(job: dict, device: str): + num_norm, num_anom = job["params"]["semi_label"] + exp_folder = job["exp_folder"] + exp_folder.mkdir(parents=True, exist_ok=True) + + cmd = CMD_TEMPLATE.format( + network_name=job["params"]["network_name"], + exp_folder=exp_folder.as_posix(), + num_known_normal=num_norm, + num_known_anomaly=num_anom, + latent_space_dim=job["params"]["latent_space_dim"], + batch_size=BATCH_SIZE, + ae_epochs=EPOCHS_AE, + deepsad_epochs=EPOCHS_DEEPSAD, + ) + + job.update({"device": device, "status": "running", "start": time.time()}) + post_json( + PING_URL, {"host": HOSTNAME, "msg": f"starting {exp_folder.name} on {device}"} + ) + print(f"EXEC [{device}] {cmd}", flush=True) + + try: + env = environ.copy() + env["CUDA_VISIBLE_DEVICES"] = device.split(":")[1] # "0" or "1" + res = subprocess.run( + cmd, shell=True, capture_output=True, text=True, env=env, check=False + ) + job.update({"end": time.time(), "returncode": res.returncode}) + job["status"] = "ok" if res.returncode == 0 else "fail" + if res.returncode == 0: + move_exp_folder(exp_folder, DONE_FOLDER) + else: + move_exp_folder(exp_folder, FAILED_FOLDER) + tail = (res.stderr or "no-stderr")[-500:] + post_json( + ERR_URL, + { + "host": HOSTNAME, + "msg": f"rc {res.returncode} {exp_folder.name} → {tail}", + }, + ) + print( + f"ERRR [{device}] rc {res.returncode} {exp_folder.name} → {tail}", + flush=True, + ) + except Exception as exc: + job.update({"end": time.time(), "status": "fail", "returncode": -999}) + move_exp_folder(exp_folder, FAILED_FOLDER) + post_json( + ERR_URL, + {"host": HOSTNAME, "msg": f'exception "{exc}" in {exp_folder.name}'}, + ) + + +# ────────────────────── graceful shutdown ────────────────────── +def shutdown(reason): + post_json(ERR_URL, {"host": HOSTNAME, "msg": f"run stopped: {reason}"}) + STOP_EVT.set() + sys.exit(0) + + +for sig in (signal.SIGINT, signal.SIGTERM): + signal.signal(sig, lambda s, f, sig=sig: shutdown(signal.Signals(sig).name)) + + +# ─────────────────────────── main() ──────────────────────────── +def wait_until_allowed_hours(): + """Sleep until the current time is between 22:00 and 04:00.""" + while True: + now = datetime.datetime.now() + hour = now.hour + # Allowed window: 22:00 (22) to 04:00 (4) + if hour >= 22 or hour < 4: + break + # Calculate seconds until 22:00 today or 22:00 tomorrow if after 4am + if hour < 22: + next_allowed = now.replace(hour=22, minute=0, second=0, microsecond=0) + else: + # After 4am but before 22:00, so next allowed is tonight + next_allowed = now.replace(hour=22, minute=0, second=0, microsecond=0) + if next_allowed < now: + next_allowed += datetime.timedelta(days=1) + wait_seconds = (next_allowed - now).total_seconds() + print(f"[info] Waiting until 22:00 to start new jobs ({int(wait_seconds/60)} min)...") + time.sleep(min(wait_seconds, 60*10)) # Sleep in chunks of max 10 minutes + +def main(): + threading.Thread(target=heartbeater, daemon=True).start() + + # two worker queues, one per GPU + q0, q1 = queue.Queue(), queue.Queue() + threading.Thread(target=worker, args=("cuda:0", q0), daemon=True).start() + threading.Thread(target=worker, args=("cuda:1", q1), daemon=True).start() + + # build full parameter combinations as jobs + keys = list(PARAM_GRID) + jobs = [] + for combo in itertools.product(*PARAM_GRID.values()): + params = dict(zip(keys, combo)) + exp_folder = OUT_FOLDER / exp_folder_name(params) + job = { + "params": params, + "exp_folder": exp_folder, + "status": "pending", + "start": None, + "end": None, + "returncode": None, + } + jobs.append(job) + + # Check which jobs are already done and mark them + done_exps = already_done_set() + #print(f"Already done jobs found: {done_exps}") + + for job in jobs: + if job["exp_folder"].name in done_exps: + job["status"] = "ok" + + # Print summary of job statuses before starting + n_done = sum(1 for job in jobs if job["status"] == "ok") + n_pending = sum(1 for job in jobs if job["status"] != "ok") + print(f"[info] {n_done} jobs already done, {n_pending} jobs pending.") + + # Add all jobs to global JOB_TABLE for bookkeeping + JOB_TABLE.extend(jobs) + + # Only enqueue jobs that are not already done + for job in jobs: + if job["status"] == "ok": + continue + # Hardcode device assignment + if job["params"]["network_name"] == "subter_LeNet": + q1.put(job) # cuda:1 + elif job["params"]["network_name"] == "subter_efficient": + q0.put(job) # cuda:0 + else: + # fallback: load-balance + (q0 if q0.qsize() <= q1.qsize() else q1).put(job) + + q0.join() + q1.join() # wait for all jobs to drain + q0.put(None) + q1.put(None) # terminate workers + STOP_EVT.set() + + post_json( + PING_URL, + { + "host": HOSTNAME, + "msg": "all jobs done", + "gpu": gpu_stats(), + "status": summarise_jobs(), + }, + ) + print("[info] grid search completed") + + +if __name__ == "__main__": + main() diff --git a/tools/plot_scripts/ae_elbow_lenet.py b/tools/plot_scripts/ae_elbow_lenet.py new file mode 100644 index 0000000..03090ac --- /dev/null +++ b/tools/plot_scripts/ae_elbow_lenet.py @@ -0,0 +1,339 @@ +import pickle +import shutil +import unittest +from datetime import datetime +from pathlib import Path + +import matplotlib.pyplot as plt +import numpy as np +from tabulate import tabulate + +# Configuration +results_folders = { + "LeNet": { + "path": Path( + "/home/fedex/mt/projects/thesis-kowalczyk-jan/Deep-SAD-PyTorch/test/DeepSAD/subter_ae_elbow_v2/" + ), + "batch_size": 256, + }, + "LeNet Efficient": { + "path": Path( + "/home/fedex/mt/projects/thesis-kowalczyk-jan/Deep-SAD-PyTorch/test/DeepSAD/subter_efficient_ae_elbow" + ), + "batch_size": 64, + }, +} +output_path = Path("/home/fedex/mt/plots/ae_elbow_lenet") +datetime_folder_name = datetime.now().strftime("%Y-%m-%d_%H-%M-%S") + +latest_folder_path = output_path / "latest" +archive_folder_path = output_path / "archive" +output_datetime_path = output_path / datetime_folder_name + +# Create output directories +output_path.mkdir(exist_ok=True, parents=True) +output_datetime_path.mkdir(exist_ok=True, parents=True) +latest_folder_path.mkdir(exist_ok=True, parents=True) +archive_folder_path.mkdir(exist_ok=True, parents=True) + + +def calculate_batch_mean_loss(scores, batch_size): + """Calculate mean loss over batches similar to the original testing code.""" + n_samples = len(scores) + n_batches = (n_samples + batch_size - 1) // batch_size + + batch_losses = [] + for i in range(0, n_samples, batch_size): + batch_scores = scores[i : i + batch_size] + batch_losses.append(np.mean(batch_scores)) + + return np.sum(batch_losses) / n_batches + + +def test_loss_calculation(results, batch_size): + """Test if our loss calculation matches the original implementation.""" + test = unittest.TestCase() + folds = results["ae_results"] + dim = results["dimension"] + + for fold_key in folds: + fold_data = folds[fold_key]["test"] + scores = np.array(fold_data["scores"]) + original_loss = fold_data["loss"] + calculated_loss = calculate_batch_mean_loss(scores, batch_size) + + try: + test.assertAlmostEqual( + original_loss, + calculated_loss, + places=5, + msg=f"Loss mismatch for dim={dim}, {fold_key}", + ) + except AssertionError as e: + print(f"Warning: {str(e)}") + print(f"Original: {original_loss:.6f}, Calculated: {calculated_loss:.6f}") + raise + + +def plot_loss_curve(dims, means, stds, title, color, output_path): + """Create and save a single loss curve plot.""" + plt.figure(figsize=(8, 5)) + plt.plot(dims, means, marker="o", color=color, label="Mean Test Loss") + plt.fill_between( + dims, + np.array(means) - np.array(stds), + np.array(means) + np.array(stds), + color=color, + alpha=0.2, + label="Std Dev", + ) + plt.xlabel("Latent Dimension") + plt.ylabel("Test Loss") + plt.title(title) + plt.legend() + plt.grid(True, alpha=0.3) + plt.xticks(dims) + plt.tight_layout() + plt.savefig(output_path, dpi=150, bbox_inches="tight") + plt.close() + + +def plot_multi_loss_curve(arch_results, title, output_path, colors=None): + """Create and save a loss curve plot with multiple architectures. + + Args: + arch_results: Dict of format {arch_name: (dims, means, stds)} + title: Plot title + output_path: Where to save the plot + colors: Optional dict of colors for each architecture + """ + plt.figure(figsize=(10, 6)) + + if colors is None: + colors = { + "LeNet": "blue", + "LeNet Asymmetric": "red", + } + + # Get unique dimensions across all architectures + all_dims = sorted( + set(dim for _, (dims, _, _) in arch_results.items() for dim in dims) + ) + + for arch_name, (dims, means, stds) in arch_results.items(): + color = colors.get(arch_name, "gray") + plt.plot(dims, means, marker="o", color=color, label=arch_name) + plt.fill_between( + dims, + np.array(means) - np.array(stds), + np.array(means) + np.array(stds), + color=color, + alpha=0.2, + ) + + plt.xlabel("Latent Dimension") + plt.ylabel("Test Loss") + plt.title(title) + plt.legend() + plt.grid(True, alpha=0.3) + plt.xticks(all_dims) # Set x-axis ticks to match data points + plt.tight_layout() + plt.savefig(output_path, dpi=150, bbox_inches="tight") + plt.close() + + +def evaluate_autoencoder_loss(): + """Main function to evaluate autoencoder loss across different latent dimensions.""" + # Results storage for each architecture + arch_results = { + name: {"dims": [], "normal": [], "anomaly": []} for name in results_folders + } + + # Process each architecture + for arch_name, config in results_folders.items(): + results_folder = config["path"] + batch_size = config["batch_size"] + result_files = sorted( + results_folder.glob("ae_elbow_results_subter_*_kfold.pkl") + ) + + dimensions = [] + normal_means = [] + normal_stds = [] + anomaly_means = [] + anomaly_stds = [] + + # Verify loss calculation + print( + f"\nVerifying loss calculation for {arch_name} (batch_size={batch_size})..." + ) + for result_file in result_files: + with open(result_file, "rb") as f: + results = pickle.load(f) + test_loss_calculation(results, batch_size) + print(f"Loss calculation verified successfully for {arch_name}!") + + # Process files for this architecture + for result_file in result_files: + with open(result_file, "rb") as f: + results = pickle.load(f) + dim = int(results["dimension"]) + folds = results["ae_results"] + + normal_fold_losses = [] + anomaly_fold_losses = [] + + all_scores = [] # Collect all scores for overall calculation + all_fold_scores = [] # Collect all fold scores for std calculation + + for fold_key in folds: + fold_data = folds[fold_key]["test"] + scores = np.array(fold_data["scores"]) + labels = np.array(fold_data["labels_exp_based"]) + + normal_scores = scores[labels == 1] + anomaly_scores = scores[labels == -1] + + normal_fold_losses.append( + calculate_batch_mean_loss(normal_scores, batch_size) + ) + anomaly_fold_losses.append( + calculate_batch_mean_loss(anomaly_scores, batch_size) + ) + + all_scores.append(scores) # Add scores to all_scores + all_fold_scores.append(fold_data["scores"]) # Add fold scores + + dimensions.append(dim) + normal_means.append(np.mean(normal_fold_losses)) + normal_stds.append(np.std(normal_fold_losses)) + anomaly_means.append(np.mean(anomaly_fold_losses)) + anomaly_stds.append(np.std(anomaly_fold_losses)) + + # Sort by dimension + sorted_data = sorted( + zip(dimensions, normal_means, normal_stds, anomaly_means, anomaly_stds) + ) + dims, n_means, n_stds, a_means, a_stds = zip(*sorted_data) + + # Store results for this architecture + arch_results[arch_name] = { + "dims": dims, + "normal": (dims, n_means, n_stds), + "anomaly": (dims, a_means, a_stds), + "overall": ( + dims, + [ + calculate_batch_mean_loss(scores, batch_size) + for scores in all_scores + ], # Use all scores + [ + np.std( + [ + calculate_batch_mean_loss(fold_scores, batch_size) + for fold_scores in fold_scores_list + ] + ) + for fold_scores_list in all_fold_scores + ], + ), + } + + # Create the three plots with all architectures + plot_multi_loss_curve( + {name: results["normal"] for name, results in arch_results.items()}, + "Normal Class Test Loss vs. Latent Dimension", + output_datetime_path / "ae_elbow_test_loss_normal.png", + ) + + plot_multi_loss_curve( + {name: results["anomaly"] for name, results in arch_results.items()}, + "Anomaly Class Test Loss vs. Latent Dimension", + output_datetime_path / "ae_elbow_test_loss_anomaly.png", + ) + + plot_multi_loss_curve( + {name: results["overall"] for name, results in arch_results.items()}, + "Overall Test Loss vs. Latent Dimension", + output_datetime_path / "ae_elbow_test_loss_overall.png", + ) + + +def print_loss_comparison(results_folders): + """Print comparison tables of original vs calculated losses for each architecture.""" + print("\nLoss Comparison Tables") + print("=" * 80) + + for arch_name, config in results_folders.items(): + results_folder = config["path"] + batch_size = config["batch_size"] + result_files = sorted( + results_folder.glob("ae_elbow_results_subter_*_kfold.pkl") + ) + + # Prepare table data + table_data = [] + headers = ["Dimension", "Original", "Calculated", "Diff"] + + for result_file in result_files: + with open(result_file, "rb") as f: + results = pickle.load(f) + + dim = int(results["dimension"]) + folds = results["ae_results"] + + # Calculate mean original loss across folds + orig_losses = [] + calc_losses = [] + for fold_key in folds: + fold_data = folds[fold_key]["test"] + orig_losses.append(fold_data["loss"]) + calc_losses.append( + calculate_batch_mean_loss(np.array(fold_data["scores"]), batch_size) + ) + + orig_mean = np.mean(orig_losses) + calc_mean = np.mean(calc_losses) + diff = abs(orig_mean - calc_mean) + + table_data.append([dim, orig_mean, calc_mean, diff]) + + # Sort by dimension + table_data.sort(key=lambda x: x[0]) + + print(f"\n{arch_name}:") + print( + tabulate( + table_data, + headers=headers, + floatfmt=".6f", + tablefmt="pipe", + numalign="right", + ) + ) + + print("\n" + "=" * 80) + + +if __name__ == "__main__": + # Print loss comparisons for all architectures + print_loss_comparison(results_folders) + + # Run main analysis + evaluate_autoencoder_loss() + + # Archive management + # Delete current latest folder + shutil.rmtree(latest_folder_path, ignore_errors=True) + latest_folder_path.mkdir(exist_ok=True, parents=True) + + # Copy contents to latest folder + for file in output_datetime_path.iterdir(): + shutil.copy2(file, latest_folder_path) + + # Copy this script for reference + shutil.copy2(__file__, output_datetime_path) + shutil.copy2(__file__, latest_folder_path) + + # Move output to archive + shutil.move(output_datetime_path, archive_folder_path) diff --git a/tools/plot_scripts/background_ml_illustrations.py b/tools/plot_scripts/background_ml_illustrations.py new file mode 100644 index 0000000..63c6365 --- /dev/null +++ b/tools/plot_scripts/background_ml_illustrations.py @@ -0,0 +1,223 @@ +""" +Downloads the cats_vs_dogs dataset, then generates four PNGs: + - supervised_grid.png + - unsupervised_clusters.png + - semi_supervised_classification.png + - semi_supervised_clustering.png +""" + +import os +import random + +import matplotlib.pyplot as plt +import numpy as np +import tensorflow as tf +import tensorflow_datasets as tfds +from sklearn.cluster import KMeans +from sklearn.decomposition import PCA +from sklearn.linear_model import LogisticRegression +from sklearn.semi_supervised import LabelSpreading + +# ----------------------------------------------------------------------------- +# CONFIGURATION +# ----------------------------------------------------------------------------- +NUM_SUPERVISED = 16 +GRID_ROWS = 4 +GRID_COLS = 4 + +UNSUP_SAMPLES = 200 + +# how many labeled points to “seed” semi-sup methods +N_LABELED_CLASS = 10 # for classification demo +N_SEEDS_PER_CLASS = 3 # for clustering demo + +OUTPUT_DIR = "outputs" + + +# ----------------------------------------------------------------------------- +# UTILITIES +# ----------------------------------------------------------------------------- +def ensure_dir(path): + os.makedirs(path, exist_ok=True) + + +# ----------------------------------------------------------------------------- +# 1) Supervised grid +# ----------------------------------------------------------------------------- +def plot_supervised_grid(ds, info, num, rows, cols, outpath): + plt.figure(figsize=(cols * 2, rows * 2)) + for i, (img, lbl) in enumerate(ds.take(num)): + ax = plt.subplot(rows, cols, i + 1) + ax.imshow(img.numpy().astype("uint8")) + ax.axis("off") + cname = info.features["label"].int2str(lbl.numpy()) + ax.set_title(cname, fontsize=9) + plt.tight_layout() + plt.savefig(outpath, dpi=150) + plt.close() + print(f"✔ Saved supervised grid → {outpath}") + + +# ----------------------------------------------------------------------------- +# 2) Unsupervised clustering (PCA + KMeans) +# ----------------------------------------------------------------------------- +def plot_unsupervised_clusters(ds, outpath): + imgs = [] + for img, _ in ds.take(UNSUP_SAMPLES): + arr = ( + tf.image.resize(img, (64, 64)).numpy().astype("float32").mean(axis=2) + ) # resize and grayscale to speed up + imgs.append(arr.ravel() / 255.0) + X = np.stack(imgs) + pca = PCA(n_components=2, random_state=0) + X2 = pca.fit_transform(X) + + km = KMeans(n_clusters=2, random_state=0) + clusters = km.fit_predict(X2) + + plt.figure(figsize=(6, 6)) + plt.scatter(X2[:, 0], X2[:, 1], c=clusters, s=15, alpha=0.6) + plt.title("Unsupervised: PCA + KMeans") + plt.xlabel("PCA 1") + plt.ylabel("PCA 2") + plt.tight_layout() + plt.savefig(outpath, dpi=150) + plt.close() + print(f"✔ Saved unsupervised clusters → {outpath}") + + return X2, clusters # return for reuse + + +# ----------------------------------------------------------------------------- +# 3) Semi‐supervised classification demo +# ----------------------------------------------------------------------------- +def plot_semi_supervised_classification(X2, y_true, outpath): + n = X2.shape[0] + all_idx = list(range(n)) + labeled_idx = random.sample(all_idx, N_LABELED_CLASS) + unlabeled_idx = list(set(all_idx) - set(labeled_idx)) + + # pure supervised + clf = LogisticRegression().fit(X2[labeled_idx], y_true[labeled_idx]) + + # semi‐supervised + y_train = np.full(n, -1, dtype=int) + y_train[labeled_idx] = y_true[labeled_idx] + ls = LabelSpreading().fit(X2, y_train) + + # grid for decision boundary + x_min, x_max = X2[:, 0].min() - 1, X2[:, 0].max() + 1 + y_min, y_max = X2[:, 1].min() - 1, X2[:, 1].max() + 1 + xx, yy = np.meshgrid(np.linspace(x_min, x_max, 200), np.linspace(y_min, y_max, 200)) + grid = np.c_[xx.ravel(), yy.ravel()] + pred_sup = clf.predict(grid).reshape(xx.shape) + pred_semi = ls.predict(grid).reshape(xx.shape) + + fig, axes = plt.subplots(1, 2, figsize=(12, 5)) + for ax, Z, title in zip( + axes, [pred_sup, pred_semi], ["Supervised only", "Semi-supervised"] + ): + ax.contourf(xx, yy, Z, alpha=0.3) + ax.scatter(X2[unlabeled_idx, 0], X2[unlabeled_idx, 1], s=20, alpha=0.4) + ax.scatter( + X2[labeled_idx, 0], + X2[labeled_idx, 1], + c=y_true[labeled_idx], + s=80, + edgecolor="k", + ) + ax.set_title(title) + ax.set_xlabel("PCA 1") + ax.set_ylabel("PCA 2") + plt.tight_layout() + plt.savefig(outpath, dpi=150) + plt.close() + print(f"✔ Saved semi-supervised classification → {outpath}") + + +# ----------------------------------------------------------------------------- +# 4) Semi‐supervised clustering (seeded KMeans) +# ----------------------------------------------------------------------------- +def plot_semi_supervised_clustering(X2, y_true, outpath): + # pick a few seeds per class + cats = np.where(y_true == 0)[0] + dogs = np.where(y_true == 1)[0] + seeds = list(np.random.choice(cats, N_SEEDS_PER_CLASS, replace=False)) + list( + np.random.choice(dogs, N_SEEDS_PER_CLASS, replace=False) + ) + + # pure KMeans + km1 = KMeans(n_clusters=2, random_state=0).fit(X2) + lab1 = km1.labels_ + + # seeded: init centers from seed means + ctr0 = X2[seeds[:N_SEEDS_PER_CLASS]].mean(axis=0) + ctr1 = X2[seeds[N_SEEDS_PER_CLASS:]].mean(axis=0) + km2 = KMeans( + n_clusters=2, init=np.vstack([ctr0, ctr1]), n_init=1, random_state=0 + ).fit(X2) + lab2 = km2.labels_ + + fig, axes = plt.subplots(1, 2, figsize=(12, 5)) + for ax, labels, title in zip(axes, [lab1, lab2], ["Pure KMeans", "Seeded KMeans"]): + ax.scatter(X2[:, 0], X2[:, 1], c=labels, s=20, alpha=0.6) + ax.scatter( + X2[seeds, 0], + X2[seeds, 1], + c=y_true[seeds], + edgecolor="k", + marker="x", + s=100, + linewidths=2, + ) + ax.set_title(title) + ax.set_xlabel("PCA 1") + ax.set_ylabel("PCA 2") + plt.tight_layout() + plt.savefig(outpath, dpi=150) + plt.close() + print(f"✔ Saved semi-supervised clustering → {outpath}") + + +# ----------------------------------------------------------------------------- +# MAIN +# ----------------------------------------------------------------------------- +if __name__ == "__main__": + ensure_dir(OUTPUT_DIR) + + # load + print("▶ Loading cats_vs_dogs...") + ds, info = tfds.load( + "cats_vs_dogs", split="train", with_info=True, as_supervised=True + ) + ds = ds.shuffle(1000, reshuffle_each_iteration=False).cache() + + # supervised + plot_supervised_grid( + ds, + info, + NUM_SUPERVISED, + GRID_ROWS, + GRID_COLS, + os.path.join(OUTPUT_DIR, "supervised_grid.png"), + ) + + # unsupervised + # also returns X2 for downstream demos + X2, _ = plot_unsupervised_clusters( + ds, os.path.join(OUTPUT_DIR, "unsupervised_clusters.png") + ) + + # collect true labels for that same subset + # (we need a y_true array for semi-sup demos) + y_true = np.array([lbl.numpy() for _, lbl in ds.take(UNSUP_SAMPLES)]) + + # semi-supervised classification + plot_semi_supervised_classification( + X2, y_true, os.path.join(OUTPUT_DIR, "semi_supervised_classification.png") + ) + + # semi-supervised clustering + plot_semi_supervised_clustering( + X2, y_true, os.path.join(OUTPUT_DIR, "semi_supervised_clustering.png") + ) diff --git a/tools/plot_scripts/background_ml_semisupervised.py b/tools/plot_scripts/background_ml_semisupervised.py new file mode 100644 index 0000000..9349050 --- /dev/null +++ b/tools/plot_scripts/background_ml_semisupervised.py @@ -0,0 +1,274 @@ +""" +Downloads the cats_vs_dogs dataset, extracts deep features using MobileNetV2, +then generates two clustering comparison illustrations using two different embedding pipelines: + - PCA followed by t-SNE (saved as: semi_supervised_clustering_tsne.png) + - PCA followed by UMAP (saved as: semi_supervised_clustering_umap.png) + +Each illustration compares: + - Unsupervised clustering using the deep embedding + KMeans + - Semi-supervised clustering using Label Spreading with a few labeled seeds + +This script saves outputs in a datetime folder and also copies the latest outputs +to a "latest" folder. All versions of the outputs and scripts are archived. +""" + +import random +import shutil +from datetime import datetime +from pathlib import Path + +import matplotlib.pyplot as plt +import numpy as np +import tensorflow as tf +import tensorflow_datasets as tfds +import umap +from scipy.optimize import linear_sum_assignment +from sklearn.cluster import KMeans +from sklearn.decomposition import PCA +from sklearn.manifold import TSNE +from sklearn.semi_supervised import LabelSpreading + +# ----------------------------------------------------------------------------- +# CONFIGURATION +# ----------------------------------------------------------------------------- +UNSUP_SAMPLES = 200 # number of samples to use for the demo +N_LABELED_CLASS = 20 # number of labeled seeds for the semi-supervised approach + +output_path = Path("/home/fedex/mt/plots/background_ml_semisupervised") +datetime_folder_name = datetime.now().strftime("%Y-%m-%d_%H-%M-%S") +latest_folder_path = output_path / "latest" +archive_folder_path = output_path / "archive" +output_datetime_path = output_path / datetime_folder_name + + +# ----------------------------------------------------------------------------- +# UTILITIES +# ----------------------------------------------------------------------------- +def ensure_dir(directory: Path): + directory.mkdir(exist_ok=True, parents=True) + + +def cluster_accuracy(y_true, y_pred): + """ + Compute clustering accuracy by determining the optimal mapping + between predicted clusters and true labels using the Hungarian algorithm. + """ + y_true = y_true.astype(np.int64) + y_pred = y_pred.astype(np.int64) + labels = np.unique(y_true) + clusters = np.unique(y_pred) + contingency = np.zeros((labels.size, clusters.size), dtype=np.int64) + for i, label in enumerate(labels): + for j, cluster in enumerate(clusters): + contingency[i, j] = np.sum((y_true == label) & (y_pred == cluster)) + row_ind, col_ind = linear_sum_assignment(-contingency) + accuracy = contingency[row_ind, col_ind].sum() / y_true.size + return accuracy + + +def plot_clustering_comparison_embedding(embedding, y_true, outpath, method_name=""): + """ + Given a 2D data embedding (e.g., from PCA+t-SNE or PCA+UMAP), this function: + - Performs unsupervised clustering with KMeans. + - Performs semi-supervised clustering with Label Spreading using a few labeled seeds. + - Computes accuracy via the Hungarian algorithm. + - Plots the decision boundaries from both methods overlaid with the true labels. + - Annotates the plot with the accuracy results. + + The 'method_name' is used in the plot title to indicate which embedding is used. + """ + n = embedding.shape[0] + all_idx = list(range(n)) + labeled_idx = random.sample(all_idx, N_LABELED_CLASS) + + # Unsupervised clustering using KMeans on all embedded data + km = KMeans(n_clusters=2, random_state=0).fit(embedding) + unsup_pred = km.predict(embedding) + unsup_accuracy = cluster_accuracy(y_true, unsup_pred) + + # Create a grid over the space for decision boundaries + x_min, x_max = embedding[:, 0].min() - 1, embedding[:, 0].max() + 1 + y_min, y_max = embedding[:, 1].min() - 1, embedding[:, 1].max() + 1 + xx, yy = np.meshgrid(np.linspace(x_min, x_max, 200), np.linspace(y_min, y_max, 200)) + grid = np.c_[xx.ravel(), yy.ravel()] + pred_unsup = km.predict(grid).reshape(xx.shape) + + # Semi-supervised clustering using Label Spreading with labeled seeds + y_train = np.full(n, -1, dtype=int) + y_train[labeled_idx] = y_true[labeled_idx] + ls = LabelSpreading().fit(embedding, y_train) + semi_pred = ls.predict(embedding) + semi_accuracy = cluster_accuracy(y_true, semi_pred) + pred_semi = ls.predict(grid).reshape(xx.shape) + + cmap = plt.cm.coolwarm + + fig, axes = plt.subplots(1, 2, figsize=(12, 6)) + + # Unsupervised plot: + ax = axes[0] + ax.contourf(xx, yy, pred_unsup, alpha=0.2, cmap=cmap) + sc1 = ax.scatter( + embedding[:, 0], + embedding[:, 1], + c=y_true, + cmap=cmap, + s=30, + alpha=0.8, + edgecolor="k", + ) + ax.set_title( + f"{method_name}\nUnsupervised (KMeans) - Acc: {unsup_accuracy:.2f}", fontsize=10 + ) + ax.set_xlabel("Dim 1") + ax.set_ylabel("Dim 2") + handles = [] + for cl in np.unique(y_true): + handles.append( + plt.Line2D( + [], + [], + marker="o", + linestyle="", + color=cmap(cl / (np.max(y_true) + 1)), + label=f"Class {cl}", + markersize=6, + ) + ) + ax.legend(handles=handles, loc="upper right") + + # Semi-supervised plot: + ax = axes[1] + ax.contourf(xx, yy, pred_semi, alpha=0.2, cmap=cmap) + sc2 = ax.scatter( + embedding[:, 0], + embedding[:, 1], + c=y_true, + cmap=cmap, + s=30, + alpha=0.8, + edgecolor="none", + ) + sc3 = ax.scatter( + embedding[labeled_idx, 0], + embedding[labeled_idx, 1], + c=y_true[labeled_idx], + cmap=cmap, + s=80, + edgecolor="k", + marker="o", + label="Labeled Seeds", + ) + ax.set_title( + f"{method_name}\nSemi-Supervised (Label Spreading) - Acc: {semi_accuracy:.2f}", + fontsize=10, + ) + ax.set_xlabel("Dim 1") + ax.set_ylabel("Dim 2") + handles = [] + for cl in np.unique(y_true): + handles.append( + plt.Line2D( + [], + [], + marker="o", + linestyle="", + color=cmap(cl / (np.max(y_true) + 1)), + label=f"Class {cl}", + markersize=6, + ) + ) + handles.append( + plt.Line2D( + [], + [], + marker="o", + linestyle="", + color="black", + label="Labeled Seed", + markersize=8, + ) + ) + ax.legend(handles=handles, loc="upper right") + + plt.tight_layout() + plt.savefig(outpath, dpi=150) + plt.close() + print(f"✔ Saved clustering comparison illustration → {outpath}") + print(f"Unsupervised Accuracy: {unsup_accuracy:.2f}") + print(f"Semi-Supervised Accuracy: {semi_accuracy:.2f}") + + +# ----------------------------------------------------------------------------- +# MAIN WITH PRE-TRAINED CNN FEATURE EXTRACTION +# ----------------------------------------------------------------------------- +if __name__ == "__main__": + # Create output directories + ensure_dir(output_path) + ensure_dir(output_datetime_path) + ensure_dir(latest_folder_path) + ensure_dir(archive_folder_path) + + print("▶ Loading cats_vs_dogs dataset...") + ds, info = tfds.load( + "cats_vs_dogs", split="train", with_info=True, as_supervised=True + ) + ds = ds.shuffle(1000, reshuffle_each_iteration=False).cache() + + # Load a pre-trained CNN (MobileNetV2) for feature extraction. + cnn_model = tf.keras.applications.MobileNetV2( + include_top=False, weights="imagenet", pooling="avg", input_shape=(224, 224, 3) + ) + + # Extract deep features for all samples. + features_list = [] + labels = [] + for img, lbl in ds.take(UNSUP_SAMPLES): + # Resize to 224x224 and keep 3 channels. + img_resized = tf.image.resize(img, (224, 224)) + # Preprocess the image for MobileNetV2. + img_preprocessed = tf.keras.applications.mobilenet_v2.preprocess_input( + img_resized + ) + # Expand dims for batch, run through CNN, then squeeze. + features = cnn_model(tf.expand_dims(img_preprocessed, axis=0)) + features = features.numpy().squeeze() + features_list.append(features) + labels.append(lbl.numpy()) + X = np.stack(features_list) + y_true = np.array(labels) + + # First, apply PCA to reduce dimensionality to 50 + pca_50 = PCA(n_components=50, random_state=0).fit_transform(X) + + # Then compute embedding with t-SNE + from sklearn.manifold import TSNE + + X_tsne = TSNE(n_components=2, random_state=0, init="pca").fit_transform(pca_50) + outfile_tsne = output_datetime_path / "semi_supervised_clustering_tsne.png" + plot_clustering_comparison_embedding( + X_tsne, y_true, outfile_tsne, "CNN + PCA + t-SNE" + ) + + # Then compute embedding with UMAP + X_umap = umap.UMAP(n_components=2, random_state=0).fit_transform(pca_50) + outfile_umap = output_datetime_path / "semi_supervised_clustering_umap.png" + plot_clustering_comparison_embedding( + X_umap, y_true, outfile_umap, "CNN + PCA + UMAP" + ) + + # ----------------------------------------------------------------------------- + # Update the 'latest' results folder: remove previous and copy current outputs + # ----------------------------------------------------------------------------- + shutil.rmtree(latest_folder_path, ignore_errors=True) + ensure_dir(latest_folder_path) + for file in output_datetime_path.iterdir(): + shutil.copy2(file, latest_folder_path) + + # Copy this script to preserve the code used for the outputs + script_path = Path(__file__) + shutil.copy2(script_path, output_datetime_path) + shutil.copy2(script_path, latest_folder_path) + + # Archive the outputs + shutil.move(output_datetime_path, archive_folder_path) diff --git a/tools/plot_scripts/background_ml_supervised.py b/tools/plot_scripts/background_ml_supervised.py new file mode 100644 index 0000000..997e877 --- /dev/null +++ b/tools/plot_scripts/background_ml_supervised.py @@ -0,0 +1,99 @@ +""" +Downloads the cats_vs_dogs dataset, then generates a supervised grid image: + - supervised_grid.png + +This script saves outputs in a datetime folder and also copies the latest outputs to a "latest" folder. +All versions of the outputs and scripts are archived. +""" + +from datetime import datetime +from pathlib import Path + +import matplotlib.pyplot as plt +import tensorflow_datasets as tfds + +# ----------------------------------------------------------------------------- +# CONFIGURATION +# ----------------------------------------------------------------------------- +# Number of supervised samples and grid dimensions +NUM_SUPERVISED = 16 +GRID_ROWS = 4 +GRID_COLS = 4 + +# Output directories for saving plots and scripts +output_path = Path("/home/fedex/mt/plots/background_ml_supervised") +datetime_folder_name = datetime.now().strftime("%Y-%m-%d_%H-%M-%S") + +latest_folder_path = output_path / "latest" +archive_folder_path = output_path / "archive" +output_datetime_path = output_path / datetime_folder_name + + +# ----------------------------------------------------------------------------- +# UTILITIES +# ----------------------------------------------------------------------------- +def ensure_dir(directory: Path): + directory.mkdir(exist_ok=True, parents=True) + + +# Create required output directories +ensure_dir(output_path) +ensure_dir(output_datetime_path) +ensure_dir(latest_folder_path) +ensure_dir(archive_folder_path) + + +# ----------------------------------------------------------------------------- +# Supervised grid plot +# ----------------------------------------------------------------------------- +def plot_supervised_grid(ds, info, num, rows, cols, outpath): + """ + Plots a grid of images from the dataset with their corresponding labels. + """ + plt.figure(figsize=(cols * 2, rows * 2)) + for i, (img, lbl) in enumerate(ds.take(num)): + ax = plt.subplot(rows, cols, i + 1) + ax.imshow(img.numpy().astype("uint8")) + ax.axis("off") + cname = info.features["label"].int2str(lbl.numpy()) + ax.set_title(cname, fontsize=9) + plt.tight_layout() + plt.savefig(outpath, dpi=150) + plt.close() + print(f"✔ Saved supervised grid → {outpath}") + + +# ----------------------------------------------------------------------------- +# MAIN +# ----------------------------------------------------------------------------- +if __name__ == "__main__": + # Download and prepare the dataset + print("▶ Loading cats_vs_dogs dataset...") + ds, info = tfds.load( + "cats_vs_dogs", split="train", with_info=True, as_supervised=True + ) + ds = ds.shuffle(1000, reshuffle_each_iteration=False).cache() + + # Generate the supervised grid image + supervised_outfile = output_datetime_path / "supervised_grid.png" + plot_supervised_grid( + ds, info, NUM_SUPERVISED, GRID_ROWS, GRID_COLS, supervised_outfile + ) + + # ----------------------------------------------------------------------------- + # Update the 'latest' results folder: remove previous and copy current outputs + # ----------------------------------------------------------------------------- + import shutil + + shutil.rmtree(latest_folder_path, ignore_errors=True) + ensure_dir(latest_folder_path) + for file in output_datetime_path.iterdir(): + shutil.copy2(file, latest_folder_path) + + # Copy this script to the output folder and to the latest folder to preserve the used code + script_path = Path(__file__) + shutil.copy2(script_path, output_datetime_path) + shutil.copy2(script_path, latest_folder_path) + + # Move the output datetime folder to the archive folder for record keeping + shutil.move(output_datetime_path, archive_folder_path) diff --git a/tools/plot_scripts/background_ml_unsupervised.py b/tools/plot_scripts/background_ml_unsupervised.py new file mode 100644 index 0000000..f91bf43 --- /dev/null +++ b/tools/plot_scripts/background_ml_unsupervised.py @@ -0,0 +1,105 @@ +""" +Downloads the cats_vs_dogs dataset, then generates an unsupervised clusters image: + - unsupervised_clusters.png + +This script saves outputs in a datetime folder and also copies the latest outputs to a "latest" folder. +All versions of the outputs and scripts are archived. +""" + +import shutil +from datetime import datetime +from pathlib import Path + +import matplotlib.pyplot as plt +import numpy as np +import tensorflow as tf +import tensorflow_datasets as tfds +from sklearn.cluster import KMeans +from sklearn.decomposition import PCA + +# ----------------------------------------------------------------------------- +# CONFIGURATION +# ----------------------------------------------------------------------------- +UNSUP_SAMPLES = 200 + +output_path = Path("/home/fedex/mt/plots/background_ml_unsupervised") +datetime_folder_name = datetime.now().strftime("%Y-%m-%d_%H-%M-%S") + +latest_folder_path = output_path / "latest" +archive_folder_path = output_path / "archive" +output_datetime_path = output_path / datetime_folder_name + + +# ----------------------------------------------------------------------------- +# UTILITIES +# ----------------------------------------------------------------------------- +def ensure_dir(directory: Path): + directory.mkdir(exist_ok=True, parents=True) + + +# Create required output directories +ensure_dir(output_path) +ensure_dir(output_datetime_path) +ensure_dir(latest_folder_path) +ensure_dir(archive_folder_path) + + +# ----------------------------------------------------------------------------- +# Unsupervised Clustering Plot (PCA + KMeans) +# ----------------------------------------------------------------------------- +def plot_unsupervised_clusters(ds, outpath): + """ + Processes a subset of images from the dataset, reduces their dimensionality with PCA, + applies KMeans clustering, and saves a scatterplot of the clusters. + """ + imgs = [] + for img, _ in ds.take(UNSUP_SAMPLES): + # resize to 64x64, convert to grayscale by averaging channels + arr = tf.image.resize(img, (64, 64)).numpy().astype("float32").mean(axis=2) + imgs.append(arr.ravel() / 255.0) + X = np.stack(imgs) + pca = PCA(n_components=2, random_state=0) + X2 = pca.fit_transform(X) + + km = KMeans(n_clusters=2, random_state=0) + clusters = km.fit_predict(X2) + + plt.figure(figsize=(6, 6)) + plt.scatter(X2[:, 0], X2[:, 1], c=clusters, s=15, alpha=0.6) + plt.title("Unsupervised: PCA + KMeans") + plt.xlabel("PCA 1") + plt.ylabel("PCA 2") + plt.tight_layout() + plt.savefig(outpath, dpi=150) + plt.close() + print(f"✔ Saved unsupervised clusters → {outpath}") + + +# ----------------------------------------------------------------------------- +# MAIN +# ----------------------------------------------------------------------------- +if __name__ == "__main__": + # Load and prepare the dataset + print("▶ Loading cats_vs_dogs dataset...") + ds, _ = tfds.load("cats_vs_dogs", split="train", with_info=True, as_supervised=True) + ds = ds.shuffle(1000, reshuffle_each_iteration=False).cache() + + # Generate the unsupervised clusters image + unsupervised_outfile = output_datetime_path / "unsupervised_clusters.png" + plot_unsupervised_clusters(ds, unsupervised_outfile) + + # ----------------------------------------------------------------------------- + # Update the 'latest' results folder: remove previous and copy current outputs + # ----------------------------------------------------------------------------- + shutil.rmtree(latest_folder_path, ignore_errors=True) + ensure_dir(latest_folder_path) + for file in output_datetime_path.iterdir(): + shutil.copy2(file, latest_folder_path) + + # Copy this script to the output folder and to the latest folder to preserve the used code + script_path = Path(__file__) + shutil.copy2(script_path, output_datetime_path) + shutil.copy2(script_path, latest_folder_path) + + # Move the output datetime folder to the archive folder for record keeping + shutil.move(output_datetime_path, archive_folder_path) diff --git a/tools/plot_scripts/data_anomalies_timeline.py b/tools/plot_scripts/data_anomalies_timeline.py new file mode 100644 index 0000000..e0f4c05 --- /dev/null +++ b/tools/plot_scripts/data_anomalies_timeline.py @@ -0,0 +1,255 @@ +import json +import pickle +import shutil +from datetime import datetime +from pathlib import Path + +import matplotlib.pyplot as plt +import numpy as np + +# define data paths +all_data_path = Path("/home/fedex/mt/data/subter") +output_path = Path("/home/fedex/mt/plots/data_anomalies_timeline") +datetime_folder_name = datetime.now().strftime("%Y-%m-%d_%H-%M-%S") + +latest_folder_path = output_path / "latest" +archive_folder_path = output_path / "archive" +output_datetime_path = output_path / datetime_folder_name +cache_path = output_path + +# if output does not exist, create it +output_path.mkdir(exist_ok=True, parents=True) +output_datetime_path.mkdir(exist_ok=True, parents=True) +latest_folder_path.mkdir(exist_ok=True, parents=True) +archive_folder_path.mkdir(exist_ok=True, parents=True) + +data_resolution = 32 * 2048 + +# find all bag files and sort them correctly by name +normal_experiment_paths, anomaly_experiment_paths = [], [] +for bag_file_path in all_data_path.iterdir(): + if bag_file_path.suffix != ".bag": + continue + if "smoke" in bag_file_path.name: + anomaly_experiment_paths.append(bag_file_path) + else: + normal_experiment_paths.append(bag_file_path) + +# Load manually labeled frames +with open( + cache_path / "manually_labeled_anomaly_frames.json", "r" +) as frame_borders_file: + manually_labeled_anomaly_frames_json = json.load(frame_borders_file) + if not manually_labeled_anomaly_frames_json: + print("No manually labeled anomaly frames found. Exiting...") + exit(1) + manually_labeled_anomaly_frames = {} + try: + for file in manually_labeled_anomaly_frames_json["files"]: + if file["filename"] not in ( + p.with_suffix(".npy").name for p in anomaly_experiment_paths + ): + print( + f"File {file['filename']} from manually labeled frames not found in anomaly experiments. Exiting..." + ) + exit(1) + manually_labeled_anomaly_frames[file["filename"]] = ( + file["semi_target_begin_frame"], + file["semi_target_end_frame"], + ) + except KeyError as e: + print(f"Missing key in manually labeled frames JSON: {e}") + exit(1) + + +def plot_combined_timeline( + normal_experiment_paths, anomaly_experiment_paths, title, num_bins=50 +): + """Plot both missing points and near-sensor measurements over normalized timeline""" + # Sort experiments by filesize first (to match original processing order) + normal_experiment_paths = sorted( + normal_experiment_paths, key=lambda path: path.stat().st_size + ) + anomaly_experiment_paths = sorted( + anomaly_experiment_paths, key=lambda path: path.stat().st_size + ) + + # Get largest normal experiment and moving anomaly experiments + baseline_path = normal_experiment_paths[-3] # largest normal experiment + moving_exp_indices = [ + i + for i, path in enumerate(anomaly_experiment_paths) + if "stationary" not in path.name + ] + moving_anomaly_paths = [anomaly_experiment_paths[i] for i in moving_exp_indices] + + # Load missing points data + missing_points_cache = Path(cache_path / "missing_points.pkl") + if not missing_points_cache.exists(): + print("Missing points cache not found!") + return + + # Load near-sensor data (using 500mm threshold) + near_sensor_cache = Path(cache_path / "particles_near_sensor_counts_500.pkl") + if not near_sensor_cache.exists(): + print("Near-sensor measurements cache not found!") + return + + # Load both cached datasets + with open(missing_points_cache, "rb") as file: + missing_points_normal, missing_points_anomaly = pickle.load(file) + with open(near_sensor_cache, "rb") as file: + near_sensor_normal, near_sensor_anomaly = pickle.load(file) + + # Get data for baseline and moving experiments + missing_data = [missing_points_normal[-3]] + [ + missing_points_anomaly[i] for i in moving_exp_indices + ] + near_sensor_data = [near_sensor_normal[-3]] + [ + near_sensor_anomaly[i] for i in moving_exp_indices + ] + all_paths = [baseline_path] + moving_anomaly_paths + + # Create figure with two y-axes and dynamic layout + fig, ax1 = plt.subplots(figsize=(12, 6), constrained_layout=True) + ax2 = ax1.twinx() + + # Color schemes - gray for baseline, colors for anomaly experiments + experiment_colors = ["#808080"] + ["#1f77b4", "#ff7f0e", "#2ca02c"] # gray + colors + + # First create custom legend handles for the metrics + from matplotlib.lines import Line2D + + metric_legend = [ + Line2D([0], [0], color="gray", linestyle="-", label="Missing Points"), + Line2D([0], [0], color="gray", linestyle="--", label="Near-Sensor (<0.5m)"), + Line2D([0], [0], color="gray", linestyle=":", label="Manually Labeled Borders"), + ] + + # Plot each experiment's data + for i, (missing_exp, near_sensor_exp) in enumerate( + zip(missing_data, near_sensor_data) + ): + # Get experiment name without the full path + exp_name = all_paths[i].stem + # Shorten experiment name if needed + exp_name = exp_name.replace("experiment_smoke_", "exp_") + + # Convert both to percentages + missing_pct = np.array(missing_exp) / data_resolution * 100 + near_sensor_pct = np.array(near_sensor_exp) / data_resolution * 100 + + # Create normalized timeline bins for both + exp_len = len(missing_pct) + bins = np.linspace(0, exp_len - 1, num_bins) + missing_binned = np.zeros(num_bins) + near_sensor_binned = np.zeros(num_bins) + + # Bin both datasets + for bin_idx in range(num_bins): + if bin_idx == num_bins - 1: + start_idx = int(bins[bin_idx]) + end_idx = exp_len + else: + start_idx = int(bins[bin_idx]) + end_idx = int(bins[bin_idx + 1]) + + missing_binned[bin_idx] = np.mean(missing_pct[start_idx:end_idx]) + near_sensor_binned[bin_idx] = np.mean(near_sensor_pct[start_idx:end_idx]) + + # Plot both metrics with same color but different line styles + color = experiment_colors[i] + ax1.plot( + range(num_bins), + missing_binned, + color=color, + linestyle="-", + alpha=0.6, + label=exp_name, + ) + ax2.plot( + range(num_bins), near_sensor_binned, color=color, linestyle="--", alpha=0.6 + ) + + # Add vertical lines for manually labeled frames if available + if all_paths[i].with_suffix(".npy").name in manually_labeled_anomaly_frames: + begin_frame, end_frame = manually_labeled_anomaly_frames[ + all_paths[i].with_suffix(".npy").name + ] + # Convert frame numbers to normalized timeline positions + begin_pos = (begin_frame / exp_len) * (num_bins - 1) + end_pos = (end_frame / exp_len) * (num_bins - 1) + + # Add vertical lines with matching color and loose dotting + ax1.axvline( + x=begin_pos, + color=color, + linestyle=":", + alpha=0.6, + ) + ax1.axvline( + x=end_pos, + color=color, + linestyle=":", + alpha=0.6, + ) + + # Customize axes + ax1.set_xlabel("Normalized Timeline") + ax1.set_xticks(np.linspace(0, num_bins - 1, 5)) + ax1.set_xticklabels([f"{x:.0f}%" for x in np.linspace(0, 100, 5)]) + + ax1.set_ylabel("Missing Points (%)") + ax2.set_ylabel("Points with <0.5m Range (%)") + + plt.title(title) + + # Create legends without fixed positions + # First get all lines and labels for experiments + lines1, labels1 = ax1.get_legend_handles_labels() + + # Combine both legends into one + all_handles = ( + lines1 + + [Line2D([0], [0], color="gray", linestyle="-", label="", alpha=0)] + + metric_legend + ) + all_labels = ( + labels1 + + [""] + + ["Missing Points", "Points Near Sensor (<0.5m)", "Manually Labeled Borders"] + ) + + # Create single legend in top right corner with consistent margins + fig.legend(all_handles, all_labels, loc="upper right", borderaxespad=4.8) + + plt.grid(True, alpha=0.3) + + # Save figure letting matplotlib handle the layout + plt.savefig(output_datetime_path / "combined_anomalies_timeline.png", dpi=150) + plt.close() + + +# Generate the combined timeline plot +plot_combined_timeline( + normal_experiment_paths, + anomaly_experiment_paths, + "Lidar Degradation Indicators Throughout Experiments\n(Baseline and Moving Anomaly Experiments)", +) + +# delete current latest folder +shutil.rmtree(latest_folder_path, ignore_errors=True) + +# create new latest folder +latest_folder_path.mkdir(exist_ok=True, parents=True) + +# copy contents of output folder to the latest folder +for file in output_datetime_path.iterdir(): + shutil.copy2(file, latest_folder_path) + +# copy this python script to preserve the code used +shutil.copy2(__file__, output_datetime_path) +shutil.copy2(__file__, latest_folder_path) + +# move output date folder to archive +shutil.move(output_datetime_path, archive_folder_path) diff --git a/tools/plot_scripts/data_icp_rsme.py b/tools/plot_scripts/data_icp_rsme.py new file mode 100644 index 0000000..cafb7fd --- /dev/null +++ b/tools/plot_scripts/data_icp_rsme.py @@ -0,0 +1,178 @@ +import pickle +import shutil +from datetime import datetime +from pathlib import Path + +import matplotlib.pyplot as plt +import numpy as np +import open3d as o3d +from pointcloudset import Dataset +from pointcloudset.io.pointcloud.open3d import to_open3d +from rich.progress import track + +# define data path containing the bag files +all_data_path = Path("/home/fedex/mt/data/subter") + +output_path = Path("/home/fedex/mt/plots/data_icp_rmse") +datetime_folder_name = datetime.now().strftime("%Y-%m-%d_%H-%M-%S") + +latest_folder_path = output_path / "latest" +archive_folder_path = output_path / "archive" +output_datetime_path = output_path / datetime_folder_name + +# if output does not exist, create it +output_path.mkdir(exist_ok=True, parents=True) +output_datetime_path.mkdir(exist_ok=True, parents=True) +latest_folder_path.mkdir(exist_ok=True, parents=True) +archive_folder_path.mkdir(exist_ok=True, parents=True) + +# Add cache folder +cache_folder = output_path / "cache" +cache_folder.mkdir(exist_ok=True, parents=True) + + +def get_cache_path(experiment_path: Path) -> Path: + """Convert experiment bag path to cache pkl path""" + return cache_folder / f"{experiment_path.stem}.pkl" + + +# find all bag files and sort them correctly by name (experiments with smoke in the name are anomalies) +normal_experiment_paths, anomaly_experiment_paths = [], [] +for bag_file_path in all_data_path.iterdir(): + if bag_file_path.suffix != ".bag": + continue + if "smoke" in bag_file_path.name: + anomaly_experiment_paths.append(bag_file_path) + else: + normal_experiment_paths.append(bag_file_path) + +# sort anomaly and normal experiments by filesize, ascending +anomaly_experiment_paths.sort(key=lambda path: path.stat().st_size) +normal_experiment_paths.sort(key=lambda path: path.stat().st_size) + + +def calculate_icp_rmse(experiment_path): + """Calculate ICP RMSE between consecutive frames in an experiment""" + cache_path = get_cache_path(experiment_path) + + # Check cache first + if cache_path.exists(): + with open(cache_path, "rb") as file: + return pickle.load(file) + + dataset = Dataset.from_file(experiment_path, topic="/ouster/points") + rmse_values = [] + + # Convert iterator to list for progress tracking + pointclouds = list(dataset) + + # Get consecutive pairs of point clouds with progress bar + for i in track( + range(len(pointclouds) - 1), + description=f"Processing {experiment_path.name}", + total=len(pointclouds) - 1, + ): + # Get consecutive point clouds and convert to Open3D format + source = to_open3d(pointclouds[i]) + target = to_open3d(pointclouds[i + 1]) + + # Apply point-to-point ICP (much faster than point-to-plane) + result = o3d.pipelines.registration.registration_icp( + source, + target, + max_correspondence_distance=0.2, # 20cm max correspondence + init=np.identity(4), # Identity matrix as initial transformation + estimation_method=o3d.pipelines.registration.TransformationEstimationPointToPoint(), + criteria=o3d.pipelines.registration.ICPConvergenceCriteria( + max_iteration=50 + ), + ) + + rmse_values.append(result.inlier_rmse) + + # Cache the results + with open(cache_path, "wb") as file: + pickle.dump(rmse_values, file, protocol=pickle.HIGHEST_PROTOCOL) + + return rmse_values + + +def plot_statistical_comparison(normal_paths, anomaly_paths, output_path): + """Create statistical comparison plots between normal and anomalous experiments""" + # Collect all RMSE values + normal_rmse = [] + anomaly_rmse = [] + + print("Loading cached RMSE values...") + for path in normal_paths: + normal_rmse.extend(calculate_icp_rmse(path)) + for path in anomaly_paths: + anomaly_rmse.extend(calculate_icp_rmse(path)) + + # Convert to numpy arrays + normal_rmse = np.array(normal_rmse) + anomaly_rmse = np.array(anomaly_rmse) + + # Create figure with two subplots side by sidnte + fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(15, 6)) + + # Box plot + data = [normal_rmse, anomaly_rmse] + ax1.boxplot(data, labels=["Normal", "Anomalous"]) + ax1.set_ylabel("ICP RMSE (meters)") + ax1.set_title("Box Plot Comparison") + ax1.grid(True, alpha=0.3) + + # Violin plot + ax2.violinplot(data) + ax2.set_xticks([1, 2]) + ax2.set_xticklabels(["Normal", "Anomalous"]) + ax2.set_ylabel("ICP RMSE (meters)") + ax2.set_title("Violin Plot Comparison") + ax2.grid(True, alpha=0.3) + + plt.suptitle( + "ICP RMSE Statistical Comparison: Normal vs Anomalous Experiments", fontsize=14 + ) + plt.tight_layout() + + # Save both linear and log scale versions + plt.savefig(output_path / "icp_rmse_distribution_linear.png", dpi=150) + + # Log scale version + ax1.set_yscale("log") + ax2.set_yscale("log") + plt.savefig(output_path / "icp_rmse_distribution_log.png", dpi=150) + plt.close() + + # Print some basic statistics + print("\nBasic Statistics:") + print( + f"Normal experiments - mean: {np.mean(normal_rmse):.4f}, std: {np.std(normal_rmse):.4f}" + ) + print( + f"Anomaly experiments - mean: {np.mean(anomaly_rmse):.4f}, std: {np.std(anomaly_rmse):.4f}" + ) + + +# Replace all plotting code with just this: +plot_statistical_comparison( + normal_experiment_paths, anomaly_experiment_paths, output_datetime_path +) + +# delete current latest folder +shutil.rmtree(latest_folder_path, ignore_errors=True) + +# create new latest folder +latest_folder_path.mkdir(exist_ok=True, parents=True) + +# copy contents of output folder to the latest folder +for file in output_datetime_path.iterdir(): + shutil.copy2(file, latest_folder_path) + +# copy this python script to preserve the code used +shutil.copy2(__file__, output_datetime_path) +shutil.copy2(__file__, latest_folder_path) + +# move output date folder to archive +shutil.move(output_datetime_path, archive_folder_path) diff --git a/tools/plot_scripts/data_missing_points_anomalies.py b/tools/plot_scripts/data_missing_points_anomalies.py new file mode 100644 index 0000000..b746bf7 --- /dev/null +++ b/tools/plot_scripts/data_missing_points_anomalies.py @@ -0,0 +1,134 @@ +import pickle +import shutil +from datetime import datetime +from pathlib import Path + +import matplotlib.pyplot as plt +import numpy as np +from pointcloudset import Dataset + +# define data paths +all_data_path = Path("/home/fedex/mt/data/subter") +output_path = Path("/home/fedex/mt/plots/data_missing_points_anomalies") +datetime_folder_name = datetime.now().strftime("%Y-%m-%d_%H-%M-%S") + +latest_folder_path = output_path / "latest" +archive_folder_path = output_path / "archive" +output_datetime_path = output_path / datetime_folder_name + +# if output does not exist, create it +output_path.mkdir(exist_ok=True, parents=True) +output_datetime_path.mkdir(exist_ok=True, parents=True) +latest_folder_path.mkdir(exist_ok=True, parents=True) +archive_folder_path.mkdir(exist_ok=True, parents=True) + +data_resolution = 32 * 2048 + +# find all bag files and sort them correctly by name +normal_experiment_paths, anomaly_experiment_paths = [], [] +for bag_file_path in all_data_path.iterdir(): + if bag_file_path.suffix != ".bag": + continue + if "smoke" in bag_file_path.name: + anomaly_experiment_paths.append(bag_file_path) + else: + normal_experiment_paths.append(bag_file_path) + + +def plot_timeline_comparison(anomaly_experiment_paths, title, num_bins=50): + """Plot missing points percentage over normalized timeline for moving anomaly experiments""" + # Sort experiments by filesize first (to match original processing order) + anomaly_experiment_paths = sorted( + anomaly_experiment_paths, key=lambda path: path.stat().st_size + ) + + # Filter out stationary experiments + moving_exp_indices = [ + i + for i, path in enumerate(anomaly_experiment_paths) + if "stationary" not in path.name + ] + moving_anomaly_paths = [anomaly_experiment_paths[i] for i in moving_exp_indices] + + # Try to load cached data from original script's location + cache_path = Path("/home/fedex/mt/plots/data_missing_points/missing_points.pkl") + if not cache_path.exists(): + print("No cached data found. Please run the original script first.") + return + + with open(cache_path, "rb") as file: + _, missing_points_anomaly = pickle.load(file) + + # Get data for moving experiments only (using original indices) + moving_anomaly_data = [missing_points_anomaly[i] for i in moving_exp_indices] + + # Create figure + plt.figure(figsize=(12, 6)) + + # Plot each experiment's timeline + for i, exp_data in enumerate(moving_anomaly_data): + # Convert to percentage + percentages = np.array(exp_data) / data_resolution * 100 + + # Create normalized timeline bins + exp_len = len(percentages) + bins = np.linspace(0, exp_len - 1, num_bins) + binned_data = np.zeros(num_bins) + + # Bin the data + for bin_idx in range(num_bins): + if bin_idx == num_bins - 1: + start_idx = int(bins[bin_idx]) + end_idx = exp_len + else: + start_idx = int(bins[bin_idx]) + end_idx = int(bins[bin_idx + 1]) + + binned_data[bin_idx] = np.mean(percentages[start_idx:end_idx]) + + # Plot with slight transparency to show overlaps + plt.plot( + range(num_bins), + binned_data, + alpha=0.6, + label=f"Experiment {moving_anomaly_paths[i].stem}", + ) + + plt.title(title) + plt.xlabel("Normalized Timeline") + # Add percentage ticks on x-axis + plt.xticks( + np.linspace(0, num_bins - 1, 5), [f"{x:.0f}%" for x in np.linspace(0, 100, 5)] + ) + plt.ylabel("Missing Points (%)") + plt.grid(True, alpha=0.3) + plt.legend() + plt.tight_layout() + + # Save the plot + plt.savefig(output_datetime_path / "missing_points_timeline.png", dpi=150) + plt.close() + + +# Generate the timeline comparison plot +plot_timeline_comparison( + anomaly_experiment_paths, + "Missing Lidar Measurements Over Time\n(Moving Anomaly Experiments Only)", +) + +# delete current latest folder +shutil.rmtree(latest_folder_path, ignore_errors=True) + +# create new latest folder +latest_folder_path.mkdir(exist_ok=True, parents=True) + +# copy contents of output folder to the latest folder +for file in output_datetime_path.iterdir(): + shutil.copy2(file, latest_folder_path) + +# copy this python script to preserve the code used +shutil.copy2(__file__, output_datetime_path) +shutil.copy2(__file__, latest_folder_path) + +# move output date folder to archive +shutil.move(output_datetime_path, archive_folder_path) diff --git a/tools/plot_scripts/data_particles_near_sensor_anomalies.py b/tools/plot_scripts/data_particles_near_sensor_anomalies.py new file mode 100644 index 0000000..225adc6 --- /dev/null +++ b/tools/plot_scripts/data_particles_near_sensor_anomalies.py @@ -0,0 +1,149 @@ +import pickle +import shutil +from datetime import datetime +from pathlib import Path + +import matplotlib.pyplot as plt +import numpy as np +from pointcloudset import Dataset + +# define data paths +all_data_path = Path("/home/fedex/mt/data/subter") +output_path = Path("/home/fedex/mt/plots/data_particles_near_sensor_anomalies") +datetime_folder_name = datetime.now().strftime("%Y-%m-%d_%H-%M-%S") + +latest_folder_path = output_path / "latest" +archive_folder_path = output_path / "archive" +output_datetime_path = output_path / datetime_folder_name + +# if output does not exist, create it +output_path.mkdir(exist_ok=True, parents=True) +output_datetime_path.mkdir(exist_ok=True, parents=True) +latest_folder_path.mkdir(exist_ok=True, parents=True) +archive_folder_path.mkdir(exist_ok=True, parents=True) + +data_resolution = 32 * 2048 + +# find all bag files and sort them correctly by name +normal_experiment_paths, anomaly_experiment_paths = [], [] +for bag_file_path in all_data_path.iterdir(): + if bag_file_path.suffix != ".bag": + continue + if "smoke" in bag_file_path.name: + anomaly_experiment_paths.append(bag_file_path) + else: + normal_experiment_paths.append(bag_file_path) + + +def plot_timeline_comparison( + anomaly_experiment_paths, title, range_threshold, num_bins=50 +): + """Plot near-sensor measurements percentage over normalized timeline for moving anomaly experiments""" + # Sort experiments by filesize first (to match original processing order) + anomaly_experiment_paths = sorted( + anomaly_experiment_paths, key=lambda path: path.stat().st_size + ) + + # Filter out stationary experiments + moving_exp_indices = [ + i + for i, path in enumerate(anomaly_experiment_paths) + if "stationary" not in path.name + ] + moving_anomaly_paths = [anomaly_experiment_paths[i] for i in moving_exp_indices] + + # Try to load cached data + cache_path = ( + Path("/home/fedex/mt/plots/data_particles_near_sensor") + / f"particles_near_sensor_counts_{range_threshold}.pkl" + ) + if not cache_path.exists(): + print( + f"No cached data found for range threshold {range_threshold}. Please run the original script first." + ) + return + + with open(cache_path, "rb") as file: + _, particles_near_sensor_anomaly = pickle.load(file) + + # Get data for moving experiments only (using original indices) + moving_anomaly_data = [particles_near_sensor_anomaly[i] for i in moving_exp_indices] + + # Create figure + plt.figure(figsize=(12, 6)) + + # Plot each experiment's timeline + for i, exp_data in enumerate(moving_anomaly_data): + # Convert to percentage + percentages = np.array(exp_data) / data_resolution * 100 + + # Create normalized timeline bins + exp_len = len(percentages) + bins = np.linspace(0, exp_len - 1, num_bins) + binned_data = np.zeros(num_bins) + + # Bin the data + for bin_idx in range(num_bins): + if bin_idx == num_bins - 1: + start_idx = int(bins[bin_idx]) + end_idx = exp_len + else: + start_idx = int(bins[bin_idx]) + end_idx = int(bins[bin_idx + 1]) + + binned_data[bin_idx] = np.mean(percentages[start_idx:end_idx]) + + # Plot with slight transparency to show overlaps + plt.plot( + range(num_bins), + binned_data, + alpha=0.6, + label=f"Experiment {moving_anomaly_paths[i].stem}", + ) + + plt.title(f"{title}\n(Range Threshold: {range_threshold / 1000:.1f}m)") + plt.xlabel("Normalized Timeline") + # Add percentage ticks on x-axis + plt.xticks( + np.linspace(0, num_bins - 1, 5), [f"{x:.0f}%" for x in np.linspace(0, 100, 5)] + ) + plt.ylabel("Near-Sensor Measurements (%)") + plt.grid(True, alpha=0.3) + plt.legend() + plt.tight_layout() + + # Save the plot + plt.savefig( + output_datetime_path + / f"near_sensor_measurements_timeline_{range_threshold}.png", + dpi=150, + ) + plt.close() + + +# Generate timeline comparison plots for each range threshold +range_thresholds = [500, 750, 1000, 1250, 1500] +for rt in range_thresholds: + print(f"Processing range threshold {rt}...") + plot_timeline_comparison( + anomaly_experiment_paths, + "Near-Sensor Lidar Measurements Over Time\n(Moving Anomaly Experiments Only)", + rt, + ) + +# delete current latest folder +shutil.rmtree(latest_folder_path, ignore_errors=True) + +# create new latest folder +latest_folder_path.mkdir(exist_ok=True, parents=True) + +# copy contents of output folder to the latest folder +for file in output_datetime_path.iterdir(): + shutil.copy2(file, latest_folder_path) + +# copy this python script to preserve the code used +shutil.copy2(__file__, output_datetime_path) +shutil.copy2(__file__, latest_folder_path) + +# move output date folder to archive +shutil.move(output_datetime_path, archive_folder_path) diff --git a/tools/print_param_counts.py b/tools/print_param_counts.py new file mode 100644 index 0000000..5e2cc93 --- /dev/null +++ b/tools/print_param_counts.py @@ -0,0 +1,172 @@ +import pickle +import re +from pathlib import Path + +import matplotlib.pyplot as plt +import numpy as np + +results_path = Path("/home/fedex/mt/results/done") + +efficient_paths, lenet_paths = dict(), dict() + +for result_folder in results_path.iterdir(): + if not result_folder.is_dir(): + continue + if "n0_a0" not in result_folder.name: + continue + + if "efficient" in result_folder.name: + match = re.search(r"subter_efficient_latent(\d+)_n0_a0", result_folder.name) + if match: + latent_value = int(match.group(1)) + else: + raise ValueError("Could not extract latent value from string using regex") + efficient_paths[latent_value] = result_folder + if "LeNet" in result_folder.name: + match = re.search(r"subter_LeNet_latent(\d+)_n0_a0", result_folder.name) + if match: + latent_value = int(match.group(1)) + else: + raise ValueError("Could not extract latent value from string using regex") + lenet_paths[latent_value] = result_folder + +"test" +"exp_basedmanual_based" +"auc" + +results = dict() +print("Efficient paths:") +latent_dims = set() +for latent_value, path in sorted(efficient_paths.items()): + latent_dims.add(latent_value) + print(f"Latent {latent_value}: {path}") + for kfold_idx in range(5): + for method in ["deepsad", "ocsvm", "isoforest"]: + with open(path / f"results_{method}_{kfold_idx}.pkl", "rb") as f: + results.setdefault("efficient", {}).setdefault( + latent_value, {} + ).setdefault(method, {})[kfold_idx] = pickle.load(f) + +print("\nLeNet paths:") +for latent_value, path in sorted(lenet_paths.items()): + print(f"Latent {latent_value}: {path}") + for kfold_idx in range(5): + for method in ["deepsad", "ocsvm", "isoforest"]: + with open(path / f"results_{method}_{kfold_idx}.pkl", "rb") as f: + results.setdefault("lenet", {}).setdefault(latent_value, {}).setdefault( + method, {} + )[kfold_idx] = pickle.load(f) + + +for latent_dim in latent_dims: + for network in ["efficient", "lenet"]: + for method in ["deepsad", "ocsvm", "isoforest"]: + if ( + latent_dim not in results[network] + or method not in results[network][latent_dim] + ): + raise ValueError( + f"Missing results for {network} with latent {latent_dim} and method {method}" + ) + if method == "deepsad": + results[network][latent_dim][method]["mean_auc_exp"] = np.mean( + [ + results[network][latent_dim][method][kfold_idx]["test"][ + "exp_based" + ]["auc"] + for kfold_idx in range(5) + ] + ) + results[network][latent_dim][method]["mean_auc_man"] = np.mean( + [ + results[network][latent_dim][method][kfold_idx]["test"][ + "manual_based" + ]["auc"] + for kfold_idx in range(5) + ] + ) + else: + results[network][latent_dim][method]["mean_auc_exp"] = np.mean( + [ + results[network][latent_dim][method][kfold_idx][ + "test_auc_exp_based" + ] + for kfold_idx in range(5) + ] + ) + results[network][latent_dim][method]["mean_auc_man"] = np.mean( + [ + results[network][latent_dim][method][kfold_idx][ + "test_auc_manual_based" + ] + for kfold_idx in range(5) + ] + ) + + +def plot_auc_comparison(results, evaluation_type): + """Plot AUC comparison across methods, architectures and latent dimensions. + + Args: + results: Dict containing all results + evaluation_type: Either 'exp' or 'man' for experiment or manual based evaluation + """ + plt.figure(figsize=(10, 6)) + + # Define markers for methods + markers = {"deepsad": "o", "ocsvm": "s", "isoforest": "^"} + + # Define base colors for architectures and method-specific lightness + base_colors = { + "efficient": "#1f77b4", # blue + "lenet": "#d62728", # red + } + # Different alpha values for methods + method_alphas = { + "deepsad": 1.0, # full intensity + "ocsvm": 0.7, # slightly lighter + "isoforest": 0.4, # even lighter + } + + # Get all latent dimensions + latent_dims = sorted(list(results["efficient"].keys())) + + # Plot each method and architecture combination + for network in ["efficient", "lenet"]: + for method in ["deepsad", "ocsvm", "isoforest"]: + auc_values = [ + results[network][dim][method][f"mean_auc_{evaluation_type}"] + for dim in latent_dims + ] + + plt.plot( + latent_dims, + auc_values, + marker=markers[method], + color=base_colors[network], + alpha=method_alphas[method], + linestyle="-" if network == "efficient" else "--", + label=f"{network.capitalize()} {method.upper()}", + markersize=8, + ) + + plt.xlabel("Latent Dimension") + plt.ylabel("Mean AUC") + if evaluation_type == "exp": + plt.title("AUC Comparison (Experiment Based Evaluation Labels)") + else: + plt.title("AUC Comparison (Manual Based Evaluation Labels)") + plt.grid(True, alpha=0.3) + plt.legend(bbox_to_anchor=(1.05, 1), loc="upper left") + plt.xticks(latent_dims) + plt.tight_layout() + return plt.gcf() + + +# Create and save both plots +for eval_type in ["exp", "man"]: + fig = plot_auc_comparison(results, eval_type) + fig.savefig( + f"auc_comp/auc_comparison_{eval_type}_based.png", dpi=300, bbox_inches="tight" + ) + plt.close(fig)