diff --git a/poetry.lock b/poetry.lock index d04a4e8..4bfc37e 100644 --- a/poetry.lock +++ b/poetry.lock @@ -153,6 +153,70 @@ files = [ {file = "numpy-2.0.2.tar.gz", hash = "sha256:883c987dee1880e2a864ab0dc9892292582510604156762362d9326444636e78"}, ] +[[package]] +name = "numpy" +version = "2.2.0" +description = "Fundamental package for array computing in Python" +optional = false +python-versions = ">=3.10" +files = [ + {file = "numpy-2.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1e25507d85da11ff5066269d0bd25d06e0a0f2e908415534f3e603d2a78e4ffa"}, + {file = "numpy-2.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a62eb442011776e4036af5c8b1a00b706c5bc02dc15eb5344b0c750428c94219"}, + {file = "numpy-2.2.0-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:b606b1aaf802e6468c2608c65ff7ece53eae1a6874b3765f69b8ceb20c5fa78e"}, + {file = "numpy-2.2.0-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:36b2b43146f646642b425dd2027730f99bac962618ec2052932157e213a040e9"}, + {file = "numpy-2.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7fe8f3583e0607ad4e43a954e35c1748b553bfe9fdac8635c02058023277d1b3"}, + {file = "numpy-2.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:122fd2fcfafdefc889c64ad99c228d5a1f9692c3a83f56c292618a59aa60ae83"}, + {file = "numpy-2.2.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3f2f5cddeaa4424a0a118924b988746db6ffa8565e5829b1841a8a3bd73eb59a"}, + {file = "numpy-2.2.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7fe4bb0695fe986a9e4deec3b6857003b4cfe5c5e4aac0b95f6a658c14635e31"}, + {file = "numpy-2.2.0-cp310-cp310-win32.whl", hash = "sha256:b30042fe92dbd79f1ba7f6898fada10bdaad1847c44f2dff9a16147e00a93661"}, + {file = "numpy-2.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:54dc1d6d66f8d37843ed281773c7174f03bf7ad826523f73435deb88ba60d2d4"}, + {file = "numpy-2.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9874bc2ff574c40ab7a5cbb7464bf9b045d617e36754a7bc93f933d52bd9ffc6"}, + {file = "numpy-2.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0da8495970f6b101ddd0c38ace92edea30e7e12b9a926b57f5fabb1ecc25bb90"}, + {file = "numpy-2.2.0-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:0557eebc699c1c34cccdd8c3778c9294e8196df27d713706895edc6f57d29608"}, + {file = "numpy-2.2.0-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:3579eaeb5e07f3ded59298ce22b65f877a86ba8e9fe701f5576c99bb17c283da"}, + {file = "numpy-2.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40deb10198bbaa531509aad0cd2f9fadb26c8b94070831e2208e7df543562b74"}, + {file = "numpy-2.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c2aed8fcf8abc3020d6a9ccb31dbc9e7d7819c56a348cc88fd44be269b37427e"}, + {file = "numpy-2.2.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a222d764352c773aa5ebde02dd84dba3279c81c6db2e482d62a3fa54e5ece69b"}, + {file = "numpy-2.2.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4e58666988605e251d42c2818c7d3d8991555381be26399303053b58a5bbf30d"}, + {file = "numpy-2.2.0-cp311-cp311-win32.whl", hash = "sha256:4723a50e1523e1de4fccd1b9a6dcea750c2102461e9a02b2ac55ffeae09a4410"}, + {file = "numpy-2.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:16757cf28621e43e252c560d25b15f18a2f11da94fea344bf26c599b9cf54b73"}, + {file = "numpy-2.2.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:cff210198bb4cae3f3c100444c5eaa573a823f05c253e7188e1362a5555235b3"}, + {file = "numpy-2.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:58b92a5828bd4d9aa0952492b7de803135038de47343b2aa3cc23f3b71a3dc4e"}, + {file = "numpy-2.2.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:ebe5e59545401fbb1b24da76f006ab19734ae71e703cdb4a8b347e84a0cece67"}, + {file = "numpy-2.2.0-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:e2b8cd48a9942ed3f85b95ca4105c45758438c7ed28fff1e4ce3e57c3b589d8e"}, + {file = "numpy-2.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:57fcc997ffc0bef234b8875a54d4058afa92b0b0c4223fc1f62f24b3b5e86038"}, + {file = "numpy-2.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85ad7d11b309bd132d74397fcf2920933c9d1dc865487128f5c03d580f2c3d03"}, + {file = "numpy-2.2.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:cb24cca1968b21355cc6f3da1a20cd1cebd8a023e3c5b09b432444617949085a"}, + {file = "numpy-2.2.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0798b138c291d792f8ea40fe3768610f3c7dd2574389e37c3f26573757c8f7ef"}, + {file = "numpy-2.2.0-cp312-cp312-win32.whl", hash = "sha256:afe8fb968743d40435c3827632fd36c5fbde633b0423da7692e426529b1759b1"}, + {file = "numpy-2.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:3a4199f519e57d517ebd48cb76b36c82da0360781c6a0353e64c0cac30ecaad3"}, + {file = "numpy-2.2.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f8c8b141ef9699ae777c6278b52c706b653bf15d135d302754f6b2e90eb30367"}, + {file = "numpy-2.2.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0f0986e917aca18f7a567b812ef7ca9391288e2acb7a4308aa9d265bd724bdae"}, + {file = "numpy-2.2.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:1c92113619f7b272838b8d6702a7f8ebe5edea0df48166c47929611d0b4dea69"}, + {file = "numpy-2.2.0-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:5a145e956b374e72ad1dff82779177d4a3c62bc8248f41b80cb5122e68f22d13"}, + {file = "numpy-2.2.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18142b497d70a34b01642b9feabb70156311b326fdddd875a9981f34a369b671"}, + {file = "numpy-2.2.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a7d41d1612c1a82b64697e894b75db6758d4f21c3ec069d841e60ebe54b5b571"}, + {file = "numpy-2.2.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a98f6f20465e7618c83252c02041517bd2f7ea29be5378f09667a8f654a5918d"}, + {file = "numpy-2.2.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e09d40edfdb4e260cb1567d8ae770ccf3b8b7e9f0d9b5c2a9992696b30ce2742"}, + {file = "numpy-2.2.0-cp313-cp313-win32.whl", hash = "sha256:3905a5fffcc23e597ee4d9fb3fcd209bd658c352657548db7316e810ca80458e"}, + {file = "numpy-2.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:a184288538e6ad699cbe6b24859206e38ce5fba28f3bcfa51c90d0502c1582b2"}, + {file = "numpy-2.2.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:7832f9e8eb00be32f15fdfb9a981d6955ea9adc8574c521d48710171b6c55e95"}, + {file = "numpy-2.2.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:f0dd071b95bbca244f4cb7f70b77d2ff3aaaba7fa16dc41f58d14854a6204e6c"}, + {file = "numpy-2.2.0-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:b0b227dcff8cdc3efbce66d4e50891f04d0a387cce282fe1e66199146a6a8fca"}, + {file = "numpy-2.2.0-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:6ab153263a7c5ccaf6dfe7e53447b74f77789f28ecb278c3b5d49db7ece10d6d"}, + {file = "numpy-2.2.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e500aba968a48e9019e42c0c199b7ec0696a97fa69037bea163b55398e390529"}, + {file = "numpy-2.2.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:440cfb3db4c5029775803794f8638fbdbf71ec702caf32735f53b008e1eaece3"}, + {file = "numpy-2.2.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a55dc7a7f0b6198b07ec0cd445fbb98b05234e8b00c5ac4874a63372ba98d4ab"}, + {file = "numpy-2.2.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4bddbaa30d78c86329b26bd6aaaea06b1e47444da99eddac7bf1e2fab717bd72"}, + {file = "numpy-2.2.0-cp313-cp313t-win32.whl", hash = "sha256:30bf971c12e4365153afb31fc73f441d4da157153f3400b82db32d04de1e4066"}, + {file = "numpy-2.2.0-cp313-cp313t-win_amd64.whl", hash = "sha256:d35717333b39d1b6bb8433fa758a55f1081543de527171543a2b710551d40881"}, + {file = "numpy-2.2.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:e12c6c1ce84628c52d6367863773f7c8c8241be554e8b79686e91a43f1733773"}, + {file = "numpy-2.2.0-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:b6207dc8fb3c8cb5668e885cef9ec7f70189bec4e276f0ff70d5aa078d32c88e"}, + {file = "numpy-2.2.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a50aeff71d0f97b6450d33940c7181b08be1441c6c193e678211bff11aa725e7"}, + {file = "numpy-2.2.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:df12a1f99b99f569a7c2ae59aa2d31724e8d835fc7f33e14f4792e3071d11221"}, + {file = "numpy-2.2.0.tar.gz", hash = "sha256:140dd80ff8981a583a60980be1a655068f8adebf7a45a06a6858c873fcdcd4a0"}, +] + [[package]] name = "packaging" version = "24.2" @@ -448,6 +512,56 @@ dev = ["cython-lint (>=0.12.2)", "doit (>=0.36.0)", "mypy", "pycodestyle", "pyde doc = ["jupyterlite-pyodide-kernel", "jupyterlite-sphinx (>=0.12.0)", "jupytext", "matplotlib (>=3.5)", "myst-nb", "numpydoc", "pooch", "pydata-sphinx-theme (>=0.15.2)", "sphinx (>=5.0.0)", "sphinx-design (>=0.4.0)"] test = ["array-api-strict", "asv", "gmpy2", "hypothesis (>=6.30)", "mpmath", "pooch", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "scikit-umfpack", "threadpoolctl"] +[[package]] +name = "scipy" +version = "1.14.1" +description = "Fundamental algorithms for scientific computing in Python" +optional = false +python-versions = ">=3.10" +files = [ + {file = "scipy-1.14.1-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:b28d2ca4add7ac16ae8bb6632a3c86e4b9e4d52d3e34267f6e1b0c1f8d87e389"}, + {file = "scipy-1.14.1-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:d0d2821003174de06b69e58cef2316a6622b60ee613121199cb2852a873f8cf3"}, + {file = "scipy-1.14.1-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:8bddf15838ba768bb5f5083c1ea012d64c9a444e16192762bd858f1e126196d0"}, + {file = "scipy-1.14.1-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:97c5dddd5932bd2a1a31c927ba5e1463a53b87ca96b5c9bdf5dfd6096e27efc3"}, + {file = "scipy-1.14.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2ff0a7e01e422c15739ecd64432743cf7aae2b03f3084288f399affcefe5222d"}, + {file = "scipy-1.14.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8e32dced201274bf96899e6491d9ba3e9a5f6b336708656466ad0522d8528f69"}, + {file = "scipy-1.14.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8426251ad1e4ad903a4514712d2fa8fdd5382c978010d1c6f5f37ef286a713ad"}, + {file = "scipy-1.14.1-cp310-cp310-win_amd64.whl", hash = "sha256:a49f6ed96f83966f576b33a44257d869756df6cf1ef4934f59dd58b25e0327e5"}, + {file = "scipy-1.14.1-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:2da0469a4ef0ecd3693761acbdc20f2fdeafb69e6819cc081308cc978153c675"}, + {file = "scipy-1.14.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:c0ee987efa6737242745f347835da2cc5bb9f1b42996a4d97d5c7ff7928cb6f2"}, + {file = "scipy-1.14.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:3a1b111fac6baec1c1d92f27e76511c9e7218f1695d61b59e05e0fe04dc59617"}, + {file = "scipy-1.14.1-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:8475230e55549ab3f207bff11ebfc91c805dc3463ef62eda3ccf593254524ce8"}, + {file = "scipy-1.14.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:278266012eb69f4a720827bdd2dc54b2271c97d84255b2faaa8f161a158c3b37"}, + {file = "scipy-1.14.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fef8c87f8abfb884dac04e97824b61299880c43f4ce675dd2cbeadd3c9b466d2"}, + {file = "scipy-1.14.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b05d43735bb2f07d689f56f7b474788a13ed8adc484a85aa65c0fd931cf9ccd2"}, + {file = "scipy-1.14.1-cp311-cp311-win_amd64.whl", hash = "sha256:716e389b694c4bb564b4fc0c51bc84d381735e0d39d3f26ec1af2556ec6aad94"}, + {file = "scipy-1.14.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:631f07b3734d34aced009aaf6fedfd0eb3498a97e581c3b1e5f14a04164a456d"}, + {file = "scipy-1.14.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:af29a935803cc707ab2ed7791c44288a682f9c8107bc00f0eccc4f92c08d6e07"}, + {file = "scipy-1.14.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:2843f2d527d9eebec9a43e6b406fb7266f3af25a751aa91d62ff416f54170bc5"}, + {file = "scipy-1.14.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:eb58ca0abd96911932f688528977858681a59d61a7ce908ffd355957f7025cfc"}, + {file = "scipy-1.14.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:30ac8812c1d2aab7131a79ba62933a2a76f582d5dbbc695192453dae67ad6310"}, + {file = "scipy-1.14.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f9ea80f2e65bdaa0b7627fb00cbeb2daf163caa015e59b7516395fe3bd1e066"}, + {file = "scipy-1.14.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:edaf02b82cd7639db00dbff629995ef185c8df4c3ffa71a5562a595765a06ce1"}, + {file = "scipy-1.14.1-cp312-cp312-win_amd64.whl", hash = "sha256:2ff38e22128e6c03ff73b6bb0f85f897d2362f8c052e3b8ad00532198fbdae3f"}, + {file = "scipy-1.14.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1729560c906963fc8389f6aac023739ff3983e727b1a4d87696b7bf108316a79"}, + {file = "scipy-1.14.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:4079b90df244709e675cdc8b93bfd8a395d59af40b72e339c2287c91860deb8e"}, + {file = "scipy-1.14.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:e0cf28db0f24a38b2a0ca33a85a54852586e43cf6fd876365c86e0657cfe7d73"}, + {file = "scipy-1.14.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:0c2f95de3b04e26f5f3ad5bb05e74ba7f68b837133a4492414b3afd79dfe540e"}, + {file = "scipy-1.14.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b99722ea48b7ea25e8e015e8341ae74624f72e5f21fc2abd45f3a93266de4c5d"}, + {file = "scipy-1.14.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5149e3fd2d686e42144a093b206aef01932a0059c2a33ddfa67f5f035bdfe13e"}, + {file = "scipy-1.14.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e4f5a7c49323533f9103d4dacf4e4f07078f360743dec7f7596949149efeec06"}, + {file = "scipy-1.14.1-cp313-cp313-win_amd64.whl", hash = "sha256:baff393942b550823bfce952bb62270ee17504d02a1801d7fd0719534dfb9c84"}, + {file = "scipy-1.14.1.tar.gz", hash = "sha256:5a275584e726026a5699459aa72f828a610821006228e841b94275c4a7c08417"}, +] + +[package.dependencies] +numpy = ">=1.23.5,<2.3" + +[package.extras] +dev = ["cython-lint (>=0.12.2)", "doit (>=0.36.0)", "mypy (==1.10.0)", "pycodestyle", "pydevtool", "rich-click", "ruff (>=0.0.292)", "types-psutil", "typing_extensions"] +doc = ["jupyterlite-pyodide-kernel", "jupyterlite-sphinx (>=0.13.1)", "jupytext", "matplotlib (>=3.5)", "myst-nb", "numpydoc", "pooch", "pydata-sphinx-theme (>=0.15.2)", "sphinx (>=5.0.0,<=7.3.7)", "sphinx-design (>=0.4.0)"] +test = ["Cython", "array-api-strict (>=2.0)", "asv", "gmpy2", "hypothesis (>=6.30)", "meson", "mpmath", "ninja", "pooch", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "scikit-umfpack", "threadpoolctl"] + [[package]] name = "tomli" version = "2.2.1" @@ -512,4 +626,4 @@ test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess [metadata] lock-version = "2.0" python-versions = "^3.9" -content-hash = "780ccef31945f82bf2061b1c3ae7486c93fde3679c826e8409328bf8cf7e4e5e" +content-hash = "c4129df26d98ea12885d07b14380f5ad735e06654941c80e999108ad4a6e5ce1" diff --git a/py360convert/__init__.py b/py360convert/__init__.py index 8293b5f..751d799 100644 --- a/py360convert/__init__.py +++ b/py360convert/__init__.py @@ -1,4 +1,25 @@ +__all__ = [ + "c2e", + "cube_dice2h", + "cube_dict2h", + "cube_h2dice", + "cube_h2dict", + "cube_h2list", + "cube_list2h", + "e2c", + "e2p", + "utils", +] + +from . import utils from .c2e import c2e from .e2c import e2c from .e2p import e2p -from .utils import * +from .utils import ( + cube_dice2h, + cube_dict2h, + cube_h2dice, + cube_h2dict, + cube_h2list, + cube_list2h, +) diff --git a/py360convert/__main__.py b/py360convert/__main__.py index 6fc343b..1d72a00 100644 --- a/py360convert/__main__.py +++ b/py360convert/__main__.py @@ -34,9 +34,9 @@ def main(): # Convert if args.convert == "c2e": - out = py360convert.c2e(img, h=args.h, w=args.w, mode=args.mode) + out = py360convert.c2e(img, h=args.h, w=args.w, mode=args.mode) # pyright: ignore[reportCallIssue] elif args.convert == "e2c": - out = py360convert.e2c(img, face_w=args.w, mode=args.mode) + out = py360convert.e2c(img, face_w=args.w, mode=args.mode) # pyright: ignore[reportCallIssue] elif args.convert == "e2p": out = py360convert.e2p( img, diff --git a/py360convert/c2e.py b/py360convert/c2e.py index dff9fdc..e5524f8 100644 --- a/py360convert/c2e.py +++ b/py360convert/c2e.py @@ -1,9 +1,77 @@ +from typing import Literal, Union, overload + import numpy as np +from numpy.typing import NDArray + +from .utils import ( + CubeFormat, + DType, + InterpolationMode, + cube_dice2h, + cube_dict2h, + cube_list2h, + equirect_facetype, + equirect_uvgrid, + sample_cubefaces, +) + + +@overload +def c2e( + cubemap: NDArray[DType], + h: int, + w: int, + mode: InterpolationMode = "bilinear", + cube_format: Literal["horizon", "dice"] = "dice", +) -> NDArray[DType]: ... + + +@overload +def c2e( + cubemap: list[NDArray[DType]], + h: int, + w: int, + mode: InterpolationMode = "bilinear", + cube_format: Literal["list"] = "list", +) -> NDArray[DType]: ... + + +@overload +def c2e( + cubemap: dict[str, NDArray[DType]], + h: int, + w: int, + mode: InterpolationMode = "bilinear", + cube_format: Literal["dict"] = "dict", +) -> NDArray[DType]: ... + -from . import utils +def c2e( + cubemap: Union[NDArray[DType], list[NDArray[DType]], dict[str, NDArray[DType]]], + h: int, + w: int, + mode: InterpolationMode = "bilinear", + cube_format: CubeFormat = "dice", +) -> NDArray: + """Convert the cubemap to equirectangular. + Parameters + ---------- + cubemap: Union[NDArray, list[NDArray], dict[str, NDArray]] + h: int + Output equirectangular height. + w: int + Output equirectangular width. + mode: Literal["bilinear", "nearest"] + Interpolation mode. + cube_format: Literal["horizon", "list", "dict", "dice"] + Format of input cubemap. -def c2e(cubemap, h, w, mode="bilinear", cube_format="dice"): + Returns + ------- + np.ndarray + Equirectangular image. + """ if mode == "bilinear": order = 1 elif mode == "nearest": @@ -12,13 +80,20 @@ def c2e(cubemap, h, w, mode="bilinear", cube_format="dice"): raise ValueError(f'Unknown mode "{mode}".') if cube_format == "horizon": - pass + if not isinstance(cubemap, np.ndarray): + raise TypeError('cubemap must be a numpy array for cube_format="horizon"') elif cube_format == "list": - cubemap = utils.cube_list2h(cubemap) + if not isinstance(cubemap, list): + raise TypeError('cubemap must be a list for cube_format="list"') + cubemap = cube_list2h(cubemap) elif cube_format == "dict": - cubemap = utils.cube_dict2h(cubemap) + if not isinstance(cubemap, dict): + raise TypeError('cubemap must be a dict for cube_format="dict"') + cubemap = cube_dict2h(cubemap) elif cube_format == "dice": - cubemap = utils.cube_dice2h(cubemap) + if not isinstance(cubemap, np.ndarray): + raise TypeError('cubemap must be a numpy array for cube_format="dice"') + cubemap = cube_dice2h(cubemap) else: raise ValueError('Unknown cube_format "{cube_format}".') @@ -30,14 +105,14 @@ def c2e(cubemap, h, w, mode="bilinear", cube_format="dice"): raise ValueError("w must be a multiple of 8.") face_w = cubemap.shape[0] - uv = utils.equirect_uvgrid(h, w) + uv = equirect_uvgrid(h, w) u, v = np.split(uv, 2, axis=-1) u = u[..., 0] v = v[..., 0] cube_faces = np.stack(np.split(cubemap, 6, 1), 0) # Get face id to each pixel: 0F 1R 2B 3L 4U 5D - tp = utils.equirect_facetype(h, w) + tp = equirect_facetype(h, w) coor_x = np.zeros((h, w)) coor_y = np.zeros((h, w)) @@ -57,12 +132,12 @@ def c2e(cubemap, h, w, mode="bilinear", cube_format="dice"): coor_y[mask] = -c * np.cos(u[mask]) # Final renormalize - coor_x = (np.clip(coor_x, -0.5, 0.5) + 0.5) * face_w - coor_y = (np.clip(coor_y, -0.5, 0.5) + 0.5) * face_w + coor_x_norm = (np.clip(coor_x, -0.5, 0.5) + 0.5) * face_w + coor_y_norm = (np.clip(coor_y, -0.5, 0.5) + 0.5) * face_w equirec = np.stack( [ - utils.sample_cubefaces(cube_faces[..., i], tp, coor_y, coor_x, order=order) + sample_cubefaces(cube_faces[..., i], tp, coor_y_norm, coor_x_norm, order=order) for i in range(cube_faces.shape[3]) ], axis=-1, diff --git a/py360convert/e2c.py b/py360convert/e2c.py index f375541..8beefb3 100644 --- a/py360convert/e2c.py +++ b/py360convert/e2c.py @@ -1,12 +1,72 @@ +from typing import Literal, Union, overload + import numpy as np +from numpy.typing import NDArray -from . import utils +from .utils import ( + CubeFormat, + DType, + InterpolationMode, + cube_h2dice, + cube_h2dict, + cube_h2list, + sample_equirec, + uv2coor, + xyz2uv, + xyzcube, +) -def e2c(e_img, face_w=256, mode="bilinear", cube_format="dice"): - """ - e_img: ndarray in shape of [H, W, *] - face_w: int, the length of each face of the cubemap +@overload +def e2c( # pyright: ignore[reportOverlappingOverload] + e_img: NDArray[DType], + face_w: int = 256, + mode: InterpolationMode = "bilinear", + cube_format: Literal["horizon", "dice"] = "dice", +) -> NDArray[DType]: ... + + +@overload +def e2c( + e_img: NDArray[DType], + face_w: int = 256, + mode: InterpolationMode = "bilinear", + cube_format: Literal["list"] = "list", +) -> list[NDArray[DType]]: ... + + +@overload +def e2c( + e_img: NDArray[DType], + face_w: int = 256, + mode: InterpolationMode = "bilinear", + cube_format: Literal["dict"] = "dict", +) -> dict[str, NDArray[DType]]: ... + + +def e2c( + e_img: NDArray[DType], + face_w: int = 256, + mode: InterpolationMode = "bilinear", + cube_format: CubeFormat = "dice", +) -> Union[NDArray[DType], list[NDArray[DType]], dict[str, NDArray[DType]]]: + """Convert equirectangular image to cubemap. + + Parameters + ---------- + e_img: ndarray + Equirectangular image in shape of [H, W, *]. + face_w: int + Length of each face of the cubemap + mode: Literal["bilinear", "nearest"] + Interpolation mode. + cube_format: Literal["horizon", "list", "dict", "dice"] + Format to return cubemap in. + + Returns + ------- + Union[NDArray, list[NDArray], dict[str, NDArray]] + Cubemap in format specified by `cube_format`. """ if e_img.ndim != 3: raise ValueError("e_img must have 3 dimensions.") @@ -16,25 +76,27 @@ def e2c(e_img, face_w=256, mode="bilinear", cube_format="dice"): elif mode == "nearest": order = 0 else: - raise NotImplementedError("unknown mode") + raise ValueError(f'Unknown mode: "{mode}".') - xyz = utils.xyzcube(face_w) - uv = utils.xyz2uv(xyz) - coor_xy = utils.uv2coor(uv, h, w) + xyz = xyzcube(face_w) + uv = xyz2uv(xyz) + coor_xy = uv2coor(uv, h, w) cubemap = np.stack( - [utils.sample_equirec(e_img[..., i], coor_xy, order=order) for i in range(e_img.shape[2])], axis=-1 + [sample_equirec(e_img[..., i], coor_xy, order=order) for i in range(e_img.shape[2])], + axis=-1, + dtype=e_img.dtype, ) if cube_format == "horizon": pass elif cube_format == "list": - cubemap = utils.cube_h2list(cubemap) + cubemap = cube_h2list(cubemap) elif cube_format == "dict": - cubemap = utils.cube_h2dict(cubemap) + cubemap = cube_h2dict(cubemap) elif cube_format == "dice": - cubemap = utils.cube_h2dice(cubemap) + cubemap = cube_h2dice(cubemap) else: - raise NotImplementedError() + raise NotImplementedError return cubemap diff --git a/py360convert/e2p.py b/py360convert/e2p.py index 3f4d74e..db79cf2 100644 --- a/py360convert/e2p.py +++ b/py360convert/e2p.py @@ -1,23 +1,61 @@ +from numbers import Real +from typing import Union + import numpy as np +from numpy.typing import NDArray -from . import utils +from .utils import ( + DType, + InterpolationMode, + sample_equirec, + uv2coor, + xyz2uv, + xyzpers, +) -def e2p(e_img, fov_deg, u_deg, v_deg, out_hw, in_rot_deg=0, mode="bilinear"): - """ - e_img: ndarray in shape of [H, W, *] +def e2p( + e_img: NDArray[DType], + fov_deg: Union[Real, tuple[float, float]], + u_deg: float, + v_deg: float, + out_hw: tuple[int, int], + in_rot_deg: float = 0, + mode: InterpolationMode = "bilinear", +) -> NDArray[DType]: + """Convert equirectangular image to perspective. + + Parameters + ---------- + e_img: ndarray + Equirectangular image in shape of [H, W, *]. fov_deg: scalar or (scalar, scalar) field of view in degree + Field of view given in float or tuple (h_fov_deg, v_fov_deg). u_deg: horizon viewing angle in range [-180, 180] + Horizontal viewing angle in range [-pi, pi]. (- Left / + Right). v_deg: vertical viewing angle in range [-90, 90] + Vertical viewing angle in range [-pi/2, pi/2]. (- Down/ + Up). + out_hw: tuple[int, int] + Size of output perspective image. + in_rot_deg: float + Inplane rotation. + mode: Literal["bilinear", "nearest"] + Interpolation mode. + + Returns + ------- + np.ndarray + Perspective image. """ if e_img.ndim != 3: raise ValueError("e_img must have 3 dimensions.") h, w = e_img.shape[:2] - try: - h_fov, v_fov = np.deg2rad(fov_deg[0]), np.deg2rad(fov_deg[1]) - except IndexError: - h_fov = v_fov = np.deg2rad(fov_deg) + if isinstance(fov_deg, Real): + h_fov = v_fov = np.deg2rad(float(fov_deg)) + else: + h_fov, v_fov = map(np.deg2rad, fov_deg) + in_rot = in_rot_deg * np.pi / 180 if mode == "bilinear": @@ -25,16 +63,14 @@ def e2p(e_img, fov_deg, u_deg, v_deg, out_hw, in_rot_deg=0, mode="bilinear"): elif mode == "nearest": order = 0 else: - raise NotImplementedError("unknown mode") + raise ValueError(f'Unknown mode: "{mode}".') u = -u_deg * np.pi / 180 v = v_deg * np.pi / 180 - xyz = utils.xyzpers(h_fov, v_fov, u, v, out_hw, in_rot) - uv = utils.xyz2uv(xyz) - coor_xy = utils.uv2coor(uv, h, w) + xyz = xyzpers(h_fov, v_fov, u, v, out_hw, in_rot) + uv = xyz2uv(xyz) + coor_xy = uv2coor(uv, h, w) - pers_img = np.stack( - [utils.sample_equirec(e_img[..., i], coor_xy, order=order) for i in range(e_img.shape[2])], axis=-1 - ) + pers_img = np.stack([sample_equirec(e_img[..., i], coor_xy, order=order) for i in range(e_img.shape[2])], axis=-1) return pers_img diff --git a/py360convert/py.typed b/py360convert/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/py360convert/utils.py b/py360convert/utils.py index 98191c9..89c3f47 100644 --- a/py360convert/utils.py +++ b/py360convert/utils.py @@ -1,12 +1,16 @@ from collections.abc import Sequence -from typing import Any +from typing import Any, Literal, Optional, TypeVar, Union import numpy as np from numpy.typing import NDArray from scipy.ndimage import map_coordinates +CubeFormat = Literal["horizon", "list", "dict", "dice"] +InterpolationMode = Literal["bilinear", "nearest"] +DType = TypeVar("DType", bound=np.generic, covariant=True) -def xyzcube(face_w): + +def xyzcube(face_w: int) -> NDArray[np.float32]: """ Return the xyz coordinates of the unit cube in [F R B L U D] format. @@ -55,23 +59,40 @@ def xyzcube(face_w): return out -def equirect_uvgrid(h, w): +def equirect_uvgrid(h: int, w: int) -> NDArray[np.float32]: u = np.linspace(-np.pi, np.pi, num=w, dtype=np.float32) v = np.linspace(np.pi, -np.pi, num=h, dtype=np.float32) / 2 return np.stack(np.meshgrid(u, v), axis=-1) -def equirect_facetype(h, w): - """ - 0F 1R 2B 3L 4U 5D +def equirect_facetype(h: int, w: int) -> NDArray[np.int32]: + """Generate a facetype index image. + + Parameters + ---------- + h: int + Height. + w: int + Width. + + Returns + ------- + ndarray + Array where the integer elements represent the face type: + * front - 0 + * right - 1 + * back - 2 + * left - 3 + * up - 4 + * down - 5 """ tp = np.roll(np.arange(4).repeat(w // 4)[None, :].repeat(h, 0), 3 * w // 8, 1) # Prepare ceil mask mask = np.zeros((h, w // 4), np.bool_) idx = np.linspace(-np.pi, np.pi, w // 4) / 4 - idx = h // 2 - np.round(np.arctan(np.cos(idx)) * h / np.pi).astype(int) + idx = (h // 2 - np.round(np.arctan(np.cos(idx)) * h / np.pi)).astype(int) for i, j in enumerate(idx): mask[:j, i] = 1 mask = np.roll(np.concatenate([mask] * 4, 1), 3 * w // 8, 1) @@ -82,7 +103,7 @@ def equirect_facetype(h, w): return tp.astype(np.int32) -def xyzpers(h_fov, v_fov, u, v, out_hw, in_rot): +def xyzpers(h_fov: float, v_fov: float, u: float, v: float, out_hw: tuple[int, int], in_rot: float) -> NDArray: out = np.ones((*out_hw, 3), np.float32) x_max = np.tan(h_fov / 2) @@ -97,120 +118,107 @@ def xyzpers(h_fov, v_fov, u, v, out_hw, in_rot): return out.dot(Rx).dot(Ry).dot(Ri) -def xyz2uv(xyz): - """ - - Transform cartesian (x,y,z) to spherical(r, u, v), and only - out put (u, v). +def xyz2uv(xyz: NDArray[DType]) -> NDArray[DType]: + """Transform cartesian (x,y,z) to spherical(r, u, v), and only outputs (u, v). Parameters ---------- - xyz: ndarray - An array object in shape of [..., 3]. + xyz: ndarray + An array object in shape of [..., 3]. Returns ------- - out: ndarray - An array object in shape of [..., 2], - any point i of this array is in [-pi, pi]. + out: ndarray + An array object in shape of [..., 2], + any point i of this array is in [-pi, pi]. Notes ----- - In this project, e2c calls utils.xyz2uv(xyz) where - xyz is in [-0.5, 0.5] x [-0.5, 0.5] x [-0.5, 0.5] - so - u is in [-pi, pi] - v is in [-pi/2, pi/2] - so - any point i of output array is in [-pi, pi] x [-pi/2, pi/2]. - + In this project, e2c calls utils.xyz2uv(xyz) where: + * xyz is in [-0.5, 0.5] x [-0.5, 0.5] x [-0.5, 0.5] + * u is in [-pi, pi] + * v is in [-pi/2, pi/2] + * any point i of output array is in [-pi, pi] x [-pi/2, pi/2]. """ x, y, z = np.split(xyz, 3, axis=-1) u = np.arctan2(x, z) - c = np.sqrt(x**2 + z**2) + c = np.sqrt(np.square(x) + np.square(z)) v = np.arctan2(y, c) - - out = np.concatenate([u, v], axis=-1) - + out = np.concatenate([u, v], axis=-1, dtype=xyz.dtype) return out -def uv2unitxyz(uv): +def uv2unitxyz(uv: NDArray[DType]) -> NDArray[DType]: u, v = np.split(uv, 2, axis=-1) y = np.sin(v) c = np.cos(v) x = c * np.sin(u) z = c * np.cos(u) - - return np.concatenate([x, y, z], axis=-1) + return np.concatenate([x, y, z], axis=-1, dtype=uv.dtype) -def uv2coor(uv, h, w): - """ +def uv2coor(uv: NDArray[DType], h: int, w: int) -> NDArray[DType]: + """Transform spherical(r, u, v) into equirectangular(x, y). - Transform spherical(r, u, v) into equirectangular(x, y) - with height h and width w. Assume that u has range 2pi and - v has range pi. Notice that the coordinate of the equirectangular - is from (0.5, 0.5) to (h-0.5, w-0.5). + Assume that u has range 2pi and v has range pi. + The coordinate of the equirectangular is from (0.5, 0.5) to (h-0.5, w-0.5). Parameters ---------- - uv: ndarray - An array object in shape of [..., 2]. - h: int - Height of the equirectangular image. - w: int - Width of the equirectangular image. + uv: ndarray + An array object in shape of [..., 2]. + h: int + Height of the equirectangular image. + w: int + Width of the equirectangular image. Returns ------- - out: ndarray - An array object in shape of [..., 2]. + out: ndarray + An array object in shape of [..., 2]. Notes ----- - In this project, e2c calls utils.uv2coor(uv, h, w) where - uv is in [-pi, pi] x [-pi/2, pi/2] - so - coor_x is in [-0.5, w-0.5] - coor_y is in [-0.5, h-0.5] + In this project, e2c calls utils.uv2coor(uv, h, w) where: + * uv is in [-pi, pi] x [-pi/2, pi/2] + * coor_x is in [-0.5, w-0.5] + * coor_y is in [-0.5, h-0.5] """ u, v = np.split(uv, 2, axis=-1) - coor_x = (u / (2 * np.pi) + 0.5) * w - 0.5 - coor_y = (-v / np.pi + 0.5) * h - 0.5 - - out = np.concatenate([coor_x, coor_y], axis=-1) - + coor_x = (u / (2 * np.pi) + 0.5) * w - 0.5 # pyright: ignore[reportOperatorIssue] + coor_y = (-v / np.pi + 0.5) * h - 0.5 # pyright: ignore[reportOperatorIssue] + out = np.concatenate([coor_x, coor_y], axis=-1, dtype=uv.dtype) return out -def coor2uv(coorxy, h, w): +def coor2uv(coorxy: NDArray[DType], h: int, w: int) -> NDArray[DType]: coor_x, coor_y = np.split(coorxy, 2, axis=-1) - u = ((coor_x + 0.5) / w - 0.5) * 2 * np.pi - v = -((coor_y + 0.5) / h - 0.5) * np.pi - - return np.concatenate([u, v], axis=-1) + u = ((coor_x + 0.5) / w - 0.5) * 2 * np.pi # pyright: ignore[reportOperatorIssue] + v = -((coor_y + 0.5) / h - 0.5) * np.pi # pyright: ignore[reportOperatorIssue] + return np.concatenate([u, v], axis=-1, dtype=coorxy.dtype) -def sample_equirec(e_img, coor_xy, order): +def sample_equirec(e_img: NDArray[DType], coor_xy: NDArray, order: int) -> NDArray[DType]: w = e_img.shape[1] coor_x, coor_y = np.split(coor_xy, 2, axis=-1) pad_u = np.roll(e_img[[0]], w // 2, 1) pad_d = np.roll(e_img[[-1]], w // 2, 1) - e_img = np.concatenate([e_img, pad_d, pad_u], 0) - return map_coordinates(e_img, [coor_y, coor_x], order=order, mode="wrap")[..., 0] + e_img = np.concatenate([e_img, pad_d, pad_u], 0, dtype=e_img.dtype) + return map_coordinates(e_img, [coor_y, coor_x], order=order, mode="wrap")[..., 0] # pyright: ignore[reportReturnType] -def sample_cubefaces(cube_faces, tp, coor_y, coor_x, order): +def sample_cubefaces( + cube_faces: NDArray[DType], tp: NDArray, coor_y: NDArray, coor_x: NDArray, order: int +) -> NDArray[DType]: cube_faces = cube_faces.copy() cube_faces[1] = np.flip(cube_faces[1], 1) cube_faces[2] = np.flip(cube_faces[2], 1) cube_faces[4] = np.flip(cube_faces[4], 0) # Pad up down - pad_ud = np.zeros((6, 2, cube_faces.shape[2])) + pad_ud = np.zeros((6, 2, cube_faces.shape[2]), dtype=cube_faces.dtype) pad_ud[0, 0] = cube_faces[5, 0, :] pad_ud[0, 1] = cube_faces[4, -1, :] pad_ud[1, 0] = cube_faces[5, :, -1] @@ -223,10 +231,10 @@ def sample_cubefaces(cube_faces, tp, coor_y, coor_x, order): pad_ud[4, 1] = cube_faces[2, 0, ::-1] pad_ud[5, 0] = cube_faces[2, -1, ::-1] pad_ud[5, 1] = cube_faces[0, -1, :] - cube_faces = np.concatenate([cube_faces, pad_ud], 1) + cube_faces = np.concatenate([cube_faces, pad_ud], 1, dtype=cube_faces.dtype) # Pad left right - pad_lr = np.zeros((6, cube_faces.shape[1], 2)) + pad_lr = np.zeros((6, cube_faces.shape[1], 2), dtype=cube_faces.dtype) pad_lr[0, :, 0] = cube_faces[1, :, 0] pad_lr[0, :, 1] = cube_faces[3, :, -1] pad_lr[1, :, 0] = cube_faces[2, :, 0] @@ -239,19 +247,19 @@ def sample_cubefaces(cube_faces, tp, coor_y, coor_x, order): pad_lr[4, 1:-1, 1] = cube_faces[3, 0, :] pad_lr[5, 1:-1, 0] = cube_faces[1, -2, :] pad_lr[5, 1:-1, 1] = cube_faces[3, -2, ::-1] - cube_faces = np.concatenate([cube_faces, pad_lr], 2) + cube_faces = np.concatenate([cube_faces, pad_lr], 2, dtype=cube_faces.dtype) - return map_coordinates(cube_faces, [tp, coor_y, coor_x], order=order, mode="wrap") + return map_coordinates(cube_faces, [tp, coor_y, coor_x], order=order, mode="wrap") # pyright: ignore[reportReturnType] -def cube_h2list(cube_h) -> list[NDArray]: +def cube_h2list(cube_h: NDArray[DType]) -> list[NDArray[DType]]: """Split an image into a list of 6 faces.""" if cube_h.shape[0] * 6 != cube_h.shape[1]: raise ValueError("Cubemap's width must by 6x its height.") return np.split(cube_h, 6, axis=1) -def cube_list2h(cube_list: list[NDArray]) -> NDArray: +def cube_list2h(cube_list: list[NDArray[DType]]) -> NDArray[DType]: """Concatenate a list of 6 face images side-by-side.""" if len(cube_list) != 6: raise ValueError(f"6 elements must be provided to construct a cube; got {len(cube_list)}.") @@ -260,21 +268,26 @@ def cube_list2h(cube_list: list[NDArray]) -> NDArray: raise ValueError( f"Face {i}'s shape {face.shape} doesn't match the first face's shape {cube_list[0].shape}." ) - return np.concatenate(cube_list, axis=1) + if face.dtype != cube_list[0].dtype: + raise ValueError( + f"Face {i}'s dtype {face.dtype} doesn't match the first face's shape {cube_list[0].dtype}." + ) + + return np.concatenate(cube_list, axis=1, dtype=cube_list[0].dtype) -def cube_h2dict(cube_h) -> dict[str, NDArray]: +def cube_h2dict(cube_h: NDArray[DType]) -> dict[str, NDArray[DType]]: return dict(zip("FRBLUD", cube_h2list(cube_h))) -def cube_dict2h(cube_dict: dict[Any, NDArray], face_k: Sequence | None = None) -> NDArray: +def cube_dict2h(cube_dict: dict[Any, NDArray[DType]], face_k: Optional[Sequence] = None) -> NDArray[DType]: face_k = face_k or "FRBLUD" if len(face_k) != 6: raise ValueError(f"6 face_k keys must be provided to construct a cube; got {len(face_k)}.") return cube_list2h([cube_dict[k] for k in face_k]) -def cube_h2dice(cube_h: NDArray) -> NDArray: +def cube_h2dice(cube_h: NDArray[DType]) -> NDArray[DType]: if cube_h.shape[0] * 6 != cube_h.shape[1]: raise ValueError("Cubemap's width must by 6x its height.") w = cube_h.shape[0] @@ -292,7 +305,7 @@ def cube_h2dice(cube_h: NDArray) -> NDArray: return cube_dice -def cube_dice2h(cube_dice: NDArray) -> NDArray: +def cube_dice2h(cube_dice: NDArray[DType]) -> NDArray[DType]: w = cube_dice.shape[0] // 3 if cube_dice.shape[0] % 3 != 0: raise ValueError("Dice image height must be a multiple of 3.") @@ -311,7 +324,7 @@ def cube_dice2h(cube_dice: NDArray) -> NDArray: return cube_h -def rotation_matrix(rad, ax): +def rotation_matrix(rad: float, ax: Union[NDArray, Sequence]): ax = np.array(ax) if ax.shape != (3,): raise ValueError(f"ax must be shape (3,); got {ax.shape}") diff --git a/pyproject.toml b/pyproject.toml index 5b81ad0..b0e1543 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,8 +27,14 @@ convert360 = "py360convert.__main__:main" [tool.poetry.dependencies] # Be as loose as possible if writing a library. python = "^3.9" -numpy = ">=1.20.0" -scipy = ">=1.2.0" +numpy = [ + {version = ">=1.20.0", python = "<3.13"}, + {version = ">=2.1.0", python = ">=3.13"}, +] +scipy = [ + {version = ">=1.2.0", python = "<3.13"}, + {version = ">=1.14.0", python = ">=3.13"}, +] pillow = ">=6.0.0" [tool.poetry.group.dev.dependencies]