Compare commits

...

6 Commits

Author SHA1 Message Date
Jan Kowalczyk
ef311d862e tools, lockfile, deps 2025-08-13 14:17:12 +02:00
Jan Kowalczyk
cd4dc583e8 devenv 2025-08-13 14:16:14 +02:00
Jan Kowalczyk
a936b754cb plot tool updates 2025-08-13 14:15:38 +02:00
Jan Kowalczyk
37ac637c9c setup chapter progress 2025-08-13 14:15:25 +02:00
Jan Kowalczyk
8a5adc6360 tool updates 2025-08-13 14:15:15 +02:00
Jan Kowalczyk
44da3c2bd9 added tools, updated deps 2025-08-13 14:14:54 +02:00
30 changed files with 4691 additions and 114 deletions

3
.gitignore vendored
View File

@@ -23,8 +23,5 @@
*.nav *.nav
*.snm *.snm
*.mp4 *.mp4
*.png
*.jpg
*.jpeg
*.npy *.npy

View File

@@ -0,0 +1,103 @@
{
"nodes": {
"devenv": {
"locked": {
"dir": "src/modules",
"lastModified": 1754730435,
"owner": "cachix",
"repo": "devenv",
"rev": "d1388a093a7225c2abe8c244109c5a4490de4077",
"type": "github"
},
"original": {
"dir": "src/modules",
"owner": "cachix",
"repo": "devenv",
"type": "github"
}
},
"flake-compat": {
"flake": false,
"locked": {
"lastModified": 1747046372,
"owner": "edolstra",
"repo": "flake-compat",
"rev": "9100a0f413b0c601e0533d1d94ffd501ce2e7885",
"type": "github"
},
"original": {
"owner": "edolstra",
"repo": "flake-compat",
"type": "github"
}
},
"git-hooks": {
"inputs": {
"flake-compat": "flake-compat",
"gitignore": "gitignore",
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1754416808,
"owner": "cachix",
"repo": "git-hooks.nix",
"rev": "9c52372878df6911f9afc1e2a1391f55e4dfc864",
"type": "github"
},
"original": {
"owner": "cachix",
"repo": "git-hooks.nix",
"type": "github"
}
},
"gitignore": {
"inputs": {
"nixpkgs": [
"git-hooks",
"nixpkgs"
]
},
"locked": {
"lastModified": 1709087332,
"owner": "hercules-ci",
"repo": "gitignore.nix",
"rev": "637db329424fd7e46cf4185293b9cc8c88c95394",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "gitignore.nix",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1754299112,
"owner": "cachix",
"repo": "devenv-nixpkgs",
"rev": "16c21c9f5c6fb978466e91182a248dd8ca1112ac",
"type": "github"
},
"original": {
"owner": "cachix",
"ref": "rolling",
"repo": "devenv-nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"devenv": "devenv",
"git-hooks": "git-hooks",
"nixpkgs": "nixpkgs",
"pre-commit-hooks": [
"git-hooks"
]
}
}
},
"root": "root",
"version": 7
}

View File

@@ -0,0 +1,24 @@
{ pkgs, ... }:
let
native_dependencies = with pkgs.python312Packages; [
torch-bin
torchvision-bin
aggdraw # for visualtorch
];
tools = with pkgs; [
ruff
];
in
{
packages = native_dependencies ++ tools;
languages.python = {
enable = true;
package = pkgs.python312;
uv = {
enable = true;
sync.enable = true;
};
venv.enable = true;
};
}

View File

@@ -0,0 +1,17 @@
# yaml-language-server: $schema=https://devenv.sh/devenv.schema.json
inputs:
nixpkgs:
url: github:cachix/devenv-nixpkgs/rolling
allowUnfree: true
cudaSupport: true
# If you're using non-OSS software, you can set allowUnfree to true.
# allowUnfree: true
# If you're willing to use a package that's vulnerable
# permittedInsecurePackages:
# - "openssl-1.1.1w"
# If you have more than one devenv you can merge them
#imports:
# - ./backend

View File

