From fad8a1085f7bf2341b01213aa38b73b50bb1e4f6 Mon Sep 17 00:00:00 2001 From: juliaj Date: Thu, 5 Feb 2026 21:36:33 -0800 Subject: [PATCH 1/3] Build and publish package to test pypi and pypi --- pixi.lock | 425 ++++++++++++++++++++++++++++++++++++++++++++++++- pyproject.toml | 12 ++ 2 files changed, 436 insertions(+), 1 deletion(-) diff --git a/pixi.lock b/pixi.lock index 358f5f1..99b82a8 100644 --- a/pixi.lock +++ b/pixi.lock @@ -175,25 +175,50 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/yaml-0.2.5-h280c20c_3.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb78ec9c_6.conda - pypi: https://files.pythonhosted.org/packages/18/a6/907a406bb7d359e6a63f99c313846d9eec4f7e6f7437809e03aa00fa3074/absl_py-2.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c5/0d/84a4380f930db0010168e0aa7b7a8fed9ba1835a8fbb1472bc6d0201d529/build-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e6/ad/3cc14f097111b4de0040c83a525973216457bbeeb63739ef1ed275c1c021/certifi-2026.1.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c0/10/d20b513afe03acc89ec33948320a5544d31f21b05368436d580dec4e234d/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/cc/8f/ec6289987824b29529d0dfda0d74a07cec60e54b9c92f3c9da4c0ac732de/contourpy-1.3.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/2b/08/f83e2e0814248b844265802d081f2fac2f1cbe6cd258e72ba14ff006823a/cryptography-46.0.4-cp311-abi3-manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/02/10/5da547df7a391dcde17f59520a231527b8571e6f46fc8efb02ccb370ab12/docutils-0.22.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e8/2d/d2a548598be01649e2d46231d151a6c56d10b964d94043a335ae56ea2d92/flatbuffers-25.12.19-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b7/37/82dbef0f6342eb01f54bca073ac1498433d6ce71e50c3c3282b655733b31/fonttools-4.61.1-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/42/77/de194443bf38daed9452139e960c632b0ef9f9a5dd9ce605fdf18ca9f1b1/id-1.6.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7f/66/b15ce62552d84bbfcec9a4873ab79d993a1dd4edb922cbfccae192bd5b5f/jaraco.classes-3.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/8d/48/aa685dbf1024c7bd82bede569e3a85f82c32fd3d79ba5fea578f0159571a/jaraco_context-6.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fd/c4/813bb09f0985cb21e959f21f2464169eca882656849adf727ac7bb7e1767/jaraco_functools-4.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b2/a3/e137168c9c44d18eff0376253da9f1e9234d0239e0ee230d2fee6cea8e55/jeepney-0.9.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/91/984aca2ec129e2757d1e4e3c81c3fcda9d0f85b74670a094cc443d9ee949/joblib-1.5.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/81/db/e655086b7f3a705df045bf0933bdd9c2f79bb3c97bfef1384598bb79a217/keyring-25.7.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/70/90/6d240beb0f24b74371762873e9b7f499f1e02166a2d9c5801f4dbf8fa12e/kiwisolver-1.4.9-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3e/f3/c5195b1ae57ef85339fd7285dfb603b22c8b4e79114bae5f4f0fcf688677/matplotlib-3.10.8-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e3/98/00cd8b2dcb563f2298655633e6611a791b2c1a7df1dae064b2b96084f1bf/mediapipe-0.10.32-py3-none-manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/a4/8e/469e5a4a2f5855992e425f3cb33804cc07bf18d48f2db061aec61ce50270/more_itertools-10.8.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/42/0f/c76bf3dba22c73c38e9b1113b017cf163f7696f50e003404ec5ecdb1e8a6/nh3-0.3.2-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/f5/c6/a18e59f3f0b8071cc85cbc8d80cd02d68aa9710170b2553a117203d46936/numpy-2.4.2-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/a9/3d/00071f3a395611a13efca22e3ee65aab25b8bf54128ae5080d8361cbb673/opencv_contrib_python-4.13.0.90-cp37-abi3-manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/11/8f/48d0b77ab2200374c66d344459b8958c86693be99526450e7aee714e03e4/pillow-12.1.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e1/67/921ec3024056483db83953ae8e48079ad62b92db7880013ca77632921dd0/readme_renderer-44.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3f/51/d4db610ef29373b879047326cbf6fa98b6c1969d6f6dc423279de2b1be2c/requests_toolbelt-1.0.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ff/9a/9afaade874b2fa6c752c36f1548f718b5b83af81ed9b76628329dab81c1b/rfc3986-2.0.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ef/45/615f5babd880b4bd7d405cc0dc348234c5ffb6ed1ea33e152ede08b2072d/rich-14.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/97/74/b7a304feb2b49df9fafa9382d4d09061a96ee9a9449a7cbea7988dda0828/scikit_learn-1.8.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/d1/fe/66d73b76d378ba8cc2fe605920c0c75092e3a65ae746e1e767d9d020a75a/scipy-1.17.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/b7/46/f5af3402b579fd5e11573ce652019a67074317e18c1935cc0b4ba9b35552/secretstorage-3.5.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1e/0a/478e441fd049002cf308520c0d62dd8333e7c6cc8d997f0dda07b9fbcc46/sounddevice-0.5.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/32/d5/f9a850d79b0851d1d4ef6456097579a9005b31fea68726a4ae5f2d82ddd9/threadpoolctl-3.6.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3a/7a/882d99539b19b1490cac5d77c67338d126e4122c8276bf640e411650c830/twine-6.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/13/41/5bf882649bd8b64ded5fbce7fb8d77fb3b868de1a3b1a6c4796402b47308/ty-0.0.15-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl - pypi: ./ linux-aarch64: - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/_openmp_mutex-4.5-2_gnu.tar.bz2 @@ -248,31 +273,56 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/zstd-1.5.7-h85ac4a6_6.conda - pypi: https://files.pythonhosted.org/packages/18/a6/907a406bb7d359e6a63f99c313846d9eec4f7e6f7437809e03aa00fa3074/absl_py-2.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c5/0d/84a4380f930db0010168e0aa7b7a8fed9ba1835a8fbb1472bc6d0201d529/build-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e6/ad/3cc14f097111b4de0040c83a525973216457bbeeb63739ef1ed275c1c021/certifi-2026.1.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9d/6a/04130023fef2a0d9c62d0bae2649b69f7b7d8d24ea5536feef50551029df/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl - pypi: https://files.pythonhosted.org/packages/d4/1c/a12359b9b2ca3a845e8f7f9ac08bdf776114eb931392fcad91743e2ea17b/contourpy-1.3.3-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl + - pypi: https://files.pythonhosted.org/packages/1a/5b/a26407d4f79d61ca4bebaa9213feafdd8806dc69d3d290ce24996d3cfe43/cryptography-46.0.4-cp311-abi3-manylinux_2_28_aarch64.whl - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/02/10/5da547df7a391dcde17f59520a231527b8571e6f46fc8efb02ccb370ab12/docutils-0.22.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e8/2d/d2a548598be01649e2d46231d151a6c56d10b964d94043a335ae56ea2d92/flatbuffers-25.12.19-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/6c/44/f3aeac0fa98e7ad527f479e161aca6c3a1e47bb6996b053d45226fe37bf2/fonttools-4.61.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl + - pypi: https://files.pythonhosted.org/packages/42/77/de194443bf38daed9452139e960c632b0ef9f9a5dd9ce605fdf18ca9f1b1/id-1.6.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7f/66/b15ce62552d84bbfcec9a4873ab79d993a1dd4edb922cbfccae192bd5b5f/jaraco.classes-3.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/8d/48/aa685dbf1024c7bd82bede569e3a85f82c32fd3d79ba5fea578f0159571a/jaraco_context-6.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fd/c4/813bb09f0985cb21e959f21f2464169eca882656849adf727ac7bb7e1767/jaraco_functools-4.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/83/81/793d78c91b0546b3b1f08e55fdd97437174171cd7d70e46098f1a4d94b7b/jax-0.7.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/de/4d/76ee71959311fe3da9951aa6f55af8f98eb3572bb322f5a7c89faf7ab933/jaxlib-0.7.1-cp312-cp312-manylinux2014_aarch64.whl + - pypi: https://files.pythonhosted.org/packages/b2/a3/e137168c9c44d18eff0376253da9f1e9234d0239e0ee230d2fee6cea8e55/jeepney-0.9.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/91/984aca2ec129e2757d1e4e3c81c3fcda9d0f85b74670a094cc443d9ee949/joblib-1.5.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/81/db/e655086b7f3a705df045bf0933bdd9c2f79bb3c97bfef1384598bb79a217/keyring-25.7.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/12/42/f36816eaf465220f683fb711efdd1bbf7a7005a2473d0e4ed421389bd26c/kiwisolver-1.4.9-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl + - pypi: https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/00/f9/7638f5cc82ec8a7aa005de48622eecc3ed7c9854b96ba15bd76b7fd27574/matplotlib-3.10.8-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl + - pypi: https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a8/f2/c8f62565abc93b9ac6a9936856d3c3c144c7f7896ef3d02bfbfad2ab6ee7/mediapipe-0.10.18-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl - pypi: https://files.pythonhosted.org/packages/54/0f/428ef6881782e5ebb7eca459689448c0394fa0a80bea3aa9262cba5445ea/ml_dtypes-0.5.4-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl + - pypi: https://files.pythonhosted.org/packages/a4/8e/469e5a4a2f5855992e425f3cb33804cc07bf18d48f2db061aec61ce50270/more_itertools-10.8.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/7f/f7/529a99324d7ef055de88b690858f4189379708abae92ace799365a797b7f/nh3-0.3.2-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl - pypi: https://files.pythonhosted.org/packages/79/f8/97f10e6755e2a7d027ca783f63044d5b1bc1ae7acb12afe6a9b4286eac17/numpy-1.26.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl - pypi: https://files.pythonhosted.org/packages/ae/7b/7e1471aa92f9f3c1bd8dbe624622b62add6f734db34fbbb9974e2ec70c34/opencv_contrib_python-4.11.0.86-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl - pypi: https://files.pythonhosted.org/packages/23/cd/066e86230ae37ed0be70aae89aabf03ca8d9f39c8aea0dec8029455b5540/opt_einsum-3.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0a/ea/f200a4c36d836100e7bc738fc48cd963d3ba6372ebc8298a889e0cfc3359/pillow-12.1.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl - pypi: https://files.pythonhosted.org/packages/bd/6d/a4a198b61808dd3d1ee187082ccc21499bc949d639feb948961b48be9a7e/protobuf-4.25.8-cp37-abi3-manylinux2014_aarch64.whl - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e1/67/921ec3024056483db83953ae8e48079ad62b92db7880013ca77632921dd0/readme_renderer-44.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3f/51/d4db610ef29373b879047326cbf6fa98b6c1969d6f6dc423279de2b1be2c/requests_toolbelt-1.0.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ff/9a/9afaade874b2fa6c752c36f1548f718b5b83af81ed9b76628329dab81c1b/rfc3986-2.0.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ef/45/615f5babd880b4bd7d405cc0dc348234c5ffb6ed1ea33e152ede08b2072d/rich-14.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/dd/47/f187b4636ff80cc63f21cd40b7b2d177134acaa10f6bb73746130ee8c2e5/scikit_learn-1.8.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl - pypi: https://files.pythonhosted.org/packages/4a/69/7c347e857224fcaf32a34a05183b9d8a7aca25f8f2d10b8a698b8388561a/scipy-1.17.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl + - pypi: https://files.pythonhosted.org/packages/b7/46/f5af3402b579fd5e11573ce652019a67074317e18c1935cc0b4ba9b35552/secretstorage-3.5.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/89/fa/d3d5ebcba3cb9e6d3775a096251860c41a6bc53a1b9461151df83fe93255/sentencepiece-0.2.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl - pypi: https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1e/0a/478e441fd049002cf308520c0d62dd8333e7c6cc8d997f0dda07b9fbcc46/sounddevice-0.5.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/32/d5/f9a850d79b0851d1d4ef6456097579a9005b31fea68726a4ae5f2d82ddd9/threadpoolctl-3.6.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3a/7a/882d99539b19b1490cac5d77c67338d126e4122c8276bf640e411650c830/twine-6.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3c/d9/244bc02599d950f7a4298fbc0c1b25cc808646b9577bdf7a83470b2d1cec/ty-0.0.15-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl + - pypi: https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl - pypi: ./ packages: - conda: https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2 @@ -319,6 +369,21 @@ packages: version: 25.4.0 sha256: adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373 requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/c5/0d/84a4380f930db0010168e0aa7b7a8fed9ba1835a8fbb1472bc6d0201d529/build-1.4.0-py3-none-any.whl + name: build + version: 1.4.0 + sha256: 6a07c1b8eb6f2b311b96fcbdbce5dab5fe637ffda0fd83c9cac622e927501596 + requires_dist: + - packaging>=24.0 + - pyproject-hooks + - colorama ; os_name == 'nt' + - importlib-metadata>=4.6 ; python_full_version < '3.10.2' + - tomli>=1.1.0 ; python_full_version < '3.11' + - uv>=0.1.18 ; extra == 'uv' + - virtualenv>=20.11 ; python_full_version < '3.10' and extra == 'virtualenv' + - virtualenv>=20.17 ; python_full_version >= '3.10' and python_full_version < '3.14' and extra == 'virtualenv' + - virtualenv>=20.31 ; python_full_version >= '3.14' and extra == 'virtualenv' + requires_python: '>=3.9' - conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-hda65f42_8.conda sha256: c30daba32ddebbb7ded490f0e371eae90f51e72db620554089103b4a6934b0d5 md5: 51a19bba1b8ebfb60df25cde030b7ebc @@ -349,6 +414,11 @@ packages: purls: [] size: 146519 timestamp: 1767500828366 +- pypi: https://files.pythonhosted.org/packages/e6/ad/3cc14f097111b4de0040c83a525973216457bbeeb63739ef1ed275c1c021/certifi-2026.1.4-py3-none-any.whl + name: certifi + version: 2026.1.4 + sha256: 9943707519e4add1115f44c2bc244f782c0249876bf51b6599fee1ffbedd685c + requires_python: '>=3.7' - conda: https://conda.anaconda.org/conda-forge/linux-64/cffi-2.0.0-py312h460c074_1.conda sha256: 7dafe8173d5f94e46cf9cd597cc8ff476a8357fbbd4433a8b5697b2864845d9c md5: 648ee28dcd4e07a1940a17da62eccd40 @@ -391,6 +461,16 @@ packages: - pkg:pypi/cfgv?source=hash-mapping size: 13589 timestamp: 1763607964133 +- pypi: https://files.pythonhosted.org/packages/9d/6a/04130023fef2a0d9c62d0bae2649b69f7b7d8d24ea5536feef50551029df/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl + name: charset-normalizer + version: 3.4.4 + sha256: b5b290ccc2a263e8d185130284f8501e3e36c5e02750fc6b6bdeb2e9e96f1e25 + requires_python: '>=3.7' +- pypi: https://files.pythonhosted.org/packages/c0/10/d20b513afe03acc89ec33948320a5544d31f21b05368436d580dec4e234d/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + name: charset-normalizer + version: 3.4.4 + sha256: 11d694519d7f29d6cd09f6ac70028dba10f92f6cdd059096db198c283794ac86 + requires_python: '>=3.7' - conda: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda sha256: ab29d57dc70786c1269633ba3dff20288b81664d3ff8d21af995742e2bb03287 md5: 962b9857ee8e7018c22f2776ffa0b2d7 @@ -452,6 +532,66 @@ packages: - pytest-xdist ; extra == 'test-no-images' - wurlitzer ; extra == 'test-no-images' requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/1a/5b/a26407d4f79d61ca4bebaa9213feafdd8806dc69d3d290ce24996d3cfe43/cryptography-46.0.4-cp311-abi3-manylinux_2_28_aarch64.whl + name: cryptography + version: 46.0.4 + sha256: 01df4f50f314fbe7009f54046e908d1754f19d0c6d3070df1e6268c5a4af09fa + requires_dist: + - cffi>=1.14 ; python_full_version == '3.8.*' and platform_python_implementation != 'PyPy' + - cffi>=2.0.0 ; python_full_version >= '3.9' and platform_python_implementation != 'PyPy' + - typing-extensions>=4.13.2 ; python_full_version < '3.11' + - bcrypt>=3.1.5 ; extra == 'ssh' + - nox[uv]>=2024.4.15 ; extra == 'nox' + - cryptography-vectors==46.0.4 ; extra == 'test' + - pytest>=7.4.0 ; extra == 'test' + - pytest-benchmark>=4.0 ; extra == 'test' + - pytest-cov>=2.10.1 ; extra == 'test' + - pytest-xdist>=3.5.0 ; extra == 'test' + - pretend>=0.7 ; extra == 'test' + - certifi>=2024 ; extra == 'test' + - pytest-randomly ; extra == 'test-randomorder' + - sphinx>=5.3.0 ; extra == 'docs' + - sphinx-rtd-theme>=3.0.0 ; extra == 'docs' + - sphinx-inline-tabs ; extra == 'docs' + - pyenchant>=3 ; extra == 'docstest' + - readme-renderer>=30.0 ; extra == 'docstest' + - sphinxcontrib-spelling>=7.3.1 ; extra == 'docstest' + - build>=1.0.0 ; extra == 'sdist' + - ruff>=0.11.11 ; extra == 'pep8test' + - mypy>=1.14 ; extra == 'pep8test' + - check-sdist ; extra == 'pep8test' + - click>=8.0.1 ; extra == 'pep8test' + requires_python: '>=3.8,!=3.9.0,!=3.9.1' +- pypi: https://files.pythonhosted.org/packages/2b/08/f83e2e0814248b844265802d081f2fac2f1cbe6cd258e72ba14ff006823a/cryptography-46.0.4-cp311-abi3-manylinux_2_28_x86_64.whl + name: cryptography + version: 46.0.4 + sha256: 0a9ad24359fee86f131836a9ac3bffc9329e956624a2d379b613f8f8abaf5255 + requires_dist: + - cffi>=1.14 ; python_full_version == '3.8.*' and platform_python_implementation != 'PyPy' + - cffi>=2.0.0 ; python_full_version >= '3.9' and platform_python_implementation != 'PyPy' + - typing-extensions>=4.13.2 ; python_full_version < '3.11' + - bcrypt>=3.1.5 ; extra == 'ssh' + - nox[uv]>=2024.4.15 ; extra == 'nox' + - cryptography-vectors==46.0.4 ; extra == 'test' + - pytest>=7.4.0 ; extra == 'test' + - pytest-benchmark>=4.0 ; extra == 'test' + - pytest-cov>=2.10.1 ; extra == 'test' + - pytest-xdist>=3.5.0 ; extra == 'test' + - pretend>=0.7 ; extra == 'test' + - certifi>=2024 ; extra == 'test' + - pytest-randomly ; extra == 'test-randomorder' + - sphinx>=5.3.0 ; extra == 'docs' + - sphinx-rtd-theme>=3.0.0 ; extra == 'docs' + - sphinx-inline-tabs ; extra == 'docs' + - pyenchant>=3 ; extra == 'docstest' + - readme-renderer>=30.0 ; extra == 'docstest' + - sphinxcontrib-spelling>=7.3.1 ; extra == 'docstest' + - build>=1.0.0 ; extra == 'sdist' + - ruff>=0.11.11 ; extra == 'pep8test' + - mypy>=1.14 ; extra == 'pep8test' + - check-sdist ; extra == 'pep8test' + - click>=8.0.1 ; extra == 'pep8test' + requires_python: '>=3.8,!=3.9.0,!=3.9.1' - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl name: cycler version: 0.12.1 @@ -476,6 +616,11 @@ packages: - pkg:pypi/distlib?source=hash-mapping size: 275642 timestamp: 1752823081585 +- pypi: https://files.pythonhosted.org/packages/02/10/5da547df7a391dcde17f59520a231527b8571e6f46fc8efb02ccb370ab12/docutils-0.22.4-py3-none-any.whl + name: docutils + version: 0.22.4 + sha256: d0013f540772d1420576855455d050a2180186c91c15779301ac2ccb3eeb68de + requires_python: '>=3.9' - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.1-pyhd8ed1ab_0.conda sha256: ee6cf346d017d954255bbcbdb424cddea4d14e4ed7e9813e429db1d795d01144 md5: 8e662bd460bda79b1ea39194e3c4c9ab @@ -572,7 +717,13 @@ packages: - pypi: ./ name: handmotion version: 0.0.1 - sha256: a30a866ccc57156cde26631dbf64d63a4cb48c8cf26df1a698263ddf54a45240 + sha256: c06afc1f29cf2ae0cc4d86757fbcb809bc1225a1ec7cb0c4f9c6d52f758cc35e + requires_dist: + - joblib + - mediapipe + - numpy + - pillow + - scikit-learn requires_python: '>=3.12,<3.13' - conda: https://conda.anaconda.org/conda-forge/linux-64/icu-78.2-h33c6efd_0.conda sha256: 142a722072fa96cf16ff98eaaf641f54ab84744af81754c292cb81e0881c0329 @@ -597,6 +748,24 @@ packages: purls: [] size: 12852963 timestamp: 1767975394622 +- pypi: https://files.pythonhosted.org/packages/42/77/de194443bf38daed9452139e960c632b0ef9f9a5dd9ce605fdf18ca9f1b1/id-1.6.1-py3-none-any.whl + name: id + version: 1.6.1 + sha256: f5ec41ed2629a508f5d0988eda142e190c9c6da971100612c4de9ad9f9b237ca + requires_dist: + - urllib3>=2,<3 + - build ; extra == 'dev' + - bump>=1.3.2 ; extra == 'dev' + - id[test,lint] ; extra == 'dev' + - bandit ; extra == 'lint' + - interrogate ; extra == 'lint' + - mypy ; extra == 'lint' + - ruff<0.14.15 ; extra == 'lint' + - pytest ; extra == 'test' + - pytest-cov ; extra == 'test' + - pretend ; extra == 'test' + - coverage[toml] ; extra == 'test' + requires_python: '>=3.9' - conda: https://conda.anaconda.org/conda-forge/noarch/identify-2.6.16-pyhd8ed1ab_0.conda sha256: 6a88cdde151469131df1948839ac2315ada99cf8d38aaacc9a7a5984e9cd8c19 md5: 8bc5851c415865334882157127e75799 @@ -609,6 +778,16 @@ packages: - pkg:pypi/identify?source=compressed-mapping size: 79302 timestamp: 1768295306539 +- pypi: https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl + name: idna + version: '3.11' + sha256: 771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea + requires_dist: + - ruff>=0.6.2 ; extra == 'all' + - mypy>=1.11.2 ; extra == 'all' + - pytest>=8.3.2 ; extra == 'all' + - flake8>=7.1.1 ; extra == 'all' + requires_python: '>=3.8' - conda: https://conda.anaconda.org/conda-forge/noarch/iniconfig-2.3.0-pyhd8ed1ab_0.conda sha256: e1a9e3b1c8fe62dc3932a616c284b5d8cbe3124bbfbedcf4ce5c828cb166ee19 md5: 9614359868482abba1bd15ce465e3c42 @@ -620,6 +799,68 @@ packages: - pkg:pypi/iniconfig?source=compressed-mapping size: 13387 timestamp: 1760831448842 +- pypi: https://files.pythonhosted.org/packages/7f/66/b15ce62552d84bbfcec9a4873ab79d993a1dd4edb922cbfccae192bd5b5f/jaraco.classes-3.4.0-py3-none-any.whl + name: jaraco-classes + version: 3.4.0 + sha256: f662826b6bed8cace05e7ff873ce0f9283b5c924470fe664fff1c2f00f581790 + requires_dist: + - more-itertools + - sphinx>=3.5 ; extra == 'docs' + - jaraco-packaging>=9.3 ; extra == 'docs' + - rst-linker>=1.9 ; extra == 'docs' + - furo ; extra == 'docs' + - sphinx-lint ; extra == 'docs' + - jaraco-tidelift>=1.4 ; extra == 'docs' + - pytest>=6 ; extra == 'testing' + - pytest-checkdocs>=2.4 ; extra == 'testing' + - pytest-cov ; extra == 'testing' + - pytest-mypy ; extra == 'testing' + - pytest-enabler>=2.2 ; extra == 'testing' + - pytest-ruff>=0.2.1 ; extra == 'testing' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/8d/48/aa685dbf1024c7bd82bede569e3a85f82c32fd3d79ba5fea578f0159571a/jaraco_context-6.1.0-py3-none-any.whl + name: jaraco-context + version: 6.1.0 + sha256: a43b5ed85815223d0d3cfdb6d7ca0d2bc8946f28f30b6f3216bda070f68badda + requires_dist: + - backports-tarfile ; python_full_version < '3.12' + - pytest>=6,!=8.1.* ; extra == 'test' + - jaraco-test>=5.6.0 ; extra == 'test' + - portend ; extra == 'test' + - sphinx>=3.5 ; extra == 'doc' + - jaraco-packaging>=9.3 ; extra == 'doc' + - rst-linker>=1.9 ; extra == 'doc' + - furo ; extra == 'doc' + - sphinx-lint ; extra == 'doc' + - jaraco-tidelift>=1.4 ; extra == 'doc' + - pytest-checkdocs>=2.4 ; extra == 'check' + - pytest-ruff>=0.2.1 ; sys_platform != 'cygwin' and extra == 'check' + - pytest-cov ; extra == 'cover' + - pytest-enabler>=3.4 ; extra == 'enabler' + - pytest-mypy>=1.0.1 ; extra == 'type' + - mypy<1.19 ; platform_python_implementation == 'PyPy' and extra == 'type' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/fd/c4/813bb09f0985cb21e959f21f2464169eca882656849adf727ac7bb7e1767/jaraco_functools-4.4.0-py3-none-any.whl + name: jaraco-functools + version: 4.4.0 + sha256: 9eec1e36f45c818d9bf307c8948eb03b2b56cd44087b3cdc989abca1f20b9176 + requires_dist: + - more-itertools + - pytest>=6,!=8.1.* ; extra == 'test' + - jaraco-classes ; extra == 'test' + - sphinx>=3.5 ; extra == 'doc' + - jaraco-packaging>=9.3 ; extra == 'doc' + - rst-linker>=1.9 ; extra == 'doc' + - furo ; extra == 'doc' + - sphinx-lint ; extra == 'doc' + - jaraco-tidelift>=1.4 ; extra == 'doc' + - pytest-checkdocs>=2.4 ; extra == 'check' + - pytest-ruff>=0.2.1 ; sys_platform != 'cygwin' and extra == 'check' + - pytest-cov ; extra == 'cover' + - pytest-enabler>=3.4 ; extra == 'enabler' + - pytest-mypy>=1.0.1 ; extra == 'type' + - mypy<1.19 ; platform_python_implementation == 'PyPy' and extra == 'type' + requires_python: '>=3.9' - pypi: https://files.pythonhosted.org/packages/83/81/793d78c91b0546b3b1f08e55fdd97437174171cd7d70e46098f1a4d94b7b/jax-0.7.1-py3-none-any.whl name: jax version: 0.7.1 @@ -659,11 +900,54 @@ packages: - numpy>=1.26 - ml-dtypes>=0.5.0 requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/b2/a3/e137168c9c44d18eff0376253da9f1e9234d0239e0ee230d2fee6cea8e55/jeepney-0.9.0-py3-none-any.whl + name: jeepney + version: 0.9.0 + sha256: 97e5714520c16fc0a45695e5365a2e11b81ea79bba796e26f9f1d178cb182683 + requires_dist: + - pytest ; extra == 'test' + - pytest-trio ; extra == 'test' + - pytest-asyncio>=0.17 ; extra == 'test' + - testpath ; extra == 'test' + - trio ; extra == 'test' + - async-timeout ; python_full_version < '3.11' and extra == 'test' + - trio ; extra == 'trio' + requires_python: '>=3.7' - pypi: https://files.pythonhosted.org/packages/7b/91/984aca2ec129e2757d1e4e3c81c3fcda9d0f85b74670a094cc443d9ee949/joblib-1.5.3-py3-none-any.whl name: joblib version: 1.5.3 sha256: 5fc3c5039fc5ca8c0276333a188bbd59d6b7ab37fe6632daa76bc7f9ec18e713 requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/81/db/e655086b7f3a705df045bf0933bdd9c2f79bb3c97bfef1384598bb79a217/keyring-25.7.0-py3-none-any.whl + name: keyring + version: 25.7.0 + sha256: be4a0b195f149690c166e850609a477c532ddbfbaed96a404d4e43f8d5e2689f + requires_dist: + - pywin32-ctypes>=0.2.0 ; sys_platform == 'win32' + - secretstorage>=3.2 ; sys_platform == 'linux' + - jeepney>=0.4.2 ; sys_platform == 'linux' + - importlib-metadata>=4.11.4 ; python_full_version < '3.12' + - jaraco-classes + - jaraco-functools + - jaraco-context + - pytest>=6,!=8.1.* ; extra == 'test' + - pyfakefs ; extra == 'test' + - sphinx>=3.5 ; extra == 'doc' + - jaraco-packaging>=9.3 ; extra == 'doc' + - rst-linker>=1.9 ; extra == 'doc' + - furo ; extra == 'doc' + - sphinx-lint ; extra == 'doc' + - jaraco-tidelift>=1.4 ; extra == 'doc' + - pytest-checkdocs>=2.4 ; extra == 'check' + - pytest-ruff>=0.2.1 ; sys_platform != 'cygwin' and extra == 'check' + - pytest-cov ; extra == 'cover' + - pytest-enabler>=3.4 ; extra == 'enabler' + - pytest-mypy>=1.0.1 ; extra == 'type' + - pygobject-stubs ; extra == 'type' + - shtab ; extra == 'type' + - types-pywin32 ; extra == 'type' + - shtab>=1.1.0 ; extra == 'completion' + requires_python: '>=3.9' - pypi: https://files.pythonhosted.org/packages/12/42/f36816eaf465220f683fb711efdd1bbf7a7005a2473d0e4ed421389bd26c/kiwisolver-1.4.9-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl name: kiwisolver version: 1.4.9 @@ -956,6 +1240,39 @@ packages: purls: [] size: 66657 timestamp: 1727963199518 +- pypi: https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl + name: markdown-it-py + version: 4.0.0 + sha256: 87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147 + requires_dist: + - mdurl~=0.1 + - psutil ; extra == 'benchmarking' + - pytest ; extra == 'benchmarking' + - pytest-benchmark ; extra == 'benchmarking' + - commonmark~=0.9 ; extra == 'compare' + - markdown~=3.4 ; extra == 'compare' + - mistletoe~=1.0 ; extra == 'compare' + - mistune~=3.0 ; extra == 'compare' + - panflute~=2.3 ; extra == 'compare' + - markdown-it-pyrs ; extra == 'compare' + - linkify-it-py>=1,<3 ; extra == 'linkify' + - mdit-py-plugins>=0.5.0 ; extra == 'plugins' + - gprof2dot ; extra == 'profiling' + - mdit-py-plugins>=0.5.0 ; extra == 'rtd' + - myst-parser ; extra == 'rtd' + - pyyaml ; extra == 'rtd' + - sphinx ; extra == 'rtd' + - sphinx-copybutton ; extra == 'rtd' + - sphinx-design ; extra == 'rtd' + - sphinx-book-theme~=1.0 ; extra == 'rtd' + - jupyter-sphinx ; extra == 'rtd' + - ipykernel ; extra == 'rtd' + - coverage ; extra == 'testing' + - pytest ; extra == 'testing' + - pytest-cov ; extra == 'testing' + - pytest-regressions ; extra == 'testing' + - requests ; extra == 'testing' + requires_python: '>=3.10' - pypi: https://files.pythonhosted.org/packages/00/f9/7638f5cc82ec8a7aa005de48622eecc3ed7c9854b96ba15bd76b7fd27574/matplotlib-3.10.8-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl name: matplotlib version: 3.10.8 @@ -994,6 +1311,11 @@ packages: - setuptools-scm>=7 ; extra == 'dev' - setuptools>=64 ; extra == 'dev' requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl + name: mdurl + version: 0.1.2 + sha256: 84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8 + requires_python: '>=3.7' - pypi: https://files.pythonhosted.org/packages/a8/f2/c8f62565abc93b9ac6a9936856d3c3c144c7f7896ef3d02bfbfad2ab6ee7/mediapipe-0.10.18-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl name: mediapipe version: 0.10.18 @@ -1037,6 +1359,11 @@ packages: - pylint>=2.6.0 ; extra == 'dev' - pyink ; extra == 'dev' requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/a4/8e/469e5a4a2f5855992e425f3cb33804cc07bf18d48f2db061aec61ce50270/more_itertools-10.8.0-py3-none-any.whl + name: more-itertools + version: 10.8.0 + sha256: 52d4362373dcf7c52546bc4af9a86ee7c4579df9a8dc268be0a2f949d376cc9b + requires_python: '>=3.9' - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-h2d0b736_3.conda sha256: 3fde293232fa3fca98635e1167de6b7c7fda83caf24b9d6c91ec9eefb4f4d586 md5: 47e340acb35de30501a76c7c799c41d7 @@ -1056,6 +1383,16 @@ packages: purls: [] size: 926034 timestamp: 1738196018799 +- pypi: https://files.pythonhosted.org/packages/42/0f/c76bf3dba22c73c38e9b1113b017cf163f7696f50e003404ec5ecdb1e8a6/nh3-0.3.2-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + name: nh3 + version: 0.3.2 + sha256: 7bb18403f02b655a1bbe4e3a4696c2ae1d6ae8f5991f7cacb684b1ae27e6c9f7 + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/7f/f7/529a99324d7ef055de88b690858f4189379708abae92ace799365a797b7f/nh3-0.3.2-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl + name: nh3 + version: 0.3.2 + sha256: c8745454cdd28bbbc90861b80a0111a195b0e3961b9fa2e672be89eb199fa5d8 + requires_python: '>=3.8' - conda: https://conda.anaconda.org/conda-forge/noarch/nodeenv-1.10.0-pyhd8ed1ab_0.conda sha256: 4fa40e3e13fc6ea0a93f67dfc76c96190afd7ea4ffc1bac2612d954b42cdc3ee md5: eb52d14a901e23c39e9e7b4a1a5c015f @@ -1282,6 +1619,11 @@ packages: - railroad-diagrams ; extra == 'diagrams' - jinja2 ; extra == 'diagrams' requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl + name: pyproject-hooks + version: 1.2.0 + sha256: 9e5c6bfa8dcc30091c74b0cf803c81fdd29d94f01992a7707bc97babb1141913 + requires_python: '>=3.7' - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-9.0.2-pyhcf101f3_0.conda sha256: 9e749fb465a8bedf0184d8b8996992a38de351f7c64e967031944978de03a520 md5: 2b694bad8a50dc2f712f5368de866480 @@ -1441,6 +1783,51 @@ packages: purls: [] size: 357597 timestamp: 1765815673644 +- pypi: https://files.pythonhosted.org/packages/e1/67/921ec3024056483db83953ae8e48079ad62b92db7880013ca77632921dd0/readme_renderer-44.0-py3-none-any.whl + name: readme-renderer + version: '44.0' + sha256: 2fbca89b81a08526aadf1357a8c2ae889ec05fb03f5da67f9769c9a592166151 + requires_dist: + - nh3>=0.2.14 + - docutils>=0.21.2 + - pygments>=2.5.1 + - cmarkgfm>=0.8.0 ; extra == 'md' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl + name: requests + version: 2.32.5 + sha256: 2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6 + requires_dist: + - charset-normalizer>=2,<4 + - idna>=2.5,<4 + - urllib3>=1.21.1,<3 + - certifi>=2017.4.17 + - pysocks>=1.5.6,!=1.5.7 ; extra == 'socks' + - chardet>=3.0.2,<6 ; extra == 'use-chardet-on-py3' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/3f/51/d4db610ef29373b879047326cbf6fa98b6c1969d6f6dc423279de2b1be2c/requests_toolbelt-1.0.0-py2.py3-none-any.whl + name: requests-toolbelt + version: 1.0.0 + sha256: cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06 + requires_dist: + - requests>=2.0.1,<3.0.0 + requires_python: '>=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*' +- pypi: https://files.pythonhosted.org/packages/ff/9a/9afaade874b2fa6c752c36f1548f718b5b83af81ed9b76628329dab81c1b/rfc3986-2.0.0-py2.py3-none-any.whl + name: rfc3986 + version: 2.0.0 + sha256: 50b1502b60e289cb37883f3dfd34532b8873c7de9f49bb546641ce9cbd256ebd + requires_dist: + - idna ; extra == 'idna2008' + requires_python: '>=3.7' +- pypi: https://files.pythonhosted.org/packages/ef/45/615f5babd880b4bd7d405cc0dc348234c5ffb6ed1ea33e152ede08b2072d/rich-14.3.2-py3-none-any.whl + name: rich + version: 14.3.2 + sha256: 08e67c3e90884651da3239ea668222d19bea7b589149d8014a21c633420dbb69 + requires_dist: + - ipywidgets>=7.5.1,<9 ; extra == 'jupyter' + - markdown-it-py>=2.2.0 + - pygments>=2.13.0,<3.0.0 + requires_python: '>=3.8.0' - conda: https://conda.anaconda.org/conda-forge/linux-64/ruff-0.15.0-h40fa522_0.conda noarch: python sha256: fc456645570586c798d2da12fe723b38ea0d0901373fd9959cab914cbb19518b @@ -1678,6 +2065,14 @@ packages: - ruff>=0.12.0 ; extra == 'dev' - cython-lint>=0.12.2 ; extra == 'dev' requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/b7/46/f5af3402b579fd5e11573ce652019a67074317e18c1935cc0b4ba9b35552/secretstorage-3.5.0-py3-none-any.whl + name: secretstorage + version: 3.5.0 + sha256: 0ce65888c0725fcb2c5bc0fdb8e5438eece02c523557ea40ce0703c266248137 + requires_dist: + - cryptography>=2.0 + - jeepney>=0.6 + requires_python: '>=3.10' - pypi: https://files.pythonhosted.org/packages/89/fa/d3d5ebcba3cb9e6d3775a096251860c41a6bc53a1b9461151df83fe93255/sentencepiece-0.2.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl name: sentencepiece version: 0.2.1 @@ -1754,6 +2149,23 @@ packages: - pkg:pypi/tomli?source=compressed-mapping size: 21453 timestamp: 1768146676791 +- pypi: https://files.pythonhosted.org/packages/3a/7a/882d99539b19b1490cac5d77c67338d126e4122c8276bf640e411650c830/twine-6.2.0-py3-none-any.whl + name: twine + version: 6.2.0 + sha256: 418ebf08ccda9a8caaebe414433b0ba5e25eb5e4a927667122fbe8f829f985d8 + requires_dist: + - readme-renderer>=35.0 + - requests>=2.20 + - requests-toolbelt>=0.8.0,!=0.9.0 + - urllib3>=1.26.0 + - importlib-metadata>=3.6 ; python_full_version < '3.10' + - keyring>=21.2.0 ; platform_machine != 'ppc64le' and platform_machine != 's390x' + - rfc3986>=1.4.0 + - rich>=12.0.0 + - packaging>=24.0 + - id + - keyring>=21.2.0 ; extra == 'keyring' + requires_python: '>=3.9' - pypi: https://files.pythonhosted.org/packages/13/41/5bf882649bd8b64ded5fbce7fb8d77fb3b868de1a3b1a6c4796402b47308/ty-0.0.15-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl name: ty version: 0.0.15 @@ -1815,6 +2227,17 @@ packages: - pkg:pypi/ukkonen?source=hash-mapping size: 15682 timestamp: 1769438785443 +- pypi: https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl + name: urllib3 + version: 2.6.3 + sha256: bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4 + requires_dist: + - brotli>=1.2.0 ; platform_python_implementation == 'CPython' and extra == 'brotli' + - brotlicffi>=1.2.0.0 ; platform_python_implementation != 'CPython' and extra == 'brotli' + - h2>=4,<5 ; extra == 'h2' + - pysocks>=1.5.6,!=1.5.7,<2.0 ; extra == 'socks' + - backports-zstd>=1.0.0 ; python_full_version < '3.14' and extra == 'zstd' + requires_python: '>=3.9' - conda: https://conda.anaconda.org/conda-forge/noarch/virtualenv-20.36.1-pyhd8ed1ab_0.conda sha256: fa0a21fdcd0a8e6cf64cc8cd349ed6ceb373f09854fd3c4365f0bc4586dccf9a md5: 6b0259cea8ffa6b66b35bae0ca01c447 diff --git a/pyproject.toml b/pyproject.toml index 74ec5b7..48ba246 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,6 +6,13 @@ readme = "README.md" keywords = [] authors = [{name = "Julia Jia"}] requires-python = ">=3.12,<3.13" +dependencies = [ + "joblib", + "mediapipe", + "numpy", + "pillow", + "scikit-learn", +] [tool.pixi.workspace] channels = ["conda-forge"] @@ -37,6 +44,9 @@ types = { cmd = "ty check" } test = { cmd = "pytest" } pre-commit = { cmd="pre-commit run --all-files" } pre-commit-install = { cmd="pre-commit install" } +build = { cmd = "python -m build" } +publish-test = { cmd = "twine upload --repository testpypi --verbose dist/*" } +publish = { cmd = "twine upload dist/*" } all = { depends-on = ["fmt", "lint", "types", "test"] } [tool.pixi.feature.dev.dependencies] @@ -47,6 +57,8 @@ pre-commit = ">=4.2" [tool.pixi.feature.dev.pypi-dependencies] ty = "*" +build = "*" +twine = "*" [tool.ruff] line-length = 100 From 13b5402c876d25ab521419d7d04bd000d3386cdc Mon Sep 17 00:00:00 2001 From: juliaj Date: Thu, 5 Feb 2026 22:24:10 -0800 Subject: [PATCH 2/3] Refactor predict code and add test with webcam --- .github/workflows/test.yml | 2 +- handmotion/predict.py | 124 +++++++++++++++++++++++++++++-------- pixi.lock | 4 +- pyproject.toml | 3 +- tests/test_predict.py | 122 ++++++++++++++++++++++++++++-------- tests/test_webcam.py | 85 +++++++++++++++++++++++++ 6 files changed, 283 insertions(+), 57 deletions(-) create mode 100644 tests/test_webcam.py diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e6dfd3f..08bf9c4 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -38,4 +38,4 @@ jobs: - run: pixi run lint - run: pixi run types - name: Run tests - run: pixi run test -m "not slow" -v --tb=short --timeout=60 + run: pixi run test -m "not slow and not manual" -v --tb=short --timeout=60 diff --git a/handmotion/predict.py b/handmotion/predict.py index 4c1fe74..92d1eeb 100644 --- a/handmotion/predict.py +++ b/handmotion/predict.py @@ -27,7 +27,9 @@ import argparse import logging from pathlib import Path +from typing import Optional, Tuple, Union +import numpy as np from PIL import Image from handmotion.data.feature_extractor import FeatureExtractor @@ -39,28 +41,36 @@ logger = logging.getLogger(__name__) -def predict_image(classifier, extractor, image_path): +def _extract_and_predict( + classifier: HandGestureClassifier, + image: Union[Image.Image, str, Path], + image_format: str = "rgb", +) -> Tuple[Optional[str], Optional[np.ndarray], Optional[list]]: """ - Predict gesture for a single image. + Internal helper to extract features and get predictions. Args: classifier: Loaded HandGestureClassifier - extractor: FeatureExtractor instance - image_path: Path to image file + image: PIL Image, or path to image file + image_format: "rgb" or "bgr" (default: "rgb") Returns: - Tuple of (predicted_label, confidence_dict) or (None, None) if no hand detected + Tuple of (prediction, probabilities, class_names) or (None, None, None) if no hand detected """ - # Load image - image = Image.open(image_path) - if image.mode != "RGB": - image = image.convert("RGB") + # Load image if path provided + if isinstance(image, (str, Path)): + image = Image.open(image) + if image.mode != "RGB": + image = image.convert("RGB") + image_format = "rgb" # PIL images are always RGB + + # Initialize feature extractor + extractor = FeatureExtractor() # Extract features - features_dict = extractor.extract(image, image_format="rgb") + features_dict = extractor.extract(image, image_format=image_format) if features_dict is None: - logger.warning(f"No hand detected in {image_path}") - return None, None + return None, None, None # Prepare features for classifier landmarks = features_dict["landmarks"].reshape(1, -1) # (1, 63) @@ -70,9 +80,71 @@ def predict_image(classifier, extractor, image_path): # Predict prediction = classifier.predict(features)[0] probabilities = classifier.predict_proba(features)[0] + class_names = classifier.label_encoder.classes_ + + return prediction, probabilities, class_names + + +def predict_image( + classifier: HandGestureClassifier, + image: Union[Image.Image, str, Path], + threshold: float = 0.5, + image_format: str = "rgb", +) -> Tuple[Optional[str], Optional[float]]: + """ + Predict gesture from an image. + + Args: + classifier: Loaded HandGestureClassifier + image: PIL Image, or path to image file + threshold: Minimum confidence to return prediction (0.0-1.0). + If confidence is below threshold, returns (None, None) + image_format: "rgb" or "bgr" (default: "rgb"). Only used if image is PIL Image. + + Returns: + Tuple of (label, confidence) or (None, None) if: + - No hand detected + - Confidence below threshold + """ + prediction, probabilities, class_names = _extract_and_predict(classifier, image, image_format) + if prediction is None: + logger.debug("No hand detected in image") + return None, None + + # Get confidence for predicted class + pred_idx = list(class_names).index(prediction) + confidence = float(probabilities[pred_idx]) + + # Check threshold + if confidence < threshold: + logger.debug(f"Confidence {confidence:.4f} below threshold {threshold} for {prediction}") + return None, None + + return prediction, confidence + + +def predict_image_with_proba( + classifier: HandGestureClassifier, + image: Union[Image.Image, str, Path], + image_format: str = "rgb", +) -> Tuple[Optional[str], Optional[dict]]: + """ + Predict gesture from an image with full probability distribution. + + Args: + classifier: Loaded HandGestureClassifier + image: PIL Image, or path to image file + image_format: "rgb" or "bgr" (default: "rgb") + + Returns: + Tuple of (predicted_label, confidence_dict) or (None, None) if no hand detected + """ + prediction, probabilities, class_names = _extract_and_predict(classifier, image, image_format) + if prediction is None: + logger.warning("No hand detected in image") + return None, None # Get class names and create confidence dict - class_names = classifier.label_encoder.classes_ confidence_dict = {class_names[i]: float(prob) for i, prob in enumerate(probabilities)} return prediction, confidence_dict @@ -116,25 +188,25 @@ def main(): classifier = HandGestureClassifier() classifier.load(model_path) - # Initialize feature extractor - extractor = FeatureExtractor() - # Predict logger.info(f"Processing image: {image_path}") - prediction, confidence = predict_image(classifier, extractor, image_path) - - if prediction is None: - print("No hand detected in image.") - return - - # Print results - print(f"\nPrediction: {prediction}") - print(f"Confidence: {confidence[prediction]:.4f}") - if args.show_proba: + prediction, confidence = predict_image_with_proba(classifier, image_path) + if prediction is None: + print("No hand detected in image.") + return + print(f"\nPrediction: {prediction}") + print(f"Confidence: {confidence[prediction]:.4f}") print("\nAll class probabilities:") for class_name, prob in sorted(confidence.items(), key=lambda x: x[1], reverse=True): print(f" {class_name}: {prob:.4f}") + else: + prediction, confidence = predict_image(classifier, image_path) + if prediction is None: + print("No hand detected in image.") + return + print(f"\nPrediction: {prediction}") + print(f"Confidence: {confidence:.4f}") if __name__ == "__main__": diff --git a/pixi.lock b/pixi.lock index 99b82a8..72e8b28 100644 --- a/pixi.lock +++ b/pixi.lock @@ -716,8 +716,8 @@ packages: requires_python: '>=3.10' - pypi: ./ name: handmotion - version: 0.0.1 - sha256: c06afc1f29cf2ae0cc4d86757fbcb809bc1225a1ec7cb0c4f9c6d52f758cc35e + version: 0.0.2 + sha256: 89207c15b3d1d1a7c36fda3c843b030ff7c5633998059923793a48a0ad83a505 requires_dist: - joblib - mediapipe diff --git a/pyproject.toml b/pyproject.toml index 48ba246..25fef82 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [project] name = "handmotion" description = "handmotion" -version = "0.0.1" +version = "0.0.2" readme = "README.md" keywords = [] authors = [{name = "Julia Jia"}] @@ -97,6 +97,7 @@ doctest_optionflags = "NORMALIZE_WHITESPACE" timeout = 300 markers = [ "slow: marks tests as slow (deselect with '-m \"not slow\"')", + "manual: marks tests as manual (excluded from CI)", ] filterwarnings = [ "ignore::pytest.PytestUnraisableExceptionWarning", diff --git a/tests/test_predict.py b/tests/test_predict.py index 177d7f4..2580ded 100644 --- a/tests/test_predict.py +++ b/tests/test_predict.py @@ -14,57 +14,125 @@ """Unit tests for prediction script.""" -from unittest.mock import MagicMock +from unittest.mock import MagicMock, patch import numpy as np +from PIL import Image -from handmotion.predict import predict_image +from handmotion.predict import predict_image, predict_image_with_proba class TestPredictImage: """Test cases for predict_image function.""" - def test_predict_image_success(self, sample_image, mock_feature_extractor, mock_classifier): + @patch("handmotion.predict.FeatureExtractor") + def test_predict_image_success(self, mock_extractor_class, sample_image, mock_classifier): """Test successful prediction.""" - prediction, confidence = predict_image( - mock_classifier, mock_feature_extractor, sample_image - ) + # Setup mock extractor + mock_extractor = MagicMock() + mock_extractor_class.return_value = mock_extractor + mock_extractor.extract.return_value = { + "landmarks": np.random.rand(21, 3), + "finger_states": np.array([True, False, True, False, True]), + } + + prediction, confidence = predict_image(mock_classifier, sample_image) assert prediction == "rock" - assert "rock" in confidence - assert confidence["rock"] == 0.9 - mock_feature_extractor.extract.assert_called_once() + assert isinstance(confidence, float) + assert 0.0 <= confidence <= 1.0 + mock_extractor.extract.assert_called_once() - def test_predict_image_no_hand_detected(self, sample_image): + @patch("handmotion.predict.FeatureExtractor") + def test_predict_image_no_hand_detected( + self, mock_extractor_class, sample_image, mock_classifier + ): """Test prediction when no hand is detected.""" - mock_extractor = MagicMock() + mock_extractor_class.return_value = mock_extractor mock_extractor.extract.return_value = None - mock_classifier = MagicMock() - prediction, confidence = predict_image(mock_classifier, mock_extractor, sample_image) + prediction, confidence = predict_image(mock_classifier, sample_image) assert prediction is None assert confidence is None mock_extractor.extract.assert_called_once() mock_classifier.predict.assert_not_called() - def test_predict_image_confidence_dict(self, sample_image, mock_feature_extractor): - """Test that confidence dict contains all classes.""" + @patch("handmotion.predict.FeatureExtractor") + def test_predict_image_below_threshold( + self, mock_extractor_class, sample_image, mock_classifier + ): + """Test prediction when confidence is below threshold.""" + mock_extractor = MagicMock() + mock_extractor_class.return_value = mock_extractor + mock_extractor.extract.return_value = { + "landmarks": np.random.rand(21, 3), + "finger_states": np.array([True, False, True, False, True]), + } + # Mock low confidence (0.3) + mock_classifier.predict_proba.return_value = np.array([[0.3, 0.35, 0.35]]) + + prediction, confidence = predict_image(mock_classifier, sample_image, threshold=0.5) + + assert prediction is None + assert confidence is None + + @patch("handmotion.predict.FeatureExtractor") + def test_predict_image_with_pil_image(self, mock_extractor_class, mock_classifier): + """Test prediction with PIL Image instead of path.""" + mock_extractor = MagicMock() + mock_extractor_class.return_value = mock_extractor + mock_extractor.extract.return_value = { + "landmarks": np.random.rand(21, 3), + "finger_states": np.array([True, False, True, False, True]), + } + + image = Image.new("RGB", (100, 100), color="red") + prediction, confidence = predict_image(mock_classifier, image) - mock_classifier = MagicMock() - mock_classifier._prepare_features.return_value = np.random.rand(1, 68) - mock_classifier.predict.return_value = np.array(["paper"]) + assert prediction == "rock" + assert isinstance(confidence, float) + mock_extractor.extract.assert_called_once() + + +class TestPredictImageWithProba: + """Test cases for predict_image_with_proba function.""" + + @patch("handmotion.predict.FeatureExtractor") + def test_predict_image_with_proba_success( + self, mock_extractor_class, sample_image, mock_classifier + ): + """Test successful prediction with full probability distribution.""" + mock_extractor = MagicMock() + mock_extractor_class.return_value = mock_extractor + mock_extractor.extract.return_value = { + "landmarks": np.random.rand(21, 3), + "finger_states": np.array([True, False, True, False, True]), + } mock_classifier.predict_proba.return_value = np.array([[0.1, 0.8, 0.1]]) mock_classifier.label_encoder.classes_ = np.array(["rock", "paper", "scissors"]) - prediction, confidence = predict_image( - mock_classifier, mock_feature_extractor, sample_image - ) + prediction, confidence_dict = predict_image_with_proba(mock_classifier, sample_image) - assert prediction == "paper" - assert len(confidence) == 3 - assert "rock" in confidence - assert "paper" in confidence - assert "scissors" in confidence - assert confidence["paper"] == 0.8 + assert prediction == "rock" + assert isinstance(confidence_dict, dict) + assert len(confidence_dict) == 3 + assert "rock" in confidence_dict + assert "paper" in confidence_dict + assert "scissors" in confidence_dict + assert confidence_dict["paper"] == 0.8 + + @patch("handmotion.predict.FeatureExtractor") + def test_predict_image_with_proba_no_hand( + self, mock_extractor_class, sample_image, mock_classifier + ): + """Test prediction with proba when no hand is detected.""" + mock_extractor = MagicMock() + mock_extractor_class.return_value = mock_extractor + mock_extractor.extract.return_value = None + + prediction, confidence_dict = predict_image_with_proba(mock_classifier, sample_image) + + assert prediction is None + assert confidence_dict is None diff --git a/tests/test_webcam.py b/tests/test_webcam.py new file mode 100644 index 0000000..8152607 --- /dev/null +++ b/tests/test_webcam.py @@ -0,0 +1,85 @@ +# Copyright (C) 2026 Julia Jia +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Manual test for webcam image capture and gesture prediction. + +This test is marked as manual and excluded from CI. + +Run manually with: + + pixi run test tests/test_webcam.py -m manual -v -s +""" + +from pathlib import Path + +import pytest + +from handmotion.model import HandGestureClassifier +from handmotion.predict import predict_image + +pytestmark = pytest.mark.manual + + +def test_webcam_capture_and_predict(): + """Test capturing a frame from webcam and predicting gesture.""" + try: + import cv2 + from PIL import Image + except ImportError: + pytest.skip("opencv-python or PIL not installed") + + # Check if model exists + model_path = Path("models/rps_classifier.joblib") + if not model_path.exists(): + pytest.skip(f"Model not found: {model_path}") + + # Load model + classifier = HandGestureClassifier() + classifier.load(model_path) + + # Open webcam + cap = cv2.VideoCapture(0) + if not cap.isOpened(): + pytest.skip("Webcam not available") + + try: + # Capture a single frame + ret, frame = cap.read() + assert ret, "Failed to capture frame from webcam" + assert frame is not None, "Frame is None" + assert frame.shape[0] > 0, "Frame height must be greater than 0" + assert frame.shape[1] > 0, "Frame width must be greater than 0" + + # Convert BGR to RGB and then to PIL Image + frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) + image = Image.fromarray(frame_rgb) + + # Predict using clean API + prediction, confidence = predict_image(classifier, image, threshold=0.5) + + if prediction is None: + pytest.skip("No hand detected in frame or confidence below threshold") + + # Validate prediction + class_names = classifier.label_encoder.classes_ + assert prediction in class_names, f"Prediction {prediction} not in class names" + assert 0.0 <= confidence <= 1.0, "Confidence out of range" + assert confidence >= 0.5, "Confidence below threshold" + + # Print results + print(f"\nPrediction: {prediction}") + print(f"Confidence: {confidence:.4f}") + + finally: + cap.release() From 0d78b3a6ce062c0320e2c2d56180f5ffc329bc0a Mon Sep 17 00:00:00 2001 From: juliaj Date: Thu, 5 Feb 2026 22:33:56 -0800 Subject: [PATCH 3/3] Fix types check failures --- handmotion/data/feature_extractor.py | 12 ++++++++++-- handmotion/predict.py | 7 ++++--- tests/test_webcam.py | 1 + 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/handmotion/data/feature_extractor.py b/handmotion/data/feature_extractor.py index 3c7d1c3..565b8b1 100644 --- a/handmotion/data/feature_extractor.py +++ b/handmotion/data/feature_extractor.py @@ -85,7 +85,9 @@ def __init__(self, model_path=None): min_hand_presence_confidence=0.5, min_tracking_confidence=0.5, ) - self.hand_landmarker = vision.HandLandmarker.create_from_options(options) + self.hand_landmarker: vision.HandLandmarker | None = ( + vision.HandLandmarker.create_from_options(options) + ) def close(self): """Close MediaPipe hand landmarker and release resources.""" @@ -104,7 +106,7 @@ def __exit__(self, exc_type, exc_val, exc_tb): """Context manager exit - ensures cleanup.""" self.close() - def extract(self, image, image_format="rgb") -> Optional[HandFeatures | None]: + def extract(self, image, image_format="rgb") -> Optional[HandFeatures]: """ Extract features from image. @@ -137,6 +139,12 @@ def extract(self, image, image_format="rgb") -> Optional[HandFeatures | None]: image_array = np.array(image) mp_image = Image(image_format=ImageFormat.SRGB, data=image_array) + if self.hand_landmarker is None: + raise RuntimeError( + "FeatureExtractor is closed. Create a new FeatureExtractor instance to extract " + "features." + ) + # Detect hand landmarks detection_result = self.hand_landmarker.detect(mp_image) diff --git a/handmotion/predict.py b/handmotion/predict.py index 92d1eeb..53a4648 100644 --- a/handmotion/predict.py +++ b/handmotion/predict.py @@ -45,7 +45,7 @@ def _extract_and_predict( classifier: HandGestureClassifier, image: Union[Image.Image, str, Path], image_format: str = "rgb", -) -> Tuple[Optional[str], Optional[np.ndarray], Optional[list]]: +) -> Tuple[Optional[str], Optional[np.ndarray], Optional[np.ndarray]]: """ Internal helper to extract features and get predictions. @@ -107,7 +107,7 @@ def predict_image( - Confidence below threshold """ prediction, probabilities, class_names = _extract_and_predict(classifier, image, image_format) - if prediction is None: + if prediction is None or probabilities is None or class_names is None: logger.debug("No hand detected in image") return None, None @@ -140,7 +140,7 @@ def predict_image_with_proba( Tuple of (predicted_label, confidence_dict) or (None, None) if no hand detected """ prediction, probabilities, class_names = _extract_and_predict(classifier, image, image_format) - if prediction is None: + if prediction is None or probabilities is None or class_names is None: logger.warning("No hand detected in image") return None, None @@ -195,6 +195,7 @@ def main(): if prediction is None: print("No hand detected in image.") return + assert confidence is not None print(f"\nPrediction: {prediction}") print(f"Confidence: {confidence[prediction]:.4f}") print("\nAll class probabilities:") diff --git a/tests/test_webcam.py b/tests/test_webcam.py index 8152607..b95f546 100644 --- a/tests/test_webcam.py +++ b/tests/test_webcam.py @@ -70,6 +70,7 @@ def test_webcam_capture_and_predict(): if prediction is None: pytest.skip("No hand detected in frame or confidence below threshold") + assert confidence is not None # Validate prediction class_names = classifier.label_encoder.classes_