@@ -12,6 +12,8 @@ dependencies = [
"kiwisolver>=1.4.8", "kiwisolver>=1.4.8",
"matplotlib>=3.10.3", "matplotlib>=3.10.3",
"numpy>=2.3.1", "numpy>=2.3.1",
"onnx>=1.18.0",
"onnxscript>=0.3.2",
"pandas>=2.3.0", "pandas>=2.3.0",
"pillow>=11.2.1", "pillow>=11.2.1",
"pyparsing>=3.2.3", "pyparsing>=3.2.3",
@@ -23,6 +25,7 @@ dependencies = [
"six>=1.17.0", "six>=1.17.0",
"torch-receptive-field", "torch-receptive-field",
"torchscan>=0.1.1", "torchscan>=0.1.1",
"visualtorch>=0.2.4",
] ]
[tool.uv.sources] [tool.uv.sources]

View File

@@ -1,11 +1,15 @@
from pathlib import Path
import torch import torch
import torch.onnx import torch.onnx
from networks.mnist_LeNet import MNIST_LeNet_Autoencoder
from networks.subter_LeNet import SubTer_LeNet_Autoencoder
from networks.subter_LeNet_rf import SubTer_Efficient_AE
def export_model_to_onnx(model, filepath, input_shape=(1, 1, 28, 28)): def export_model_to_onnx(model, filepath):
model.eval() # Set the model to evaluation mode model.eval() # Set the model to evaluation mode
dummy_input = torch.randn(input_shape) # Create a dummy input tensor dummy_input = torch.randn(model.input_dim) # Create a dummy input tensor
torch.onnx.export( torch.onnx.export(
model, # model being run model, # model being run
dummy_input, # model input (or a tuple for multiple inputs) dummy_input, # model input (or a tuple for multiple inputs)
@@ -23,13 +27,17 @@ def export_model_to_onnx(model, filepath, input_shape=(1, 1, 28, 28)):
if __name__ == "__main__": if __name__ == "__main__":
# Initialize the autoencoder model output_folder_path = Path("./onnx_models")
autoencoder = MNIST_LeNet_Autoencoder(rep_dim=32) output_folder_path.mkdir(parents=True, exist_ok=True)
# Define the file path where the ONNX model will be saved models_to_visualize = [
onnx_file_path = "mnist_lenet_autoencoder.onnx" (
SubTer_LeNet_Autoencoder(rep_dim=32),
output_folder_path / "subter_lenet_ae.onnx",
),
(SubTer_Efficient_AE(rep_dim=32), output_folder_path / "subter_ef_ae.onnx"),
]
# Export the model for model, output_path in models_to_visualize:
export_model_to_onnx(autoencoder, onnx_file_path) export_model_to_onnx(model, output_path)
print(f"Model has been exported to {output_path}")
print(f"Model has been exported to {onnx_file_path}")

998
Deep-SAD-PyTorch/uv.lock generated Normal file
View File

@@ -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" },
]

View File

@@ -525,7 +525,7 @@ The pre-training results are used in two more key ways. First, the encoder weigh
In the main training step, DeepSAD's network is trained using SGD backpropagation. The unlabeled training data is used with the goal to minimize an data-encompassing hypersphere. Since one of the pre-conditions of training was the significant prevelance of normal data over anomalies in the training set, normal samples collectively cluster more tightly around the centroid, while the rarer anomalous samples do not contribute as significantly to the optimization, resulting in them staying further from the hypersphere center. The labeled data includes binary class labels signifying their status as either normal or anomalous samples. Labeled anomalies are pushed away from the center by defining their optimization target as maximizing the distance between them and $\mathbf{c}$. Labeled normal samples are treated similar to unlabeled samples with the difference that DeepSAD includes a hyperparameter capable of controling the proportion with which labeled and unlabeled data contribute to the overall optimization. The resulting network has learned to map normal data samples closer to $\mathbf{c}$ in the latent space and anomalies further away. In the main training step, DeepSAD's network is trained using SGD backpropagation. The unlabeled training data is used with the goal to minimize an data-encompassing hypersphere. Since one of the pre-conditions of training was the significant prevelance of normal data over anomalies in the training set, normal samples collectively cluster more tightly around the centroid, while the rarer anomalous samples do not contribute as significantly to the optimization, resulting in them staying further from the hypersphere center. The labeled data includes binary class labels signifying their status as either normal or anomalous samples. Labeled anomalies are pushed away from the center by defining their optimization target as maximizing the distance between them and $\mathbf{c}$. Labeled normal samples are treated similar to unlabeled samples with the difference that DeepSAD includes a hyperparameter capable of controling the proportion with which labeled and unlabeled data contribute to the overall optimization. The resulting network has learned to map normal data samples closer to $\mathbf{c}$ in the latent space and anomalies further away.
\fig{deepsad_procedure}{diagrams/deepsad_procedure}{(WORK IN PROGRESS) Depiction of DeepSAD's training procedure, including data flows and tweakable hyperparameters.} \fig{deepsad_procedure}{diagrams/deepsad_procedure/deepsad_procedure}{(WORK IN PROGRESS) Depiction of DeepSAD's training procedure, including data flows and tweakable hyperparameters.}
\threadtodo \threadtodo
{how to use the trained network?} {how to use the trained network?}
@@ -988,8 +988,20 @@ We pass instances of this \texttt{Dataset} to PyTorchs \texttt{DataLoader}, e
%\todo[inline]{semi-supervised trainingn labels, k_fold, inference dataloader} %\todo[inline]{semi-supervised trainingn labels, k_fold, inference dataloader}
DeepSAD supports both unsupervised and semi-supervised training by optionally incorporating a small number of labeled samples. To control this, our custom PyTorch \texttt{Dataset} accepts two integer parameters, \texttt{num\_labelled\_normal} and \texttt{num\_labelled\_anomalous}, which specify how many samples of each class should retain their labels during training. All other samples are assigned a label of 0 (``unknown'') and treated as unlabeled.
When using semi-supervised mode, we begin with the manually-defined evaluation labels. We then randomly un-label (set to 0) enough samples of each class until exactly \texttt{num\_labelled\_normal} normals and \texttt{num\_labelled\_anomalous} anomalies remain labeled. This mechanism allows us to systematically compare unsupervised mode, where \texttt{num\_labelled\_normal} = \texttt{num\_labelled\_anomalous} = 0, and Semi-supervised modes with varying label budgets.
To obtain robust performance estimates on our relatively small dataset, we implement $k$-fold cross-validation. A single integer parameter, \texttt{num\_folds}, controls the number of splits. We use scikit-learns \texttt{KFold} (from \texttt{sklearn.model\_selection}) with \texttt{shuffle=True} and a fixed random seed to partition each experiments frames into \texttt{num\_folds} disjoint folds. Training then proceeds across $k$ rounds, each time training on $(k-1)/k$ of the data and evaluating on the remaining $1/k$. In our experiments, we set \texttt{num\_folds=5}, yielding an 80/20 train/evaluation split per fold.
For inference (i.e.\ model validation on held-out experiments), we provide a second \texttt{Dataset} class that loads the full NumPy file for a single experiment (no k-fold splitting), does not assign any labels to the frames nor does it shuffle frames, preserving temporal order. This setup enables seamless, frame-by-frame scoring of complete runs—crucial for analyzing degradation dynamics over an entire traversal.
\section{Model Configuration \& Evaluation Protocol} \section{Model Configuration \& Evaluation Protocol}
Since the neural network architecture trained in the deepsad method is not fixed as described in section~\ref{sec:algorithm_details} but rather chosen based on the input data, we also had to choose an autoencoder architecture befitting our preprocessed lidar data projections. Since \citetitle{degradation_quantification_rain}~\cite{degradation_quantification_rain} reported success in training DeepSAD on similar data we firstly adapted their utilized network architecture for our usecase, which is based on the simple and well understood LeNet architecture. Additionally we were interested in evaluating the importance of a well-suited network architecture for DeepSAD's performance and therefore designed a second network architecture henceforth called "efficient architecture" to incorporate a few modern techniques, befitting our usecase.
\newsubsubsectionNoTOC{Network architectures (LeNet variant, custom encoder) and how they suit the pointcloud input} \newsubsubsectionNoTOC{Network architectures (LeNet variant, custom encoder) and how they suit the pointcloud input}
\threadtodo \threadtodo

View File

@@ -25,6 +25,7 @@
texlab texlab
zathura zathura
wmctrl wmctrl
python312
]; ];
in in
{ {

3
tools/.gitignore vendored
View File

@@ -7,4 +7,7 @@ tmp
.envrc .envrc
.vscode .vscode
test test
*.jpg
*.jpeg
*.png

174
tools/ae_elbow_eval.py Normal file
View File

@@ -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",
)

View File

@@ -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)=}")

102
tools/calculate_rf_sizes.py Normal file
View File

@@ -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}°"
)

View File

@@ -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()

600
tools/elbow_structure.txt Normal file
View File

@@ -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

View File

@@ -1,13 +1,18 @@
import json
import pickle import pickle
from pathlib import Path from pathlib import Path
import matplotlib.pyplot as plt import matplotlib.pyplot as plt
import numpy as np import numpy as np
from rich.progress import track
from scipy.stats import sem, t from scipy.stats import sem, t
from sklearn.metrics import auc
models = ["deepsad", "isoforest", "ocsvm"]
evaluation_types = ["exp_based", "manual_based"]
parent_results_path = Path("/home/fedex/mt/results/done")
base_output_path = Path("/home/fedex/mt/results/tmp_plots")
# Confidence interval function
def confidence_interval(data, confidence=0.95): def confidence_interval(data, confidence=0.95):
n = len(data) n = len(data)
mean = np.mean(data) mean = np.mean(data)
@@ -16,67 +21,178 @@ def confidence_interval(data, confidence=0.95):
return mean, h return mean, h
# Load ROC and AUC values from pickle files def load_results_data(folder):
roc_data = [] experiment_data = {}
auc_scores = []
isoforest_roc_data = []
isoforest_auc_scores = []
results_path = Path( json_config_path = folder / "config.json"
"/home/fedex/mt/projects/thesis-kowalczyk-jan/Deep-SAD-PyTorch/log/DeepSAD/subter_kfold_0_0" with json_config_path.open("r") as f:
) config = json.load(f)
try:
net = config["net_name"]
num_known_normal, num_known_anomalous = (
config["num_known_normal"],
config["num_known_outlier"],
)
semi_known_nums = (num_known_normal, num_known_anomalous)
latent_dim = config["latent_space_dim"]
for i in range(5): exp_title = f"{net} - {num_known_normal} normal, {num_known_anomalous} anomalous, latent dim {latent_dim}"
with (results_path / f"results_{i}.pkl").open("rb") as f:
data = pickle.load(f)
roc_data.append(data["test_roc"])
auc_scores.append(data["test_auc"])
with (results_path / f"results.isoforest_{i}.pkl").open("rb") as f:
data = pickle.load(f)
isoforest_roc_data.append(data["test_roc"])
isoforest_auc_scores.append(data["test_auc"])
# Calculate mean and confidence interval for AUC scores if not config["k_fold"]:
mean_auc, auc_ci = confidence_interval(auc_scores) raise ValueError(f"{folder.name} was not trained as k-fold. Exiting...")
# Combine ROC curves k_fold_num = config["k_fold_num"]
mean_fpr = np.linspace(0, 1, 100) except KeyError as e:
tprs = [] print(f"Missing key in config.json for experiment folder {folder.name}: {e}")
raise
for fpr, tpr, _ in roc_data: experiment_data["exp_title"] = exp_title
interp_tpr = np.interp(mean_fpr, fpr, tpr) experiment_data["k_fold_num"] = k_fold_num
interp_tpr[0] = 0.0 experiment_data["semi_known_nums"] = semi_known_nums
tprs.append(interp_tpr) experiment_data["folder"] = folder
experiment_data["net"] = net
experiment_data["latent_dim"] = latent_dim
mean_tpr = np.mean(tprs, axis=0) roc_data = {}
mean_tpr[-1] = 1.0 roc_auc_data = {}
std_tpr = np.std(tprs, axis=0) prc_data = {}
# Plot ROC curves with confidence margins for model in models:
plt.figure() # You can adjust the number of folds if needed
plt.plot( for fold_idx in range(k_fold_num):
mean_fpr, results_file = folder / f"results_{model}_{fold_idx}.pkl"
mean_tpr, if not results_file.exists():
color="b", print(
label=f"Mean ROC (AUC = {mean_auc:.2f} ± {auc_ci:.2f})", f"Expected results file {results_file.name} does not exist. Skipping..."
) )
plt.fill_between( with results_file.open("rb") as f:
mean_fpr, data = pickle.load(f)
mean_tpr - std_tpr, try:
mean_tpr + std_tpr, if model == "deepsad":
color="b", test_results = data["test"]
alpha=0.2, for evaluation_type in evaluation_types:
label="± 1 std. dev.", eval_type_results = test_results[evaluation_type]
) roc_data.setdefault(model, {}).setdefault(
evaluation_type, {}
)[fold_idx] = eval_type_results["roc"]
roc_auc_data.setdefault(model, {}).setdefault(
evaluation_type, {}
)[fold_idx] = eval_type_results["auc"]
prc_data.setdefault(model, {}).setdefault(
evaluation_type, {}
)[fold_idx] = eval_type_results["prc"]
elif model in ["isoforest", "ocsvm"]:
for evaluation_type in evaluation_types:
roc_data.setdefault(model, {}).setdefault(
evaluation_type, {}
)[fold_idx] = data[f"test_roc_{evaluation_type}"]
roc_auc_data.setdefault(model, {}).setdefault(
evaluation_type, {}
)[fold_idx] = data[f"test_auc_{evaluation_type}"]
prc_data.setdefault(model, {}).setdefault(
evaluation_type, {}
)[fold_idx] = data[f"test_prc_{evaluation_type}"]
# Plot each fold's ROC curve (optional) except KeyError as e:
for i, (fpr, tpr, _) in enumerate(roc_data): print(f"Missing key in results file {results_file.name}: {e}")
plt.plot(fpr, tpr, lw=1, alpha=0.3, label=f"Fold {i + 1} ROC") raise
# Labels and legend experiment_data["roc_data"] = roc_data
plt.plot([0, 1], [0, 1], "k--", label="Chance") experiment_data["roc_auc_data"] = roc_auc_data
plt.xlabel("False Positive Rate") experiment_data["prc_data"] = prc_data
plt.ylabel("True Positive Rate") return experiment_data
plt.title("ROC Curve with 5-Fold Cross-Validation")
plt.legend(loc="lower right")
plt.savefig("roc_curve_0_0.png") def plot_roc_curve(experiment_data, output_path):
try:
k_fold_num = experiment_data["k_fold_num"]
roc_data = experiment_data["roc_data"]
roc_auc_data = experiment_data["roc_auc_data"]
folder = experiment_data["folder"]
exp_title = experiment_data["exp_title"]
except KeyError as e:
print(f"Missing key in experiment data: {e}")
raise
for evaluation_type in evaluation_types:
plt.figure(figsize=(8, 6))
for model in models:
# Gather all folds' ROC data for this model and evaluation_type
fold_rocs = []
auc_scores = []
for fold_idx in range(k_fold_num):
try:
fpr, tpr, thresholds = roc_data[model][evaluation_type][fold_idx]
fold_rocs.append((fpr, tpr))
auc_scores.append(roc_auc_data[model][evaluation_type][fold_idx])
except KeyError:
continue
if not fold_rocs:
print(
f"No ROC data for model {model}, evaluation {evaluation_type} in {folder.name}"
)
continue
# Interpolate TPRs to a common FPR grid
mean_fpr = np.linspace(0, 1, 100)
interp_tprs = []
for fpr, tpr in fold_rocs:
interp_tpr = np.interp(mean_fpr, fpr, tpr)
interp_tpr[0] = 0.0
interp_tprs.append(interp_tpr)
mean_tpr = np.mean(interp_tprs, axis=0)
std_tpr = np.std(interp_tprs, axis=0)
mean_tpr[-1] = 1.0
# Mean and CI for AUC
mean_auc, auc_ci = confidence_interval(auc_scores)
# Plot mean ROC and std band
plt.plot(
mean_fpr,
mean_tpr,
label=f"{model} (AUC={mean_auc:.2f}±{auc_ci:.2f})",
)
plt.fill_between(
mean_fpr,
mean_tpr - std_tpr,
mean_tpr + std_tpr,
alpha=0.15,
)
plt.plot([0, 1], [0, 1], "k--", label="Chance")
plt.xlabel("False Positive Rate")
plt.ylabel("True Positive Rate")
plt.title(f"ROC Curve ({exp_title} - {evaluation_type})")
plt.legend(loc="lower right")
plt.tight_layout()
plt.savefig(
(output_path / f"roc_curve_{folder.name}_{evaluation_type}.png").as_posix()
)
plt.close()
def main():
base_output_path.mkdir(exist_ok=True, parents=True)
# Find all subfolders (skip files)
subfolders = [f for f in parent_results_path.iterdir() if f.is_dir()]
print(f"Found {len(subfolders)} subfolders in {parent_results_path}")
all_experiments_data = []
for folder in track(
subfolders, description="[cyan]Loading data...", total=len(subfolders)
):
all_experiments_data.append(load_results_data(folder))
print("Data loading complete. Plotting ROC curves...")
roc_curves_output_path = base_output_path / "roc_curves"
roc_curves_output_path.mkdir(exist_ok=True, parents=True)
for experiment_data in track(
all_experiments_data,
description="[green]Plotting ROC curves...",
total=len(all_experiments_data),
):
plot_roc_curve(experiment_data, roc_curves_output_path)
if __name__ == "__main__":
main()

295
tools/gridsearch_new.py Normal file
View File

@@ -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()

View File

@@ -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)

View File

@@ -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) Semisupervised 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])
# semisupervised
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) Semisupervised 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")
)

View File

@@ -1,40 +0,0 @@
{
description = "Python 3.13 devshell with tensorflow-datasets, matplotlib, scikit-learn and numpy";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
flake-utils.url = "github:numtide/flake-utils";
};
outputs =
{
self,
nixpkgs,
flake-utils,
...
}:
flake-utils.lib.eachDefaultSystem (
system:
let
pkgs = import nixpkgs {
inherit system;
# optional: config = { allowUnfree = true; };
};
in
{
devShells.default = pkgs.mkShell {
name = "py313-devshell";
# bring in the Python 3.13 packages
buildInputs =
with pkgs;
[ python313 ]
++ (with pkgs.python313Packages; [
tensorflow-datasets
matplotlib
scikit-learn
numpy
]);
};
}
);
}

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -150,7 +150,7 @@ def plot_data_points(normal_experiment_paths, anomaly_experiment_paths, title):
plt.savefig(output_datetime_path / "missing_points_density.png") plt.savefig(output_datetime_path / "missing_points_density.png")
# create another density version which does not plot number of missing points but percentage of measurements that are missing (total number of points is 32*2048) # create another density version which does not plot number of missing points but percentage of measurements that are missing (total number of points is 32*2048)
bins = np.linspace(0, 1, 100) bins = np.linspace(0, 0.6, 100)
plt.clf() plt.clf()
plt.figure(figsize=(10, 5)) plt.figure(figsize=(10, 5))
plt.hist( plt.hist(

View File

@@ -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)

View File

@@ -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)

View File

@@ -78,11 +78,19 @@ def plot_tsne_latent_space(normal_data, anomaly_data, title="TSNE of Latent Spac
Plot the TSNE representation of the latent space. Plot the TSNE representation of the latent space.
This function first applies a PCA-based dimensionality reduction for efficiency. This function first applies a PCA-based dimensionality reduction for efficiency.
""" """
# Hardcoded variables to choose every nth normal sample and mth anomaly sample
n = 10 # Change this value to select every nth normal sample
m = 2 # Change this value to select every mth anomaly sample
# Select every nth normal sample and mth anomaly sample
normal_data = normal_data[::n]
anomaly_data = anomaly_data[::m]
# Combine normal and anomaly data # Combine normal and anomaly data
combined_data = np.vstack((normal_data, anomaly_data)) combined_data = np.vstack((normal_data, anomaly_data))
# Initial dimensionality reduction with PCA # Initial dimensionality reduction with PCA
reduced_data = reduce_dimensionality(combined_data, n_components=50) reduced_data = reduce_dimensionality(combined_data, n_components=100)
# Apply TSNE transformation on the PCA-reduced data # Apply TSNE transformation on the PCA-reduced data
tsne = TSNE(n_components=2, random_state=42) tsne = TSNE(n_components=2, random_state=42)

172
tools/print_param_counts.py Normal file
View File

@@ -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)