diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..82d958a --- /dev/null +++ b/.envrc @@ -0,0 +1,6 @@ +#!/usr/bin/env bash + +# This file is automatically loaded with `direnv` if allowed. +# It enters you into the venv. + +source .localenv diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..683940d --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +__pycache__ +/data/models/ +/data/archives/ +/experiments/logdir/ +/.env/ +/.direnv/ +*.zip +*.sh +default.yaml # pandoc preview enhanced diff --git a/.localenv b/.localenv new file mode 100644 index 0000000..8fd87dc --- /dev/null +++ b/.localenv @@ -0,0 +1,71 @@ +#!/usr/bin/env bash + +# ======================= +# bootstrap a venv +# ======================= + +LOCAL_ENV_NAME="py310-$(basename $(pwd))" +LOCAL_ENV_DIR="$(pwd)/.env/$LOCAL_ENV_NAME" +mkdir -p "$LOCAL_ENV_DIR" + +# make configs and caches a part of venv +export POETRY_CACHE_DIR="$LOCAL_ENV_DIR/xdg/cache/poetry" +export PIP_CACHE_DIR="$LOCAL_ENV_DIR/xdg/cache/pip" +mkdir -p "$POETRY_CACHE_DIR" "$PIP_CACHE_DIR" + +#export POETRY_VIRTUALENVS_IN_PROJECT=true # store venv in ./.venv/ +#export POETRY_VIRTUALENVS_CREATE=false # install globally +export SETUPTOOLS_USE_DISTUTILS=stdlib # https://github.com/pre-commit/pre-commit/issues/2178#issuecomment-1002163763 +export IFIELD_PRETTY_TRACEBACK=1 +#export SHOW_LOCALS=1 # locals in tracebacks +export PYTHON_KEYRING_BACKEND="keyring.backends.null.Keyring" + +# ensure we have the correct python and poetry. Bootstrap via conda if missing +if ! command -v python310 >/dev/null || ! command -v poetry >/dev/null; then + source .localenv-bootstrap-conda + + if command -v mamba >/dev/null; then + CONDA=mamba + elif command -v conda >/dev/null; then + CONDA=conda + else + >&2 echo "ERROR: 'poetry' nor 'conda'/'mamba' could be found!" + exit 1 + fi + + function verbose { + echo +"$(printf " %q" "$@")" + "$@" + } + + if ! ($CONDA env list | grep -q "^$LOCAL_ENV_NAME "); then + verbose $CONDA create --yes --name "$LOCAL_ENV_NAME" -c conda-forge \ + python==3.10.8 poetry==1.3.1 #python-lsp-server + true + fi + + verbose conda activate "$LOCAL_ENV_NAME" || exit $? + #verbose $CONDA activate "$LOCAL_ENV_NAME" || exit $? + + unset -f verbose +fi + + +# enter poetry venv +# source .envrc +poetry run true # ensure venv exists +#source "$(poetry env info -p)/bin/activate" +export VIRTUAL_ENV=$(poetry env info --path) +export POETRY_ACTIVE=1 +export PATH="$VIRTUAL_ENV/bin":"$PATH" +# NOTE: poetry currently reuses and populates the conda venv. +# See: https://github.com/python-poetry/poetry/issues/1724 + + +# ensure output dirs exist +mkdir -p experiments/logdir + +# first-time-setup poetry +if ! command -v fix-my-functions >/dev/null; then + poetry install +fi diff --git a/.localenv-bootstrap-conda b/.localenv-bootstrap-conda new file mode 100644 index 0000000..d6e52b5 --- /dev/null +++ b/.localenv-bootstrap-conda @@ -0,0 +1,53 @@ +#!/usr/bin/env bash + +# ======================= +# bootstrap a conda venv +# ======================= + +CONDA_ENV_DIR="${LOCAL_ENV_DIR:-$(pwd)/.conda310}" +mkdir -p "$CONDA_ENV_DIR" +#touch "$HOME/.Xauthority" + +MINICONDA_PY310_URL="https://repo.anaconda.com/miniconda/Miniconda3-py310_22.11.1-1-Linux-x86_64.sh" +MINICONDA_PY310_HASH="00938c3534750a0e4069499baf8f4e6dc1c2e471c86a59caa0dd03f4a9269db6" + +# Check if conda is available +if ! command -v conda >/dev/null; then + export PATH="$CONDA_ENV_DIR/conda/bin:$PATH" +fi + +# Check again if conda is available, install miniconda if not +if ! command -v conda >/dev/null; then + (set -e #x + function verbose { + echo +"$(printf " %q" "$@")" + "$@" + } + + if command -v curl >/dev/null; then + verbose curl -sLo "$CONDA_ENV_DIR/miniconda_py310.sh" "$MINICONDA_PY310_URL" + elif command -v wget >/dev/null; then + verbose wget -O "$CONDA_ENV_DIR/miniconda_py310.sh" "$MINICONDA_PY310_URL" + else + echo "ERROR: unable to download miniconda!" + exit 1 + fi + + verbose test "$(sha256sum "$CONDA_ENV_DIR/miniconda_py310.sh")" = "$MINICONDA_PY310_HASH" + verbose chmod +x "$CONDA_ENV_DIR/miniconda_py310.sh" + + verbose "$CONDA_ENV_DIR/miniconda_py310.sh" -b -u -p "$CONDA_ENV_DIR/conda" + verbose rm "$CONDA_ENV_DIR/miniconda_py310.sh" + + eval "$(conda shell.bash hook)" # basically `conda init`, without modifying .bashrc + verbose conda install --yes --name base mamba -c conda-forge + + ) || exit $? +fi + +unset CONDA_ENV_DIR +unset MINICONDA_PY310_URL +unset MINICONDA_PY310_HASH + +# Enter conda environment +eval "$(conda shell.bash hook)" # basically `conda init`, without modifying .bashrc diff --git a/.remoteenv b/.remoteenv new file mode 100644 index 0000000..42519a5 --- /dev/null +++ b/.remoteenv @@ -0,0 +1,29 @@ +#!/usr/bin/env bash +# this file is used by remote-cli + +# Assumes repo is put in a "remotes/name-hash" folder, +# the default behaviour of remote-exec +REMOTES_DIR="$(dirname $(pwd))" +LOCAL_ENV_NAME="py310-$(basename $(pwd))" +LOCAL_ENV_DIR="$REMOTES_DIR/envs/$REMOTE_ENV_NAME" + +#export XDG_CACHE_HOME="$LOCAL_ENV_DIR/xdg/cache" +#export XDG_DATA_HOME="$LOCAL_ENV_DIR/xdg/share" +#export XDG_STATE_HOME="$LOCAL_ENV_DIR/xdg/state" +#mkdir -p "$XDG_CACHE_HOME" "$XDG_DATA_HOME" "$XDG_STATE_HOME" +export XDG_CONFIG_HOME="$LOCAL_ENV_DIR/xdg/config" +mkdir -p "$XDG_CONFIG_HOME" + + +export PYOPENGL_PLATFORM=egl # makes pyrender work headless +#export PYOPENGL_PLATFORM=osmesa # makes pyrender work headless +export SDL_VIDEODRIVER=dummy # pygame + +source .localenv + +# SLURM logs output dir +if command -v sbatch >/dev/null; then + mkdir -p slurm_logs + test -L experiments/logdir/slurm_logs || + ln -s ../../slurm_logs experiments/logdir/ +fi diff --git a/.remoteignore.toml b/.remoteignore.toml new file mode 100644 index 0000000..c8bd856 --- /dev/null +++ b/.remoteignore.toml @@ -0,0 +1,30 @@ +[push] +exclude = [ + "*.egg-info", + "*.pyc", + ".ipynb_checkpoints", + ".mypy_cache", + ".remote.toml", + ".remoteignore.toml", + ".venv", + ".wandb", + "__pycache__", + "data/models", + "docs", + "experiments/logdir", + "poetry.toml", + "slurm_logs", + "tmp", +] +include = [] + +[pull] +exclude = [ + "*", +] +include = [] + +[both] +exclude = [ +] +include = [] diff --git a/README.md b/README.md index 6cc3f36..e01b2b1 100644 --- a/README.md +++ b/README.md @@ -1 +1,127 @@ -This is where the code for the paper _"MARF: The Medial Atom Ray Field Object Representation"_ will be published. +# MARF: The Medial Atom Ray Field Object Representation + +
+ +![](figures/nn-architecture.svg) + +[Publication](https://doi.org/10.1016/j.cag.2023.06.032) | [Arxiv](https://arxiv.org/abs/2307.00037) | [Training data](https://mega.nz/file/9tsz3SbA#V6SIXpCFC4hbqWaFFvKmmS8BKir7rltXuhsqpEpE9wo) | [Network weights](https://mega.nz/file/t01AyTLK#7ZNMNgbqT9x2mhq5dxLuKeKyP7G0slfQX1RaZxifayw) + +
+ +**TL;DR:** We achieve _fast_ surface rendering by predicting _n_ maximally inscribed spherical intersection candidates for each camera ray. + +--- + +## Entering the Virtual Environment + +The environment is defined in `pyproject.toml` using [Poetry](https://github.com/python-poetry/poetry) and reproducibly locked in `poetry.lock`. +We propose three ways to enter the venv: + +```shell +# Requires Python 3.10 and Poetry +poetry install +poetry shell + +# Will bootstrap a Miniconda 3.10 environment into .env/ if needed, then run poetry +source .localenv +``` + + +## Evaluation + +### Pretrained models + +You can download our pre-trained models` from . +It should be unpacked into the root directory, such that the `experiment` folder gets merged. + +### The interactive renderer + +We automatically create experiment names with a schema of `{{model}}-{{experiment-name}}-{{hparams-summary}}-{{date}}-{{random-uid}}`. +You can load experiment weights using either the full path, or just the `random-uid` bit. + +From the `experiments` directory: + +```shell +./marf.py model {{experiment}} viewer +``` + +If you have downloaded our pre-trained network weights, consider trying: + +```shell +./marf.py model nqzh viewer # Stanford Bunny (single-shape) +./marf.py model wznx viewer # Stanford Buddha (single-shape) +./marf.py model mxwd viewer # Stanford Armadillo (single-shape) +./marf.py model camo viewer # Stanford Dragon (single-shape) +./marf.py model ksul viewer # Stanford Lucy (single-shape) +./marf.py model oxrf viewer # COSEG four-legged (multi-shape) +``` + +## Training and Evaluation Data + +You can download a pre-computed archive from . +It should be extracted into the root directory such that a `data` directory is added to the root directory. + +
+ +Optionally, you may compute the data yourself. + + +Single-shape training data: + +```shell +# takes takes about 23 minutes, mainly due to lucy +download-stanford bunny happy_buddha dragon armadillo lucy +preprocess-stanford bunny happy_buddha dragon armadillo lucy \ + --precompute-mesh-sv-scan-uv \ + --compute-miss-distances \ + --fill-missing-uv-points +``` + +Multi-shape training data: + +```shell +# takes takes about 29 minutes +download-coseg four-legged --shapes +preprocess-coseg four-legged \ + --precompute-mesh-sv-scan-uv \ + --compute-miss-distances \ + --fill-missing-uv-points +``` + +Evaluation data: + +```shell +# takes takes about 2 hour 20 minutes, mainly due to lucy +preprocess-stanford bunny happy_buddha dragon armadillo lucy \ + --precompute-mesh-sphere-scan \ + --compute-miss-distances +``` + +```shell +# takes takes about 4 hours +preprocess-coseg four-legged \ + --precompute-mesh-sphere-scan \ + --compute-miss-distances +``` +
+ + +## Training + +Our experiments are defined using YAML config files, optionally templated using Jinja2 as a preprocessor. +These templates accept additional input from the command line in the form of `-Okey=value` options. +Our whole experiment matrix is defined in `marf.yaml.j12`. We select between different experiment groups using `-Omode={single,ablation,multi}`, and which experiment using `-Oselect={{integer}}` + +From the `experiments` directory: + +CPU mode: + +```shell +./marf.py model marf.yaml.j2 -Oexperiment_name=cpu_test -Omode=single -Oselect=0 fit +``` + +GPU mode: + +```shell +./marf.py model marf.yaml.j2 -Oexperiment_name=cpu_test -Omode=single -Oselect=0 fit --accelerator gpu --devices 1 +``` diff --git a/ablation.md b/ablation.md new file mode 100644 index 0000000..34fcb3b --- /dev/null +++ b/ablation.md @@ -0,0 +1,139 @@ +### MARF +- `experiment-stanfordv12-bunny-both2marf-16atom-50xinscr-10dmiss-geom-25cnrml-8x512fc-leaky_relu-hit-0minatomstdngxp-500sphgrow-10mdrop-layernorm-multi_view-10dmv-nocond-100cwu500clr70tvs-2023-05-31-0010-nqzh` +- `experiment-stanfordv12-happy_buddha-both2marf-16atom-50xinscr-10dmiss-geom-25cnrml-8x512fc-leaky_relu-hit-0minatomstdngxp-500sphgrow-10mdrop-layernorm-multi_view-10dmv-nocond-100cwu500clr70tvs-2023-05-31-0312-wznx` +- `experiment-stanfordv12-armadillo-both2marf-16atom-50xinscr-10dmiss-geom-25cnrml-8x512fc-leaky_relu-hit-0minatomstdngxp-500sphgrow-10mdrop-layernorm-multi_view-10dmv-nocond-100cwu500clr70tvs-2023-05-30-1944-mxwd` +- `experiment-stanfordv12-dragon-both2marf-16atom-50xinscr-10dmiss-geom-25cnrml-8x512fc-leaky_relu-hit-0minatomstdngxp-500sphgrow-10mdrop-layernorm-multi_view-10dmv-nocond-100cwu500clr70tvs-2023-05-31-0529-camo` +- `experiment-stanfordv12-lucy-both2marf-16atom-50xinscr-10dmiss-geom-25cnrml-8x512fc-leaky_relu-hit-0minatomstdngxp-500sphgrow-10mdrop-layernorm-multi_view-10dmv-nocond-100cwu500clr70tvs-2023-05-31-0743-ksul` + +### LFN encoding +- `experiment-stanfordv12-dragon-plkr2marf-16atom-50xinscr-10dmiss-geom-25cnrml-8x512fc-leaky_relu-hit-0minatomstdngxp-500sphgrow-10mdrop-layernorm-multi_view-10dmv-nocond-100cwu500clr70tvs-2023-05-31-0539-xjte` +- `experiment-stanfordv12-lucy-plkr2marf-16atom-50xinscr-10dmiss-geom-25cnrml-8x512fc-leaky_relu-hit-0minatomstdngxp-500sphgrow-10mdrop-layernorm-multi_view-10dmv-nocond-100cwu500clr70tvs-2023-05-31-0753-ayvt` +- `experiment-stanfordv12-bunny-plkr2marf-16atom-50xinscr-10dmiss-geom-25cnrml-8x512fc-leaky_relu-hit-0minatomstdngxp-500sphgrow-10mdrop-layernorm-multi_view-10dmv-nocond-100cwu500clr70tvs-2023-05-31-0022-axft` +- `experiment-stanfordv12-happy_buddha-plkr2marf-16atom-50xinscr-10dmiss-geom-25cnrml-8x512fc-leaky_relu-hit-0minatomstdngxp-500sphgrow-10mdrop-layernorm-multi_view-10dmv-nocond-100cwu500clr70tvs-2023-05-31-0322-xfoc` +- `experiment-stanfordv12-armadillo-plkr2marf-16atom-50xinscr-10dmiss-geom-25cnrml-8x512fc-leaky_relu-hit-0minatomstdngxp-500sphgrow-10mdrop-layernorm-multi_view-10dmv-nocond-100cwu500clr70tvs-2023-05-30-2039-vbks` + +### PRIF encoding +- `experiment-stanfordv12-armadillo-prpft2marf-16atom-50xinscr-10dmiss-geom-25cnrml-8x512fc-leaky_relu-hit-0minatomstdngxp-500sphgrow-10mdrop-layernorm-multi_view-10dmv-nocond-100cwu500clr70tvs-2023-05-30-2033-nkxm` +- `experiment-stanfordv12-happy_buddha-prpft2marf-16atom-50xinscr-10dmiss-geom-25cnrml-8x512fc-leaky_relu-hit-0minatomstdngxp-500sphgrow-10mdrop-layernorm-multi_view-10dmv-nocond-100cwu500clr70tvs-2023-05-31-0313-huci` +- `experiment-stanfordv12-dragon-prpft2marf-16atom-50xinscr-10dmiss-geom-25cnrml-8x512fc-leaky_relu-hit-0minatomstdngxp-500sphgrow-10mdrop-layernorm-multi_view-10dmv-nocond-100cwu500clr70tvs-2023-05-31-0537-dxsb` +- `experiment-stanfordv12-bunny-prpft2marf-16atom-50xinscr-10dmiss-geom-25cnrml-8x512fc-leaky_relu-hit-0minatomstdngxp-500sphgrow-10mdrop-layernorm-multi_view-10dmv-nocond-100cwu500clr70tvs-2023-05-31-0011-tzic` +- `experiment-stanfordv12-lucy-prpft2marf-16atom-50xinscr-10dmiss-geom-25cnrml-8x512fc-leaky_relu-hit-0minatomstdngxp-500sphgrow-10mdrop-layernorm-multi_view-10dmv-nocond-100cwu500clr70tvs-2023-05-31-0744-hzvw` + +### No init scheme. +- `experiment-stanfordv12-happy_buddha-both2marf-16atom-50xinscr-10dmiss-nogeom-25cnrml-8x512fc-leaky_relu-hit-0minatomstdngxp-500sphgrow-10mdrop-layernorm-multi_view-10dmv-nocond-100cwu500clr70tvs-2023-05-31-0444-uohy` +- `experiment-stanfordv12-armadillo-both2marf-16atom-50xinscr-10dmiss-nogeom-25cnrml-8x512fc-leaky_relu-hit-0minatomstdngxp-500sphgrow-10mdrop-layernorm-multi_view-10dmv-nocond-100cwu500clr70tvs-2023-05-30-2307-wjcf` +- `experiment-stanfordv12-dragon-both2marf-16atom-50xinscr-10dmiss-nogeom-25cnrml-8x512fc-leaky_relu-hit-0minatomstdngxp-500sphgrow-10mdrop-layernorm-multi_view-10dmv-nocond-100cwu500clr70tvs-2023-05-31-0707-eanc` +- `experiment-stanfordv12-bunny-both2marf-16atom-50xinscr-10dmiss-nogeom-25cnrml-8x512fc-leaky_relu-hit-0minatomstdngxp-500sphgrow-10mdrop-layernorm-multi_view-10dmv-nocond-100cwu500clr70tvs-2023-05-31-0225-kcfw` +- `experiment-stanfordv12-lucy-both2marf-16atom-50xinscr-10dmiss-nogeom-25cnrml-8x512fc-leaky_relu-hit-0minatomstdngxp-500sphgrow-10mdrop-layernorm-multi_view-10dmv-nocond-100cwu500clr70tvs-2023-05-31-0852-lkfh` + +### 1 atom candidate +- `experiment-stanfordv12-lucy-both2marf-1atom-50xinscr-10dmiss-geom-25cnrml-8x512fc-leaky_relu-hit-0minatomstdngxp-500sphgrow-10mdrop-layernorm-multi_view-10dmv-nocond-100cwu500clr70tvs-2023-05-31-0755-qzth` +- `experiment-stanfordv12-bunny-both2marf-1atom-50xinscr-10dmiss-geom-25cnrml-8x512fc-leaky_relu-hit-0minatomstdngxp-500sphgrow-10mdrop-layernorm-multi_view-10dmv-nocond-100cwu500clr70tvs-2023-05-31-0027-ycnl` +- `experiment-stanfordv12-armadillo-both2marf-1atom-50xinscr-10dmiss-geom-25cnrml-8x512fc-leaky_relu-hit-0minatomstdngxp-500sphgrow-10mdrop-layernorm-multi_view-10dmv-nocond-100cwu500clr70tvs-2023-05-30-2121-fwvo` +- `experiment-stanfordv12-dragon-both2marf-1atom-50xinscr-10dmiss-geom-25cnrml-8x512fc-leaky_relu-hit-0minatomstdngxp-500sphgrow-10mdrop-layernorm-multi_view-10dmv-nocond-100cwu500clr70tvs-2023-05-31-0541-nvhs` +- `experiment-stanfordv12-happy_buddha-both2marf-1atom-50xinscr-10dmiss-geom-25cnrml-8x512fc-leaky_relu-hit-0minatomstdngxp-500sphgrow-10mdrop-layernorm-multi_view-10dmv-nocond-100cwu500clr70tvs-2023-05-31-0324-cuyw` + +### 4 atom candidates +- `experiment-stanfordv12-armadillo-both2marf-4atom-50xinscr-10dmiss-geom-25cnrml-8x512fc-leaky_relu-hit-0minatomstdngxp-500sphgrow-10mdrop-layernorm-multi_view-10dmv-nocond-100cwu500clr70tvs-2023-05-30-2122-qiwg` +- `experiment-stanfordv12-dragon-both2marf-4atom-50xinscr-10dmiss-geom-25cnrml-8x512fc-leaky_relu-hit-0minatomstdngxp-500sphgrow-10mdrop-layernorm-multi_view-10dmv-nocond-100cwu500clr70tvs-2023-05-31-0544-ihkx` +- `experiment-stanfordv12-lucy-both2marf-4atom-50xinscr-10dmiss-geom-25cnrml-8x512fc-leaky_relu-hit-0minatomstdngxp-500sphgrow-10mdrop-layernorm-multi_view-10dmv-nocond-100cwu500clr70tvs-2023-05-31-0757-jwxm` +- `experiment-stanfordv12-happy_buddha-both2marf-4atom-50xinscr-10dmiss-geom-25cnrml-8x512fc-leaky_relu-hit-0minatomstdngxp-500sphgrow-10mdrop-layernorm-multi_view-10dmv-nocond-100cwu500clr70tvs-2023-05-31-0328-chhs` +- `experiment-stanfordv12-bunny-both2marf-4atom-50xinscr-10dmiss-geom-25cnrml-8x512fc-leaky_relu-hit-0minatomstdngxp-500sphgrow-10mdrop-layernorm-multi_view-10dmv-nocond-100cwu500clr70tvs-2023-05-31-0038-zymb` + +### 8 atom candidates +- `experiment-stanfordv12-bunny-both2marf-8atom-50xinscr-10dmiss-geom-25cnrml-8x512fc-leaky_relu-hit-0minatomstdngxp-500sphgrow-10mdrop-layernorm-multi_view-10dmv-nocond-100cwu500clr70tvs-2023-05-31-0055-ogpd` +- `experiment-stanfordv12-lucy-both2marf-8atom-50xinscr-10dmiss-geom-25cnrml-8x512fc-leaky_relu-hit-0minatomstdngxp-500sphgrow-10mdrop-layernorm-multi_view-10dmv-nocond-100cwu500clr70tvs-2023-05-31-0757-frxb` +- `experiment-stanfordv12-happy_buddha-both2marf-8atom-50xinscr-10dmiss-geom-25cnrml-8x512fc-leaky_relu-hit-0minatomstdngxp-500sphgrow-10mdrop-layernorm-multi_view-10dmv-nocond-100cwu500clr70tvs-2023-05-31-0337-twys` +- `experiment-stanfordv12-dragon-both2marf-8atom-50xinscr-10dmiss-geom-25cnrml-8x512fc-leaky_relu-hit-0minatomstdngxp-500sphgrow-10mdrop-layernorm-multi_view-10dmv-nocond-100cwu500clr70tvs-2023-05-31-0551-bubw` +- `experiment-stanfordv12-armadillo-both2marf-8atom-50xinscr-10dmiss-geom-25cnrml-8x512fc-leaky_relu-hit-0minatomstdngxp-500sphgrow-10mdrop-layernorm-multi_view-10dmv-nocond-100cwu500clr70tvs-2023-05-30-2137-nnlj` + +### 32 atom candidates +- `experiment-stanfordv12-bunny-both2marf-32atom-50xinscr-10dmiss-geom-25cnrml-8x512fc-leaky_relu-hit-0minatomstdngxp-500sphgrow-10mdrop-layernorm-multi_view-10dmv-nocond-100cwu500clr70tvs-2023-05-31-0056-ourc` +- `experiment-stanfordv12-armadillo-both2marf-32atom-50xinscr-10dmiss-geom-25cnrml-8x512fc-leaky_relu-hit-0minatomstdngxp-500sphgrow-10mdrop-layernorm-multi_view-10dmv-nocond-100cwu500clr70tvs-2023-05-30-2141-byaj` +- `experiment-stanfordv12-dragon-both2marf-32atom-50xinscr-10dmiss-geom-25cnrml-8x512fc-leaky_relu-hit-0minatomstdngxp-500sphgrow-10mdrop-layernorm-multi_view-10dmv-nocond-100cwu500clr70tvs-2023-05-31-0554-zobg` +- `experiment-stanfordv12-happy_buddha-both2marf-32atom-50xinscr-10dmiss-geom-25cnrml-8x512fc-leaky_relu-hit-0minatomstdngxp-500sphgrow-10mdrop-layernorm-multi_view-10dmv-nocond-100cwu500clr70tvs-2023-05-31-0337-rmyq` +- `experiment-stanfordv12-lucy-both2marf-32atom-50xinscr-10dmiss-geom-25cnrml-8x512fc-leaky_relu-hit-0minatomstdngxp-500sphgrow-10mdrop-layernorm-multi_view-10dmv-nocond-100cwu500clr70tvs-2023-05-31-0800-lqen` + +### 64 atom candidates +- `experiment-stanfordv12-happy_buddha-both2marf-64atom-50xinscr-10dmiss-geom-25cnrml-8x512fc-leaky_relu-hit-0minatomstdngxp-500sphgrow-10mdrop-layernorm-multi_view-10dmv-nocond-100cwu500clr70tvs-2023-05-31-0339-whcx` +- `experiment-stanfordv12-bunny-both2marf-64atom-50xinscr-10dmiss-geom-25cnrml-8x512fc-leaky_relu-hit-0minatomstdngxp-500sphgrow-10mdrop-layernorm-multi_view-10dmv-nocond-100cwu500clr70tvs-2023-05-31-0058-seen` +- `experiment-stanfordv12-lucy-both2marf-64atom-50xinscr-10dmiss-geom-25cnrml-8x512fc-leaky_relu-hit-0minatomstdngxp-500sphgrow-10mdrop-layernorm-multi_view-10dmv-nocond-100cwu500clr70tvs-2023-05-31-0806-ycxj` +- `experiment-stanfordv12-armadillo-both2marf-64atom-50xinscr-10dmiss-geom-25cnrml-8x512fc-leaky_relu-hit-0minatomstdngxp-500sphgrow-10mdrop-layernorm-multi_view-10dmv-nocond-100cwu500clr70tvs-2023-05-30-2153-wnfq` +- `experiment-stanfordv12-dragon-both2marf-64atom-50xinscr-10dmiss-geom-25cnrml-8x512fc-leaky_relu-hit-0minatomstdngxp-500sphgrow-10mdrop-layernorm-multi_view-10dmv-nocond-100cwu500clr70tvs-2023-05-31-0555-zgcb` + +### No intersection loss +- `experiment-stanfordv12-happy_buddha-both2marf-16atom-50xinscr-10dmiss-geom-0chit-25cnrml-8x512fc-leaky_relu-hit-0minatomstdngxp-500sphgrow-10mdrop-layernorm-multi_view-10dmv-nocond-100cwu500clr70tvs-2023-05-31-1053-ydnh` +- `experiment-stanfordv12-lucy-both2marf-16atom-50xinscr-10dmiss-geom-0chit-25cnrml-8x512fc-leaky_relu-hit-0minatomstdngxp-500sphgrow-10mdrop-layernorm-multi_view-10dmv-nocond-100cwu500clr70tvs-2023-05-31-1111-fawl` +- `experiment-stanfordv12-bunny-both2marf-16atom-50xinscr-10dmiss-geom-0chit-25cnrml-8x512fc-leaky_relu-hit-0minatomstdngxp-500sphgrow-10mdrop-layernorm-multi_view-10dmv-nocond-100cwu500clr70tvs-2023-05-31-1045-umwl` +- `experiment-stanfordv12-dragon-both2marf-16atom-50xinscr-10dmiss-geom-0chit-25cnrml-8x512fc-leaky_relu-hit-0minatomstdngxp-500sphgrow-10mdrop-layernorm-multi_view-10dmv-nocond-100cwu500clr70tvs-2023-05-31-1103-lwmb` +- `experiment-stanfordv12-armadillo-both2marf-16atom-50xinscr-10dmiss-geom-0chit-25cnrml-8x512fc-leaky_relu-hit-0minatomstdngxp-500sphgrow-10mdrop-layernorm-multi_view-10dmv-nocond-100cwu500clr70tvs-2023-05-31-1041-lhcc` + +### No silhouette loss +- `experiment-stanfordv12-armadillo-both2marf-16atom-50xinscr-0dmiss-geom-20chit-25cnrml-8x512fc-leaky_relu-hit-0minatomstdngxp-500sphgrow-10mdrop-layernorm-multi_view-10dmv-nocond-100cwu500clr70tvs-2023-05-31-1042-fsuw` +- `experiment-stanfordv12-bunny-both2marf-16atom-50xinscr-0dmiss-geom-20chit-25cnrml-8x512fc-leaky_relu-hit-0minatomstdngxp-500sphgrow-10mdrop-layernorm-multi_view-10dmv-nocond-100cwu500clr70tvs-2023-05-31-1046-nszw` +- `experiment-stanfordv12-dragon-both2marf-16atom-50xinscr-0dmiss-geom-20chit-25cnrml-8x512fc-leaky_relu-hit-0minatomstdngxp-500sphgrow-10mdrop-layernorm-multi_view-10dmv-nocond-100cwu500clr70tvs-2023-05-31-1111-mlal` +- `experiment-stanfordv12-happy_buddha-both2marf-16atom-50xinscr-0dmiss-geom-20chit-25cnrml-8x512fc-leaky_relu-hit-0minatomstdngxp-500sphgrow-10mdrop-layernorm-multi_view-10dmv-nocond-100cwu500clr70tvs-2023-05-31-1055-cvkg` +- `experiment-stanfordv12-lucy-both2marf-16atom-50xinscr-0dmiss-geom-20chit-25cnrml-8x512fc-leaky_relu-hit-0minatomstdngxp-500sphgrow-10mdrop-layernorm-multi_view-10dmv-nocond-100cwu500clr70tvs-2023-05-31-1114-pdyh` + +### More silhouette loss +- `experiment-stanfordv12-bunny-both2marf-16atom-50xinscr-50dmiss-geom-25cnrml-8x512fc-leaky_relu-hit-0minatomstdngxp-500sphgrow-10mdrop-layernorm-multi_view-10dmv-nocond-100cwu500clr70tvs-2023-05-31-0157-yekm` +- `experiment-stanfordv12-armadillo-both2marf-16atom-50xinscr-50dmiss-geom-25cnrml-8x512fc-leaky_relu-hit-0minatomstdngxp-500sphgrow-10mdrop-layernorm-multi_view-10dmv-nocond-100cwu500clr70tvs-2023-05-30-2243-nlrv` +- `experiment-stanfordv12-dragon-both2marf-16atom-50xinscr-50dmiss-geom-25cnrml-8x512fc-leaky_relu-hit-0minatomstdngxp-500sphgrow-10mdrop-layernorm-multi_view-10dmv-nocond-100cwu500clr70tvs-2023-05-31-0639-yros` +- `experiment-stanfordv12-lucy-both2marf-16atom-50xinscr-50dmiss-geom-25cnrml-8x512fc-leaky_relu-hit-0minatomstdngxp-500sphgrow-10mdrop-layernorm-multi_view-10dmv-nocond-100cwu500clr70tvs-2023-05-31-0842-xktg` +- `experiment-stanfordv12-happy_buddha-both2marf-16atom-50xinscr-50dmiss-geom-25cnrml-8x512fc-leaky_relu-hit-0minatomstdngxp-500sphgrow-10mdrop-layernorm-multi_view-10dmv-nocond-100cwu500clr70tvs-2023-05-31-0423-ibxs` + +### No normal loss +- `experiment-stanfordv12-dragon-both2marf-16atom-50xinscr-10dmiss-geom-nocnrml-8x512fc-leaky_relu-hit-0minatomstdngxp-500sphgrow-10mdrop-layernorm-multi_view-10dmv-nocond-100cwu500clr70tvs-2023-05-31-0614-ttta` +- `experiment-stanfordv12-bunny-both2marf-16atom-50xinscr-10dmiss-geom-nocnrml-8x512fc-leaky_relu-hit-0minatomstdngxp-500sphgrow-10mdrop-layernorm-multi_view-10dmv-nocond-100cwu500clr70tvs-2023-05-31-0106-bnke` +- `experiment-stanfordv12-armadillo-both2marf-16atom-50xinscr-10dmiss-geom-nocnrml-8x512fc-leaky_relu-hit-0minatomstdngxp-500sphgrow-10mdrop-layernorm-multi_view-10dmv-nocond-100cwu500clr70tvs-2023-05-30-2154-bxwl` +- `experiment-stanfordv12-lucy-both2marf-16atom-50xinscr-10dmiss-geom-nocnrml-8x512fc-leaky_relu-hit-0minatomstdngxp-500sphgrow-10mdrop-layernorm-multi_view-10dmv-nocond-100cwu500clr70tvs-2023-05-31-0811-qqgu` +- `experiment-stanfordv12-happy_buddha-both2marf-16atom-50xinscr-10dmiss-geom-nocnrml-8x512fc-leaky_relu-hit-0minatomstdngxp-500sphgrow-10mdrop-layernorm-multi_view-10dmv-nocond-100cwu500clr70tvs-2023-05-31-0357-gwca` + +### No inscription loss +- `experiment-stanfordv12-bunny-both2marf-16atom-noxinscr-10dmiss-geom-25cnrml-8x512fc-leaky_relu-hit-0minatomstdngxp-500sphgrow-10mdrop-layernorm-multi_view-10dmv-nocond-100cwu500clr70tvs-2023-05-31-0227-xrqt` +- `experiment-stanfordv12-armadillo-both2marf-16atom-noxinscr-10dmiss-geom-25cnrml-8x512fc-leaky_relu-hit-0minatomstdngxp-500sphgrow-10mdrop-layernorm-multi_view-10dmv-nocond-100cwu500clr70tvs-2023-05-30-2312-cgzv` +- `experiment-stanfordv12-happy_buddha-both2marf-16atom-noxinscr-10dmiss-geom-25cnrml-8x512fc-leaky_relu-hit-0minatomstdngxp-500sphgrow-10mdrop-layernorm-multi_view-10dmv-nocond-100cwu500clr70tvs-2023-05-31-0452-rerr` +- `experiment-stanfordv12-dragon-both2marf-16atom-noxinscr-10dmiss-geom-25cnrml-8x512fc-leaky_relu-hit-0minatomstdngxp-500sphgrow-10mdrop-layernorm-multi_view-10dmv-nocond-100cwu500clr70tvs-2023-05-31-0709-tfgg` +- `experiment-stanfordv12-lucy-both2marf-16atom-noxinscr-10dmiss-geom-25cnrml-8x512fc-leaky_relu-hit-0minatomstdngxp-500sphgrow-10mdrop-layernorm-multi_view-10dmv-nocond-100cwu500clr70tvs-2023-05-31-0856-ctvc` + +### More inscription loss +- `experiment-stanfordv12-happy_buddha-both2marf-16atom-250xinscr-10dmiss-geom-25cnrml-8x512fc-leaky_relu-hit-0minatomstdngxp-500sphgrow-10mdrop-layernorm-multi_view-10dmv-nocond-100cwu500clr70tvs-2023-05-31-0459-kyyh` +- `experiment-stanfordv12-bunny-both2marf-16atom-250xinscr-10dmiss-geom-25cnrml-8x512fc-leaky_relu-hit-0minatomstdngxp-500sphgrow-10mdrop-layernorm-multi_view-10dmv-nocond-100cwu500clr70tvs-2023-05-31-0243-qqqj` +- `experiment-stanfordv12-armadillo-both2marf-16atom-250xinscr-10dmiss-geom-25cnrml-8x512fc-leaky_relu-hit-0minatomstdngxp-500sphgrow-10mdrop-layernorm-multi_view-10dmv-nocond-100cwu500clr70tvs-2023-05-30-2336-yclo` +- `experiment-stanfordv12-lucy-both2marf-16atom-250xinscr-10dmiss-geom-25cnrml-8x512fc-leaky_relu-hit-0minatomstdngxp-500sphgrow-10mdrop-layernorm-multi_view-10dmv-nocond-100cwu500clr70tvs-2023-05-31-0913-mulv` +- `experiment-stanfordv12-dragon-both2marf-16atom-250xinscr-10dmiss-geom-25cnrml-8x512fc-leaky_relu-hit-0minatomstdngxp-500sphgrow-10mdrop-layernorm-multi_view-10dmv-nocond-100cwu500clr70tvs-2023-05-31-0714-zugg` + +### No maximality reg. +- `experiment-stanfordv12-lucy-both2marf-16atom-50xinscr-10dmiss-geom-25cnrml-8x512fc-leaky_relu-hit-0minatomstdngxp-0sphgrow-10mdrop-layernorm-multi_view-10dmv-nocond-100cwu500clr70tvs-2023-05-31-0842-cvln` +- `experiment-stanfordv12-happy_buddha-both2marf-16atom-50xinscr-10dmiss-geom-25cnrml-8x512fc-leaky_relu-hit-0minatomstdngxp-0sphgrow-10mdrop-layernorm-multi_view-10dmv-nocond-100cwu500clr70tvs-2023-05-31-0425-vpen` +- `experiment-stanfordv12-bunny-both2marf-16atom-50xinscr-10dmiss-geom-25cnrml-8x512fc-leaky_relu-hit-0minatomstdngxp-0sphgrow-10mdrop-layernorm-multi_view-10dmv-nocond-100cwu500clr70tvs-2023-05-31-0207-qpdb` +- `experiment-stanfordv12-armadillo-both2marf-16atom-50xinscr-10dmiss-geom-25cnrml-8x512fc-leaky_relu-hit-0minatomstdngxp-0sphgrow-10mdrop-layernorm-multi_view-10dmv-nocond-100cwu500clr70tvs-2023-05-30-2251-zqvi` +- `experiment-stanfordv12-dragon-both2marf-16atom-50xinscr-10dmiss-geom-25cnrml-8x512fc-leaky_relu-hit-0minatomstdngxp-0sphgrow-10mdrop-layernorm-multi_view-10dmv-nocond-100cwu500clr70tvs-2023-05-31-0641-ucdo` + +### More maximality reg. +- `experiment-stanfordv12-dragon-both2marf-16atom-50xinscr-10dmiss-geom-25cnrml-8x512fc-leaky_relu-hit-0minatomstdngxp-5000sphgrow-10mdrop-layernorm-multi_view-10dmv-nocond-100cwu500clr70tvs-2023-05-31-0659-bqvf` +- `experiment-stanfordv12-armadillo-both2marf-16atom-50xinscr-10dmiss-geom-25cnrml-8x512fc-leaky_relu-hit-0minatomstdngxp-5000sphgrow-10mdrop-layernorm-multi_view-10dmv-nocond-100cwu500clr70tvs-2023-05-30-2256-escz` +- `experiment-stanfordv12-bunny-both2marf-16atom-50xinscr-10dmiss-geom-25cnrml-8x512fc-leaky_relu-hit-0minatomstdngxp-5000sphgrow-10mdrop-layernorm-multi_view-10dmv-nocond-100cwu500clr70tvs-2023-05-31-0208-wmvs` +- `experiment-stanfordv12-happy_buddha-both2marf-16atom-50xinscr-10dmiss-geom-25cnrml-8x512fc-leaky_relu-hit-0minatomstdngxp-5000sphgrow-10mdrop-layernorm-multi_view-10dmv-nocond-100cwu500clr70tvs-2023-05-31-0442-gdah` +- `experiment-stanfordv12-lucy-both2marf-16atom-50xinscr-10dmiss-geom-25cnrml-8x512fc-leaky_relu-hit-0minatomstdngxp-5000sphgrow-10mdrop-layernorm-multi_view-10dmv-nocond-100cwu500clr70tvs-2023-05-31-0845-halc` + +### No specialization reg. +- `experiment-stanfordv12-lucy-both2marf-16atom-50xinscr-10dmiss-geom-25cnrml-8x512fc-leaky_relu-hit-nominatomstdngxp-500sphgrow-10mdrop-layernorm-multi_view-10dmv-nocond-100cwu500clr70tvs-2023-05-31-0913-odyn` +- `experiment-stanfordv12-bunny-both2marf-16atom-50xinscr-10dmiss-geom-25cnrml-8x512fc-leaky_relu-hit-nominatomstdngxp-500sphgrow-10mdrop-layernorm-multi_view-10dmv-nocond-100cwu500clr70tvs-2023-05-31-0251-xzig` +- `experiment-stanfordv12-dragon-both2marf-16atom-50xinscr-10dmiss-geom-25cnrml-8x512fc-leaky_relu-hit-nominatomstdngxp-500sphgrow-10mdrop-layernorm-multi_view-10dmv-nocond-100cwu500clr70tvs-2023-05-31-0722-gxps` +- `experiment-stanfordv12-armadillo-both2marf-16atom-50xinscr-10dmiss-geom-25cnrml-8x512fc-leaky_relu-hit-nominatomstdngxp-500sphgrow-10mdrop-layernorm-multi_view-10dmv-nocond-100cwu500clr70tvs-2023-05-30-2342-zybo` +- `experiment-stanfordv12-happy_buddha-both2marf-16atom-50xinscr-10dmiss-geom-25cnrml-8x512fc-leaky_relu-hit-nominatomstdngxp-500sphgrow-10mdrop-layernorm-multi_view-10dmv-nocond-100cwu500clr70tvs-2023-05-31-0507-tvlt` + +### No multi-view loss +- `experiment-stanfordv12-bunny-both2marf-16atom-50xinscr-10dmiss-geom-25cnrml-8x512fc-leaky_relu-hit-0minatomstdngxp-500sphgrow-10mdrop-layernorm-nogradreg-nocond-100cwu500clr70tvs-2023-05-31-0310-wbqj` +- `experiment-stanfordv12-armadillo-both2marf-16atom-50xinscr-10dmiss-geom-25cnrml-8x512fc-leaky_relu-hit-0minatomstdngxp-500sphgrow-10mdrop-layernorm-nogradreg-nocond-100cwu500clr70tvs-2023-05-30-2357-qnct` +- `experiment-stanfordv12-happy_buddha-both2marf-16atom-50xinscr-10dmiss-geom-25cnrml-8x512fc-leaky_relu-hit-0minatomstdngxp-500sphgrow-10mdrop-layernorm-nogradreg-nocond-100cwu500clr70tvs-2023-05-31-0527-psnk` +- `experiment-stanfordv12-lucy-both2marf-16atom-50xinscr-10dmiss-geom-25cnrml-8x512fc-leaky_relu-hit-0minatomstdngxp-500sphgrow-10mdrop-layernorm-nogradreg-nocond-100cwu500clr70tvs-2023-05-31-0927-wxcq` +- `experiment-stanfordv12-dragon-both2marf-16atom-50xinscr-10dmiss-geom-25cnrml-8x512fc-leaky_relu-hit-0minatomstdngxp-500sphgrow-10mdrop-layernorm-nogradreg-nocond-100cwu500clr70tvs-2023-05-31-0743-pdbc` + +### More multi-view loss +- `experiment-stanfordv12-happy_buddha-both2marf-16atom-50xinscr-10dmiss-geom-25cnrml-8x512fc-leaky_relu-hit-0minatomstdngxp-500sphgrow-10mdrop-layernorm-multi_view-20dmv-nocond-100cwu500clr70tvs-2023-05-31-0510-caah` +- `experiment-stanfordv12-dragon-both2marf-16atom-50xinscr-10dmiss-geom-25cnrml-8x512fc-leaky_relu-hit-0minatomstdngxp-500sphgrow-10mdrop-layernorm-multi_view-20dmv-nocond-100cwu500clr70tvs-2023-05-31-0726-zkyg` +- `experiment-stanfordv12-bunny-both2marf-16atom-50xinscr-10dmiss-geom-25cnrml-8x512fc-leaky_relu-hit-0minatomstdngxp-500sphgrow-10mdrop-layernorm-multi_view-20dmv-nocond-100cwu500clr70tvs-2023-05-31-0254-akbq` +- `experiment-stanfordv12-lucy-both2marf-16atom-50xinscr-10dmiss-geom-25cnrml-8x512fc-leaky_relu-hit-0minatomstdngxp-500sphgrow-10mdrop-layernorm-multi_view-20dmv-nocond-100cwu500clr70tvs-2023-05-31-0924-aahb` +- `experiment-stanfordv12-armadillo-both2marf-16atom-50xinscr-10dmiss-geom-25cnrml-8x512fc-leaky_relu-hit-0minatomstdngxp-500sphgrow-10mdrop-layernorm-multi_view-20dmv-nocond-100cwu500clr70tvs-2023-05-30-2352-xlrn` diff --git a/experiments/marf.py b/experiments/marf.py new file mode 100755 index 0000000..73bf151 --- /dev/null +++ b/experiments/marf.py @@ -0,0 +1,624 @@ +#!/usr/bin/env python3 +from abc import ABC, abstractmethod +from argparse import Namespace +from collections import defaultdict +from datetime import datetime +from ifield import logging +from ifield.cli import CliInterface +from ifield.data.common.scan import SingleViewUVScan +from ifield.data.coseg import read as coseg_read +from ifield.data.stanford import read as stanford_read +from ifield.datasets import stanford, coseg, common +from ifield.models import intersection_fields +from ifield.utils.operators import diff +from ifield.viewer.ray_field import ModelViewer +from munch import Munch +from pathlib import Path +from pytorch3d.loss.chamfer import chamfer_distance +from pytorch_lightning.utilities import rank_zero_only +from torch.nn import functional as F +from torch.utils.data import DataLoader, Subset +from tqdm import tqdm +from trimesh import Trimesh +from typing import Union +import builtins +import itertools +import json +import numpy as np +import pytorch_lightning as pl +import rich +import rich.pretty +import statistics +import torch +pl.seed_everything(31337) +torch.set_float32_matmul_precision('medium') + + +IField = intersection_fields.IntersectionFieldAutoDecoderModel # brevity + + +class RayFieldAdDataModuleBase(pl.LightningDataModule, ABC): + @property + @abstractmethod + def observation_ids(self) -> list[str]: + ... + + @abstractmethod + def mk_ad_dataset(self) -> common.AutodecoderDataset: + ... + + @staticmethod + @abstractmethod + def get_trimesh_from_uid(uid) -> Trimesh: + ... + + @staticmethod + @abstractmethod + def get_sphere_scan_from_uid(uid) -> SingleViewUVScan: + ... + + def setup(self, stage=None): + assert stage in ["fit", None] # fit is for train/val, None is for all. "test" not supported ATM + + if not self.hparams.data_dir is None: + coseg.config.DATA_PATH = self.hparams.data_dir + step = self.hparams.step # brevity + + dataset = self.mk_ad_dataset() + n_items_pre_step_mapping = len(dataset) + + if step > 1: + dataset = common.TransformExtendedDataset(dataset) + + for sx in range(step): + for sy in range(step): + def make_slicer(sx, sy, step) -> callable: # the closure is required + if step > 1: + return lambda t: t[sx::step, sy::step] + else: + return lambda t: t + @dataset.map(slicer=make_slicer(sx, sy, step)) + def unpack(sample: tuple[str, SingleViewUVScan], slicer: callable): + scan: SingleViewUVScan = sample[1] + assert not scan.hits.shape[0] % step, f"{scan.hits.shape[0]=} not divisible by {step=}" + assert not scan.hits.shape[1] % step, f"{scan.hits.shape[1]=} not divisible by {step=}" + + return { + "z_uid" : sample[0], + "origins" : scan.cam_pos, + "dirs" : slicer(scan.ray_dirs), + "points" : slicer(scan.points), + "hits" : slicer(scan.hits), + "miss" : slicer(scan.miss), + "normals" : slicer(scan.normals), + "distances" : slicer(scan.distances), + } + + # Split each object into train/val with SampleSplit + n_items = len(dataset) + n_val = int(n_items * self.hparams.val_fraction) + n_train = n_items - n_val + self.generator = torch.Generator().manual_seed(self.hparams.prng_seed) + + # split the dataset such that all steps are in same part + assert n_items == n_items_pre_step_mapping * step * step, (n_items, n_items_pre_step_mapping, step) + indices = [ + i*step*step + sx*step + sy + for i in torch.randperm(n_items_pre_step_mapping, generator=self.generator).tolist() + for sx in range(step) + for sy in range(step) + ] + self.dataset_train = Subset(dataset, sorted(indices[:n_train], key=lambda x: torch.rand(1, generator=self.generator).tolist()[0])) + self.dataset_val = Subset(dataset, sorted(indices[n_train:n_train+n_val], key=lambda x: torch.rand(1, generator=self.generator).tolist()[0])) + + assert len(self.dataset_train) % self.hparams.batch_size == 0 + assert len(self.dataset_val) % self.hparams.batch_size == 0 + + def train_dataloader(self): + return DataLoader(self.dataset_train, + batch_size = self.hparams.batch_size, + drop_last = self.hparams.drop_last, + num_workers = self.hparams.num_workers, + persistent_workers = self.hparams.persistent_workers, + pin_memory = self.hparams.pin_memory, + prefetch_factor = self.hparams.prefetch_factor, + shuffle = self.hparams.shuffle, + generator = self.generator, + ) + + def val_dataloader(self): + return DataLoader(self.dataset_val, + batch_size = self.hparams.batch_size, + drop_last = self.hparams.drop_last, + num_workers = self.hparams.num_workers, + persistent_workers = self.hparams.persistent_workers, + pin_memory = self.hparams.pin_memory, + prefetch_factor = self.hparams.prefetch_factor, + generator = self.generator, + ) + + +class StanfordUVDataModule(RayFieldAdDataModuleBase): + skyward = "+Z" + def __init__(self, + data_dir : Union[str, Path, None] = None, + obj_names : list[str] = ["bunny"], # empty means all + + prng_seed : int = 1337, + step : int = 2, + batch_size : int = 5, + drop_last : bool = False, + num_workers : int = 8, + persistent_workers : bool = True, + pin_memory : int = True, + prefetch_factor : int = 2, + shuffle : bool = True, + val_fraction : float = 0.30, + ): + super().__init__() + if not obj_names: + obj_names = stanford_read.list_object_names() + self.save_hyperparameters() + + @property + def observation_ids(self) -> list[str]: + return self.hparams.obj_names + + def mk_ad_dataset(self) -> common.AutodecoderDataset: + return stanford.AutodecoderSingleViewUVScanDataset( + obj_names = self.hparams.obj_names, + data_path = self.hparams.data_dir, + ) + + @staticmethod + def get_trimesh_from_uid(obj_name) -> Trimesh: + import mesh_to_sdf + mesh = stanford_read.read_mesh(obj_name) + return mesh_to_sdf.scale_to_unit_sphere(mesh) + + @staticmethod + def get_sphere_scan_from_uid(obj_name) -> SingleViewUVScan: + return stanford_read.read_mesh_mesh_sphere_scan(obj_name) + + +class CosegUVDataModule(RayFieldAdDataModuleBase): + skyward = "+Y" + def __init__(self, + data_dir : Union[str, Path, None] = None, + object_sets : tuple[str] = ["tele-aliens"], # empty means all + + prng_seed : int = 1337, + step : int = 2, + batch_size : int = 5, + drop_last : bool = False, + num_workers : int = 8, + persistent_workers : bool = True, + pin_memory : int = True, + prefetch_factor : int = 2, + shuffle : bool = True, + val_fraction : float = 0.30, + ): + super().__init__() + if not object_sets: + object_sets = coseg_read.list_object_sets() + object_sets = tuple(object_sets) + self.save_hyperparameters() + + @property + def observation_ids(self) -> list[str]: + return coseg_read.list_model_id_strings(self.hparams.object_sets) + + def mk_ad_dataset(self) -> common.AutodecoderDataset: + return coseg.AutodecoderSingleViewUVScanDataset( + object_sets = self.hparams.object_sets, + data_path = self.hparams.data_dir, + ) + + @staticmethod + def get_trimesh_from_uid(string_uid): + raise NotImplementedError + + @staticmethod + def get_sphere_scan_from_uid(string_uid) -> SingleViewUVScan: + uid = coseg_read.model_id_string_to_uid(string_uid) + return coseg_read.read_mesh_mesh_sphere_scan(*uid) + + +def mk_cli(args=None) -> CliInterface: + cli = CliInterface( + module_cls = IField, + datamodule_cls = [StanfordUVDataModule, CosegUVDataModule], + workdir = Path(__file__).parent.resolve(), + experiment_name_prefix = "ifield", + ) + cli.trainer_defaults.update(dict( + precision = 16, + min_epochs = 5, + )) + + @cli.register_pre_training_callback + def populate_autodecoder_z_uids(args: Namespace, config: Munch, module: IField, trainer: pl.Trainer, datamodule: RayFieldAdDataModuleBase, logger: logging.Logger): + module.set_observation_ids(datamodule.observation_ids) + rank = getattr(rank_zero_only, "rank", 0) + rich.print(f"[rank {rank}] {len(datamodule.observation_ids) = }") + rich.print(f"[rank {rank}] {len(datamodule.observation_ids) > 1 = }") + rich.print(f"[rank {rank}] {module.is_conditioned = }") + + @cli.register_action(help="Interactive window with direct renderings from the model", args=[ + ("--shading", dict(type=int, default=ModelViewer.vizmodes_shading .index("lambertian"), help=f"Rendering mode. {{{', '.join(f'{i}: {m!r}'for i, m in enumerate(ModelViewer.vizmodes_shading))}}}")), + ("--centroid", dict(type=int, default=ModelViewer.vizmodes_centroids.index("best-centroids-colored"), help=f"Rendering mode. {{{', '.join(f'{i}: {m!r}'for i, m in enumerate(ModelViewer.vizmodes_centroids))}}}")), + ("--spheres", dict(type=int, default=ModelViewer.vizmodes_spheres .index(None), help=f"Rendering mode. {{{', '.join(f'{i}: {m!r}'for i, m in enumerate(ModelViewer.vizmodes_spheres))}}}")), + ("--analytical-normals", dict(action="store_true")), + ("--ground-truth", dict(action="store_true")), + ("--solo-atom",dict(type=int, default=None, help="Rendering mode")), + ("--res", dict(type=int, nargs=2, default=(210, 160), help="Rendering resolution")), + ("--bg", dict(choices=["map", "white", "black"], default="map")), + ("--skyward", dict(type=str, default="+Z", help='one of: "+X", "-X", "+Y", "-Y", ["+Z"], "-Z"')), + ("--scale", dict(type=int, default=3, help="Rendering scale")), + ("--fps", dict(type=int, default=None, help="FPS upper limit")), + ("--cam-state",dict(type=str, default=None, help="json cam state, expored with CTRL+H")), + ("--write", dict(type=Path, default=None, help="Where to write a screenshot.")), + ]) + @torch.no_grad() + def viewer(args: Namespace, config: Munch, model: IField): + datamodule_cls: RayFieldAdDataModuleBase = cli.get_datamodule_cls_from_config(args, config) + + if torch.cuda.is_available() and torch.cuda.device_count() > 0: + model.to("cuda") + viewer = ModelViewer(model, start_uid=next(iter(model.keys())), + name = config.experiment_name, + screenshot_dir = Path(__file__).parent.parent / "images/pygame-viewer", + res = args.res, + skyward = args.skyward, + scale = args.scale, + mesh_gt_getter = datamodule_cls.get_trimesh_from_uid, + ) + viewer.display_mode_shading = args.shading + viewer.display_mode_centroid = args.centroid + viewer.display_mode_spheres = args.spheres + if args.ground_truth: viewer.display_mode_normals = viewer.vizmodes_normals.index("ground_truth") + if args.analytical_normals: viewer.display_mode_normals = viewer.vizmodes_normals.index("analytical") + viewer.atom_index_solo = args.solo_atom + viewer.fps_cap = args.fps + viewer.display_sphere_map_bg = { "map": True, "white": 255, "black": 0 }[args.bg] + if args.cam_state is not None: + viewer.cam_state = json.loads(args.cam_state) + if args.write is None: + viewer.run() + else: + assert args.write.suffix == ".png", args.write.name + viewer.render_headless(args.write, + n_frames = 1, + fps = 1, + state_callback = None, + ) + + @cli.register_action(help="Prerender direct renderings from the model", args=[ + ("output_path",dict(type=Path, help="Where to store the output. We recommend a .mp4 suffix.")), + ("uids", dict(type=str, nargs="*")), + ("--frames", dict(type=int, default=60, help="Number of per interpolation. Default is 60")), + ("--fps", dict(type=int, default=60, help="Default is 60")), + ("--shading", dict(type=int, default=ModelViewer.vizmodes_shading .index("lambertian"), help=f"Rendering mode. {{{', '.join(f'{i}: {m!r}'for i, m in enumerate(ModelViewer.vizmodes_shading))}}}")), + ("--centroid", dict(type=int, default=ModelViewer.vizmodes_centroids.index("best-centroids-colored"), help=f"Rendering mode. {{{', '.join(f'{i}: {m!r}'for i, m in enumerate(ModelViewer.vizmodes_centroids))}}}")), + ("--spheres", dict(type=int, default=ModelViewer.vizmodes_spheres .index(None), help=f"Rendering mode. {{{', '.join(f'{i}: {m!r}'for i, m in enumerate(ModelViewer.vizmodes_spheres))}}}")), + ("--analytical-normals", dict(action="store_true")), + ("--solo-atom",dict(type=int, default=None, help="Rendering mode")), + ("--res", dict(type=int, nargs=2, default=(240, 240), help="Rendering resolution. Default is 240 240")), + ("--bg", dict(choices=["map", "white", "black"], default="map")), + ("--skyward", dict(type=str, default="+Z", help='one of: "+X", "-X", "+Y", "-Y", ["+Z"], "-Z"')), + ("--bitrate", dict(type=str, default="1500k", help="Encoding bitrate. Default is 1500k")), + ("--cam-state",dict(type=str, default=None, help="json cam state, expored with CTRL+H")), + ]) + @torch.no_grad() + def render_video_interpolation(args: Namespace, config: Munch, model: IField, **kw): + if torch.cuda.is_available() and torch.cuda.device_count() > 0: + model.to("cuda") + uids = args.uids or list(model.keys()) + assert len(uids) > 1 + if not args.uids: uids.append(uids[0]) + viewer = ModelViewer(model, uids[0], + name = config.experiment_name, + screenshot_dir = Path(__file__).parent.parent / "images/pygame-viewer", + res = args.res, + skyward = args.skyward, + ) + if args.cam_state is not None: + viewer.cam_state = json.loads(args.cam_state) + viewer.display_mode_shading = args.shading + viewer.display_mode_centroid = args.centroid + viewer.display_mode_spheres = args.spheres + if args.analytical_normals: viewer.display_mode_normals = viewer.vizmodes_normals.index("analytical") + viewer.atom_index_solo = args.solo_atom + viewer.display_sphere_map_bg = { "map": True, "white": 255, "black": 0 }[args.bg] + def state_callback(self: ModelViewer, frame: int): + if frame % args.frames: + self.lambertian_color = (0.8, 0.8, 1.0) + else: + self.lambertian_color = (1.0, 1.0, 1.0) + self.fps = args.frames + idx = frame // args.frames + 1 + if idx != len(uids): + self.current_uid = uids[idx] + print(f"Writing video to {str(args.output_path)!r}...") + viewer.render_headless(args.output_path, + n_frames = args.frames * (len(uids)-1) + 1, + fps = args.fps, + state_callback = state_callback, + bitrate = args.bitrate, + ) + + @cli.register_action(help="Prerender direct renderings from the model", args=[ + ("output_path",dict(type=Path, help="Where to store the output. We recommend a .mp4 suffix.")), + ("--frames", dict(type=int, default=180, help="Number of frames. Default is 180")), + ("--fps", dict(type=int, default=60, help="Default is 60")), + ("--shading", dict(type=int, default=ModelViewer.vizmodes_shading .index("lambertian"), help=f"Rendering mode. {{{', '.join(f'{i}: {m!r}'for i, m in enumerate(ModelViewer.vizmodes_shading))}}}")), + ("--centroid", dict(type=int, default=ModelViewer.vizmodes_centroids.index("best-centroids-colored"), help=f"Rendering mode. {{{', '.join(f'{i}: {m!r}'for i, m in enumerate(ModelViewer.vizmodes_centroids))}}}")), + ("--spheres", dict(type=int, default=ModelViewer.vizmodes_spheres .index(None), help=f"Rendering mode. {{{', '.join(f'{i}: {m!r}'for i, m in enumerate(ModelViewer.vizmodes_spheres))}}}")), + ("--analytical-normals", dict(action="store_true")), + ("--solo-atom",dict(type=int, default=None, help="Rendering mode")), + ("--res", dict(type=int, nargs=2, default=(320, 240), help="Rendering resolution. Default is 320 240")), + ("--bg", dict(choices=["map", "white", "black"], default="map")), + ("--skyward", dict(type=str, default="+Z", help='one of: "+X", "-X", "+Y", "-Y", ["+Z"], "-Z"')), + ("--bitrate", dict(type=str, default="1500k", help="Encoding bitrate. Default is 1500k")), + ("--cam-state",dict(type=str, default=None, help="json cam state, expored with CTRL+H")), + ]) + @torch.no_grad() + def render_video_spin(args: Namespace, config: Munch, model: IField, **kw): + if torch.cuda.is_available() and torch.cuda.device_count() > 0: + model.to("cuda") + viewer = ModelViewer(model, start_uid=next(iter(model.keys())), + name = config.experiment_name, + screenshot_dir = Path(__file__).parent.parent / "images/pygame-viewer", + res = args.res, + skyward = args.skyward, + ) + if args.cam_state is not None: + viewer.cam_state = json.loads(args.cam_state) + viewer.display_mode_shading = args.shading + viewer.display_mode_centroid = args.centroid + viewer.display_mode_spheres = args.spheres + if args.analytical_normals: viewer.display_mode_normals = viewer.vizmodes_normals.index("analytical") + viewer.atom_index_solo = args.solo_atom + viewer.display_sphere_map_bg = { "map": True, "white": 255, "black": 0 }[args.bg] + cam_rot_x_init = viewer.cam_rot_x + def state_callback(self: ModelViewer, frame: int): + self.cam_rot_x = cam_rot_x_init + 3.14 * (frame / args.frames) * 2 + print(f"Writing video to {str(args.output_path)!r}...") + viewer.render_headless(args.output_path, + n_frames = args.frames, + fps = args.fps, + state_callback = state_callback, + bitrate = args.bitrate, + ) + + @cli.register_action(help="foo", args=[ + ("fname", dict(type=Path, help="where to write json")), + ("-t", "--transpose", dict(action="store_true", help="transpose the output")), + ("--single-shape", dict(action="store_true", help="break after first shape")), + ("--batch-size", dict(type=int, default=40_000, help="tradeoff between vram usage and efficiency")), + ("--n-cd", dict(type=int, default=30_000, help="Number of points to use when computing chamfer distance")), + ("--filter-outliers", dict(action="store_true", help="like in PRIF")), + ]) + @torch.enable_grad() + def compute_scores(args: Namespace, config: Munch, model: IField, **kw): + datamodule_cls: RayFieldAdDataModuleBase = cli.get_datamodule_cls_from_config(args, config) + model.eval() + if torch.cuda.is_available() and torch.cuda.device_count() > 0: + model.to("cuda") + + def T(array: np.ndarray, **kw) -> torch.Tensor: + if isinstance(array, torch.Tensor): return array + return torch.tensor(array, device=model.device, dtype=model.dtype if isinstance(array, np.floating) else None, **kw) + + MEDIAL = model.hparams.output_mode == "medial_sphere" + if not MEDIAL: assert model.hparams.output_mode == "orthogonal_plane" + + + uids = sorted(model.keys()) + if args.single_shape: uids = [uids[0]] + rich.print(f"{datamodule_cls.__name__ = }") + rich.print(f"{len(uids) = }") + + # accumulators for IoU and F-Score, CD and COS + + # sum reduction: + n = defaultdict(int) + n_gt_hits = defaultdict(int) + n_gt_miss = defaultdict(int) + n_gt_missing = defaultdict(int) + n_outliers = defaultdict(int) + p_mse = defaultdict(int) + s_mse = defaultdict(int) + cossim_med = defaultdict(int) # medial normals + cossim_jac = defaultdict(int) # jacovian normals + TP,FN,FP,TN = [defaultdict(int) for _ in range(4)] # IoU and f-score + # mean reduction: + cd_dist = {} # chamfer distance + cd_cos_med = {} # chamfer medial normals + cd_cos_jac = {} # chamfer jacovian normals + all_metrics = dict( + n=n, n_gt_hits=n_gt_hits, n_gt_miss=n_gt_miss, n_gt_missing=n_gt_missing, p_mse=p_mse, + cossim_jac=cossim_jac, + TP=TP, FN=FN, FP=FP, TN=TN, cd_dist=cd_dist, + cd_cos_jac=cd_cos_jac, + ) + if MEDIAL: + all_metrics["s_mse"] = s_mse + all_metrics["cossim_med"] = cossim_med + all_metrics["cd_cos_med"] = cd_cos_med + if args.filter_outliers: + all_metrics["n_outliers"] = n_outliers + + t = datetime.now() + for uid in tqdm(uids, desc="Dataset", position=0, leave=True, disable=len(uids)<=1): + sphere_scan_gt = datamodule_cls.get_sphere_scan_from_uid(uid) + + z = model[uid].detach() + + all_intersections = [] + all_medial_normals = [] + all_jacobian_normals = [] + + step = args.batch_size + for i in tqdm(range(0, sphere_scan_gt.hits.shape[0], step), desc=f"Item {uid!r}", position=1, leave=False): + # prepare batch and gt + origins = T(sphere_scan_gt.cam_pos [i:i+step, :], requires_grad = True) + dirs = T(sphere_scan_gt.ray_dirs [i:i+step, :]) + gt_hits = T(sphere_scan_gt.hits [i:i+step]) + gt_miss = T(sphere_scan_gt.miss [i:i+step]) + gt_missing = T(sphere_scan_gt.missing [i:i+step]) + gt_points = T(sphere_scan_gt.points [i:i+step, :]) + gt_normals = T(sphere_scan_gt.normals [i:i+step, :]) + gt_distances = T(sphere_scan_gt.distances[i:i+step]) + + # forward + if MEDIAL: + ( + depths, + silhouettes, + intersections, + medial_normals, + is_intersecting, + sphere_centers, + sphere_radii, + ) = model({ + "origins" : origins, + "dirs" : dirs, + }, z, intersections_only=False, allow_nans=False) + else: + silhouettes = medial_normals = None + intersections, is_intersecting = model({ + "origins" : origins, + "dirs" : dirs, + }, z, normalize_origins = True) + is_intersecting = is_intersecting > 0.5 + jac = diff.jacobian(intersections, origins, detach=True) + + # outlier removal (PRIF) + if args.filter_outliers: + outliers = jac.norm(dim=-2).norm(dim=-1) > 5 + n_outliers[uid] += outliers[is_intersecting].sum().item() + # We count filtered points as misses + is_intersecting &= ~outliers + + model.zero_grad() + jacobian_normals = model.compute_normals_from_intersection_origin_jacobian(jac, dirs) + + all_intersections .append(intersections .detach()[is_intersecting.detach(), :]) + all_medial_normals .append(medial_normals .detach()[is_intersecting.detach(), :]) if MEDIAL else None + all_jacobian_normals.append(jacobian_normals.detach()[is_intersecting.detach(), :]) + + # accumulate metrics + with torch.no_grad(): + n [uid] += dirs.shape[0] + n_gt_hits [uid] += gt_hits.sum().item() + n_gt_miss [uid] += gt_miss.sum().item() + n_gt_missing [uid] += gt_missing.sum().item() + p_mse [uid] += (gt_points [gt_hits, :] - intersections[gt_hits, :]).norm(2, dim=-1).pow(2).sum().item() + if MEDIAL: s_mse [uid] += (gt_distances[gt_miss] - silhouettes [gt_miss] ) .pow(2).sum().item() + if MEDIAL: cossim_med[uid] += (1-F.cosine_similarity(gt_normals[gt_hits, :], medial_normals [gt_hits, :], dim=-1).abs()).sum().item() # to match what pytorch3d does for CD + cossim_jac [uid] += (1-F.cosine_similarity(gt_normals[gt_hits, :], jacobian_normals[gt_hits, :], dim=-1).abs()).sum().item() # to match what pytorch3d does for CD + not_intersecting = ~is_intersecting + TP [uid] += ((gt_hits | gt_missing) & is_intersecting).sum().item() # True Positive + FN [uid] += ((gt_hits | gt_missing) & not_intersecting).sum().item() # False Negative + FP [uid] += (gt_miss & is_intersecting).sum().item() # False Positive + TN [uid] += (gt_miss & not_intersecting).sum().item() # True Negative + + all_intersections = torch.cat(all_intersections, dim=0) + all_medial_normals = torch.cat(all_medial_normals, dim=0) if MEDIAL else None + all_jacobian_normals = torch.cat(all_jacobian_normals, dim=0) + + hits = sphere_scan_gt.hits # brevity + print() + + assert all_intersections.shape[0] >= args.n_cd + idx_cd_pred = torch.randperm(all_intersections.shape[0])[:args.n_cd] + idx_cd_gt = torch.randperm(hits.sum()) [:args.n_cd] + + print("cd... ", end="") + tt = datetime.now() + loss_cd, loss_cos_jac = chamfer_distance( + x = all_intersections [None, :, :][:, idx_cd_pred, :].detach(), + x_normals = all_jacobian_normals [None, :, :][:, idx_cd_pred, :].detach(), + y = T(sphere_scan_gt.points [None, hits, :][:, idx_cd_gt, :]), + y_normals = T(sphere_scan_gt.normals[None, hits, :][:, idx_cd_gt, :]), + batch_reduction = "sum", point_reduction = "sum", + ) + if MEDIAL: _, loss_cos_med = chamfer_distance( + x = all_intersections [None, :, :][:, idx_cd_pred, :].detach(), + x_normals = all_medial_normals [None, :, :][:, idx_cd_pred, :].detach(), + y = T(sphere_scan_gt.points [None, hits, :][:, idx_cd_gt, :]), + y_normals = T(sphere_scan_gt.normals[None, hits, :][:, idx_cd_gt, :]), + batch_reduction = "sum", point_reduction = "sum", + ) + print(datetime.now() - tt) + + cd_dist [uid] = loss_cd.item() + cd_cos_med [uid] = loss_cos_med.item() if MEDIAL else None + cd_cos_jac [uid] = loss_cos_jac.item() + + print() + model.zero_grad(set_to_none=True) + print("Total time:", datetime.now() - t) + print("Time per item:", (datetime.now() - t) / len(uids)) if len(uids) > 1 else None + + sum = lambda *xs: builtins .sum (itertools.chain(*(x.values() for x in xs))) + mean = lambda *xs: statistics.mean (itertools.chain(*(x.values() for x in xs))) + stdev = lambda *xs: statistics.stdev(itertools.chain(*(x.values() for x in xs))) + n_cd = args.n_cd + P = sum(TP)/(sum(TP, FP)) + R = sum(TP)/(sum(TP, FN)) + print(f"{mean(n) = :11.1f} (rays per object)") + print(f"{mean(n_gt_hits) = :11.1f} (gt rays hitting per object)") + print(f"{mean(n_gt_miss) = :11.1f} (gt rays missing per object)") + print(f"{mean(n_gt_missing) = :11.1f} (gt rays unknown per object)") + print(f"{mean(n_outliers) = :11.1f} (gt rays unknown per object)") if args.filter_outliers else None + print(f"{n_cd = :11.0f} (cd rays per object)") + print(f"{mean(n_gt_hits) / mean(n) = :11.8f} (fraction rays hitting per object)") + print(f"{mean(n_gt_miss) / mean(n) = :11.8f} (fraction rays missing per object)") + print(f"{mean(n_gt_missing)/ mean(n) = :11.8f} (fraction rays unknown per object)") + print(f"{mean(n_outliers) / mean(n) = :11.8f} (fraction rays unknown per object)") if args.filter_outliers else None + print(f"{sum(TP)/sum(n) = :11.8f} (total ray TP)") + print(f"{sum(TN)/sum(n) = :11.8f} (total ray TN)") + print(f"{sum(FP)/sum(n) = :11.8f} (total ray FP)") + print(f"{sum(FN)/sum(n) = :11.8f} (total ray FN)") + print(f"{sum(TP, FN, FP)/sum(n) = :11.8f} (total ray union)") + print(f"{sum(TP)/sum(TP, FN, FP) = :11.8f} (total ray IoU)") + print(f"{sum(TP)/(sum(TP, FP)) = :11.8f} -> P (total ray precision)") + print(f"{sum(TP)/(sum(TP, FN)) = :11.8f} -> R (total ray recall)") + print(f"{2*(P*R)/(P+R) = :11.8f} (total ray F-score)") + print(f"{sum(p_mse)/sum(n_gt_hits) = :11.8f} (mean ray intersection mean squared error)") + print(f"{sum(s_mse)/sum(n_gt_miss) = :11.8f} (mean ray silhoutette mean squared error)") + print(f"{sum(cossim_med)/sum(n_gt_hits) = :11.8f} (mean ray medial reduced cosine similarity)") if MEDIAL else None + print(f"{sum(cossim_jac)/sum(n_gt_hits) = :11.8f} (mean ray analytical reduced cosine similarity)") + print(f"{mean(cd_dist) /n_cd * 1e3 = :11.8f} (mean chamfer distance)") + print(f"{mean(cd_cos_med)/n_cd = :11.8f} (mean chamfer reduced medial cossim distance)") if MEDIAL else None + print(f"{mean(cd_cos_jac)/n_cd = :11.8f} (mean chamfer reduced analytical cossim distance)") + print(f"{stdev(cd_dist) /n_cd * 1e3 = :11.8f} (stdev chamfer distance)") if len(cd_dist) > 1 else None + print(f"{stdev(cd_cos_med)/n_cd = :11.8f} (stdev chamfer reduced medial cossim distance)") if len(cd_cos_med) > 1 and MEDIAL else None + print(f"{stdev(cd_cos_jac)/n_cd = :11.8f} (stdev chamfer reduced analytical cossim distance)") if len(cd_cos_jac) > 1 else None + + if args.transpose: + all_metrics, old_metrics = defaultdict(dict), all_metrics + for m, table in old_metrics.items(): + for uid, vals in table.items(): + all_metrics[uid][m] = vals + all_metrics["_hparams"] = dict(n_cd=args.n_cd) + else: + all_metrics["n_cd"] = args.n_cd + + if str(args.fname) == "-": + print("{", ',\n'.join( + f" {json.dumps(k)}: {json.dumps(v)}" + for k, v in all_metrics.items() + ), "}", sep="\n") + else: + args.fname.parent.mkdir(parents=True, exist_ok=True) + with args.fname.open("w") as f: + json.dump(all_metrics, f, indent=2) + + return cli + + +if __name__ == "__main__": + mk_cli().run() diff --git a/experiments/marf.yaml.j2 b/experiments/marf.yaml.j2 new file mode 100755 index 0000000..c12a7f7 --- /dev/null +++ b/experiments/marf.yaml.j2 @@ -0,0 +1,263 @@ +#!/usr/bin/env -S python ./marf.py module +{% do require_defined("select", select, 0, "$SLURM_ARRAY_TASK_ID") %}{# requires jinja2.ext.do #} +{% do require_defined("mode", mode, "single", "ablation", "multi", strict=true, exchaustive=true) %}{# requires jinja2.ext.do #} +{% set counter = itertools.count(start=0, step=1) %} +{% set do_condition = mode == "multi" %} +{% set do_ablation = mode == "ablation" %} + +{% set hp_matrix = namespace() %}{# hyper parameter matrix #} + +{% set hp_matrix.input_mode = [ + "both", + "perp_foot", + "plucker", +] if do_ablation else [ "both" ] %} +{% set hp_matrix.output_mode = ["medial_sphere", "orthogonal_plane"] %}{##} +{% set hp_matrix.output_mode = ["medial_sphere"] %}{##} +{% set hp_matrix.n_atoms = [16, 1, 4, 8, 32, 64] if do_ablation else [16] %}{##} +{% set hp_matrix.normal_coeff = [0.25, 0] if do_ablation else [0.25] %}{##} +{% set hp_matrix.dataset_item = [objname] if objname is defined else (["armadillo", "bunny", "happy_buddha", "dragon", "lucy"] if not do_condition else ["four-legged"]) %}{##} +{% set hp_matrix.test_val_split_frac = [0.7] %}{##} +{% set hp_matrix.lr_coeff = [5] %}{##} +{% set hp_matrix.warmup_epochs = [1] if not do_condition else [0.1] %}{##} +{% set hp_matrix.improve_miss_grads = [True] %}{##} +{% set hp_matrix.normalize_ray_dirs = [True] %}{##} +{% set hp_matrix.intersection_coeff = [2, 0] if do_ablation else [2] %}{##} +{% set hp_matrix.miss_distance_coeff = [1, 0, 5] if do_ablation else [1] %}{##} +{% set hp_matrix.relative_out = [False] %}{##} +{% set hp_matrix.hidden_features = [512] %}{# like deepsdf and prif #} +{% set hp_matrix.hidden_layers = [8] %}{# like deepsdf, nerf, prif #} +{% set hp_matrix.nonlinearity = ["leaky_relu"] %}{##} +{% set hp_matrix.omega = [30] %}{##} +{% set hp_matrix.normalization = ["layernorm"] %}{##} +{% set hp_matrix.dropout_percent = [1] %}{##} +{% set hp_matrix.sphere_grow_reg_coeff = [500, 0, 5000] if do_ablation else [500] %}{##} +{% set hp_matrix.geom_init = [True, False] if do_ablation else [True] %}{##} +{% set hp_matrix.loss_inscription = [50, 0, 250] if do_ablation else [50] %}{##} +{% set hp_matrix.atom_centroid_norm_std_reg_negexp = [0, None] if do_ablation else [0] %}{##} +{% set hp_matrix.curvature_reg_coeff = [0.2] %}{##} +{% set hp_matrix.multi_view_reg_coeff = [1, 2] if do_ablation else [1] %}{##} +{% set hp_matrix.grad_reg = [ "multi_view", "nogradreg" ] if do_ablation else [ "multi_view" ] %} + +{#% for hp in cartesian_hparams(hp_matrix) %}{##} +{% for hp in ablation_hparams(hp_matrix, caartesian_keys=["output_mode", "dataset_item", "nonlinearity", "test_val_split_frac"]) %} + +{% if hp.output_mode == "orthogonal_plane"%} +{% if hp.normal_coeff == 0 %}{% set hp.normal_coeff = 0.25 %} +{% elif hp.normal_coeff == 0.25 %}{% set hp.normal_coeff = 0 %} +{% endif %} +{% if hp.grad_reg == "multi_view" %}{% set hp.grad_reg = "nogradreg" %} +{% elif hp.grad_reg == "nogradreg" %}{% set hp.grad_reg = "multi_view" %} +{% endif %} +{% endif %} + +{# filter bad/uninteresting hparam combos #} +{% if ( hp.nonlinearity != "sine" and hp.omega != 30 ) + or ( hp.nonlinearity == "sine" and hp.normalization in ("layernorm", "layernorm_na") ) + or ( hp.multi_view_reg_coeff != 1 and "multi_view" not in hp.grad_reg ) + or ( "curvature" not in hp.grad_reg and hp.curvature_reg_coeff != 0.2 ) + or ( hp.output_mode == "orthogonal_plane" and hp.input_mode != "both" ) + or ( hp.output_mode == "orthogonal_plane" and hp.atom_centroid_norm_std_reg_negexp != 0 ) + or ( hp.output_mode == "orthogonal_plane" and hp.n_atoms != 16 ) + or ( hp.output_mode == "orthogonal_plane" and hp.sphere_grow_reg_coeff != 500 ) + or ( hp.output_mode == "orthogonal_plane" and hp.loss_inscription != 50 ) + or ( hp.output_mode == "orthogonal_plane" and hp.miss_distance_coeff != 1 ) + or ( hp.output_mode == "orthogonal_plane" and hp.test_val_split_frac != 0.7 ) + or ( hp.output_mode == "orthogonal_plane" and hp.lr_coeff != 5 ) + or ( hp.output_mode == "orthogonal_plane" and not hp.geom_init ) + or ( hp.output_mode == "orthogonal_plane" and not hp.intersection_coeff ) +%} + {% continue %}{# requires jinja2.ext.loopcontrols #} +{% endif %} + +{% set index = next(counter) %} +{% if select is not defined and index > 0 %}---{% endif %} +{% if select is not defined or int(select) == index %} + +trainer: + gradient_clip_val : 1.0 + max_epochs : 200 + min_epochs : 200 + log_every_n_steps : 20 + +{% if not do_condition %} + +StanfordUVDataModule: + obj_names : ["{{ hp.dataset_item }}"] + step : 4 + batch_size : 8 + val_fraction : {{ 1-hp.test_val_split_frac }} + +{% else %}{# if do_condition #} + +CosegUVDataModule: + object_sets : ["{{ hp.dataset_item }}"] + step : 4 + batch_size : 8 + val_fraction : {{ 1-hp.test_val_split_frac }} + +{% endif %}{# if do_condition #} + +logging: + save_dir : logdir + type : tensorboard + project : ifield + +{% autoescape false %} +{% do require_defined("experiment_name", experiment_name, "single-shape" if do_condition else "multi-shape", strict=true) %} +{% set input_mode_abbr = hp.input_mode + .replace("plucker", "plkr") + .replace("perp_foot", "prpft") +%} +{% set output_mode_abbr = hp.output_mode + .replace("medial_sphere", "marf") + .replace("orthogonal_plane", "prif") +%} +experiment_name: experiment-{{ "" if experiment_name is not defined else experiment_name }} +{#--#}-{{ hp.dataset_item }} +{#--#}-{{ input_mode_abbr }}2{{ output_mode_abbr }} +{#--#} +{%- if hp.output_mode == "medial_sphere" -%} + {#--#}-{{ hp.n_atoms }}atom + {#--# }-{{ "rel" if hp.relative_out else "norel" }} + {#--# }-{{ "e" if hp.improve_miss_grads else "0" }}sqrt + {#--#}-{{ int(hp.loss_inscription) if hp.loss_inscription else "no" }}xinscr + {#--#}-{{ int(hp.miss_distance_coeff * 10) }}dmiss + {#--#}-{{ "geom" if hp.geom_init else "nogeom" }} + {#--#}{% if "curvature" in hp.grad_reg %} + {#- -#}-{{ int(hp.curvature_reg_coeff*10) }}crv + {#--#}{%- endif -%} +{%- elif hp.output_mode == "orthogonal_plane" -%} + {#--#} +{%- endif -%} +{#--#}-{{ int(hp.intersection_coeff*10) }}chit +{#--#}-{{ int(hp.normal_coeff*100) or "no" }}cnrml +{#--# }-{{ "do" if hp.normalize_ray_dirs else "no" }}raynorm +{#--#}-{{ hp.hidden_layers }}x{{ hp.hidden_features }}fc +{#--#}-{{ hp.nonlinearity or "linear" }} +{#--#} +{%- if hp.nonlinearity == "sine" -%} + {#--#}-{{ hp.omega }}omega + {#--#} +{%- endif -%} +{%- if hp.output_mode == "medial_sphere" -%} + {#--#}-{{ str(hp.atom_centroid_norm_std_reg_negexp).replace(*"-n") if hp.atom_centroid_norm_std_reg_negexp is not none else 'no' }}minatomstdngxp + {#--#}-{{ hp.sphere_grow_reg_coeff }}sphgrow + {#--#} +{%- endif -%} +{#--#}-{{ int(hp.dropout_percent*10) }}mdrop +{#--#}-{{ hp.normalization or "nonorm" }} +{#--#}-{{ hp.grad_reg }} +{#--#}{% if "multi_view" in hp.grad_reg %} +{#- -#}-{{ int(hp.multi_view_reg_coeff*10) }}dmv +{#--#}{%- endif -%} +{#--#}-{{ "concat" if do_condition else "nocond" }} +{#--#}-{{ int(hp.warmup_epochs*100) }}cwu{{ int(hp.lr_coeff*100) }}clr{{ int(hp.test_val_split_frac*100) }}tvs +{#--#}-{{ gen_run_uid(4) }} # select with --Oselect={{ index }} +{#--#} +{##} + +{% endautoescape %} +IntersectionFieldAutoDecoderModel: + _extra: # used for easier introspection with jq + dataset_item: {{ hp.dataset_item | to_json}} + dataset_test_val_frac: {{ hp.test_val_split_frac }} + select: {{ index }} + + input_mode : {{ hp.input_mode }} # in {plucker, perp_foot, both} + output_mode : {{ hp.output_mode }} # in {medial_sphere, orthogonal_plane} + #latent_features : 256 # int + #latent_features : 128 # int + latent_features : 16 # int + hidden_features : {{ hp.hidden_features }} # int + hidden_layers : {{ hp.hidden_layers }} # int + + improve_miss_grads : {{ bool(hp.improve_miss_grads) | to_json }} + normalize_ray_dirs : {{ bool(hp.normalize_ray_dirs) | to_json }} + + loss_intersection : {{ hp.intersection_coeff }} + loss_intersection_l2 : 0 + loss_intersection_proj : 0 + loss_intersection_proj_l2 : 0 + + loss_normal_cossim : {{ hp.normal_coeff }} * EaseSin(85, 15) + loss_normal_euclid : 0 + loss_normal_cossim_proj : 0 + loss_normal_euclid_proj : 0 + +{% if "multi_view" in hp.grad_reg %} + loss_multi_view_reg : 0.1 * {{ hp.multi_view_reg_coeff }} * Linear(50) +{% else %} + loss_multi_view_reg : 0 +{% endif %} + +{% if hp.output_mode == "orthogonal_plane" %} + + loss_hit_cross_entropy : 1 + +{% elif hp.output_mode == "medial_sphere" %} + + loss_hit_nodistance_l1 : 0 + loss_hit_nodistance_l2 : 100 * {{ hp.miss_distance_coeff }} + loss_miss_distance_l1 : 0 + loss_miss_distance_l2 : 10 * {{ hp.miss_distance_coeff }} + + loss_inscription_hits : {{ 0.4 * hp.loss_inscription }} + loss_inscription_miss : 0 + loss_inscription_hits_l2 : 0 + loss_inscription_miss_l2 : {{ 6 * hp.loss_inscription }} + + loss_sphere_grow_reg : 1e-6 * {{ hp.sphere_grow_reg_coeff }} # constant + loss_atom_centroid_norm_std_reg: (0.09*(1-Linear(40)) + 0.01) * {{ 10**(-hp.atom_centroid_norm_std_reg_negexp) if hp.atom_centroid_norm_std_reg_negexp is not none else 0 }} + +{% else %}{#endif hp.output_mode == "medial_sphere" #} + THIS IS INVALID YAML +{% endif %} + + loss_embedding_norm : 0.01**2 * Linear(30, 0.1) + + opt_learning_rate : {{ hp.lr_coeff }} * 10**(-4-0.5*EaseSin(170, 30)) # layernorm + opt_warmup : {{ hp.warmup_epochs }} + opt_weight_decay : 5e-6 # float + +{% if hp.output_mode == "medial_sphere" %} + + # MedialAtomNet: + n_atoms : {{ hp.n_atoms }} # int + {% if hp.geom_init %} + final_init_wrr: [0.05, 0.6, 0.1] + {% else %} + final_init_wrr: null + {% endif %} + +{% endif %} + + + # FCBlock: + normalization : {{ hp.normalization or "null" }} # in {null, layernorm, layernorm_na, weightnorm} + nonlinearity : {{ hp.nonlinearity or "null" }} # in {null, relu, leaky_relu, silu, softplus, elu, selu, sine, sigmoid, tanh } + {% set middle = 1 + hp.hidden_layers // 2 + (hp.hidden_layers % 2) %}{##} + concat_skipped_layers : [{{ middle }}, -1] +{% if do_condition %} + concat_conditioned_layers : [0, {{ middle }}] +{% else %} + concat_conditioned_layers : [] +{% endif %} + + # FCLayer: + negative_slope : 0.01 # float + omega_0 : {{ hp.omega }} # float + residual_mode : null # in {null, identity} + +{% endif %}{# -Oselect #} + + +{% endfor %} + + +{% set index = next(counter) %} +# number of possible -Oselect: {{ index }}, from 0 to {{ index-1 }} +# local: for select in {0..{{ index-1 }}}; do python ... -Omode={{ mode }} -Oselect=$select ... ; done +# local: for select in {0..{{ index-1 }}}; do python -O {{ argv[0] }} model marf.yaml.j2 -Omode={{ mode }} -Oselect=$select -Oexperiment_name='{{ experiment_name }}' fit --accelerator gpu ; done +# slurm: sbatch --array=0-{{ index-1 }} runcommand.slurm python ... -Omode={{ mode }} -Oselect=\$SLURM_ARRAY_TASK_ID ... +# slurm: sbatch --array=0-{{ index-1 }} runcommand.slurm python -O {{ argv[0] }} model marf.yaml.j2 -Omode={{ mode }} -Oselect=\$SLURM_ARRAY_TASK_ID -Oexperiment_name='{{ experiment_name }}' fit --accelerator gpu --devices -1 --strategy ddp diff --git a/experiments/summary.py b/experiments/summary.py new file mode 100755 index 0000000..7cbaae8 --- /dev/null +++ b/experiments/summary.py @@ -0,0 +1,849 @@ +#!/usr/bin/env python +from concurrent.futures import ThreadPoolExecutor, Future, ProcessPoolExecutor +from functools import partial +from more_itertools import first, last, tail +from munch import Munch, DefaultMunch, munchify, unmunchify +from pathlib import Path +from statistics import mean, StatisticsError +from mpl_toolkits.axes_grid1 import make_axes_locatable +from typing import Iterable, Optional, Literal +from math import isnan +import json +import stat +import matplotlib +import matplotlib.colors as mcolors +import matplotlib.pyplot as plt +import os, os.path +import re +import shlex +import time +import itertools +import shutil +import subprocess +import sys +import traceback +import typer +import warnings +import yaml +import tempfile + +EXPERIMENTS = Path(__file__).resolve() +LOGDIR = EXPERIMENTS / "logdir" +TENSORBOARD = LOGDIR / "tensorboard" +SLURM_LOGS = LOGDIR / "slurm_logs" +CACHED_SUMMARIES = LOGDIR / "cached_summaries" +COMPUTED_SCORES = LOGDIR / "computed_scores" + +MISSING = object() + +class SafeLoaderIgnoreUnknown(yaml.SafeLoader): + def ignore_unknown(self, node): + return None +SafeLoaderIgnoreUnknown.add_constructor(None, SafeLoaderIgnoreUnknown.ignore_unknown) + +def camel_to_snake_case(text: str, sep: str = "_", join_abbreviations: bool = False) -> str: + parts = ( + part.lower() + for part in re.split(r'(?=[A-Z])', text) + if part + ) + if join_abbreviations: # this operation is not reversible + parts = list(parts) + if len(parts) > 1: + for i, (a, b) in list(enumerate(zip(parts[:-1], parts[1:])))[::-1]: + if len(a) == len(b) == 1: + parts[i] = parts[i] + parts.pop(i+1) + return sep.join(parts) + +def flatten_dict(data: dict, key_mapper: callable = lambda x: x) -> dict: + if not any(isinstance(val, dict) for val in data.values()): + return data + else: + return { + k: v + for k, v in data.items() + if not isinstance(v, dict) + } | { + f"{key_mapper(p)}/{k}":v + for p,d in data.items() + if isinstance(d, dict) + for k,v in d.items() + } + +def parse_jsonl(data: str) -> Iterable[dict]: + yield from map(json.loads, (line for line in data.splitlines() if line.strip())) + +def read_jsonl(path: Path) -> Iterable[dict]: + with path.open("r") as f: + data = f.read() + yield from parse_jsonl(data) + +def get_experiment_paths(filter: str | None, assert_dumped = False) -> Iterable[Path]: + for path in TENSORBOARD.iterdir(): + if filter is not None and not re.search(filter, path.name): continue + if not path.is_dir(): continue + + if not (path / "hparams.yaml").is_file(): + warnings.warn(f"Missing hparams: {path}") + continue + if not any(path.glob("events.out.tfevents.*")): + warnings.warn(f"Missing tfevents: {path}") + continue + + if __debug__ and assert_dumped: + assert (path / "scalars/epoch.json").is_file(), path + assert (path / "scalars/IntersectionFieldAutoDecoderModel.validation_step/loss.json").is_file(), path + assert (path / "scalars/IntersectionFieldAutoDecoderModel.training_step/loss.json").is_file(), path + + yield path + +def dump_pl_tensorboard_hparams(experiment: Path): + with (experiment / "hparams.yaml").open() as f: + hparams = yaml.load(f, Loader=SafeLoaderIgnoreUnknown) + + shebang = None + with (experiment / "config.yaml").open("w") as f: + raw_yaml = hparams.get('_pickled_cli_args', {}).get('_raw_yaml', "").replace("\n\r", "\n") + if raw_yaml.startswith("#!"): # preserve shebang + shebang, _, raw_yaml = raw_yaml.partition("\n") + f.write(f"{shebang}\n") + f.write(f"# {' '.join(map(shlex.quote, hparams.get('_pickled_cli_args', {}).get('sys_argv', ['None'])))}\n\n") + f.write(raw_yaml) + if shebang is not None: + os.chmod(experiment / "config.yaml", (experiment / "config.yaml").stat().st_mode | stat.S_IXUSR) + print(experiment / "config.yaml", "written!", file=sys.stderr) + + with (experiment / "environ.yaml").open("w") as f: + yaml.safe_dump(hparams.get('_pickled_cli_args', {}).get('host', {}).get('environ'), f) + print(experiment / "environ.yaml", "written!", file=sys.stderr) + + with (experiment / "repo.patch").open("w") as f: + f.write(hparams.get('_pickled_cli_args', {}).get('host', {}).get('vcs', "None")) + print(experiment / "repo.patch", "written!", file=sys.stderr) + +def dump_simple_tf_events_to_jsonl(output_dir: Path, *tf_files: Path): + from google.protobuf.json_format import MessageToDict + import tensorboard.backend.event_processing.event_accumulator + s, l = {}, [] # reused sentinels + + #resource.setrlimit(resource.RLIMIT_NOFILE, (2**16,-1)) + file_handles = {} + try: + for tffile in tf_files: + loader = tensorboard.backend.event_processing.event_file_loader.LegacyEventFileLoader(str(tffile)) + for event in loader.Load(): + for summary in MessageToDict(event).get("summary", s).get("value", l): + if "simpleValue" in summary: + tag = summary["tag"] + if tag not in file_handles: + fname = output_dir / f"{tag}.json" + print(f"Opening {str(fname)!r}...", file=sys.stderr) + fname.parent.mkdir(parents=True, exist_ok=True) + file_handles[tag] = fname.open("w") # ("a") + val = summary["simpleValue"] + data = json.dumps({ + "step" : event.step, + "value" : float(val) if isinstance(val, str) else val, + "wall_time" : event.wall_time, + }) + file_handles[tag].write(f"{data}\n") + finally: + if file_handles: + print("Closing json files...", file=sys.stderr) + for k, v in file_handles.items(): + v.close() + + +NO_FILTER = { + "__uid", + "_minutes", + "_epochs", + "_hp_nonlinearity", + "_val_uloss_intersection", + "_val_uloss_normal_cossim", + "_val_uloss_intersection", +} +def filter_jsonl_columns(data: Iterable[dict | None], no_filter=NO_FILTER) -> list[dict]: + def merge_siren_omega(data: dict) -> dict: + return { + key: ( + f"{val}-{data.get('hp_omega_0', 'ERROR')}" + if (key.removeprefix("_"), val) == ("hp_nonlinearity", "sine") else + val + ) + for key, val in data.items() + if key != "hp_omega_0" + } + + def remove_uninteresting_cols(rows: list[dict]) -> Iterable[dict]: + unique_vals = {} + def register_val(key, val): + unique_vals.setdefault(key, set()).add(repr(val)) + return val + + whitelisted = { + key + for row in rows + for key, val in row.items() + if register_val(key, val) and val not in ("None", "0", "0.0") + } + for key in unique_vals: + for row in rows: + if key not in row: + unique_vals[key].add(MISSING) + for key, vals in unique_vals.items(): + if key not in whitelisted: continue + if len(vals) == 1: + whitelisted.remove(key) + + whitelisted.update(no_filter) + + yield from ( + { + key: val + for key, val in row.items() + if key in whitelisted + } + for row in rows + ) + + def pessemize_types(rows: list[dict]) -> Iterable[dict]: + types = {} + order = (str, float, int, bool, tuple, type(None)) + for row in rows: + for key, val in row.items(): + if isinstance(val, list): val = tuple(val) + assert type(val) in order, (type(val), val) + index = order.index(type(val)) + types[key] = min(types.get(key, 999), index) + + yield from ( + { + key: order[types[key]](val) if val is not None else None + for key, val in row.items() + } + for row in rows + ) + + data = (row for row in data if row is not None) + data = map(partial(flatten_dict, key_mapper=camel_to_snake_case), data) + data = map(merge_siren_omega, data) + data = remove_uninteresting_cols(list(data)) + data = pessemize_types(list(data)) + + return data + +PlotMode = Literal["stackplot", "lineplot"] + +def plot_losses(experiments: list[Path], mode: PlotMode, write: bool = False, dump: bool = False, training: bool = False, unscaled: bool = False, force=True): + def get_losses(experiment: Path, training: bool = True, unscaled: bool = False) -> Iterable[Path]: + if not training and unscaled: + return experiment.glob("scalars/*.validation_step/unscaled_loss_*.json") + elif not training and not unscaled: + return experiment.glob("scalars/*.validation_step/loss_*.json") + elif training and unscaled: + return experiment.glob("scalars/*.training_step/unscaled_loss_*.json") + elif training and not unscaled: + return experiment.glob("scalars/*.training_step/loss_*.json") + + print("Mapping colors...") + configurations = [ + dict(unscaled=unscaled, training=training), + ] if not write else [ + dict(unscaled=False, training=False), + dict(unscaled=False, training=True), + dict(unscaled=True, training=False), + dict(unscaled=True, training=True), + ] + legends = set( + f"""{ + loss.parent.name.split(".", 1)[0] + }.{ + loss.name.removesuffix(loss.suffix).removeprefix("unscaled_") + }""" + for experiment in experiments + for kw in configurations + for loss in get_losses(experiment, **kw) + ) + colormap = dict(zip( + sorted(legends), + itertools.cycle(mcolors.TABLEAU_COLORS), + )) + + def mkplot(experiment: Path, training: bool = True, unscaled: bool = False) -> tuple[bool, str]: + label = f"{'unscaled' if unscaled else 'scaled'} {'training' if training else 'validation'}" + if write: + old_savefig_fname = experiment / f"{label.replace(' ', '-')}-{mode}.png" + savefig_fname = experiment / "plots" / f"{label.replace(' ', '-')}-{mode}.png" + savefig_fname.parent.mkdir(exist_ok=True, parents=True) + if old_savefig_fname.is_file(): + old_savefig_fname.rename(savefig_fname) + if savefig_fname.is_file() and not force: + return True, "savefig_fname already exists" + + # Get and sort data + losses = {} + for loss in get_losses(experiment, training=training, unscaled=unscaled): + model = loss.parent.name.split(".", 1)[0] + name = loss.name.removesuffix(loss.suffix).removeprefix("unscaled_") + losses[f"{model}.{name}"] = (loss, list(read_jsonl(loss))) + losses = dict(sorted(losses.items())) # sort keys + if not losses: + return True, "no losses" + + # unwrap + steps = [i["step"] for i in first(losses.values())[1]] + values = [ + [i["value"] if not isnan(i["value"]) else 0 for i in data] + for name, (scalar, data) in losses.items() + ] + + # normalize + if mode == "stackplot": + totals = list(map(sum, zip(*values))) + values = [ + [i / t for i, t in zip(data, totals)] + for data in values + ] + + print(experiment.name, label) + fig, ax = plt.subplots(figsize=(16, 12)) + + if mode == "stackplot": + ax.stackplot(steps, values, + colors = list(map(colormap.__getitem__, losses.keys())), + labels = list( + label.split(".", 1)[1].removeprefix("loss_") + for label in losses.keys() + ), + ) + ax.set_xlim(0, steps[-1]) + ax.set_ylim(0, 1) + ax.invert_yaxis() + + elif mode == "lineplot": + for data, color, label in zip( + values, + map(colormap.__getitem__, losses.keys()), + list(losses.keys()), + ): + ax.plot(steps, data, + color = color, + label = label, + ) + ax.set_xlim(0, steps[-1]) + + else: + raise ValueError(f"{mode=}") + + ax.legend() + ax.set_title(f"{label} loss\n{experiment.name}") + ax.set_xlabel("Step") + ax.set_ylabel("loss%") + + if mode == "stackplot": + ax2 = make_axes_locatable(ax).append_axes("bottom", 0.8, pad=0.05, sharex=ax) + ax2.stackplot( steps, totals ) + + for tl in ax.get_xticklabels(): tl.set_visible(False) + + fig.tight_layout() + + if write: + fig.savefig(savefig_fname, dpi=300) + print(savefig_fname) + plt.close(fig) + + return False, None + + print("Plotting...") + if write: + matplotlib.use('agg') # fixes "WARNING: QApplication was not created in the main() thread." + any_error = False + if write: + with ThreadPoolExecutor(max_workers=None) as pool: + futures = [ + (experiment, pool.submit(mkplot, experiment, **kw)) + for experiment in experiments + for kw in configurations + ] + else: + def mkfuture(item): + f = Future() + f.set_result(item) + return f + futures = [ + (experiment, mkfuture(mkplot(experiment, **kw))) + for experiment in experiments + for kw in configurations + ] + + for experiment, future in futures: + try: + err, msg = future.result() + except Exception: + traceback.print_exc(file=sys.stderr) + any_error = True + continue + if err: + print(f"{msg}: {experiment.name}") + any_error = True + continue + + if not any_error and not write: # show in main thread + plt.show() + elif not write: + print("There were errors, will not show figure...", file=sys.stderr) + + + +# ========= + +app = typer.Typer(no_args_is_help=True, add_completion=False) + +@app.command(help="Dump simple tensorboard events to json and extract some pytorch lightning hparams") +def tf_dump(tfevent_files: list[Path], j: int = typer.Option(1, "-j"), force: bool = False): + # expand to all tfevents files (there may be more than one) + tfevent_files = sorted(set([ + tffile + for tffile in tfevent_files + if tffile.name.startswith("events.out.tfevents.") + ] + [ + tffile + for experiment_dir in tfevent_files + if experiment_dir.is_dir() + for tffile in experiment_dir.glob("events.out.tfevents.*") + ] + [ + tffile + for hparam_file in tfevent_files + if hparam_file.name in ("hparams.yaml", "config.yaml") + for tffile in hparam_file.parent.glob("events.out.tfevents.*") + ])) + + # filter already dumped + if not force: + tfevent_files = [ + tffile + for tffile in tfevent_files + if not ( + (tffile.parent / "scalars/epoch.json").is_file() + and + tffile.stat().st_mtime < (tffile.parent / "scalars/epoch.json").stat().st_mtime + ) + ] + + if not tfevent_files: + raise typer.BadParameter("Nothing to be done, consider --force") + + jobs = {} + for tffile in tfevent_files: + if not tffile.is_file(): + print("ERROR: file not found:", tffile, file=sys.stderr) + continue + output_dir = tffile.parent / "scalars" + jobs.setdefault(output_dir, []).append(tffile) + with ProcessPoolExecutor() as p: + for experiment in set(tffile.parent for tffile in tfevent_files): + p.submit(dump_pl_tensorboard_hparams, experiment) + for output_dir, tffiles in jobs.items(): + p.submit(dump_simple_tf_events_to_jsonl, output_dir, *tffiles) + +@app.command(help="Propose experiment regexes") +def propose(cmd: str = typer.Argument("summary"), null: bool = False): + def get(): + for i in TENSORBOARD.iterdir(): + if not i.is_dir(): continue + if not (i / "hparams.yaml").is_file(): continue + prefix, name, *hparams, year, month, day, hhmm, uid = i.name.split("-") + yield f"{name}.*-{year}-{month}-{day}" + proposals = sorted(set(get()), key=lambda x: x.split(".*-", 1)[1]) + print("\n".join( + f"{'>/dev/null ' if null else ''}{sys.argv[0]} {cmd or 'summary'} {shlex.quote(i)}" + for i in proposals + )) + +@app.command("list", help="List used experiment regexes") +def list_cached_summaries(cmd: str = typer.Argument("summary")): + if not CACHED_SUMMARIES.is_dir(): + cached = [] + else: + cached = [ + i.name.removesuffix(".jsonl") + for i in CACHED_SUMMARIES.iterdir() + if i.suffix == ".jsonl" + if i.is_file() and i.stat().st_size + ] + def order(key: str) -> list[str]: + return re.sub(r'[^0-9\-]', '', key.split(".*")[-1]).strip("-").split("-") + [key] + + print("\n".join( + f"{sys.argv[0]} {cmd or 'summary'} {shlex.quote(i)}" + for i in sorted(cached, key=order) + )) + +@app.command(help="Precompute the summary of a experiment regex") +def compute_summary(filter: str, force: bool = False, dump: bool = False, no_cache: bool = False): + cache = CACHED_SUMMARIES / f"{filter}.jsonl" + if cache.is_file() and cache.stat().st_size: + if not force: + raise FileExistsError(cache) + + def mk_summary(path: Path) -> dict | None: + cache = path / "train_summary.json" + if cache.is_file() and cache.stat().st_size and cache.stat().st_mtime > (path/"scalars/epoch.json").stat().st_mtime: + with cache.open() as f: + return json.load(f) + else: + with (path / "hparams.yaml").open() as f: + hparams = munchify(yaml.load(f, Loader=SafeLoaderIgnoreUnknown), factory=partial(DefaultMunch, None)) + config = hparams._pickled_cli_args._raw_yaml + config = munchify(yaml.load(config, Loader=SafeLoaderIgnoreUnknown), factory=partial(DefaultMunch, None)) + + try: + train_loss = list(read_jsonl(path / "scalars/IntersectionFieldAutoDecoderModel.training_step/loss.json")) + val_loss = list(read_jsonl(path / "scalars/IntersectionFieldAutoDecoderModel.validation_step/loss.json")) + except: + traceback.print_exc(file=sys.stderr) + return None + + out = Munch() + out.uid = path.name.rsplit("-", 1)[-1] + out.name = path.name + out.date = "-".join(path.name.split("-")[-5:-1]) + out.epochs = int(last(read_jsonl(path / "scalars/epoch.json"))["value"]) + out.steps = val_loss[-1]["step"] + out.gpu = hparams._pickled_cli_args.host.gpus[1][1] + + if val_loss[-1]["wall_time"] - val_loss[0]["wall_time"] > 0: + out.batches_per_second = val_loss[-1]["step"] / (val_loss[-1]["wall_time"] - val_loss[0]["wall_time"]) + else: + out.batches_per_second = 0 + + out.minutes = (val_loss[-1]["wall_time"] - train_loss[0]["wall_time"]) / 60 + + if (path / "scalars/PsutilMonitor/gpu.00.memory.used.json").is_file(): + max(i["value"] for i in read_jsonl(path / "scalars/PsutilMonitor/gpu.00.memory.used.json")) + + for metric_path in (path / "scalars/IntersectionFieldAutoDecoderModel.validation_step").glob("*.json"): + if not metric_path.is_file() or not metric_path.stat().st_size: continue + + metric_name = metric_path.name.removesuffix(".json") + metric_data = read_jsonl(metric_path) + try: + out[f"val_{metric_name}"] = mean(i["value"] for i in tail(5, metric_data)) + except StatisticsError: + out[f"val_{metric_name}"] = float('nan') + + for metric_path in (path / "scalars/IntersectionFieldAutoDecoderModel.training_step").glob("*.json"): + if not any(i in metric_path.name for i in ("miss_radius_grad", "sphere_center_grad", "loss_tangential_reg", "multi_view")): continue + if not metric_path.is_file() or not metric_path.stat().st_size: continue + + metric_name = metric_path.name.removesuffix(".json") + metric_data = read_jsonl(metric_path) + try: + out[f"train_{metric_name}"] = mean(i["value"] for i in tail(5, metric_data)) + except StatisticsError: + out[f"train_{metric_name}"] = float('nan') + + out.hostname = hparams._pickled_cli_args.host.hostname + + for key, val in config.IntersectionFieldAutoDecoderModel.items(): + if isinstance(val, dict): + out.update({f"hp_{key}_{k}": v for k, v in val.items()}) + elif isinstance(val, float | int | str | bool | None): + out[f"hp_{key}"] = val + + with cache.open("w") as f: + json.dump(unmunchify(out), f) + + return dict(out) + + experiments = list(get_experiment_paths(filter, assert_dumped=not dump)) + if not experiments: + raise typer.BadParameter("No matching experiment") + if dump: + try: + tf_dump(experiments) # force=force_dump) + except typer.BadParameter: + pass + + # does literally nothing, thanks GIL + with ThreadPoolExecutor() as p: + results = list(p.map(mk_summary, experiments)) + + if any(result is None for result in results): + if all(result is None for result in results): + print("No summary succeeded", file=sys.stderr) + raise typer.Exit(exit_code=1) + warnings.warn("Some summaries failed:\n" + "\n".join( + str(experiment) + for result, experiment in zip(results, experiments) + if result is None + )) + + summaries = "\n".join( map(json.dumps, results) ) + if not no_cache: + cache.parent.mkdir(parents=True, exist_ok=True) + with cache.open("w") as f: + f.write(summaries) + return summaries + +@app.command(help="Show the summary of a experiment regex, precompute it if needed") +def summary(filter: Optional[str] = typer.Argument(None), force: bool = False, dump: bool = False, all: bool = False): + if filter is None: + return list_cached_summaries("summary") + + def key_mangler(key: str) -> str: + for pattern, sub in ( + (r'^val_unscaled_loss_', r'val_uloss_'), + (r'^train_unscaled_loss_', r'train_uloss_'), + (r'^val_loss_', r'val_sloss_'), + (r'^train_loss_', r'train_sloss_'), + ): + key = re.sub(pattern, sub, key) + + return key + + cache = CACHED_SUMMARIES / f"{filter}.jsonl" + if force or not (cache.is_file() and cache.stat().st_size): + compute_summary(filter, force=force, dump=dump) + assert cache.is_file() and cache.stat().st_size, (cache, cache.stat()) + + if os.isatty(0) and os.isatty(1) and shutil.which("vd"): + rows = read_jsonl(cache) + rows = ({key_mangler(k): v for k, v in row.items()} if row is not None else None for row in rows) + if not all: + rows = filter_jsonl_columns(rows) + rows = ({k: v for k, v in row.items() if not k.startswith(("val_sloss_", "train_sloss_"))} for row in rows) + data = "\n".join(map(json.dumps, rows)) + subprocess.run(["vd", + #"--play", EXPERIMENTS / "set-key-columns.vd", + "-f", "jsonl" + ], input=data, text=True, check=True) + else: + with cache.open() as f: + print(f.read()) + +@app.command(help="Filter uninteresting keys from jsonl stdin") +def filter_cols(): + rows = map(json.loads, (line for line in sys.stdin.readlines() if line.strip())) + rows = filter_jsonl_columns(rows) + print(*map(json.dumps, rows), sep="\n") + +@app.command(help="Run a command for each experiment matched by experiment regex") +def exec(filter: str, cmd: list[str], j: int = typer.Option(1, "-j"), dumped: bool = False, undumped: bool = False): + # inspired by fd / gnu parallel + def populate_cmd(experiment: Path, cmd: Iterable[str]) -> Iterable[str]: + any = False + for i in cmd: + if i == "{}": + any = True + yield str(experiment / "hparams.yaml") + elif i == "{//}": + any = True + yield str(experiment) + else: + yield i + if not any: + yield str(experiment / "hparams.yaml") + + with ThreadPoolExecutor(max_workers=j or None) as p: + results = p.map(subprocess.run, ( + list(populate_cmd(experiment, cmd)) + for experiment in get_experiment_paths(filter) + if not dumped or (experiment / "scalars/epoch.json").is_file() + if not undumped or not (experiment / "scalars/epoch.json").is_file() + )) + + if any(i.returncode for i in results): + return typer.Exit(1) + +@app.command(help="Show stackplot of experiment loss") +def stackplot(filter: str, write: bool = False, dump: bool = False, training: bool = False, unscaled: bool = False, force: bool = False): + experiments = list(get_experiment_paths(filter, assert_dumped=not dump)) + if not experiments: + raise typer.BadParameter("No match") + if dump: + try: + tf_dump(experiments) + except typer.BadParameter: + pass + + plot_losses(experiments, + mode = "stackplot", + write = write, + dump = dump, + training = training, + unscaled = unscaled, + force = force, + ) + +@app.command(help="Show stackplot of experiment loss") +def lineplot(filter: str, write: bool = False, dump: bool = False, training: bool = False, unscaled: bool = False, force: bool = False): + experiments = list(get_experiment_paths(filter, assert_dumped=not dump)) + if not experiments: + raise typer.BadParameter("No match") + if dump: + try: + tf_dump(experiments) + except typer.BadParameter: + pass + + plot_losses(experiments, + mode = "lineplot", + write = write, + dump = dump, + training = training, + unscaled = unscaled, + force = force, + ) + +@app.command(help="Open tensorboard for the experiments matching the regex") +def tensorboard(filter: Optional[str] = typer.Argument(None), watch: bool = False): + if filter is None: + return list_cached_summaries("tensorboard") + experiments = list(get_experiment_paths(filter, assert_dumped=False)) + if not experiments: + raise typer.BadParameter("No match") + + with tempfile.TemporaryDirectory(suffix=f"ifield-{filter}") as d: + treefarm = Path(d) + with ThreadPoolExecutor(max_workers=2) as p: + for experiment in experiments: + (treefarm / experiment.name).symlink_to(experiment) + + cmd = ["tensorboard", "--logdir", d] + print("+", *map(shlex.quote, cmd), file=sys.stderr) + tensorboard = p.submit(subprocess.run, cmd, check=True) + if not watch: + tensorboard.result() + + else: + all_experiments = set(get_experiment_paths(None, assert_dumped=False)) + while not tensorboard.done(): + time.sleep(10) + new_experiments = set(get_experiment_paths(None, assert_dumped=False)) - all_experiments + if new_experiments: + for experiment in new_experiments: + print(f"Adding {experiment.name!r}...", file=sys.stderr) + (treefarm / experiment.name).symlink_to(experiment) + all_experiments.update(new_experiments) + +@app.command(help="Compute evaluation metrics") +def metrics(filter: Optional[str] = typer.Argument(None), dump: bool = False, dry: bool = False, prefix: Optional[str] = typer.Option(None), derive: bool = False, each: bool = False, no_total: bool = False): + if filter is None: + return list_cached_summaries("metrics --derive") + experiments = list(get_experiment_paths(filter, assert_dumped=False)) + if not experiments: + raise typer.BadParameter("No match") + if dump: + try: + tf_dump(experiments) + except typer.BadParameter: + pass + + def run(*cmd): + if prefix is not None: + cmd = [*shlex.split(prefix), *cmd] + if dry: + print(*map(shlex.quote, map(str, cmd))) + else: + print("+", *map(shlex.quote, map(str, cmd))) + subprocess.run(cmd) + + for experiment in experiments: + if no_total: continue + if not (experiment / "compute-scores/metrics.json").is_file(): + run( + "python", "./marf.py", "module", "--best", experiment / "hparams.yaml", + "compute-scores", experiment / "compute-scores/metrics.json", + "--transpose", + ) + if not (experiment / "compute-scores/metrics-last.json").is_file(): + run( + "python", "./marf.py", "module", "--last", experiment / "hparams.yaml", + "compute-scores", experiment / "compute-scores/metrics-last.json", + "--transpose", + ) + if "2prif-" not in experiment.name: continue + if not (experiment / "compute-scores/metrics-sans_outliers.json").is_file(): + run( + "python", "./marf.py", "module", "--best", experiment / "hparams.yaml", + "compute-scores", experiment / "compute-scores/metrics-sans_outliers.json", + "--transpose", "--filter-outliers" + ) + if not (experiment / "compute-scores/metrics-last-sans_outliers.json").is_file(): + run( + "python", "./marf.py", "module", "--last", experiment / "hparams.yaml", + "compute-scores", experiment / "compute-scores/metrics-last-sans_outliers.json", + "--transpose", "--filter-outliers" + ) + + if dry: return + if prefix is not None: + print("prefix was used, assuming a job scheduler was used, will not print scores.", file=sys.stderr) + return + + metrics = [ + *(experiment / "compute-scores/metrics.json" for experiment in experiments), + *(experiment / "compute-scores/metrics-last.json" for experiment in experiments), + *(experiment / "compute-scores/metrics-sans_outliers.json" for experiment in experiments if "2prif-" in experiment.name), + *(experiment / "compute-scores/metrics-last-sans_outliers.json" for experiment in experiments if "2prif-" in experiment.name), + ] + if not no_total: + assert all(metric.exists() for metric in metrics) + else: + metrics = (metric for metric in metrics if metric.exists()) + + out = [] + for metric in metrics: + experiment = metric.parent.parent.name + is_last = metric.name in ("metrics-last.json", "metrics-last-sans_outliers.json") + with metric.open() as f: + data = json.load(f) + + if derive: + derived = {} + objs = [i for i in data.keys() if i != "_hparams"] + for obj in (objs if each else []) + [None]: + if obj is None: + d = DefaultMunch(0) + for obj in objs: + for k, v in data[obj].items(): + d[k] += v + obj = "_all_" + n_cd = data["_hparams"]["n_cd"] * len(objs) + n_emd = data["_hparams"]["n_emd"] * len(objs) + else: + d = munchify(data[obj]) + n_cd = data["_hparams"]["n_cd"] + n_emd = data["_hparams"]["n_emd"] + + precision = d.TP / (d.TP + d.FP) + recall = d.TP / (d.TP + d.FN) + derived[obj] = dict( + filtered = d.n_outliers / d.n if "n_outliers" in d else None, + iou = d.TP / (d.TP + d.FN + d.FP), + precision = precision, + recall = recall, + f_score = 2 * (precision * recall) / (precision + recall), + cd = d.cd_dist / n_cd, + emd = d.emd / n_emd, + cos_med = 1 - (d.cd_cos_med / n_cd) if "cd_cos_med" in d else None, + cos_jac = 1 - (d.cd_cos_jac / n_cd), + ) + data = derived if each else derived["_all_"] + + data["uid"] = experiment.rsplit("-", 1)[-1] + data["experiment_name"] = experiment + data["is_last"] = is_last + + out.append(json.dumps(data)) + + if derive and not each and os.isatty(0) and os.isatty(1) and shutil.which("vd"): + subprocess.run(["vd", "-f", "jsonl"], input="\n".join(out), text=True, check=True) + else: + print("\n".join(out)) + +if __name__ == "__main__": + app() diff --git a/figures/nn-architecture.svg b/figures/nn-architecture.svg new file mode 100644 index 0000000..8112a36 --- /dev/null +++ b/figures/nn-architecture.svg @@ -0,0 +1,822 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ifield/__init__.py b/ifield/__init__.py new file mode 100644 index 0000000..0dc46c4 --- /dev/null +++ b/ifield/__init__.py @@ -0,0 +1,57 @@ +def setup_print_hooks(): + import os + if not os.environ.get("IFIELD_PRETTY_TRACEBACK", None): + return + + from rich.traceback import install + from rich.console import Console + import warnings, sys + + if not os.isatty(2): + # https://github.com/Textualize/rich/issues/1809 + os.environ.setdefault("COLUMNS", "120") + + install( + show_locals = bool(os.environ.get("SHOW_LOCALS", "")), + width = None, + ) + + # custom warnings + # https://github.com/Textualize/rich/issues/433 + + from rich.traceback import install + from rich.console import Console + import warnings, sys + + + def showwarning(message, category, filename, lineno, file=None, line=None): + msg = warnings.WarningMessage(message, category, filename, lineno, file, line) + + if file is None: + file = sys.stderr + if file is None: + # sys.stderr is None when run with pythonw.exe: + # warnings get lost + return + text = warnings._formatwarnmsg(msg) + if file.isatty(): + Console(file=file, stderr=True).print(text) + else: + try: + file.write(text) + except OSError: + # the file (probably stderr) is invalid - this warning gets lost. + pass + warnings.showwarning = showwarning + + def warning_no_src_line(message, category, filename, lineno, file=None, line=None): + if (file or sys.stderr) is not None: + if (file or sys.stderr).isatty(): + if file is None or file is sys.stderr: + return f"[yellow]{category.__name__}[/yellow]: {message}\n ({filename}:{lineno})" + return f"{category.__name__}: {message} ({filename}:{lineno})\n" + warnings.formatwarning = warning_no_src_line + + +setup_print_hooks() +del setup_print_hooks diff --git a/ifield/cli.py b/ifield/cli.py new file mode 100644 index 0000000..51999d0 --- /dev/null +++ b/ifield/cli.py @@ -0,0 +1,1006 @@ +from . import logging, param +from .utils import helpers +from .utils.helpers import camel_to_snake_case +from argparse import ArgumentParser, _SubParsersAction, Namespace +from contextlib import contextmanager +from datetime import datetime +from functools import partial +from munch import Munch, munchify +from pathlib import Path +from pytorch_lightning.utilities.exceptions import MisconfigurationException +from serve_me_once import serve_once_in_background, gen_random_port +from torch import nn +from tqdm import tqdm +from typing import Optional, Callable, TypeVar, Union, Any +import argparse, collections, copy +import inspect, io, os, platform, psutil, pygments, pygments.lexers, pygments.formatters +import pytorch_lightning as pl, re, rich, rich.pretty, shlex, shutil, string, subprocess, sys, textwrap +import traceback, time, torch, torchviz, urllib.parse, warnings, webbrowser, yaml + + +CONSOLE = rich.console.Console(width=None if os.isatty(1) else 140) +torch.set_printoptions(threshold=200) + +# https://gist.github.com/pypt/94d747fe5180851196eb#gistcomment-3595282 +#class UniqueKeyYAMLLoader(yaml.SafeLoader): +class UniqueKeyYAMLLoader(yaml.Loader): + def construct_mapping(self, node, deep=False): + mapping = set() + for key_node, value_node in node.value: + key = self.construct_object(key_node, deep=deep) + if key in mapping: + raise KeyError(f"Duplicate {key!r} key found in YAML.") + mapping.add(key) + return super().construct_mapping(node, deep) + +# load scientific notation correctly as floats and not as strings +# basically, support for the to_json filter in jinja +# https://stackoverflow.com/a/30462009 +# https://github.com/yaml/pyyaml/issues/173 +UniqueKeyYAMLLoader.add_implicit_resolver( + u'tag:yaml.org,2002:float', + re.compile(u'''^(?: + [-+]?(?:[0-9][0-9_]*)\\.[0-9_]*(?:[eE][-+]?[0-9]+)? + |[-+]?(?:[0-9][0-9_]*)(?:[eE][-+]?[0-9]+) + |\\.[0-9_]+(?:[eE][-+][0-9]+)? + |[-+]?[0-9][0-9_]*(?::[0-5]?[0-9])+\\.[0-9_]* + |[-+]?\\.(?:inf|Inf|INF) + |\\.(?:nan|NaN|NAN))$''', re.X), + list(u'-+0123456789.')) + +class IgnorantActionsContainer(argparse._ActionsContainer): + """ + Ignores conflicts with + Must be enabled with ArgumentParser(conflict_handler="ignore") + """ + # https://stackoverflow.com/a/71782808 + def _handle_conflict_ignore(self, action, conflicting_actions): + pass +argparse.ArgumentParser.__bases__ = (argparse._AttributeHolder, IgnorantActionsContainer) +argparse._ArgumentGroup.__bases__ = (IgnorantActionsContainer,) + +@contextmanager +def ignore_action_container_conflicts(parser: Union[argparse.ArgumentParser, argparse._ArgumentGroup]): + old = parser.conflict_handler + parser.conflict_handler = "ignore" + yield + parser.conflict_handler = old + +def _print_with_syntax_highlighting(language, string, indent=""): + if os.isatty(1): + string = pygments.highlight(string, + lexer = pygments.lexers.get_lexer_by_name(language), + formatter = pygments.formatters.Terminal256Formatter(style="monokai"), + ) + if indent: + string = textwrap.indent(string, indent) + print(string) + +def print_column_dict(data: dict, n_columns: int = 2, prefix: str=" "): + small = {k: v for k, v in data.items() if not isinstance(v, dict) and len(repr(v)) <= 40} + wide = {k: v for k, v in data.items() if not isinstance(v, dict) and len(repr(v)) > 40} + dicts = {k: v for k, v in data.items() if isinstance(v, dict)} + kw = dict( + crop = False, + overflow = "ignore", + ) + if small: + CONSOLE.print(helpers.columnize_dict(small, prefix=prefix, n_columns=n_columns, sep=" "), **kw) + key_len = max(map(len, map(repr, wide.keys()))) if wide else 0 + for key, val in wide.items(): + CONSOLE.print(f"{prefix}{repr(key).ljust(key_len)} : {val!r},", **kw) + for key, val in dicts.items(): + CONSOLE.print(f"{prefix}{key!r}: {{", **kw) + print_column_dict(val, n_columns=n_columns, prefix=prefix+" ") + CONSOLE.print(f"{prefix}}},", **kw) + + +M = TypeVar("M", bound=nn.Module) +DM = TypeVar("DM", bound=pl.LightningDataModule) +FitHook = Callable[[Namespace, Munch, M, pl.Trainer, DM, logging.Logger], None] + +class CliInterface: + trainer_defaults: dict + + def __init__(self, *, module_cls: type[M], workdir: Path, datamodule_cls: Union[list[type[DM]], type[DM], None] = None, experiment_name_prefix = "experiment"): + self.module_cls = module_cls + self.datamodule_cls = [datamodule_cls] if not isinstance(datamodule_cls, list) and datamodule_cls is not None else datamodule_cls + self.workdir = workdir + self.experiment_name_prefix = experiment_name_prefix + + self.trainer_defaults = dict( + enable_model_summary = False, + ) + + self.pre_fit_handlers: list[FitHook] = [] + self.post_fit_handlers: list[FitHook] = [] + + self._registered_actions : dict[str, tuple[Callable[M, None], list, dict, Optional[callable]]] = {} + self._included_in_config_template : dict[str, tuple[callable, dict]] = {} + + self.register_action(_func=self.repr, help="Print str(module).", args=[]) + self.register_action(_func=self.yaml, help="Print evaluated config.", args=[]) + self.register_action(_func=self.hparams, help="Print hparams, like during training.", args=[]) + self.register_action(_func=self.dot, help="Print graphviz graph of computation graph.", args=[ + ("-e", "--eval", dict(action="store_true")), + ("-f", "--filter", dict(action="store_true")), + ]) + self.register_action(_func=self.jit, help="Print a TorchScript graph of the model", args=[]) + self.register_action(_func=self.trace, help="Dump a TorchScript trace of the model.", args=[ + ("output_file", dict(type=Path, + help="Path to write the .pt file. Use \"-\" to instead open the trace in Netron.app")), + ]) + self.register_action(_func=self.onnx, help="Dump a ONNX trace of the model.", args=[ + ("output_file", dict(type=Path, + help="Path to write the .onnx file. Use \"-\" to instead open the onnx in Netron.app")), + ]) + + if self.datamodule_cls: + names = [i.__name__ for i in self.datamodule_cls] + names_snake = [datamodule_name_to_snake_case(i) for i in names] + assert len(names) == len(set(names)),\ + f"Datamodule names are not unique: {names!r}" + assert len(names) == len(set(names_snake)),\ + f"Datamodule snake-names are not unique: {names_snake!r}" + + self.register_action(_func=self.test_dataloader, + help="Benchmark the speed of the dataloader", + args=[ + ("datamodule", dict(type=str, default=None, nargs='?', choices=names_snake, + help="Which dataloader to test. Defaults to the first one found in config.")), + ("--limit-cores", dict(type=int, default=None, + help="Limits the cpu affinity to N cores. Perfect to simulate a SLURM environ.")), + ("--profile", dict(type=Path, default=None, + help="Profile using cProfile, marshaling the result to a .prof or .log file.")), + ("-n", "--n-rounds", dict(type=int, default=3, + help="Number of times to read the dataloader.")), + ], + conflict_handler = "ignore" if len(self.datamodule_cls) > 1 else "error", + add_argparse_args=[i.add_argparse_args for i in self.datamodule_cls], + ) + + + # decorator + def register_pre_training_callback(self, func: FitHook): + self.pre_fit_handlers.append(func) + return func + + # decorator + def register_post_training_callback(self, func: FitHook): + self.post_fit_handlers.append(func) + return func + + # decorator + def register_action(self, *, + help : str, + args : list[tuple[Any, ..., dict]] = [], + _func : Optional[Callable[[Namespace, Munch, M], None]] = None, + add_argparse_args : Union[list[Callable[[ArgumentParser], ArgumentParser]], Callable[[ArgumentParser], ArgumentParser], None] = None, + **kw, + ): + def wrapper(action: Callable[[Namespace, Munch, M], None]): + cli_name = action.__name__.lower().replace("_", "-") + self._registered_actions[cli_name] = ( + action, + args, + kw | {"help": help}, + add_argparse_args, + ) + return action + if _func is not None: # shortcut + return wrapper(_func) + else: + return wrapper + + def make_parser(self, + parser : ArgumentParser = None, + subparsers : _SubParsersAction = None, + add_trainer : bool = False, + ) -> tuple[ArgumentParser, _SubParsersAction, _SubParsersAction]: + if parser is None: + parser = ArgumentParser() + if subparsers is None: + subparsers = parser.add_subparsers(dest="mode", required=True) + + parser.add_argument("-pm", "--post-mortem", action="store_true", + help="Start a debugger if a uncaught exception is thrown.") + + # Template generation and exploration + parser_template = subparsers.add_parser("template", + help="Generate or evaluate a config template") + if 1: # fold me + parser_mode_mutex = parser_template.add_mutually_exclusive_group()#(required=True) + parser_mode_mutex.add_argument("-e", "--evaluate", metavar="TEMPLATE", type=Path, + help="Read jinja2 yaml config template file, then evaluate and print it.") + parser_mode_mutex.add_argument("-p", "--parse", metavar="TEMPLATE", type=Path, + help="Read jinja2 yaml config template file, then evaluate, parse and print it.") + + def pair(data: str) -> tuple[str, str]: + key, sep, value = data.partition("=") + if not sep: + if key in os.environ: + value = os.environ[key] + else: + raise ValueError(f"the variable {key!r} was not given any value, and none was found in the environment.") + elif "$" in value: + value = string.Template(value).substitute(os.environ) + return (key, value) + parser_template.add_argument("-O", dest="jinja2_variables", action="append", type=pair, + help="Variable available as string in the jinja2. (a=b). b will be expanded as an" + " env var if prefixed with $, or set equal to the env var a if =b is omitted.") + + parser_template.add_argument("-s", "--strict", action="store_true", + help="Enable {% do require_defined(\"var\",var) %}".replace("%", "%%")) + parser_template.add_argument("-d", "--defined-only", action="store_true", + help="Disallow any use of undefined variables") + + + # Load a module + parser_module = subparsers.add_parser("module", aliases=["model"], + help="Load a config template, evaluate it and use the resulting module") + if 1: # fold me + parser_module.add_argument("module_file", type=Path, + help="Jinja2 yaml config template or pytorch-lightning .ckpt file.") + parser_module.add_argument("-O", dest="jinja2_variables", action="append", type=pair, + help="Variable available as string in the jinja2. (a=b). b will be expanded as an" + " env var if prefixed with $, or set equal to the env var a if =b is omitted.") + parser_module.add_argument("--last", action="store_true", + help="if multiple ckpt match, prefer the last one") + parser_module.add_argument("--best", action="store_true", + help="if multiple ckpt match, prefer the best one") + + parser_module.add_argument("--add-shape-prehook", action="store_true", + help="Add a forward hook which prints the tensor shapes of all inputs, but not the outputs.") + parser_module.add_argument("--add-shape-hook", action="store_true", + help="Add a forward hook which prints the tensor shapes of all inputs AND outputs.") + parser_module.add_argument("--add-oob-hook", action="store_true", + help="Add a forward hook checking for INF and NaN values in inputs or outputs.") + parser_module.add_argument("--add-oob-hook-input", action="store_true", + help="Add a forward hook checking for INF and NaN values in inputs.") + parser_module.add_argument("--add-oob-hook-output", action="store_true", + help="Add a forward hook checking for INF and NaN values in outputs.") + + + module_actions_subparser = parser_module.add_subparsers(dest="action", required=True) + + # add pluggables + for name, (action, args, kw, add_argparse_args) in self._registered_actions.items(): + action_parser = module_actions_subparser.add_parser(name, **kw) + if add_argparse_args is not None and add_argparse_args: + for func in add_argparse_args if isinstance(add_argparse_args, list) else [add_argparse_args]: + action_parser = func(action_parser) + for *a, kw in args: + action_parser.add_argument(*a, **kw) + + # Module: train or test + if self.datamodule_cls: + parser_trainer = module_actions_subparser.add_parser("fit", aliases=["test"], + help="Train/fit or evaluate the module with train/val or test data.") + + # pl.Trainer + parser_trainer = pl.Trainer.add_argparse_args(parser_trainer) + + # datamodule + parser_trainer.add_argument("datamodule", type=str, default=None, nargs='?', + choices=[datamodule_name_to_snake_case(i) for i in self.datamodule_cls], + help="Which dataloader to test. Defaults to the first one found in config.") + if len(self.datamodule_cls) > 1: + # check that none of the datamodules conflict with trainer or module + for datamodule_cls in self.datamodule_cls: + datamodule_cls.add_argparse_args(copy.deepcopy(parser_trainer)) # will raise on conflict + # Merge the datamodule options, the above sanity check makes it "okay" + with ignore_action_container_conflicts(parser_trainer): + for datamodule_cls in self.datamodule_cls: + parser_trainer = datamodule_cls.add_argparse_args(parser_trainer) + + # defaults and jinja template + self._included_in_config_template.clear() + remove_options_from_parser(parser_trainer, "--logger") + parser_trainer.set_defaults(**self.trainer_defaults) + self.add_to_jinja_template("trainer", pl.Trainer, defaults=self.trainer_defaults, exclude_list={ + # not yaml friendly, already covered anyway: + "logger", + "plugins", + "callbacks", + # deprecated or covered by callbacks: + "stochastic_weight_avg", + "enable_model_summary", + "track_grad_norm", + "log_gpu_memory", + }) + for datamodule_cls in self.datamodule_cls: + self.add_to_jinja_template(datamodule_cls.__name__, datamodule_cls, + comment=f"select with {datamodule_name_to_snake_case(datamodule_cls)!r}")#, commented=False) + self.add_to_jinja_template("logging", logging, save_dir = "logdir", commented=False) + + return parser, subparsers, module_actions_subparser + + def add_to_jinja_template(self, name: str, func: callable, **kwargs): + """ + Basically a call to `make_jinja_template`. + Will ensure the keys are present in the output from `from_argparse_args`. + """ + self._included_in_config_template[name] = (func, dict(commented=True) | kwargs) + + def make_jinja_template(self) -> str: + return "\n".join([ + f'#!/usr/bin/env -S python {sys.argv[0]} module', + r'{% do require_defined("select", select, 0, "$SLURM_ARRAY_TASK_ID") %}{# requires jinja2.ext.do #}', + r"{% set counter = itertools.count(start=0, step=1) %}", + r"", + r"{% set hp_matrix = namespace() %}{# hyper parameter matrix #}", + r"{% set hp_matrix.my_hparam = [0] %}{##}", + r"", + r"{% for hp in cartesian_hparams(hp_matrix) %}{##}", + r"{#% for hp in ablation_hparams(hp_matrix, caartesian_keys=[]) %}{##}", + r"", + r"{% set index = next(counter) %}", + r"{% if select is not defined and index > 0 %}---{% endif %}", + r"{% if select is not defined or int(select) == index %}", + r"", + *[ + func.make_jinja_template(name=name, **kwargs) + if hasattr(func, "make_jinja_template") else + param.make_jinja_template(func, name=name, **kwargs) + for name, (func, kwargs) in self._included_in_config_template.items() + ], + r"{% autoescape false %}", + r'{% do require_defined("experiment_name", experiment_name, "test", strict=true) %}', + f"experiment_name: { self.experiment_name_prefix }-{{{{ experiment_name }}}}", + r'{#--#}-{{ hp.my_hparam }}', + r'{#--#}-{{ gen_run_uid(4) }} # select with -Oselect={{ index }}', + r"{% endautoescape %}", + self.module_cls.make_jinja_template(), + r"{% endif %}{# -Oselect #}", + r"", + r"{% endfor %}", + r"", + r"{% set index = next(counter) %}", + r"# number of possible 'select': {{ index }}, from 0 to {{ index-1 }}", + r"# local: for select in {0..{{ index-1 }}}; do python ... -Oselect=$select ... ; done", + r"# local: for select in {0..{{ index-1 }}}; do python -O {{ argv[0] }} model marf.yaml.j2 -Oselect=$select -Oexperiment_name='{{ experiment_name }}' fit --accelerator gpu ; done", + r"# slurm: sbatch --array=0-{{ index-1 }} runcommand.slurm python ... -Oselect=\$SLURM_ARRAY_TASK_ID ...", + r"# slurm: sbatch --array=0-{{ index-1 }} runcommand.slurm python -O {{ argv[0] }} model this-file.yaml.j2 -Oselect=\$SLURM_ARRAY_TASK_ID -Oexperiment_name='{{ experiment_name }}' fit --accelerator gpu --devices -1 --strategy ddp" + ]) + + def run(self, args=None, args_hook: Optional[Callable[[ArgumentParser, _SubParsersAction, _SubParsersAction], None]] = None): + parser, mode_subparser, action_subparser = self.make_parser() + if args_hook is not None: + args_hook(parser, mode_subparser, action_subparser) + args = parser.parse_args(args) # may exit + if os.isatty(0) and args.post_mortem: + warnings.warn("post-mortem debugging is enabled without any TTY attached. Will be ignored.") + if args.post_mortem and os.isatty(0): + try: + self.handle_args(args) + except Exception: + # print exception + sys.excepthook(*sys.exc_info()) + # debug + *debug_module, debug_func = os.environ.get("PYTHONBREAKPOINT", "pdb.set_trace").split(".") + __import__(".".join(debug_module)).post_mortem() + exit(1) + else: + self.handle_args(args) + + def handle_args(self, args: Namespace): + """ + May call exit() + """ + if args.mode == "template": + + if args.evaluate or args.parse: + template_file = args.evaluate or args.parse + env = param.make_jinja_env(globals=param.make_jinja_globals(enable_require_defined=args.strict), allow_undef=not args.defined_only) + if str(template_file) == "-": + template = env.from_string(sys.stdin.read(), globals=dict(args.jinja2_variables or [])) + else: + template = env.get_template(str(template_file.absolute()), globals=dict(args.jinja2_variables or [])) + config_yaml = param.squash_newlines(template.render())#.lstrip("\n").rstrip() + if args.evaluate: + _print_with_syntax_highlighting("yaml+jinja", config_yaml) + else: + config = yaml.load(config_yaml, UniqueKeyYAMLLoader) + CONSOLE.print(config) + + else: + _print_with_syntax_highlighting("yaml+jinja", self.make_jinja_template()) + + elif args.mode in ("module", "model"): + + module: nn.Module + + if not args.module_file.is_file(): + matches = [*Path("logdir/tensorboard").rglob(f"*-{args.module_file}/checkpoints/*.ckpt")] + if len(matches) == 1: + args.module_file, = matches + elif len(matches) > 1: + if (args.last or args.best) and len(set(match.parent.parent.name for match in matches)) == 1: + if args.last: + args.module_file, = (match for match in matches if match.name == "last.ckpt") + elif args.best: + args.module_file, = (match for match in matches if match.name.startswith("epoch=")) + else: + assert False + else: + raise ValueError("uid matches multiple paths:\n"+"\n".join(map(str, matches))) + else: + raise ValueError("path does not exist, and is not a uid") + + # load module from cli args + if args.module_file.suffix == ".ckpt": # from checkpoint + # load from checkpoint + rich.print(f"Loading module from {str(args.module_file)!r}...", file=sys.stderr) + module = self.module_cls.load_from_checkpoint(args.module_file) + + if (args.module_file.parent.parent / "hparams.yaml").is_file(): + with (args.module_file.parent.parent / "hparams.yaml").open() as f: + config_yaml = yaml.load(f.read(), UniqueKeyYAMLLoader)["_pickled_cli_args"]["_raw_yaml"] + else: + with (args.module_file.parent.parent / "config.yaml").open() as f: + config_yaml = f.read() + + config = munchify(yaml.load(config_yaml, UniqueKeyYAMLLoader) | {"_raw_yaml": config_yaml}) + + else: # from yaml + + # read, evaluate and parse config + if args.module_file.suffix == ".j2" or str(args.module_file) == "-": + env = param.make_jinja_env() + if str(args.module_file) == "-": + template = env.from_string(sys.stdin.read(), globals=dict(args.jinja2_variables or [])) + else: # jinja+yaml file + template = env.get_template(str(args.module_file.absolute()), globals=dict(args.jinja2_variables or [])) + config_yaml = param.squash_newlines(template.render()).lstrip("\n").rstrip() + else: # yaml file (the git diffs in _pickled_cli_args may trigger jinja's escape sequences) + with args.module_file.open() as f: + config_yaml = f.read().lstrip("\n").rstrip() + + config = yaml.load(config_yaml, UniqueKeyYAMLLoader) + + if "_pickled_cli_args" in config: # hparams.yaml in tensorboard logdir + config_yaml = config["_pickled_cli_args"]["_raw_yaml"] + config = yaml.load(config_yaml, UniqueKeyYAMLLoader) + + from_checkpoint: Optional[Path] = None + if (args.module_file.parent / "checkpoints").glob("*.ckpt"): + checkpoints_fnames = list((args.module_file.parent / "checkpoints").glob("*.ckpt")) + if len(checkpoints_fnames) == 1: + from_checkpoint = checkpoints_fnames[0] + elif args.last: + from_checkpoint, = (i for i in checkpoints_fnames if i.name == "last.ckpt") + elif args.best: + from_checkpoint, = (i for i in checkpoints_fnames if i.name.startswith("epoch=")) + elif len(checkpoints_fnames) > 1: + rich.print(f"[yellow]WARNING:[/] {str(args.module_file.parent / 'checkpoints')!r} contains more than one checkpoint, unable to automatically load one.", file=sys.stderr) + + config = munchify(config | {"_raw_yaml": config_yaml}) + + # Ensure date and uid to experiment name, allowing for reruns and organization + assert config.experiment_name + assert re.match(r'^.*-[0-9]{4}-[0-9]{2}-[0-9]{2}-[0-9]{4}-[a-z]{4}$', config.experiment_name),\ + config.experiment_name + + # init the module + if from_checkpoint: + rich.print(f"Loading module from {str(from_checkpoint)!r}...", file=sys.stderr) + module = self.module_cls.load_from_checkpoint(from_checkpoint) + else: + module = self.module_cls(**{k:v for k, v in config[self.module_cls.__name__].items() if k != "_extra"}) + + # optional debugging forward hooks + + if args.add_shape_hook or args.add_shape_prehook: + def shape_forward_hook(is_prehook: bool, name: str, module: nn.Module, input, output=None): + def tensor_to_shape(val): + if isinstance(val, torch.Tensor): + return tuple(val.shape) + elif isinstance(val, (str, float, int)) or val is None: + return 1 + else: + assert 0, (val, name) + with torch.no_grad(): + rich.print( + f"{name}.forward({helpers.map_tree(tensor_to_shape, input)})" + if is_prehook else + f"{name}.forward({helpers.map_tree(tensor_to_shape, input)})" + f" -> {helpers.map_tree(tensor_to_shape, output)}" + , file=sys.stderr) + + for submodule_name, submodule in module.named_modules(): + if submodule_name: + submodule_name = f"{module.__class__.__qualname__}.{submodule_name}" + else: + submodule_name = f"{module.__class__.__qualname__}" + if args.add_shape_prehook: + submodule.register_forward_pre_hook(partial(shape_forward_hook, True, submodule_name)) + if args.add_shape_hook: + submodule.register_forward_hook(partial(shape_forward_hook, False, submodule_name)) + + if args.add_oob_hook or args.add_oob_hook_input or args.add_oob_hook_output: + def oob_forward_hook(name: str, module: nn.Module, input, output): + def raise_if_oob(key, val): + if isinstance(val, collections.abc.Mapping): + for k, subval in val.items(): + raise_if_oob(f"{key}[{k!r}]", subval) + elif isinstance(val, (tuple, list)): + for i, subval in enumerate(val): + raise_if_oob(f"{key}[{i}]", subval) + elif isinstance(val, torch.Tensor): + assert not torch.isinf(val).any(), \ + f"INFs found in {key}" + assert not val.isnan().any(), \ + f"NaNs found in {key}" + elif isinstance(val, (str, float, int)): + pass + elif val is None: + warnings.warn(f"None found in {key}") + else: + assert False, val + with torch.no_grad(): + if args.add_oob_hook or args.add_oob_hook_input: + raise_if_oob(f"{name}.forward input", input) + if args.add_oob_hook or args.add_oob_hook_output: + raise_if_oob(f"{name}.forward output", output) + + for submodule_name, submodule in module.named_modules(): + submodule.register_forward_hook(partial(oob_forward_hook, + f"{module.__class__.__qualname__}.{submodule_name}" + if submodule_name else + f"{module.__class__.__qualname__}" + )) + + # Ensure all the top-level config keys are there + for key in self._included_in_config_template.keys(): + if key in (i.__name__ for i in self.datamodule_cls): + continue + if key not in config or config[key] is None: + config[key] = {} + + # Run registered action + if args.action in self._registered_actions: + action, *_ = self._registered_actions[args.action] + action(args, config, module) + elif args.action in ("fit", "test") and self.datamodule_cls is not None: + self.fit(args, config, module) + else: + raise ValueError(f"{args.mode=}, {args.action=}") + + else: + raise ValueError(f"{args.mode=}") + + def get_datamodule_cls_from_config(self, args: Namespace, config: Munch) -> DM: + assert self.datamodule_cls + cli = getattr(args, "datamodule", None) + datamodule_cls: pl.LightningDataModule + if cli is not None: + datamodule_cls, = (i for i in self.datamodule_cls if datamodule_name_to_snake_case(i) == cli) + else: + datamodules = { + cls.__name__: cls + for cls in self.datamodule_cls + } + for key in config.keys(): + if key in datamodules: + datamodule_cls = datamodules[key] + break + else: + datamodule_cls = self.datamodule_cls[0] + warnings.warn(f"None of the following datamodules were found in config: {set(datamodules.keys())!r}. {datamodule_cls.__name__!r} was chosen as the default.") + + return datamodule_cls + + def init_datamodule_cls_from_config(self, args: Namespace, config: Munch) -> DM: + datamodule_cls = self.get_datamodule_cls_from_config(args, config) + return datamodule_cls.from_argparse_args(args, **(config.get(datamodule_cls.__name__) or {})) + + + # Module actions + + def repr(self, args: Namespace, config: Munch, module: M): + rich.print(module) + + def yaml(self, args: Namespace, config: Munch, module: M): + _print_with_syntax_highlighting("yaml+jinja", config["_raw_yaml"]) + + def dot(self, args: Namespace, config: Munch, module: M): + module.train(not args.eval) + assert not args.filter, "not implemented! pipe it through examples/scripts/filter_dot.py in the meanwhile" + + example_input_array = module.example_input_array + assert example_input_array is not None, f"{module.__class__.__qualname__}.example_input_array=None" + assert isinstance(example_input_array, (tuple, dict, torch.Tensor)), type(example_input_array) + + def set_requires_grad(val): + if isinstance(val, torch.Tensor): + val.requires_grad = True + return val + + with torch.enable_grad(): + outputs = module(*helpers.map_tree(set_requires_grad, example_input_array)) + + dot = torchviz.make_dot(outputs, params=dict(module.named_parameters()), show_attrs=False, show_saved=False) + _print_with_syntax_highlighting("dot", str(dot)) + + def jit(self, args: Namespace, config: Munch, module: M): + example_input_array = module.example_input_array + assert example_input_array is not None, f"{module.__class__.__qualname__}.example_input_array=None" + assert isinstance(example_input_array, (tuple, dict, torch.Tensor)), type(example_input_array) + trace = torch.jit.trace_module(module, {"forward": example_input_array}) + _print_with_syntax_highlighting("python", str(trace.inlined_graph)) + + def trace(self, args: Namespace, config: Munch, module: M): + if isinstance(module, pl.LightningModule): + trace = module.to_torchscript(method="trace") + else: + example_input_array = module.example_input_array + assert example_input_array is not None, f"{module.__class__.__qualname__}.example_input_array is None" + assert isinstance(module, torch.Module) + trace = torch.jit.trace_module(module, {"forward": example_input_array}) + + use_netron = str(args.output_file) == "-" + trace_f = io.BytesIO() if use_netron else args.output_file + + torch.jit.save(trace, trace_f) + + if use_netron: + open_in_netron(f"{self.module_cls.__name__}.pt", trace_f.getvalue()) + + def onnx(self, args: Namespace, config: Munch, module: M): + example_input_array = module.example_input_array + assert example_input_array is not None, f"{module.__class__.__qualname__}.example_input_array=None" + assert isinstance(example_input_array, (tuple, dict, torch.Tensor)), type(example_input_array) + + use_netron = str(args.output_file) == "-" + onnx_f = io.BytesIO() if use_netron else args.output_file + + torch.onnx.export(module, + tuple(example_input_array), + onnx_f, + export_params = True, + opset_version = 17, + do_constant_folding = True, + input_names = ["input"], + output_names = ["output"], + dynamic_axes = { + "input" : {0 : "batch_size"}, + "output" : {0 : "batch_size"}, + }, + ) + + if use_netron: + open_in_netron(f"{self.module_cls.__name__}.onnx", onnx_f.getvalue()) + + def hparams(self, args: Namespace, config: Munch, module: M): + assert isinstance(module, self.module_cls) + print(f"{self.module_cls.__qualname__} hparams:") + print_column_dict(map_type_to_repr(module.hparams, nn.Module, lambda t: f"{t.__class__.__qualname__}"), 3) + + + def fit(self, args: Namespace, config: Munch, module: M): + is_rank_zero = pl.utilities.rank_zero_only.rank == 0 + + metric_prefix = f"{module.__class__.__name__}.validation_step/" + + pl_callbacks = [ + pl.callbacks.LearningRateMonitor(log_momentum=True), + pl.callbacks.EarlyStopping(monitor=metric_prefix+getattr(module, "metric_early_stop", "loss"), patience=200, check_on_train_epoch_end=False, verbose=True), + pl.callbacks.ModelCheckpoint(monitor=metric_prefix+getattr(module, "metric_best_model", "loss"), mode="min", save_top_k=1, save_last=True), + logging.ModelOutputMonitor(), + logging.EpochTimeMonitor(), + (pl.callbacks.RichModelSummary if os.isatty(1) else pl.callbacks.ModelSummary)(max_depth=30), + logging.PsutilMonitor(), + ] + if os.isatty(1): + pl_callbacks.append( pl.callbacks.RichProgressBar() ) + + trainer: pl.Trainer + logger = logging.make_logger(config.experiment_name, config.trainer.get("default_root_dir", args.default_root_dir or self.workdir), **config.logging) + trainer = pl.Trainer.from_argparse_args(args, logger=logger, callbacks=pl_callbacks, **config.trainer) + + datamodule = self.init_datamodule_cls_from_config(args, config) + + for f in self.pre_fit_handlers: + print(f"pre-train hook {f.__name__!r}...") + f(args, config, module, trainer, datamodule, logger) + + # print and log hparams/config + if 1: # fold me + if is_rank_zero: + CONSOLE.print(f"Experiment name: {config.experiment_name!r}", soft_wrap=False, crop=False, no_wrap=False, overflow="ignore") + + # parser.args and sys.argv + pickled_cli_args = dict( + sys_argv = sys.argv, + parser_args = args.__dict__, + config = config.copy(), + _raw_yaml = config["_raw_yaml"], + ) + del pickled_cli_args["config"]["_raw_yaml"] + for k,v in pickled_cli_args["parser_args"].items(): + if isinstance(v, Path): + pickled_cli_args["parser_args"][k] = str(v) + + # trainer + params_trainer = inspect.signature(pl.Trainer.__init__).parameters + trainer_hparams = vars(pl.Trainer.parse_argparser(args)) + trainer_hparams = { name: trainer_hparams[name] for name in params_trainer if name in trainer_hparams } + if is_rank_zero: + print("pl.Trainer hparams:") + print_column_dict(trainer_hparams, 3) + pickled_cli_args.update(trainer_hparams=trainer_hparams) + + # module + assert isinstance(module, self.module_cls) + if is_rank_zero: + print(f"{self.module_cls.__qualname__} hparams:") + print_column_dict(map_type_to_repr(module.hparams, nn.Module, lambda t: f"{t.__class__.__qualname__}"), 3) + pickled_cli_args.update(module_hparams={ + k : v + for k, v in module.hparams.items() + if k != "_raw_yaml" + }) + + # module extra state, like autodecoder uids + for submodule_name, submodule in module.named_modules(): + if not submodule_name: + submodule_name = module.__class__.__qualname__ + else: + submodule_name = module.__class__.__qualname__ + "." + submodule_name + try: + state = submodule.get_extra_state() + except RuntimeError: + continue + if "extra_state" not in pickled_cli_args: + pickled_cli_args["extra_state"] = {} + pickled_cli_args["extra_state"][submodule_name] = state + + # datamodule + if self.datamodule_cls: + assert datamodule is not None and any(isinstance(datamodule, i) for i in self.datamodule_cls), datamodule + for datamodule_cls in self.datamodule_cls: + params_d = inspect.signature(datamodule_cls.__init__).parameters + assert {"self"} == set(params_trainer).intersection(params_d), \ + f"trainer and datamodule has overlapping params: {set(params_trainer).intersection(params_d) - {'self'}}" + + if is_rank_zero: + print(f"{datamodule.__class__.__qualname__} hparams:") + print_column_dict(datamodule.hparams) + pickled_cli_args.update(datamodule_hparams=dict(datamodule.hparams)) + + # logger + if logger is not None: + print(f"{logger.__class__.__qualname__} hparams:") + print_column_dict(config.logging) + pickled_cli_args.update(logger_hparams = {"_class": logger.__class__.__name__} | config.logging) + + # host info + def cmd(cmd: Union[str, list[str]]) -> str: + if isinstance(cmd, str): + cmd = shlex.split(cmd) + if shutil.which(cmd[0]): + try: + return subprocess.run(cmd, + capture_output=True, + check=True, + text=True, + ).stdout.strip() + except subprocess.CalledProcessError as e: + warnings.warn(f"{e.__class__.__name__}: {e}") + return f"{e.__class__.__name__}: {e}\n{e.output = }\n{e.stderr = }" + else: + warnings.warn(f"command {cmd[0]!r} not found") + return f"*command {cmd[0]!r} not found*" + + pickled_cli_args.update(host = dict( + platform = textwrap.dedent(f""" + {platform.architecture() = } + {platform.java_ver() = } + {platform.libc_ver() = } + {platform.mac_ver() = } + {platform.machine() = } + {platform.node() = } + {platform.platform() = } + {platform.processor() = } + {platform.python_branch() = } + {platform.python_build() = } + {platform.python_compiler() = } + {platform.python_implementation() = } + {platform.python_revision() = } + {platform.python_version() = } + {platform.release() = } + {platform.system() = } + {platform.uname() = } + {platform.version() = } + """.rstrip()).lstrip(), + cuda = dict( + gpus = [ + torch.cuda.get_device_name(i) + for i in range(torch.cuda.device_count()) + ], + available = torch.cuda.is_available(), + version = torch.version.cuda, + ), + hostname = cmd("hostname --fqdn"), + cwd = os.getcwd(), + date = datetime.now().astimezone().isoformat(), + date_utc = datetime.utcnow().isoformat(), + ifconfig = cmd("ifconfig"), + lspci = cmd("lspci"), + lsusb = cmd("lsusb"), + lsblk = cmd("lsblk"), + mount = cmd("mount"), + environ = os.environ.copy(), + vcs = f"commit {cmd('git rev-parse HEAD')}\n{cmd('git status')}\n{cmd('git diff --stat --patch HEAD')}", + venv_pip = cmd("pip list --format=freeze"), + venv_conda = cmd("conda list"), + venv_poetry = cmd("poetry show -t"), + gpus = [i.split(", ") for i in cmd("nvidia-smi --query-gpu=index,name,memory.total,driver_version,uuid --format=csv").splitlines()], + )) + + if logger is not None: + logging.log_config(logger, _pickled_cli_args=pickled_cli_args) + + warnings.filterwarnings(action="ignore", category=torch.jit.TracerWarning) + + if __debug__ and is_rank_zero: + warnings.warn("You're running python with assertions active. Enable optimizations with `python -O` for improved performance.") + + # train + + t_begin = datetime.now() + if args.action == "fit": + trainer.fit(module, datamodule) + elif args.action == "test": + trainer.test(module, datamodule) + else: + raise ValueError(f"{args.mode=}, {args.action=}") + + if not is_rank_zero: + return + + t_end = datetime.now() + print(f"Training time: {t_end - t_begin}") + + for f in self.post_fit_handlers: + print(f"post-train hook {f.__name__!r}...") + try: + f(args, config, module, trainer, datamodule, logger) + except Exception: + traceback.print_exc() + + rich.print(f"Experiment name: {config.experiment_name!r}") + rich.print(f"Best model path: {helpers.make_relative(trainer.checkpoint_callback.best_model_path).__str__()!r}") + rich.print(f"Last model path: {helpers.make_relative(trainer.checkpoint_callback.last_model_path).__str__()!r}") + + def test_dataloader(self, args: Namespace, config: Munch, module: M): + # limit CPU affinity + if args.limit_cores is not None: + # https://stackoverflow.com/a/40856471 + p = psutil.Process() + assert len(p.cpu_affinity()) >= args.limit_cores + cpus = list(range(args.limit_cores)) + p.cpu_affinity(cpus) + print("Process limited to CPUs", cpus) + + datamodule = self.init_datamodule_cls_from_config(args, config) + + # setup + rich.print(f"Setup {datamodule.__class__.__qualname__}...") + datamodule.prepare_data() + datamodule.setup("fit") + try: + train = datamodule.train_dataloader() + except (MisconfigurationException, NotImplementedError): + train = None + try: + val = datamodule.val_dataloader() + except (MisconfigurationException, NotImplementedError): + val = None + try: + test = datamodule.test_dataloader() + except (MisconfigurationException, NotImplementedError): + test = None + + # inspect + rich.print("batch[0] = ", end="") + rich.pretty.pprint( + map_type_to_repr( + next(iter(train)), + torch.Tensor, + lambda x: f"Tensor(..., shape={x.shape}, dtype={x.dtype}, device={x.device})", + ), + indent_guides = False, + ) + + if args.profile is not None: + import cProfile + profile = cProfile.Profile() + profile.enable() + + # measure + n_train, td_train = 0, 0 + n_val, td_val = 0, 0 + n_test, td_test = 0, 0 + try: + for i in range(args.n_rounds): + print(f"Round {i+1} of {args.n_rounds}") + if train is not None: + epoch = time.perf_counter_ns() + n_train += sum(1 for _ in tqdm(train, desc=f"train {i+1}/{args.n_rounds}")) + td_train += time.perf_counter_ns() - epoch + if val is not None: + epoch = time.perf_counter_ns() + n_val += sum(1 for _ in tqdm(val, desc=f"val {i+1}/{args.n_rounds}")) + td_val += time.perf_counter_ns() - epoch + if test is not None: + epoch = time.perf_counter_ns() + n_test += sum(1 for _ in tqdm(test, desc=f"train {i+1}/{args.n_rounds}")) + td_test += time.perf_counter_ns() - epoch + except KeyboardInterrupt: + rich.print("Recieved a `KeyboardInterrupt`...") + + if args.profile is not None: + profile.disable() + if args.profile != "-": + profile.dump_stats(args.profile) + profile.print_stats("tottime") + + # summary + for label, data, n, td in [ + ("train", train, n_train, td_train), + ("val", val, n_val, td_val), + ("test", test, n_test, td_test), + ]: + if not n: continue + if data is not None: + print(f"{label}:", + f" - per epoch: {td / args.n_rounds * 1e-9 :11.6f} s", + f" - per batch: {td / n * 1e-9 :11.6f} s", + f" - batches/s: {n / (td * 1e-9):11.6f}", + sep="\n") + + datamodule.teardown("fit") + + + +# helpers: + +def open_in_netron(filename: str, data: bytes, *, timeout: float = 10): + # filename is only used to determine the filetype + url = serve_once_in_background( + data, + mime_type = "application/octet-stream", + timeout = timeout, + port = gen_random_port(), + ) + url = f"https://netron.app/?url={urllib.parse.quote(url)}{filename}" + print("Open in Netron:", url) + webbrowser.get("firefox").open_new_tab(url) + if timeout: + time.sleep(timeout) + +def remove_options_from_parser(parser: ArgumentParser, *options: str): + options = set(options) + # https://stackoverflow.com/questions/32807319/disable-remove-argument-in-argparse/36863647#36863647 + for action in parser._actions: + if action.option_strings: + option = action.option_strings[0] + if option in options: + parser._handle_conflict_resolve(None, [(option, action)]) + +def map_type_to_repr(batch, type_match: type, repr_func: callable): + def mapper(value): + if isinstance(value, type_match): + return helpers.CustomRepr(repr_func(value)) + else: + return value + return helpers.map_tree(mapper, batch) + +def datamodule_name_to_snake_case(datamodule: Union[str, type[DM]]) -> str: + if not isinstance(datamodule, str): + datamodule = datamodule.__name__ + datamodule = datamodule.replace("DataModule", "Datamodule") + if datamodule != "Datamodule": + datamodule = datamodule.removesuffix("Datamodule") + return camel_to_snake_case(datamodule, sep="-", join_abbreviations=True) diff --git a/ifield/cli_utils.py b/ifield/cli_utils.py new file mode 100644 index 0000000..bd50bdd --- /dev/null +++ b/ifield/cli_utils.py @@ -0,0 +1,174 @@ +#!/usr/bin/env python3 +from .data.common.scan import SingleViewScan, SingleViewUVScan +from datetime import datetime +import re +import click +import gzip +import h5py as h5 +import matplotlib.pyplot as plt +import numpy as np +import pyrender +import trimesh +import trimesh.transformations as T + +__doc__ = """ +Here are a bunch of helper scripts exposed as cli command by poetry +""" + + +# these entrypoints are exposed by poetry as shell commands + +@click.command() +@click.argument("h5file") +@click.argument("key", default="") +def show_h5_items(h5file: str, key: str): + "Show contents of HDF5 dataset" + f = h5.File(h5file, "r") + if not key: + mlen = max(map(len, f.keys())) + for i in sorted(f.keys()): + print(i.ljust(mlen), ":", + str (f[i].dtype).ljust(10), + repr(f[i].shape).ljust(16), + "mean:", f[i][:].mean() + ) + else: + if not f[key].shape: + print(f[key].value) + else: + print(f[key][:]) + + +@click.command() +@click.argument("h5file") +@click.argument("key", default="") +def show_h5_img(h5file: str, key: str): + "Show a 2D HDF5 dataset as an image" + f = h5.File(h5file, "r") + if not key: + mlen = max(map(len, f.keys())) + for i in sorted(f.keys()): + print(i.ljust(mlen), ":", str(f[i].dtype).ljust(10), f[i].shape) + else: + plt.imshow(f[key]) + plt.show() + + +@click.command() +@click.argument("h5file") +@click.option("--force-distances", is_flag=True, help="Always show miss distances.") +@click.option("--uv", is_flag=True, help="Load as UV scan cloud and convert it.") +@click.option("--show-unit-sphere", is_flag=True, help="Show the unit sphere.") +@click.option("--missing", is_flag=True, help="Show miss points that are not hits nor misses as purple.") +def show_h5_scan_cloud( + h5file : str, + force_distances : bool = False, + uv : bool = False, + missing : bool = False, + show_unit_sphere = False, + ): + "Show a SingleViewScan HDF5 dataset" + print("Reading data...") + t = datetime.now() + if uv: + scan = SingleViewUVScan.from_h5_file(h5file) + if missing and scan.any_missing: + if not scan.has_missing: + scan.fill_missing_points() + points_missing = scan.points[scan.missing] + else: + missing = False + if not scan.is_single_view: + scan.cam_pos = None + scan = scan.to_scan() + else: + scan = SingleViewScan.from_h5_file(h5file) + if missing: + uvscan = scan.to_uv_scan() + if scan.any_missing: + uvscan.fill_missing_points() + points_missing = uvscan.points[uvscan.missing] + else: + missing = False + print("loadtime: ", datetime.now() - t) + + if force_distances and not scan.has_miss_distances: + print("Computing miss distances...") + scan.compute_miss_distances() + use_miss_distances = force_distances + print("Constructing scene...") + if not scan.has_colors: + scan.colors_hit = np.zeros_like(scan.points_hit) + scan.colors_miss = np.zeros_like(scan.points_miss) + scan.colors_hit [:] = ( 31/255, 119/255, 180/255) + scan.colors_miss[:] = (243/255, 156/255, 18/255) + use_miss_distances = True + if scan.has_miss_distances and use_miss_distances: + sdm = scan.distances_miss / scan.distances_miss.max() + sdm = sdm[..., None] + scan.colors_miss \ + = np.array([0.8, 0, 0])[None, :] * sdm \ + + np.array([0, 1, 0.2])[None, :] * (1-sdm) + + + scene = pyrender.Scene() + + scene.add(pyrender.Mesh.from_points(scan.points_hit, colors=scan.colors_hit, normals=scan.normals_hit)) + scene.add(pyrender.Mesh.from_points(scan.points_miss, colors=scan.colors_miss)) + + if missing: + scene.add(pyrender.Mesh.from_points(points_missing, colors=(np.array((0xff, 0x00, 0xff))/255)[None, :].repeat(points_missing.shape[0], axis=0))) + + # camera: + if not scan.points_cam is None: + camera_mesh = trimesh.creation.uv_sphere(radius=scan.points_hit_std.max()*0.2) + camera_mesh.visual.vertex_colors = [0.0, 0.8, 0.0] + tfs = np.tile(np.eye(4), (len(scan.points_cam), 1, 1)) + tfs[:,:3,3] = scan.points_cam + scene.add(pyrender.Mesh.from_trimesh(camera_mesh, poses=tfs)) + + # UV sphere: + if show_unit_sphere: + unit_sphere_mesh = trimesh.creation.uv_sphere(radius=1) + unit_sphere_mesh.invert() + unit_sphere_mesh.visual.vertex_colors = [0.8, 0.8, 0.0] + scene.add(pyrender.Mesh.from_trimesh(unit_sphere_mesh, poses=np.eye(4)[None, ...])) + + print("Launch!") + viewer = pyrender.Viewer(scene, use_raymond_lighting=True, point_size=2) + + +@click.command() +@click.argument("meshfile") +@click.option('--aabb', is_flag=True) +@click.option('--z-skyward', is_flag=True) +def show_model( + meshfile : str, + aabb : bool, + z_skyward : bool, + ): + "Show a 3D model with pyrender, supports .gz suffix" + if meshfile.endswith(".gz"): + with gzip.open(meshfile, "r") as f: + mesh = trimesh.load(f, file_type=meshfile.split(".", 1)[1].removesuffix(".gz")) + else: + mesh = trimesh.load(meshfile) + + if isinstance(mesh, trimesh.Scene): + mesh = mesh.dump(concatenate=True) + + if aabb: + from .data.common.mesh import rotate_to_closest_axis_aligned_bounds + mesh.apply_transform(rotate_to_closest_axis_aligned_bounds(mesh, fail_ok=True)) + + if z_skyward: + mesh.apply_transform(T.rotation_matrix(np.pi/2, (1, 0, 0))) + + print( + *(i.strip() for i in pyrender.Viewer.__doc__.splitlines() if re.search(r"- ``[a-z0-9]``: ", i)), + sep="\n" + ) + + scene = pyrender.Scene() + scene.add(pyrender.Mesh.from_trimesh(mesh)) + pyrender.Viewer(scene, use_raymond_lighting=True) diff --git a/ifield/data/__init__.py b/ifield/data/__init__.py new file mode 100644 index 0000000..dd50c82 --- /dev/null +++ b/ifield/data/__init__.py @@ -0,0 +1,3 @@ +__doc__ = """ +Submodules to read and process datasets +""" diff --git a/ifield/data/common/__init__.py b/ifield/data/common/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ifield/data/common/download.py b/ifield/data/common/download.py new file mode 100644 index 0000000..3f583e8 --- /dev/null +++ b/ifield/data/common/download.py @@ -0,0 +1,90 @@ +from ...utils.helpers import make_relative +from pathlib import Path +from tqdm import tqdm +from typing import Union, Optional +import io +import os +import json +import requests + +PathLike = Union[os.PathLike, str] + +__doc__ = """ +Here are some helper functions for processing data. +""" + +def check_url(url): # HTTP HEAD + return requests.head(url).ok + +def download_stream( + url : str, + file_object, + block_size : int = 1024, + silent : bool = False, + label : Optional[str] = None, + ): + resp = requests.get(url, stream=True) + total_size = int(resp.headers.get("content-length", 0)) + if not silent: + progress_bar = tqdm(total=total_size , unit="iB", unit_scale=True, desc=label) + + for chunk in resp.iter_content(block_size): + if not silent: + progress_bar.update(len(chunk)) + file_object.write(chunk) + + if not silent: + progress_bar.close() + if total_size != 0 and progress_bar.n != total_size: + print("ERROR, something went wrong") + +def download_data( + url : str, + block_size : int = 1024, + silent : bool = False, + label : Optional[str] = None, + ) -> bytearray: + f = io.BytesIO() + download_stream(url, f, block_size=block_size, silent=silent, label=label) + f.seek(0) + return bytearray(f.read()) + +def download_file( + url : str, + fname : Union[Path, str], + block_size : int = 1024, + silent = False, + ): + if not isinstance(fname, Path): + fname = Path(fname) + with fname.open("wb") as f: + download_stream(url, f, block_size=block_size, silent=silent, label=make_relative(fname, Path.cwd()).name) + +def is_downloaded( + target_dir : PathLike, + url : str, + *, + add : bool = False, + dbfiles : Union[list[PathLike], PathLike], + ): + if not isinstance(target_dir, os.PathLike): + target_dir = Path(target_dir) + if not isinstance(dbfiles, list): + dbfiles = [dbfiles] + if not dbfiles: + raise ValueError("'dbfiles' empty") + downloaded = set() + for dbfile_fname in dbfiles: + dbfile_fname = target_dir / dbfile_fname + if dbfile_fname.is_file(): + with open(dbfile_fname, "r") as f: + downloaded.update(json.load(f)["downloaded"]) + + if add and url not in downloaded: + downloaded.add(url) + with open(dbfiles[0], "w") as f: + data = {"downloaded": sorted(downloaded)} + json.dump(data, f, indent=2, sort_keys=True) + return True + + return url in downloaded diff --git a/ifield/data/common/h5_dataclasses.py b/ifield/data/common/h5_dataclasses.py new file mode 100644 index 0000000..c7cd46f --- /dev/null +++ b/ifield/data/common/h5_dataclasses.py @@ -0,0 +1,370 @@ +#!/usr/bin/env python3 +from abc import abstractmethod, ABCMeta +from collections import namedtuple +from pathlib import Path +import copy +import dataclasses +import functools +import h5py as h5 +import hdf5plugin +import numpy as np +import operator +import os +import sys +import typing + +__all__ = [ + "DataclassMeta", + "Dataclass", + "H5Dataclass", + "H5Array", + "H5ArrayNoSlice", +] + +T = typing.TypeVar("T") +NoneType = type(None) +PathLike = typing.Union[os.PathLike, str] +H5Array = typing._alias(np.ndarray, 0, inst=False, name="H5Array") +H5ArrayNoSlice = typing._alias(np.ndarray, 0, inst=False, name="H5ArrayNoSlice") + +DataclassField = namedtuple("DataclassField", [ + "name", + "type", + "is_optional", + "is_array", + "is_sliceable", + "is_prefix", +]) + +def strip_optional(val: type) -> type: + if typing.get_origin(val) is typing.Union: + union = set(typing.get_args(val)) + if len(union - {NoneType}) == 1: + val, = union - {NoneType} + else: + raise TypeError(f"Non-'typing.Optional' 'typing.Union' is not supported: {typing._type_repr(val)!r}") + return val + +def is_array(val, *, _inner=False): + """ + Hacky way to check if a value or type is an array. + The hack omits having to depend on large frameworks such as pytorch or pandas + """ + val = strip_optional(val) + if val is H5Array or val is H5ArrayNoSlice: + return True + + if typing._type_repr(val) in ( + "numpy.ndarray", + "torch.Tensor", + ): + return True + if not _inner: + return is_array(type(val), _inner=True) + return False + +def prod(numbers: typing.Iterable[T], initial: typing.Optional[T] = None) -> T: + if initial is not None: + return functools.reduce(operator.mul, numbers, initial) + else: + return functools.reduce(operator.mul, numbers) + +class DataclassMeta(type): + def __new__( + mcls, + name : str, + bases : tuple[type, ...], + attrs : dict[str, typing.Any], + **kwargs, + ): + cls = super().__new__(mcls, name, bases, attrs, **kwargs) + if sys.version_info[:2] >= (3, 10) and not hasattr(cls, "__slots__"): + cls = dataclasses.dataclass(slots=True)(cls) + else: + cls = dataclasses.dataclass(cls) + return cls + +class DataclassABCMeta(DataclassMeta, ABCMeta): + pass + +class Dataclass(metaclass=DataclassMeta): + def __getitem__(self, key: str) -> typing.Any: + if key in self.keys(): + return getattr(self, key) + raise KeyError(key) + + def __setitem__(self, key: str, value: typing.Any): + if key in self.keys(): + return setattr(self, key, value) + raise KeyError(key) + + def keys(self) -> typing.KeysView: + return self.as_dict().keys() + + def values(self) -> typing.ValuesView: + return self.as_dict().values() + + def items(self) -> typing.ItemsView: + return self.as_dict().items() + + def as_dict(self, properties_to_include: set[str] = None, **kw) -> dict[str, typing.Any]: + out = dataclasses.asdict(self, **kw) + for name in (properties_to_include or []): + out[name] = getattr(self, name) + return out + + def as_tuple(self, properties_to_include: list[str]) -> tuple: + out = dataclasses.astuple(self) + if not properties_to_include: + return out + else: + return ( + *out, + *(getattr(self, name) for name in properties_to_include), + ) + + def copy(self: T, *, deep=True) -> T: + return (copy.deepcopy if deep else copy.copy)(self) + +class H5Dataclass(Dataclass): + # settable with class params: + _prefix : str = dataclasses.field(init=False, repr=False, default="") + _n_pages : int = dataclasses.field(init=False, repr=False, default=10) + _require_all : bool = dataclasses.field(init=False, repr=False, default=False) + + def __init_subclass__(cls, + prefix : typing.Optional[str] = None, + n_pages : typing.Optional[int] = None, + require_all : typing.Optional[bool] = None, + **kw, + ): + super().__init_subclass__(**kw) + assert dataclasses.is_dataclass(cls) + if prefix is not None: cls._prefix = prefix + if n_pages is not None: cls._n_pages = n_pages + if require_all is not None: cls._require_all = require_all + + @classmethod + def _get_fields(cls) -> typing.Iterable[DataclassField]: + for field in dataclasses.fields(cls): + if not field.init: + continue + assert field.name not in ("_prefix", "_n_pages", "_require_all"), ( + f"{field.name!r} can not be in {cls.__qualname__}.__init__.\n" + "Set it with dataclasses.field(default=YOUR_VALUE, init=False, repr=False)" + ) + if isinstance(field.type, str): + raise TypeError("Type hints are strings, perhaps avoid using `from __future__ import annotations`") + + type_inner = strip_optional(field.type) + is_prefix = typing.get_origin(type_inner) is dict and typing.get_args(type_inner)[:1] == (str,) + field_type = typing.get_args(type_inner)[1] if is_prefix else field.type + if field.default is None or typing.get_origin(field.type) is typing.Union and NoneType in typing.get_args(field.type): + field_type = typing.Optional[field_type] + + yield DataclassField( + name = field.name, + type = strip_optional(field_type), + is_optional = typing.get_origin(field_type) is typing.Union and NoneType in typing.get_args(field_type), + is_array = is_array(field_type), + is_sliceable = is_array(field_type) and strip_optional(field_type) is not H5ArrayNoSlice, + is_prefix = is_prefix, + ) + + @classmethod + def from_h5_file(cls : type[T], + fname : typing.Union[PathLike, str], + *, + page : typing.Optional[int] = None, + n_pages : typing.Optional[int] = None, + read_slice : slice = slice(None), + require_even_pages : bool = True, + ) -> T: + if not isinstance(fname, Path): + fname = Path(fname) + if n_pages is None: + n_pages = cls._n_pages + if not fname.exists(): + raise FileNotFoundError(str(fname)) + if not h5.is_hdf5(fname): + raise TypeError(f"Not a HDF5 file: {str(fname)!r}") + + # if this class has no fields, print a example class: + if not any(field.init for field in dataclasses.fields(cls)): + with h5.File(fname, "r") as f: + klen = max(map(len, f.keys())) + example_cls = f"\nclass {cls.__name__}(Dataclass, require_all=True):\n" + "\n".join( + f" {k.ljust(klen)} : " + + ( + "H5Array" if prod(v.shape, 1) > 1 else ( + "float" if issubclass(v.dtype.type, np.floating) else ( + "int" if issubclass(v.dtype.type, np.integer) else ( + "bool" if issubclass(v.dtype.type, np.bool_) else ( + "typing.Any" + ))))).ljust(14 + 1) + + f" #{repr(v).split(':', 1)[1].removesuffix('>')}" + for k, v in f.items() + ) + raise NotImplementedError(f"{cls!r} has no fields!\nPerhaps try the following:{example_cls}") + + fields_consumed = set() + + def make_kwarg( + file : h5.File, + keys : typing.KeysView, + field : DataclassField, + ) -> tuple[str, typing.Any]: + if field.is_optional: + if field.name not in keys: + return field.name, None + if field.is_sliceable: + if page is not None: + n_items = int(f[cls._prefix + field.name].shape[0]) + page_len = n_items // n_pages + modulus = n_items % n_pages + if modulus: page_len += 1 # round up + if require_even_pages and modulus: + raise ValueError(f"Field {field.name!r} {tuple(f[cls._prefix + field.name].shape)} is not cleanly divisible into {n_pages} pages") + this_slice = slice( + start = page_len * page, + stop = page_len * (page+1), + step = read_slice.step, # inherit step + ) + else: + this_slice = read_slice + else: + this_slice = slice(None) # read all + + # array or scalar? + def read_dataset(var): + # https://docs.h5py.org/en/stable/high/dataset.html#reading-writing-data + if field.is_array: + return var[this_slice] + if var.shape == (1,): + return var[0] + else: + return var[()] + + if field.is_prefix: + fields_consumed.update( + key + for key in keys if key.startswith(f"{cls._prefix}{field.name}_") + ) + return field.name, { + key.removeprefix(f"{cls._prefix}{field.name}_") : read_dataset(file[key]) + for key in keys if key.startswith(f"{cls._prefix}{field.name}_") + } + else: + fields_consumed.add(cls._prefix + field.name) + return field.name, read_dataset(file[cls._prefix + field.name]) + + with h5.File(fname, "r") as f: + keys = f.keys() + init_dict = dict( make_kwarg(f, keys, i) for i in cls._get_fields() ) + + try: + out = cls(**init_dict) + except Exception as e: + class_attrs = set(field.name for field in dataclasses.fields(cls)) + file_attr = set(init_dict.keys()) + raise e.__class__(f"{e}. {class_attrs=}, {file_attr=}, diff={class_attrs.symmetric_difference(file_attr)}") from e + + if cls._require_all: + fields_not_consumed = set(keys) - fields_consumed + if fields_not_consumed: + raise ValueError(f"Not all HDF5 fields consumed: {fields_not_consumed!r}") + + return out + + def to_h5_file(self, + fname : PathLike, + mkdir : bool = False, + ): + if not isinstance(fname, Path): + fname = Path(fname) + if not fname.parent.is_dir(): + if mkdir: + fname.parent.mkdir(parents=True) + else: + raise NotADirectoryError(fname.parent) + + with h5.File(fname, "w") as f: + for field in type(self)._get_fields(): + if field.is_optional and getattr(self, field.name) is None: + continue + value = getattr(self, field.name) + if field.is_array: + if any(type(i) is not np.ndarray for i in (value.values() if field.is_prefix else [value])): + raise TypeError( + "When dumping a H5Dataclass, make sure the array fields are " + f"numpy arrays (the type of {field.name!r} is {typing._type_repr(type(value))}).\n" + "Example: h5dataclass.map_arrays(torch.Tensor.numpy)" + ) + else: + pass + + def write_value(key: str, value: typing.Any): + if field.is_array: + f.create_dataset(key, data=value, **hdf5plugin.LZ4()) + else: + f.create_dataset(key, data=value) + + if field.is_prefix: + for k, v in value.items(): + write_value(self._prefix + field.name + "_" + k, v) + else: + write_value(self._prefix + field.name, value) + + def map_arrays(self: T, func: typing.Callable[[H5Array], H5Array], do_copy: bool = False) -> T: + if do_copy: # shallow + self = self.copy(deep=False) + for field in type(self)._get_fields(): + if field.is_optional and getattr(self, field.name) is None: + continue + if field.is_prefix and field.is_array: + setattr(self, field.name, { + k : func(v) + for k, v in getattr(self, field.name).items() + }) + elif field.is_array: + setattr(self, field.name, func(getattr(self, field.name))) + + return self + + def astype(self: T, t: type, do_copy: bool = False, convert_nonfloats: bool = False) -> T: + return self.map_arrays(lambda x: x.astype(t) if convert_nonfloats or not np.issubdtype(x.dtype, int) else x) + + def copy(self: T, *, deep=True) -> T: + out = super().copy(deep=deep) + if not deep: + for field in type(self)._get_fields(): + if field.is_prefix: + out[field.name] = copy.copy(field.name) + return out + + @property + def shape(self) -> dict[str, tuple[int, ...]]: + return { + key: value.shape + for key, value in self.items() + if hasattr(value, "shape") + } + +class TransformableDataclassMixin(metaclass=DataclassABCMeta): + + @abstractmethod + def transform(self: T, mat4: np.ndarray, inplace=False) -> T: + ... + + def transform_to(self: T, name: str, inverse_name: str = None, *, inplace=False) -> T: + mtx = self.transforms[name] + out = self.transform(mtx, inplace=inplace) + out.transforms.pop(name) # consumed + + inv = np.linalg.inv(mtx) + for key in list(out.transforms.keys()): # maintain the other transforms + out.transforms[key] = out.transforms[key] @ inv + if inverse_name is not None: # store inverse + out.transforms[inverse_name] = inv + + return out diff --git a/ifield/data/common/mesh.py b/ifield/data/common/mesh.py new file mode 100644 index 0000000..fc5a6bc --- /dev/null +++ b/ifield/data/common/mesh.py @@ -0,0 +1,48 @@ +from math import pi +from trimesh import Trimesh +import numpy as np +import os +import trimesh +import trimesh.transformations as T + +DEBUG = bool(os.environ.get("IFIELD_DEBUG", "")) + +__doc__ = """ +Here are some helper functions for processing data. +""" + +def rotate_to_closest_axis_aligned_bounds( + mesh : Trimesh, + order_axes : bool = True, + fail_ok : bool = True, + ) -> np.ndarray: + to_origin_mat4, extents = trimesh.bounds.oriented_bounds(mesh, ordered=not order_axes) + to_aabb_rot_mat4 = T.euler_matrix(*T.decompose_matrix(to_origin_mat4)[3]) + + if not order_axes: + return to_aabb_rot_mat4 + + v = pi / 4 * 1.01 # tolerance + v2 = pi / 2 + + faces = ( + (0, 0), + (1, 0), + (2, 0), + (3, 0), + (0, 1), + (0,-1), + ) + orientations = [ # 6 faces x 4 rotations per face + (f[0] * v2, f[1] * v2, i * v2) + for i in range(4) + for f in faces] + + for x, y, z in orientations: + mat4 = T.euler_matrix(x, y, z) @ to_aabb_rot_mat4 + ai, aj, ak = T.euler_from_matrix(mat4) + if abs(ai) <= v and abs(aj) <= v and abs(ak) <= v: + return mat4 + + if fail_ok: return to_aabb_rot_mat4 + raise Exception("Unable to orient mesh") diff --git a/ifield/data/common/points.py b/ifield/data/common/points.py new file mode 100644 index 0000000..58cb9da --- /dev/null +++ b/ifield/data/common/points.py @@ -0,0 +1,297 @@ +from __future__ import annotations +from ...utils.helpers import compose +from functools import reduce, lru_cache +from math import ceil +from typing import Iterable +import numpy as np +import operator + +__doc__ = """ +Here are some helper functions for processing data. +""" + + +def img2col(img: np.ndarray, psize: int) -> np.ndarray: + # based of ycb_generate_point_cloud.py provided by YCB + + n_channels = 1 if len(img.shape) == 2 else img.shape[0] + n_channels, rows, cols = (1,) * (3 - len(img.shape)) + img.shape + + # pad the image + img_pad = np.zeros(( + n_channels, + int(ceil(1.0 * rows / psize) * psize), + int(ceil(1.0 * cols / psize) * psize), + )) + img_pad[:, 0:rows, 0:cols] = img + + # allocate output buffer + final = np.zeros(( + img_pad.shape[1], + img_pad.shape[2], + n_channels, + psize, + psize, + )) + + for c in range(n_channels): + for x in range(psize): + for y in range(psize): + img_shift = np.vstack(( + img_pad[c, x:], + img_pad[c, :x])) + img_shift = np.column_stack(( + img_shift[:, y:], + img_shift[:, :y])) + final[x::psize, y::psize, c] = np.swapaxes( + img_shift.reshape( + int(img_pad.shape[1] / psize), psize, + int(img_pad.shape[2] / psize), psize), + 1, + 2) + + # crop output and unwrap axes with size==1 + return np.squeeze(final[ + 0:rows - psize + 1, + 0:cols - psize + 1]) + +def filter_depth_discontinuities(depth_map: np.ndarray, filt_size = 7, thresh = 1000) -> np.ndarray: + """ + Removes data close to discontinuities, with size filt_size. + """ + # based of ycb_generate_point_cloud.py provided by YCB + + # Ensure that filter sizes are okay + assert filt_size % 2, "Can only use odd filter sizes." + + # Compute discontinuities + offset = int(filt_size - 1) // 2 + patches = 1.0 * img2col(depth_map, filt_size) + mids = patches[:, :, offset, offset] + mins = np.min(patches, axis=(2, 3)) + maxes = np.max(patches, axis=(2, 3)) + + discont = np.maximum( + np.abs(mins - mids), + np.abs(maxes - mids)) + mark = discont > thresh + + # Account for offsets + final_mark = np.zeros(depth_map.shape, dtype=np.uint16) + final_mark[offset:offset + mark.shape[0], + offset:offset + mark.shape[1]] = mark + + return depth_map * (1 - final_mark) + +def reorient_depth_map( + depth_map : np.ndarray, + rgb_map : np.ndarray, + depth_mat3 : np.ndarray, # 3x3 intrinsic camera matrix + depth_vec5 : np.ndarray, # 5 distortion parameters (k1, k2, p1, p2, k3) + rgb_mat3 : np.ndarray, # 3x3 intrinsic camera matrix + rgb_vec5 : np.ndarray, # 5 distortion parameters (k1, k2, p1, p2, k3) + ir_to_rgb_mat4 : np.ndarray, # extrinsic transformation matrix from depth to rgb camera viewpoint + rgb_mask_map : np.ndarray = None, + _output_points = False, # retval (H, W) if false else (N, XYZRGB) + _output_hits_uvs = False, # retval[1] is dtype=bool of hits shaped like depth_map + ) -> np.ndarray: + + """ + Corrects depth_map to be from the same view as the rgb_map, with the same dimensions. + If _output_points is True, the points returned are in the rgb camera space. + """ + # based of ycb_generate_point_cloud.py provided by YCB + # now faster AND more easy on the GIL + + height_old, width_old, *_ = depth_map.shape + height, width, *_ = rgb_map.shape + + + d_cx, r_cx = depth_mat3[0, 2], rgb_mat3[0, 2] # optical center + d_cy, r_cy = depth_mat3[1, 2], rgb_mat3[1, 2] + d_fx, r_fx = depth_mat3[0, 0], rgb_mat3[0, 0] # focal length + d_fy, r_fy = depth_mat3[1, 1], rgb_mat3[1, 1] + d_k1, d_k2, d_p1, d_p2, d_k3 = depth_vec5 + c_k1, c_k2, c_p1, c_p2, c_k3 = rgb_vec5 + + # make a UV grid over depth_map + u, v = np.meshgrid( + np.arange(width_old), + np.arange(height_old), + ) + + # compute xyz coordinates for all depths + xyz_depth = np.stack(( + (u - d_cx) / d_fx, + (v - d_cy) / d_fy, + depth_map, + np.ones(depth_map.shape) + )).reshape((4, -1)) + xyz_depth = xyz_depth[:, xyz_depth[2] != 0] + + # undistort depth coordinates + d_x, d_y = xyz_depth[:2] + r = np.linalg.norm(xyz_depth[:2], axis=0) + xyz_depth[0, :] \ + = d_x / (1 + d_k1*r**2 + d_k2*r**4 + d_k3*r**6) \ + - (2*d_p1*d_x*d_y + d_p2*(r**2 + 2*d_x**2)) + xyz_depth[1, :] \ + = d_y / (1 + d_k1*r**2 + d_k2*r**4 + d_k3*r**6) \ + - (d_p1*(r**2 + 2*d_y**2) + 2*d_p2*d_x*d_y) + + # unproject x and y + xyz_depth[0, :] *= xyz_depth[2, :] + xyz_depth[1, :] *= xyz_depth[2, :] + + # convert depths to RGB camera viewpoint + xyz_rgb = ir_to_rgb_mat4 @ xyz_depth + + # project depths to RGB canvas + rgb_z_inv = 1 / xyz_rgb[2] # perspective correction + rgb_uv = np.stack(( + xyz_rgb[0] * rgb_z_inv * r_fx + r_cx + 0.5, + xyz_rgb[1] * rgb_z_inv * r_fy + r_cy + 0.5, + )).astype(np.int) + + # mask of the rgb_xyz values within view of rgb_map + mask = reduce(operator.and_, [ + rgb_uv[0] >= 0, + rgb_uv[1] >= 0, + rgb_uv[0] < width, + rgb_uv[1] < height, + ]) + if rgb_mask_map is not None: + mask[mask] &= rgb_mask_map[ + rgb_uv[1, mask], + rgb_uv[0, mask]] + + if not _output_points: # output image + output = np.zeros((height, width), dtype=depth_map.dtype) + output[ + rgb_uv[1, mask], + rgb_uv[0, mask], + ] = xyz_rgb[2, mask] + + else: # output pointcloud + rgbs = rgb_map[ # lookup rgb values using rgb_uv + rgb_uv[1, mask], + rgb_uv[0, mask]] + output = np.stack(( + xyz_rgb[0, mask], # x + xyz_rgb[1, mask], # y + xyz_rgb[2, mask], # z + rgbs[:, 0], # r + rgbs[:, 1], # g + rgbs[:, 2], # b + )).T + + # output for realsies + if not _output_hits_uvs: #raw + return output + else: # with hit mask + uv = np.zeros((height, width), dtype=bool) + # filter points overlapping in the depth map + uv_indices = ( + rgb_uv[1, mask], + rgb_uv[0, mask], + ) + _, chosen = np.unique( uv_indices[0] << 32 | uv_indices[1], return_index=True ) + output = output[chosen, :] + uv[uv_indices] = True + return output, uv + +def join_rgb_and_depth_to_points(*a, **kw) -> np.ndarray: + return reorient_depth_map(*a, _output_points=True, **kw) + +@compose(np.array) # block lru cache mutation +@lru_cache(maxsize=1) +@compose(list) +def generate_equidistant_sphere_points( + n : int, + centroid : np.ndarray = (0, 0, 0), + radius : float = 1, + compute_sphere_coordinates : bool = False, + compute_normals : bool = False, + shift_theta : bool = False, + ) -> Iterable[tuple[float, ...]]: + # Deserno M. How to generate equidistributed points on the surface of a sphere + # https://www.cmu.edu/biolphys/deserno/pdf/sphere_equi.pdf + + if compute_sphere_coordinates and compute_normals: + raise ValueError( + "'compute_sphere_coordinates' and 'compute_normals' are mutually exclusive" + ) + + n_count = 0 + a = 4 * np.pi / n + d = np.sqrt(a) + n_theta = round(np.pi / d) + d_theta = np.pi / n_theta + d_phi = a / d_theta + + for i in range(0, n_theta): + theta = np.pi * (i + 0.5) / n_theta + n_phi = round(2 * np.pi * np.sin(theta) / d_phi) + + for j in range(0, n_phi): + phi = 2 * np.pi * j / n_phi + + if compute_sphere_coordinates: # (theta, phi) + yield ( + theta if shift_theta else theta - 0.5*np.pi, + phi, + ) + elif compute_normals: # (x, y, z, nx, ny, nz) + yield ( + centroid[0] + radius * np.sin(theta) * np.cos(phi), + centroid[1] + radius * np.sin(theta) * np.sin(phi), + centroid[2] + radius * np.cos(theta), + np.sin(theta) * np.cos(phi), + np.sin(theta) * np.sin(phi), + np.cos(theta), + ) + else: # (x, y, z) + yield ( + centroid[0] + radius * np.sin(theta) * np.cos(phi), + centroid[1] + radius * np.sin(theta) * np.sin(phi), + centroid[2] + radius * np.cos(theta), + ) + n_count += 1 + + +def generate_random_sphere_points( + n : int, + centroid : np.ndarray = (0, 0, 0), + radius : float = 1, + compute_sphere_coordinates : bool = False, + compute_normals : bool = False, + shift_theta : bool = False, # depends on convention + ) -> np.ndarray: + if compute_sphere_coordinates and compute_normals: + raise ValueError( + "'compute_sphere_coordinates' and 'compute_normals' are mutually exclusive" + ) + + theta = np.arcsin(np.random.uniform(-1, 1, n)) # inverse transform sampling + phi = np.random.uniform(0, 2*np.pi, n) + + if compute_sphere_coordinates: # (theta, phi) + return np.stack(( + theta if not shift_theta else 0.5*np.pi + theta, + phi, + ), axis=1) + elif compute_normals: # (x, y, z, nx, ny, nz) + return np.stack(( + centroid[0] + radius * np.cos(theta) * np.cos(phi), + centroid[1] + radius * np.cos(theta) * np.sin(phi), + centroid[2] + radius * np.sin(theta), + np.cos(theta) * np.cos(phi), + np.cos(theta) * np.sin(phi), + np.sin(theta), + ), axis=1) + else: # (x, y, z) + return np.stack(( + centroid[0] + radius * np.cos(theta) * np.cos(phi), + centroid[1] + radius * np.cos(theta) * np.sin(phi), + centroid[2] + radius * np.sin(theta), + ), axis=1) diff --git a/ifield/data/common/processing.py b/ifield/data/common/processing.py new file mode 100644 index 0000000..f884685 --- /dev/null +++ b/ifield/data/common/processing.py @@ -0,0 +1,85 @@ +from .h5_dataclasses import H5Dataclass +from datetime import datetime, timedelta +from pathlib import Path +from typing import Hashable, Optional, Callable +import os + +DEBUG = bool(os.environ.get("IFIELD_DEBUG", "")) + +__doc__ = """ +Here are some helper functions for processing data. +""" + +# multiprocessing does not work due to my rediculous use of closures, which seemingly cannot be pickled +# paralelize it in the shell instead + +def precompute_data( + computer : Callable[[Hashable], Optional[H5Dataclass]], + identifiers : list[Hashable], + output_paths : list[Path], + page : tuple[int, int] = (0, 1), + *, + force : bool = False, + debug : bool = False, + ): + """ + precomputes data and stores them as HDF5 datasets using `.to_file(path: Path)` + """ + + page, n_pages = page + assert len(identifiers) == len(output_paths) + + total = len(identifiers) + identifier_max_len = max(map(len, map(str, identifiers))) + t_epoch = None + def log(state: str, is_start = False): + nonlocal t_epoch + if is_start: t_epoch = datetime.now() + td = timedelta(0) if is_start else datetime.now() - t_epoch + print(" - " + f"{str(index+1).rjust(len(str(total)))}/{total}: " + f"{str(identifier).ljust(identifier_max_len)} @ {td}: {state}" + ) + + print(f"precompute_data(computer={computer.__module__}.{computer.__qualname__}, identifiers=..., force={force}, page={page})") + t_begin = datetime.now() + failed = [] + + # pagination + page_size = total // n_pages + bool(total % n_pages) + jobs = list(zip(identifiers, output_paths))[page_size*page : page_size*(page+1)] + + for index, (identifier, output_path) in enumerate(jobs, start=page_size*page): + if not force and output_path.exists() and output_path.stat().st_size > 0: + continue + + log("compute", is_start=True) + + # compute + try: + res = computer(identifier) + except Exception as e: + failed.append(identifier) + log(f"failed compute: {e.__class__.__name__}: {e}") + if DEBUG or debug: raise e + continue + if res is None: + failed.append(identifier) + log("no result") + continue + + # write to file + try: + output_path.parent.mkdir(parents=True, exist_ok=True) + res.to_h5_file(output_path) + except Exception as e: + failed.append(identifier) + log(f"failed write: {e.__class__.__name__}: {e}") + if output_path.is_file(): output_path.unlink() # cleanup + if DEBUG or debug: raise e + continue + + log("done") + + print("precompute_data finished in", datetime.now() - t_begin) + print("failed:", failed or None) diff --git a/ifield/data/common/scan.py b/ifield/data/common/scan.py new file mode 100644 index 0000000..0dc6782 --- /dev/null +++ b/ifield/data/common/scan.py @@ -0,0 +1,768 @@ +from ...utils.helpers import compose +from . import points +from .h5_dataclasses import H5Dataclass, H5Array, H5ArrayNoSlice, TransformableDataclassMixin +from methodtools import lru_cache +from sklearn.neighbors import BallTree +import faiss +from trimesh import Trimesh +from typing import Iterable +from typing import Optional, TypeVar +import mesh_to_sdf +import mesh_to_sdf.scan as sdf_scan +import numpy as np +import trimesh +import trimesh.transformations as T +import warnings + +__doc__ = """ +Here are some helper types for data. +""" + +_T = TypeVar("T") + +class InvalidateLRUOnWriteMixin: + def __setattr__(self, key, value): + if not key.startswith("__wire|"): + for attr in dir(self): + if attr.startswith("__wire|"): + getattr(self, attr).cache_clear() + return super().__setattr__(key, value) +def lru_property(func): + return lru_cache(maxsize=1)(property(func)) + +class SingleViewScan(H5Dataclass, TransformableDataclassMixin, InvalidateLRUOnWriteMixin, require_all=True): + points_hit : H5ArrayNoSlice # (N, 3) + normals_hit : Optional[H5ArrayNoSlice] # (N, 3) + points_miss : H5ArrayNoSlice # (M, 3) + distances_miss : Optional[H5ArrayNoSlice] # (M) + colors_hit : Optional[H5ArrayNoSlice] # (N, 3) + colors_miss : Optional[H5ArrayNoSlice] # (M, 3) + uv_hits : Optional[H5ArrayNoSlice] # (H, W) dtype=bool + uv_miss : Optional[H5ArrayNoSlice] # (H, W) dtype=bool (the reason we store both is due to missing data depth sensor data or filtered backfaces) + cam_pos : H5ArrayNoSlice # (3) + cam_mat4 : Optional[H5ArrayNoSlice] # (4, 4) + proj_mat4 : Optional[H5ArrayNoSlice] # (4, 4) + transforms : dict[str, H5ArrayNoSlice] # a map of 4x4 transformation matrices + + def transform(self: _T, mat4: np.ndarray, inplace=False) -> _T: + scale_xyz = mat4[:3, :3].sum(axis=0) # https://math.stackexchange.com/a/1463487 + assert all(scale_xyz - scale_xyz[0] < 1e-8), f"differenty scaled axes: {scale_xyz}" + + out = self if inplace else self.copy(deep=False) + out.points_hit = T.transform_points(self.points_hit, mat4) + out.normals_hit = T.transform_points(self.normals_hit, mat4) if self.normals_hit is not None else None + out.points_miss = T.transform_points(self.points_miss, mat4) + out.distances_miss = self.distances_miss * scale_xyz + out.cam_pos = T.transform_points(self.points_cam, mat4)[-1] + out.cam_mat4 = (mat4 @ self.cam_mat4) if self.cam_mat4 is not None else None + out.proj_mat4 = (mat4 @ self.proj_mat4) if self.proj_mat4 is not None else None + return out + + def compute_miss_distances(self: _T, *, copy: bool = False, deep: bool = False) -> _T: + assert not self.has_miss_distances + if not self.is_hitting: + raise ValueError("No hits to compute the ray distance towards") + + out = self.copy(deep=deep) if copy else self + out.distances_miss \ + = distance_from_rays_to_point_cloud( + ray_origins = out.points_cam, + ray_dirs = out.ray_dirs_miss, + points = out.points_hit, + ).astype(out.points_cam.dtype) + + return out + + @lru_property + def points(self) -> np.ndarray: # (N+M+1, 3) + return np.concatenate(( + self.points_hit, + self.points_miss, + self.points_cam, + )) + + @lru_property + def uv_points(self) -> np.ndarray: # (N+M+1, 3) + if not self.has_uv: raise ValueError + out = np.full((*self.uv_hits.shape, 3), np.nan, dtype=self.points_hit.dtype) + out[self.uv_hits, :] = self.points_hit + out[self.uv_miss, :] = self.points_miss + return out + + @lru_property + def uv_normals(self) -> np.ndarray: # (N+M+1, 3) + if not self.has_uv: raise ValueError + out = np.full((*self.uv_hits.shape, 3), np.nan, dtype=self.normals_hit.dtype) + out[self.uv_hits, :] = self.normals_hit + return out + + @lru_property + def points_cam(self) -> Optional[np.ndarray]: # (1, 3) + if self.cam_pos is None: return None + return self.cam_pos[None, :] + + @lru_property + def points_hit_centroid(self) -> np.ndarray: + return self.points_hit.mean(axis=0) + + @lru_property + def points_hit_std(self) -> np.ndarray: + return self.points_hit.std(axis=0) + + @lru_property + def is_hitting(self) -> bool: + return len(self.points_hit) > 0 + + @lru_property + def is_empty(self) -> bool: + return not (len(self.points_hit) or len(self.points_miss)) + + @lru_property + def has_colors(self) -> bool: + return self.colors_hit is not None or self.colors_miss is not None + + @lru_property + def has_normals(self) -> bool: + return self.normals_hit is not None + + @lru_property + def has_uv(self) -> bool: + return self.uv_hits is not None + + @lru_property + def has_miss_distances(self) -> bool: + return self.distances_miss is not None + + @lru_property + def xyzrgb_hit(self) -> np.ndarray: # (N, 6) + if self.colors_hit is None: raise ValueError + return np.concatenate([self.points_hit, self.colors_hit], axis=1) + + @lru_property + def xyzrgb_miss(self) -> np.ndarray: # (M, 6) + if self.colors_miss is None: raise ValueError + return np.concatenate([self.points_miss, self.colors_miss], axis=1) + + @lru_property + def ray_dirs_hit(self) -> np.ndarray: # (N, 3) + out = self.points_hit - self.points_cam + out /= np.linalg.norm(out, axis=-1)[:, None] # normalize + return out + + @lru_property + def ray_dirs_miss(self) -> np.ndarray: # (N, 3) + out = self.points_miss - self.points_cam + out /= np.linalg.norm(out, axis=-1)[:, None] # normalize + return out + + @classmethod + def from_mesh_single_view(cls, mesh: Trimesh, *, compute_miss_distances: bool = False, **kw) -> "SingleViewScan": + if "phi" not in kw and not "theta" in kw: + kw["theta"], kw["phi"] = points.generate_random_sphere_points(1, compute_sphere_coordinates=True)[0] + scan = sample_single_view_scan_from_mesh(mesh, **kw) + if compute_miss_distances and scan.is_hitting: + scan.compute_miss_distances() + return scan + + def to_uv_scan(self) -> "SingleViewUVScan": + return SingleViewUVScan.from_scan(self) + + @classmethod + def from_uv_scan(self, uvscan: "SingleViewUVScan") -> "SingleViewUVScan": + return uvscan.to_scan() + +# The same, but with support for pagination (should have been this way since the start...) +class SingleViewUVScan(H5Dataclass, TransformableDataclassMixin, InvalidateLRUOnWriteMixin, require_all=True): + # B may be (N) or (H, W), the latter may be flattened + hits : H5Array # (*B) dtype=bool + miss : H5Array # (*B) dtype=bool (the reason we store both is due to missing data depth sensor data or filtered backface hits) + points : H5Array # (*B, 3) on far plane if miss, NaN if neither hit or miss + normals : Optional[H5Array] # (*B, 3) NaN if not hit + colors : Optional[H5Array] # (*B, 3) + distances : Optional[H5Array] # (*B) NaN if not miss + cam_pos : Optional[H5ArrayNoSlice] # (3) or (*B, 3) + cam_mat4 : Optional[H5ArrayNoSlice] # (4, 4) + proj_mat4 : Optional[H5ArrayNoSlice] # (4, 4) + transforms : dict[str, H5ArrayNoSlice] # a map of 4x4 transformation matrices + + @classmethod + def from_scan(cls, scan: SingleViewScan): + if not scan.has_uv: + raise ValueError("Scan cloud has no UV data") + hits, miss = scan.uv_hits, scan.uv_miss + dtype = scan.points_hit.dtype + assert hits.ndim in (1, 2), hits.ndim + assert hits.shape == miss.shape, (hits.shape, miss.shape) + + points = np.full((*hits.shape, 3), np.nan, dtype=dtype) + points[hits, :] = scan.points_hit + points[miss, :] = scan.points_miss + + normals = None + if scan.has_normals: + normals = np.full((*hits.shape, 3), np.nan, dtype=dtype) + normals[hits, :] = scan.normals_hit + + distances = None + if scan.has_miss_distances: + distances = np.full(hits.shape, np.nan, dtype=dtype) + distances[miss] = scan.distances_miss + + colors = None + if scan.has_colors: + colors = np.full((*hits.shape, 3), np.nan, dtype=dtype) + if scan.colors_hit is not None: + colors[hits, :] = scan.colors_hit + if scan.colors_miss is not None: + colors[miss, :] = scan.colors_miss + + return cls( + hits = hits, + miss = miss, + points = points, + normals = normals, + colors = colors, + distances = distances, + cam_pos = scan.cam_pos, + cam_mat4 = scan.cam_mat4, + proj_mat4 = scan.proj_mat4, + transforms = scan.transforms, + ) + + def to_scan(self) -> "SingleViewScan": + if not self.is_single_view: raise ValueError + return SingleViewScan( + points_hit = self.points [self.hits, :], + points_miss = self.points [self.miss, :], + normals_hit = self.normals [self.hits, :] if self.has_normals else None, + distances_miss = self.distances[self.miss] if self.has_miss_distances else None, + colors_hit = self.colors [self.hits, :] if self.has_colors else None, + colors_miss = self.colors [self.miss, :] if self.has_colors else None, + uv_hits = self.hits, + uv_miss = self.miss, + cam_pos = self.cam_pos, + cam_mat4 = self.cam_mat4, + proj_mat4 = self.proj_mat4, + transforms = self.transforms, + ) + + def to_mesh(self) -> trimesh.Trimesh: + faces: list[(tuple[int, int],)*3] = [] + for x in range(self.hits.shape[0]-1): + for y in range(self.hits.shape[1]-1): + c11 = x, y + c12 = x, y+1 + c22 = x+1, y+1 + c21 = x+1, y + + n = sum(map(self.hits.__getitem__, (c11, c12, c22, c21))) + if n == 3: + faces.append((*filter(self.hits.__getitem__, (c11, c12, c22, c21)),)) + elif n == 4: + faces.append((c11, c12, c22)) + faces.append((c11, c22, c21)) + xy2idx = {c:i for i, c in enumerate(set(k for j in faces for k in j))} + assert self.colors is not None + return trimesh.Trimesh( + vertices = [self.points[i] for i in xy2idx.keys()], + vertex_colors = [self.colors[i] for i in xy2idx.keys()] if self.colors is not None else None, + faces = [tuple(xy2idx[i] for i in face) for face in faces], + ) + + def transform(self: _T, mat4: np.ndarray, inplace=False) -> _T: + scale_xyz = mat4[:3, :3].sum(axis=0) # https://math.stackexchange.com/a/1463487 + assert all(scale_xyz - scale_xyz[0] < 1e-8), f"differenty scaled axes: {scale_xyz}" + + unflat = self.hits.shape + flat = np.product(unflat) + + out = self if inplace else self.copy(deep=False) + out.points = T.transform_points(self.points .reshape((*flat, 3)), mat4).reshape((*unflat, 3)) + out.normals = T.transform_points(self.normals.reshape((*flat, 3)), mat4).reshape((*unflat, 3)) if self.normals_hit is not None else None + out.distances = self.distances_miss * scale_xyz + out.cam_pos = T.transform_points(self.cam_pos[None, ...], mat4)[0] + out.cam_mat4 = (mat4 @ self.cam_mat4) if self.cam_mat4 is not None else None + out.proj_mat4 = (mat4 @ self.proj_mat4) if self.proj_mat4 is not None else None + return out + + def compute_miss_distances(self: _T, *, copy: bool = False, deep: bool = False, surface_points: Optional[np.ndarray] = None) -> _T: + assert not self.has_miss_distances + + shape = self.hits.shape + + out = self.copy(deep=deep) if copy else self + out.distances = np.zeros(shape, dtype=self.points.dtype) + if self.is_hitting: + out.distances[self.miss] \ + = distance_from_rays_to_point_cloud( + ray_origins = self.cam_pos_unsqueezed_miss, + ray_dirs = self.ray_dirs_miss, + points = surface_points if surface_points is not None else self.points[self.hits], + ) + + return out + + def fill_missing_points(self: _T, *, copy: bool = False, deep: bool = False) -> _T: + """ + Fill in missing points as hitting the far plane. + """ + if not self.is_2d: + raise ValueError("Cannot fill missing points for non-2d scan!") + if not self.is_single_view: + raise ValueError("Cannot fill missing points for non-single-view scans!") + if self.cam_mat4 is None: + raise ValueError("cam_mat4 is None") + if self.proj_mat4 is None: + raise ValueError("proj_mat4 is None") + + uv = np.argwhere(self.missing).astype(self.points.dtype) + uv[:, 0] /= (self.missing.shape[1] - 1) / 2 + uv[:, 1] /= (self.missing.shape[0] - 1) / 2 + uv -= 1 + uv = np.stack(( + uv[:, 1], + -uv[:, 0], + np.ones(uv.shape[0]), # far clipping plane + np.ones(uv.shape[0]), # homogeneous coordinate + ), axis=-1) + uv = uv @ (self.cam_mat4 @ np.linalg.inv(self.proj_mat4)).T + + out = self.copy(deep=deep) if copy else self + out.points[self.missing, :] = uv[:, :3] / uv[:, 3][:, None] + return out + + @lru_property + def is_hitting(self) -> bool: + return np.any(self.hits) + + @lru_property + def has_colors(self) -> bool: + return not self.colors is None + + @lru_property + def has_normals(self) -> bool: + return not self.normals is None + + @lru_property + def has_miss_distances(self) -> bool: + return not self.distances is None + + @lru_property + def any_missing(self) -> bool: + return np.any(self.missing) + + @lru_property + def has_missing(self) -> bool: + return self.any_missing and not np.any(np.isnan(self.points[self.missing])) + + @lru_property + def cam_pos_unsqueezed(self) -> H5Array: + if self.cam_pos.ndim != 1: + return self.cam_pos + else: + cam_pos = self.cam_pos + for _ in range(self.hits.ndim): + cam_pos = cam_pos[None, ...] + return cam_pos + + @lru_property + def cam_pos_unsqueezed_hit(self) -> H5Array: + if self.cam_pos.ndim != 1: + return self.cam_pos[self.hits, :] + else: + return self.cam_pos[None, :] + + @lru_property + def cam_pos_unsqueezed_miss(self) -> H5Array: + if self.cam_pos.ndim != 1: + return self.cam_pos[self.miss, :] + else: + return self.cam_pos[None, :] + + @lru_property + def ray_dirs(self) -> H5Array: + return (self.points - self.cam_pos_unsqueezed) * (1 / self.depths[..., None]) + + @lru_property + def ray_dirs_hit(self) -> H5Array: + out = self.points[self.hits, :] - self.cam_pos_unsqueezed_hit + out /= np.linalg.norm(out, axis=-1)[..., None] # normalize + return out + + @lru_property + def ray_dirs_miss(self) -> H5Array: + out = self.points[self.miss, :] - self.cam_pos_unsqueezed_miss + out /= np.linalg.norm(out, axis=-1)[..., None] # normalize + return out + + @lru_property + def depths(self) -> H5Array: + return np.linalg.norm(self.points - self.cam_pos_unsqueezed, axis=-1) + + @lru_property + def missing(self) -> H5Array: + return ~(self.hits | self.miss) + + @classmethod + def from_mesh_single_view(cls, mesh: Trimesh, *, compute_miss_distances: bool = False, **kw) -> "SingleViewUVScan": + if "phi" not in kw and not "theta" in kw: + kw["theta"], kw["phi"] = points.generate_random_sphere_points(1, compute_sphere_coordinates=True)[0] + scan = sample_single_view_scan_from_mesh(mesh, **kw).to_uv_scan() + if compute_miss_distances: + scan.compute_miss_distances() + assert scan.is_2d + return scan + + @classmethod + def from_mesh_sphere_view(cls, mesh: Trimesh, *, compute_miss_distances: bool = False, **kw) -> "SingleViewUVScan": + scan = sample_sphere_view_scan_from_mesh(mesh, **kw) + if compute_miss_distances: + surface_points = None + if scan.hits.sum() > mesh.vertices.shape[0]: + surface_points = mesh.vertices.astype(scan.points.dtype) + if not kw.get("no_unit_sphere", False): + translation, scale = compute_unit_sphere_transform(mesh, dtype=scan.points.dtype) + surface_points = (surface_points + translation) * scale + scan.compute_miss_distances(surface_points=surface_points) + assert scan.is_flat + return scan + + def flatten_and_permute_(self: _T, copy=False) -> _T: # inplace by default + n_items = np.product(self.hits.shape) + permutation = np.random.permutation(n_items) + + out = self.copy(deep=False) if copy else self + out.hits = out.hits .reshape((n_items, ))[permutation] + out.miss = out.miss .reshape((n_items, ))[permutation] + out.points = out.points .reshape((n_items, 3))[permutation, :] + out.normals = out.normals .reshape((n_items, 3))[permutation, :] if out.has_normals else None + out.colors = out.colors .reshape((n_items, 3))[permutation, :] if out.has_colors else None + out.distances = out.distances.reshape((n_items, ))[permutation] if out.has_miss_distances else None + return out + + @property + def is_single_view(self) -> bool: + return np.product(self.cam_pos.shape[:-1]) == 1 if not self.cam_pos is None else True + + @property + def is_flat(self) -> bool: + return len(self.hits.shape) == 1 + + @property + def is_2d(self) -> bool: + return len(self.hits.shape) == 2 + + +# transforms can be found in pytorch3d.transforms and in open3d +# and in trimesh.transformations + +def sample_single_view_scans_from_mesh( + mesh : Trimesh, + *, + n_batches : int, + scan_resolution : int = 400, + compute_normals : bool = False, + fov : float = 1.0472, # 60 degrees in radians, vertical field of view. + camera_distance : float = 2, + no_filter_backhits : bool = False, + ) -> Iterable[SingleViewScan]: + + normalized_mesh_cache = [] + + for _ in range(n_batches): + theta, phi = points.generate_random_sphere_points(1, compute_sphere_coordinates=True)[0] + + yield sample_single_view_scan_from_mesh( + mesh = mesh, + phi = phi, + theta = theta, + _mesh_is_normalized = False, + scan_resolution = scan_resolution, + compute_normals = compute_normals, + fov = fov, + camera_distance = camera_distance, + no_filter_backhits = no_filter_backhits, + _mesh_cache = normalized_mesh_cache, + ) + +def sample_single_view_scan_from_mesh( + mesh : Trimesh, + *, + phi : float, + theta : float, + scan_resolution : int = 200, + compute_normals : bool = False, + fov : float = 1.0472, # 60 degrees in radians, vertical field of view. + camera_distance : float = 2, + no_filter_backhits : bool = False, + no_unit_sphere : bool = False, + dtype : type = np.float32, + _mesh_cache : Optional[list] = None, # provide a list if mesh is reused + ) -> SingleViewScan: + + # scale and center to unit sphere + is_cache = isinstance(_mesh_cache, list) + if is_cache and _mesh_cache and _mesh_cache[0] is mesh: + _, mesh, translation, scale = _mesh_cache + else: + if is_cache: + if _mesh_cache: + _mesh_cache.clear() + _mesh_cache.append(mesh) + translation, scale = compute_unit_sphere_transform(mesh) + mesh = mesh_to_sdf.scale_to_unit_sphere(mesh) + if is_cache: + _mesh_cache.extend((mesh, translation, scale)) + + z_near = 1 + z_far = 3 + cam_mat4 = sdf_scan.get_camera_transform_looking_at_origin(phi, theta, camera_distance=camera_distance) + cam_pos = cam_mat4 @ np.array([0, 0, 0, 1]) + + scan = sdf_scan.Scan(mesh, + camera_transform = cam_mat4, + resolution = scan_resolution, + calculate_normals = compute_normals, + fov = fov, + z_near = z_near, + z_far = z_far, + no_flip_backfaced_normals = True + ) + + # all the scan rays that hit the far plane, based on sdf_scan.Scan.__init__ + misses = np.argwhere(scan.depth_buffer == 0) + points_miss = np.ones((misses.shape[0], 4)) + points_miss[:, [1, 0]] = misses.astype(float) / (scan_resolution -1) * 2 - 1 + points_miss[:, 1] *= -1 + points_miss[:, 2] = 1 # far plane in clipping space + points_miss = points_miss @ (cam_mat4 @ np.linalg.inv(scan.projection_matrix)).T + points_miss /= points_miss[:, 3][:, np.newaxis] + points_miss = points_miss[:, :3] + + uv_hits = scan.depth_buffer != 0 + uv_miss = ~uv_hits + + if not no_filter_backhits: + if not compute_normals: + raise ValueError("not `no_filter_backhits` requires `compute_normals`") + # inner product + mask = np.einsum('ij,ij->i', scan.points - cam_pos[:3][None, :], scan.normals) < 0 + scan.points = scan.points [mask, :] + scan.normals = scan.normals[mask, :] + uv_hits[uv_hits] = mask + + transforms = {} + + # undo unit-sphere transform + if no_unit_sphere: + scan.points = scan.points * (1 / scale) - translation + points_miss = points_miss * (1 / scale) - translation + cam_pos[:3] = cam_pos[:3] * (1 / scale) - translation + cam_mat4[:3, :] *= 1 / scale + cam_mat4[:3, 3] -= translation + + transforms["unit_sphere"] = T.scale_and_translate(scale=scale, translate=translation) + transforms["model"] = np.eye(4) + else: + transforms["model"] = np.linalg.inv(T.scale_and_translate(scale=scale, translate=translation)) + transforms["unit_sphere"] = np.eye(4) + + return SingleViewScan( + normals_hit = scan.normals .astype(dtype), + points_hit = scan.points .astype(dtype), + points_miss = points_miss .astype(dtype), + distances_miss = None, + colors_hit = None, + colors_miss = None, + uv_hits = uv_hits .astype(bool), + uv_miss = uv_miss .astype(bool), + cam_pos = cam_pos[:3] .astype(dtype), + cam_mat4 = cam_mat4 .astype(dtype), + proj_mat4 = scan.projection_matrix .astype(dtype), + transforms = {k:v.astype(dtype) for k, v in transforms.items()}, + ) + +def sample_sphere_view_scan_from_mesh( + mesh : Trimesh, + *, + sphere_points : int = 4000, # resulting rays are n*(n-1) + compute_normals : bool = False, + no_filter_backhits : bool = False, + no_unit_sphere : bool = False, + no_permute : bool = False, + dtype : type = np.float32, + **kw, + ) -> SingleViewUVScan: + translation, scale = compute_unit_sphere_transform(mesh, dtype=dtype) + + # get unit-sphere points, then transform to model space + two_sphere = generate_equidistant_sphere_rays(sphere_points, **kw).astype(dtype) # (n*(n-1), 2, 3) + two_sphere = two_sphere / scale - translation # we transform after cache lookup + + if mesh.ray.__class__.__module__.split(".")[-1] != "ray_pyembree": + warnings.warn("Pyembree not found, the ray-tracing will be SLOW!") + + ( + locations, + index_ray, + index_tri, + ) = mesh.ray.intersects_location( + two_sphere[:, 0, :], + two_sphere[:, 1, :] - two_sphere[:, 0, :], # direction, not target coordinate + multiple_hits=False, + ) + + + if compute_normals: + location_normals = mesh.face_normals[index_tri] + + batch = two_sphere.shape[:1] + hits = np.zeros((*batch,), dtype=np.bool) + miss = np.ones((*batch,), dtype=np.bool) + cam_pos = two_sphere[:, 0, :] + intersections = two_sphere[:, 1, :] # far-plane, effectively + normals = np.zeros((*batch, 3), dtype=dtype) + + index_ray_front = index_ray + if not no_filter_backhits: + if not compute_normals: + raise ValueError("not `no_filter_backhits` requires `compute_normals`") + mask = ((intersections[index_ray] - cam_pos[index_ray]) * location_normals).sum(axis=-1) <= 0 + index_ray_front = index_ray[mask] + + + hits[index_ray_front] = True + miss[index_ray] = False + intersections[index_ray] = locations + normals[index_ray] = location_normals + + + if not no_permute: + assert len(batch) == 1, batch + permutation = np.random.permutation(*batch) + hits = hits [permutation] + miss = miss [permutation] + intersections = intersections[permutation, :] + normals = normals [permutation, :] + cam_pos = cam_pos [permutation, :] + + # apply unit sphere transform + if not no_unit_sphere: + intersections = (intersections + translation) * scale + cam_pos = (cam_pos + translation) * scale + + return SingleViewUVScan( + hits = hits, + miss = miss, + points = intersections, + normals = normals, + colors = None, # colors + distances = None, + cam_pos = cam_pos, + cam_mat4 = None, + proj_mat4 = None, + transforms = {}, + ) + +def distance_from_rays_to_point_cloud( + ray_origins : np.ndarray, # (*A, 3) + ray_dirs : np.ndarray, # (*A, 3) + points : np.ndarray, # (*B, 3) + dirs_normalized : bool = False, + n_steps : int = 40, + ) -> np.ndarray: # (A) + + # anything outside of this volume will never constribute to the result + max_norm = max( + np.linalg.norm(ray_origins, axis=-1).max(), + np.linalg.norm(points, axis=-1).max(), + ) * 1.02 + + if not dirs_normalized: + ray_dirs = ray_dirs / np.linalg.norm(ray_dirs, axis=-1)[..., None] + + + # deal with single-view clouds + if ray_origins.shape != ray_dirs.shape: + ray_origins = np.broadcast_to(ray_origins, ray_dirs.shape) + + n_points = np.product(points.shape[:-1]) + use_faiss = n_points > 160000*4 + if not use_faiss: + index = BallTree(points) + else: + # http://ann-benchmarks.com/index.html + assert np.issubdtype(points.dtype, np.float32) + assert np.issubdtype(ray_origins.dtype, np.float32) + assert np.issubdtype(ray_dirs.dtype, np.float32) + index = faiss.index_factory(points.shape[-1], "NSG32,Flat") # https://github.com/facebookresearch/faiss/wiki/The-index-factory + + index.nprobe = 5 # 10 # default is 1 + index.train(points) + index.add(points) + + if not use_faiss: + min_d, min_n = index.query(ray_origins, k=1, return_distance=True) + else: + min_d, min_n = index.search(ray_origins, k=1) + min_d = np.sqrt(min_d) + acc_d = min_d.copy() + + for step in range(1, n_steps+1): + query_points = ray_origins + acc_d * ray_dirs + if max_norm is not None: + qmask = np.linalg.norm(query_points, axis=-1) < max_norm + if not qmask.any(): break + query_points = query_points[qmask] + else: + qmask = slice(None) + if not use_faiss: + current_d, current_n = index.query(query_points, k=1, return_distance=True) + else: + current_d, current_n = index.search(query_points, k=1) + current_d = np.sqrt(current_d) + if max_norm is not None: + min_d[qmask] = np.minimum(current_d, min_d[qmask]) + new_min_mask = min_d[qmask] == current_d + qmask2 = qmask.copy() + qmask2[qmask2] = new_min_mask[..., 0] + min_n[qmask2] = current_n[new_min_mask[..., 0]] + acc_d[qmask] += current_d * 0.25 + else: + np.minimum(current_d, min_d, out=min_d) + new_min_mask = min_d == current_d + min_n[new_min_mask] = current_n[new_min_mask] + acc_d += current_d * 0.25 + + closest_points = points[min_n[:, 0], :] # k=1 + distances = np.linalg.norm(np.cross(closest_points - ray_origins, ray_dirs, axis=-1), axis=-1) + return distances + +# helpers + +@compose(np.array) # make copy to avoid lru cache mutation +@lru_cache(maxsize=1) +def generate_equidistant_sphere_rays(n : int, **kw) -> np.ndarray: # output (n*n(-1)) rays, n may be off + sphere_points = points.generate_equidistant_sphere_points(n=n, **kw) + + indices = np.indices((len(sphere_points),))[0] # (N) + # cartesian product + cprod = np.transpose([np.tile(indices, len(indices)), np.repeat(indices, len(indices))]) # (N**2, 2) + # filter repeated combinations + permutations = cprod[cprod[:, 0] != cprod[:, 1], :] # (N*(N-1), 2) + # lookup sphere points + two_sphere = sphere_points[permutations, :] # (N*(N-1), 2, 3) + + return two_sphere + +def compute_unit_sphere_transform(mesh: Trimesh, *, dtype=type) -> tuple[np.ndarray, float]: + """ + returns translation and scale which mesh_to_sdf applies to meshes before computing their SDF cloud + """ + # the transformation applied by mesh_to_sdf.scale_to_unit_sphere(mesh) + translation = -mesh.bounding_box.centroid + scale = 1 / np.max(np.linalg.norm(mesh.vertices + translation, axis=1)) + if dtype is not None: + translation = translation.astype(dtype) + scale = scale .astype(dtype) + return translation, scale diff --git a/ifield/data/common/types.py b/ifield/data/common/types.py new file mode 100644 index 0000000..3da8d31 --- /dev/null +++ b/ifield/data/common/types.py @@ -0,0 +1,6 @@ +__doc__ = """ +Some helper types. +""" + +class MalformedMesh(Exception): + pass diff --git a/ifield/data/config.py b/ifield/data/config.py new file mode 100644 index 0000000..16cb0a9 --- /dev/null +++ b/ifield/data/config.py @@ -0,0 +1,28 @@ +from ..utils.helpers import make_relative +from pathlib import Path +from typing import Optional +import os +import warnings + + +def data_path_get(dataset_name: str, no_warn: bool = False) -> Path: + dataset_envvar = f"IFIELD_DATA_MODELS_{dataset_name.replace(*'-_').upper()}" + if dataset_envvar in os.environ: + data_path = Path(os.environ[dataset_envvar]) + elif "IFIELD_DATA_MODELS" in os.environ: + data_path = Path(os.environ["IFIELD_DATA_MODELS"]) / dataset_name + else: + data_path = Path(__file__).resolve().parent.parent.parent / "data" / "models" / dataset_name + if not data_path.is_dir() and not no_warn: + warnings.warn(f"{make_relative(data_path, Path.cwd()).__str__()!r} is not a directory!") + return data_path + +def data_path_persist(dataset_name: Optional[str], path: os.PathLike) -> os.PathLike: + "Persist the datapath, ensuring subprocesses also will use it. The path passes through." + + if dataset_name is None: + os.environ["IFIELD_DATA_MODELS"] = str(path) + else: + os.environ[f"IFIELD_DATA_MODELS_{dataset_name.replace(*'-_').upper()}"] = str(path) + + return path diff --git a/ifield/data/coseg/__init__.py b/ifield/data/coseg/__init__.py new file mode 100644 index 0000000..2ecc7c8 --- /dev/null +++ b/ifield/data/coseg/__init__.py @@ -0,0 +1,56 @@ +from ..config import data_path_get, data_path_persist +from collections import namedtuple +import os + + +# Data source: +# http://irc.cs.sdu.edu.cn/~yunhai/public_html/ssl/ssd.htm + +__ALL__ = ["config", "Model", "MODELS"] + +Archive = namedtuple("Archive", "url fname download_size_str") + +@(lambda x: x()) # singleton +class config: + DATA_PATH = property( + doc = """ + Path to the dataset. The following envvars override it: + ${IFIELD_DATA_MODELS}/coseg + ${IFIELD_DATA_MODELS_COSEG} + """, + fget = lambda self: data_path_get ("coseg"), + fset = lambda self, path: data_path_persist("coseg", path), + ) + + @property + def IS_DOWNLOADED_DB(self) -> list[os.PathLike]: + return [ + self.DATA_PATH / "downloaded.json", + ] + + SHAPES: dict[str, Archive] = { + "candelabra" : Archive("http://irc.cs.sdu.edu.cn/~yunhai/public_html/ssl/data/Candelabra/shapes.zip", "candelabra-shapes.zip", "3,3M"), + "chair" : Archive("http://irc.cs.sdu.edu.cn/~yunhai/public_html/ssl/data/Chair/shapes.zip", "chair-shapes.zip", "3,2M"), + "four-legged" : Archive("http://irc.cs.sdu.edu.cn/~yunhai/public_html/ssl/data/Four-legged/shapes.zip", "four-legged-shapes.zip", "2,9M"), + "goblets" : Archive("http://irc.cs.sdu.edu.cn/~yunhai/public_html/ssl/data/Goblets/shapes.zip", "goblets-shapes.zip", "500K"), + "guitars" : Archive("http://irc.cs.sdu.edu.cn/~yunhai/public_html/ssl/data/Guitars/shapes.zip", "guitars-shapes.zip", "1,9M"), + "lampes" : Archive("http://irc.cs.sdu.edu.cn/~yunhai/public_html/ssl/data/Lampes/shapes.zip", "lampes-shapes.zip", "2,4M"), + "vases" : Archive("http://irc.cs.sdu.edu.cn/~yunhai/public_html/ssl/data/Vases/shapes.zip", "vases-shapes.zip", "5,5M"), + "irons" : Archive("http://irc.cs.sdu.edu.cn/~yunhai/public_html/ssl/data/Irons/shapes.zip", "irons-shapes.zip", "1,2M"), + "tele-aliens" : Archive("http://irc.cs.sdu.edu.cn/~yunhai/public_html/ssl/data/Tele-aliens/shapes.zip", "tele-aliens-shapes.zip", "15M"), + "large-vases" : Archive("http://irc.cs.sdu.edu.cn/~yunhai/public_html/ssl/data/Large-Vases/shapes.zip", "large-vases-shapes.zip", "6,2M"), + "large-chairs": Archive("http://irc.cs.sdu.edu.cn/~yunhai/public_html/ssl/data/Large-Chairs/shapes.zip", "large-chairs-shapes.zip", "14M"), + } + GROUND_TRUTHS: dict[str, Archive] = { + "candelabra" : Archive("http://irc.cs.sdu.edu.cn/~yunhai/public_html/ssl/data/Candelabra/gt.zip", "candelabra-gt.zip", "68K"), + "chair" : Archive("http://irc.cs.sdu.edu.cn/~yunhai/public_html/ssl/data/Chair/gt.zip", "chair-gt.zip", "20K"), + "four-legged" : Archive("http://irc.cs.sdu.edu.cn/~yunhai/public_html/ssl/data/Four-legged/gt.zip", "four-legged-gt.zip", "24K"), + "goblets" : Archive("http://irc.cs.sdu.edu.cn/~yunhai/public_html/ssl/data/Goblets/gt.zip", "goblets-gt.zip", "4,0K"), + "guitars" : Archive("http://irc.cs.sdu.edu.cn/~yunhai/public_html/ssl/data/Guitars/gt.zip", "guitars-gt.zip", "12K"), + "lampes" : Archive("http://irc.cs.sdu.edu.cn/~yunhai/public_html/ssl/data/Lampes/gt.zip", "lampes-gt.zip", "60K"), + "vases" : Archive("http://irc.cs.sdu.edu.cn/~yunhai/public_html/ssl/data/Vases/gt.zip", "vases-gt.zip", "40K"), + "irons" : Archive("http://irc.cs.sdu.edu.cn/~yunhai/public_html/ssl/data/Irons/gt.zip", "irons-gt.zip", "8,0K"), + "tele-aliens" : Archive("http://irc.cs.sdu.edu.cn/~yunhai/public_html/ssl/data/Tele-aliens/gt.zip", "tele-aliens-gt.zip", "72K"), + "large-vases" : Archive("http://irc.cs.sdu.edu.cn/~yunhai/public_html/ssl/data/Large-Vases/gt.zip", "large-vases-gt.zip", "68K"), + "large-chairs": Archive("http://irc.cs.sdu.edu.cn/~yunhai/public_html/ssl/data/Large-Chairs/gt.zip", "large-chairs-gt.zip", "116K"), + } diff --git a/ifield/data/coseg/download.py b/ifield/data/coseg/download.py new file mode 100644 index 0000000..47a4c3e --- /dev/null +++ b/ifield/data/coseg/download.py @@ -0,0 +1,135 @@ +#!/usr/bin/env python3 +from . import config +from ...utils.helpers import make_relative +from ..common import download +from pathlib import Path +from textwrap import dedent +import argparse +import io +import zipfile + + + +def is_downloaded(*a, **kw): + return download.is_downloaded(*a, dbfiles=config.IS_DOWNLOADED_DB, **kw) + +def download_and_extract(target_dir: Path, url_dict: dict[str, str], *, force=False, silent=False) -> bool: + target_dir.mkdir(parents=True, exist_ok=True) + + ret = False + for url, fname in url_dict.items(): + if not force: + if is_downloaded(target_dir, url): continue + if not download.check_url(url): + print("ERROR:", url) + continue + ret = True + + if force or not (target_dir / "archives" / fname).is_file(): + + data = download.download_data(url, silent=silent, label=fname) + assert url.endswith(".zip") + + print("writing...") + + (target_dir / "archives").mkdir(parents=True, exist_ok=True) + with (target_dir / "archives" / fname).open("wb") as f: + f.write(data) + del data + + print(f"extracting {fname}...") + + with zipfile.ZipFile(target_dir / "archives" / fname, 'r') as f: + f.extractall(target_dir / Path(fname).stem.removesuffix("-shapes").removesuffix("-gt")) + + is_downloaded(target_dir, url, add=True) + + return ret + +def make_parser() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser(description=dedent(""" + Download The COSEG Shape Dataset. + More info: http://irc.cs.sdu.edu.cn/~yunhai/public_html/ssl/ssd.htm + + Example: + + download-coseg --shapes chairs + """), formatter_class=argparse.RawTextHelpFormatter) + + arg = parser.add_argument + + arg("sets", nargs="*", default=[], + help="Which set to download, defaults to none.") + arg("--all", action="store_true", + help="Download all sets") + arg("--dir", default=str(config.DATA_PATH), + help=f"The target directory. Default is {make_relative(config.DATA_PATH, Path.cwd()).__str__()!r}") + + arg("--shapes", action="store_true", + help="Download the 3d shapes for each chosen set") + arg("--gts", action="store_true", + help="Download the ground-truth segmentation data for each chosen set") + + arg("--list", action="store_true", + help="Lists all the sets") + arg("--list-urls", action="store_true", + help="Lists the urls to download") + arg("--list-sizes", action="store_true", + help="Lists the download size of each set") + arg("--silent", action="store_true", + help="") + arg("--force", action="store_true", + help="Download again even if already downloaded") + + return parser + +# entrypoint +def cli(parser=make_parser()): + args = parser.parse_args() + + assert set(config.SHAPES.keys()) == set(config.GROUND_TRUTHS.keys()) + + set_names = sorted(set(args.sets)) + if args.all: + assert not set_names, "--all is mutually exclusive from manually selected sets" + set_names = sorted(config.SHAPES.keys()) + + if args.list: + print(*config.SHAPES.keys(), sep="\n") + exit() + + if args.list_sizes: + print(*(f"{set_name:<15}{config.SHAPES[set_name].download_size_str}" for set_name in (set_names or config.SHAPES.keys())), sep="\n") + exit() + + try: + url_dict \ + = {config.SHAPES[set_name].url : config.SHAPES[set_name].fname for set_name in set_names if args.shapes} \ + | {config.GROUND_TRUTHS[set_name].url : config.GROUND_TRUTHS[set_name].fname for set_name in set_names if args.gts} + except KeyError: + print("Error: unrecognized object name:", *set(set_names).difference(config.SHAPES.keys()), sep="\n") + exit(1) + + if not url_dict: + if set_names and not (args.shapes or args.gts): + print("Error: Provide at least one of --shapes of --gts") + else: + print("Error: No object set was selected for download!") + exit(1) + + if args.list_urls: + print(*url_dict.keys(), sep="\n") + exit() + + print("Download start") + any_downloaded = download_and_extract( + target_dir = Path(args.dir), + url_dict = url_dict, + force = args.force, + silent = args.silent, + ) + if not any_downloaded: + print("Everything has already been downloaded, skipping.") + +if __name__ == "__main__": + cli() diff --git a/ifield/data/coseg/preprocess.py b/ifield/data/coseg/preprocess.py new file mode 100644 index 0000000..65b0b70 --- /dev/null +++ b/ifield/data/coseg/preprocess.py @@ -0,0 +1,137 @@ +#!/usr/bin/env python3 +import os; os.environ.setdefault("PYOPENGL_PLATFORM", "egl") +from . import config, read +from ...utils.helpers import make_relative +from pathlib import Path +from textwrap import dedent +import argparse + + +def make_parser() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser(description=dedent(""" + Preprocess the COSEG dataset. Depends on `download-coseg --shapes ...` having been run. + """), formatter_class=argparse.RawTextHelpFormatter) + + arg = parser.add_argument # brevity + + arg("items", nargs="*", default=[], + help="Which object-set[/model-id] to process, defaults to all downloaded. Format: OBJECT-SET[/MODEL-ID]") + arg("--dir", default=str(config.DATA_PATH), + help=f"The target directory. Default is {make_relative(config.DATA_PATH, Path.cwd()).__str__()!r}") + arg("--force", action="store_true", + help="Overwrite existing files") + arg("--list-models", action="store_true", + help="List the downloaded models available for preprocessing") + arg("--list-object-sets", action="store_true", + help="List the downloaded object-sets available for preprocessing") + arg("--list-pages", type=int, default=None, + help="List the downloaded models available for preprocessing, paginated into N pages.") + arg("--page", nargs=2, type=int, default=[0, 1], + help="Subset of parts to compute. Use to parallelize. (page, total), page is 0 indexed") + + arg2 = parser.add_argument_group("preprocessing targets").add_argument # brevity + arg2("--precompute-mesh-sv-scan-clouds", action="store_true", + help="Compute single-view hit+miss point clouds from 100 synthetic scans.") + arg2("--precompute-mesh-sv-scan-uvs", action="store_true", + help="Compute single-view hit+miss UV clouds from 100 synthetic scans.") + arg2("--precompute-mesh-sphere-scan", action="store_true", + help="Compute a sphere-view hit+miss cloud cast from n to n unit sphere points.") + + arg3 = parser.add_argument_group("modifiers").add_argument # brevity + arg3("--n-sphere-points", type=int, default=4000, + help="The number of unit-sphere points to sample rays from. Final result: n*(n-1).") + arg3("--compute-miss-distances", action="store_true", + help="Compute the distance to the nearest hit for each miss in the hit+miss clouds.") + arg3("--fill-missing-uv-points", action="store_true", + help="TODO") + arg3("--no-filter-backhits", action="store_true", + help="Do not filter scan hits on backside of mesh faces.") + arg3("--no-unit-sphere", action="store_true", + help="Do not center the objects to the unit sphere.") + arg3("--convert-ok", action="store_true", + help="Allow reusing point clouds for uv clouds and vice versa. (does not account for other hparams)") + arg3("--debug", action="store_true", + help="Abort on failiure.") + + return parser + +# entrypoint +def cli(parser=make_parser()): + args = parser.parse_args() + if not any(getattr(args, k) for k in dir(args) if k.startswith("precompute_")) and not (args.list_models or args.list_object_sets or args.list_pages): + parser.error("no preprocessing target selected") # exits + + config.DATA_PATH = Path(args.dir) + + object_sets = [i for i in args.items if "/" not in i] + models = [i.split("/") for i in args.items if "/" in i] + + # convert/expand synsets to models + # they are mutually exclusive + if object_sets: assert not models + if models: assert not object_sets + if not models: + models = read.list_model_ids(tuple(object_sets) or None) + + if args.list_models: + try: + print(*(f"{object_set_id}/{model_id}" for object_set_id, model_id in models), sep="\n") + except BrokenPipeError: + pass + parser.exit() + + if args.list_object_sets: + try: + print(*sorted(set(object_set_id for object_set_id, model_id in models)), sep="\n") + except BrokenPipeError: + pass + parser.exit() + + if args.list_pages is not None: + try: + print(*( + f"--page {i} {args.list_pages} {object_set_id}/{model_id}" + for object_set_id, model_id in models + for i in range(args.list_pages) + ), sep="\n") + except BrokenPipeError: + pass + parser.exit() + + if args.precompute_mesh_sv_scan_clouds: + read.precompute_mesh_scan_point_clouds( + models, + compute_miss_distances = args.compute_miss_distances, + no_filter_backhits = args.no_filter_backhits, + no_unit_sphere = args.no_unit_sphere, + convert_ok = args.convert_ok, + page = args.page, + force = args.force, + debug = args.debug, + ) + if args.precompute_mesh_sv_scan_uvs: + read.precompute_mesh_scan_uvs( + models, + compute_miss_distances = args.compute_miss_distances, + fill_missing_points = args.fill_missing_uv_points, + no_filter_backhits = args.no_filter_backhits, + no_unit_sphere = args.no_unit_sphere, + convert_ok = args.convert_ok, + page = args.page, + force = args.force, + debug = args.debug, + ) + if args.precompute_mesh_sphere_scan: + read.precompute_mesh_sphere_scan( + models, + sphere_points = args.n_sphere_points, + compute_miss_distances = args.compute_miss_distances, + no_filter_backhits = args.no_filter_backhits, + no_unit_sphere = args.no_unit_sphere, + page = args.page, + force = args.force, + debug = args.debug, + ) + +if __name__ == "__main__": + cli() diff --git a/ifield/data/coseg/read.py b/ifield/data/coseg/read.py new file mode 100644 index 0000000..1e3dd6e --- /dev/null +++ b/ifield/data/coseg/read.py @@ -0,0 +1,290 @@ +from . import config +from ..common import points +from ..common import processing +from ..common.scan import SingleViewScan, SingleViewUVScan +from ..common.types import MalformedMesh +from functools import lru_cache +from typing import Optional, Iterable +import numpy as np +import trimesh +import trimesh.transformations as T + +__doc__ = """ +Here are functions for reading and preprocessing coseg benchmark data + +There are essentially a few sets per object: + "img" - meaning the RGBD images (none found in coseg) + "mesh_scans" - meaning synthetic scans of a mesh +""" + +MESH_TRANSFORM_SKYWARD = T.rotation_matrix(np.pi/2, (1, 0, 0)) # rotate to be upright in pyrender +MESH_POSE_CORRECTIONS = { # to gain a shared canonical orientation + ("four-legged", 381): T.rotation_matrix( -1*np.pi/2, (0, 0, 1)), + ("four-legged", 382): T.rotation_matrix( 1*np.pi/2, (0, 0, 1)), + ("four-legged", 383): T.rotation_matrix( -1*np.pi/2, (0, 0, 1)), + ("four-legged", 384): T.rotation_matrix( -1*np.pi/2, (0, 0, 1)), + ("four-legged", 385): T.rotation_matrix( -1*np.pi/2, (0, 0, 1)), + ("four-legged", 386): T.rotation_matrix( 1*np.pi/2, (0, 0, 1)), + ("four-legged", 387): T.rotation_matrix(-0.2*np.pi/2, (0, 1, 0))@T.rotation_matrix(1*np.pi/2, (0, 0, 1)), + ("four-legged", 388): T.rotation_matrix( -1*np.pi/2, (0, 0, 1)), + ("four-legged", 389): T.rotation_matrix( -1*np.pi/2, (0, 0, 1)), + ("four-legged", 390): T.rotation_matrix( 0*np.pi/2, (0, 0, 1)), + ("four-legged", 391): T.rotation_matrix( 0*np.pi/2, (0, 0, 1)), + ("four-legged", 392): T.rotation_matrix( -1*np.pi/2, (0, 0, 1)), + ("four-legged", 393): T.rotation_matrix( 0*np.pi/2, (0, 0, 1)), + ("four-legged", 394): T.rotation_matrix( -1*np.pi/2, (0, 0, 1)), + ("four-legged", 395): T.rotation_matrix(-0.2*np.pi/2, (0, 1, 0))@T.rotation_matrix(1*np.pi/2, (0, 0, 1)), + ("four-legged", 396): T.rotation_matrix( 1*np.pi/2, (0, 0, 1)), + ("four-legged", 397): T.rotation_matrix( 0*np.pi/2, (0, 0, 1)), + ("four-legged", 398): T.rotation_matrix( -1*np.pi/2, (0, 0, 1)), + ("four-legged", 399): T.rotation_matrix( 0*np.pi/2, (0, 0, 1)), + ("four-legged", 400): T.rotation_matrix( 0*np.pi/2, (0, 0, 1)), +} + + +ModelUid = tuple[str, int] + +@lru_cache(maxsize=1) +def list_object_sets() -> list[str]: + return sorted( + object_set.name + for object_set in config.DATA_PATH.iterdir() + if (object_set / "shapes").is_dir() and object_set.name != "archive" + ) + +@lru_cache(maxsize=1) +def list_model_ids(object_sets: Optional[tuple[str]] = None) -> list[ModelUid]: + return sorted( + (object_set.name, int(model.stem)) + for object_set in config.DATA_PATH.iterdir() + if (object_set / "shapes").is_dir() and object_set.name != "archive" and (object_sets is None or object_set.name in object_sets) + for model in (object_set / "shapes").iterdir() + if model.is_file() and model.suffix == ".off" + ) + +def list_model_id_strings(object_sets: Optional[tuple[str]] = None) -> list[str]: + return [model_uid_to_string(object_set_id, model_id) for object_set_id, model_id in list_model_ids(object_sets)] + +def model_uid_to_string(object_set_id: str, model_id: int) -> str: + return f"{object_set_id}-{model_id}" + +def model_id_string_to_uid(model_string_uid: str) -> ModelUid: + object_set, split, model = model_string_uid.rpartition("-") + assert split == "-" + return (object_set, int(model)) + +@lru_cache(maxsize=1) +def list_mesh_scan_sphere_coords(n_poses: int = 50) -> list[tuple[float, float]]: # (theta, phi) + return points.generate_equidistant_sphere_points(n_poses, compute_sphere_coordinates=True) + +def mesh_scan_identifier(*, phi: float, theta: float) -> str: + return ( + f"{'np'[theta>=0]}{abs(theta):.2f}" + f"{'np'[phi >=0]}{abs(phi) :.2f}" + ).replace(".", "d") + +@lru_cache(maxsize=1) +def list_mesh_scan_identifiers(n_poses: int = 50) -> list[str]: + out = [ + mesh_scan_identifier(phi=phi, theta=theta) + for theta, phi in list_mesh_scan_sphere_coords(n_poses) + ] + assert len(out) == len(set(out)) + return out + +# === + +def read_mesh(object_set_id: str, model_id: int) -> trimesh.Trimesh: + path = config.DATA_PATH / object_set_id / "shapes" / f"{model_id}.off" + if not path.is_file(): + raise FileNotFoundError(f"{path = }") + try: + mesh = trimesh.load(path, force="mesh") + except Exception as e: + raise MalformedMesh(f"Trimesh raised: {e.__class__.__name__}: {e}") from e + + pose = MESH_POSE_CORRECTIONS.get((object_set_id, int(model_id))) + mesh.apply_transform(pose @ MESH_TRANSFORM_SKYWARD if pose is not None else MESH_TRANSFORM_SKYWARD) + return mesh + +# === single-view scan clouds + +def compute_mesh_scan_point_cloud( + object_set_id : str, + model_id : int, + phi : float, + theta : float, + *, + compute_miss_distances : bool = False, + fill_missing_points : bool = False, + compute_normals : bool = True, + convert_ok : bool = False, + **kw, + ) -> SingleViewScan: + + if convert_ok: + try: + return read_mesh_scan_uv(object_set_id, model_id, phi=phi, theta=theta).to_scan() + except FileNotFoundError: + pass + + mesh = read_mesh(object_set_id, model_id) + scan = SingleViewScan.from_mesh_single_view(mesh, + phi = phi, + theta = theta, + compute_normals = compute_normals, + **kw, + ) + if compute_miss_distances: + scan.compute_miss_distances() + if fill_missing_points: + scan.fill_missing_points() + + return scan + +def precompute_mesh_scan_point_clouds(models: Iterable[ModelUid], *, n_poses: int = 50, page: tuple[int, int] = (0, 1), force = False, debug = False, **kw): + "precomputes all single-view scan clouds and stores them as HDF5 datasets" + cam_poses = list_mesh_scan_sphere_coords(n_poses=n_poses) + pose_identifiers = list_mesh_scan_identifiers (n_poses=n_poses) + assert len(cam_poses) == len(pose_identifiers) + paths = list_mesh_scan_point_cloud_h5_fnames(models, pose_identifiers, n_poses=n_poses) + mlen_syn = max(len(object_set_id) for object_set_id, model_id in models) + mlen_mod = max(len(str(model_id)) for object_set_id, model_id in models) + pretty_identifiers = [ + f"{object_set_id.ljust(mlen_syn)} @ {str(model_id).ljust(mlen_mod)} @ {i:>5} @ ({itentifier}: {theta:.2f}, {phi:.2f})" + for object_set_id, model_id in models + for i, (itentifier, (theta, phi)) in enumerate(zip(pose_identifiers, cam_poses)) + ] + mesh_cache = [] + def computer(pretty_identifier: str) -> SingleViewScan: + object_set_id, model_id, index, _ = map(str.strip, pretty_identifier.split("@")) + theta, phi = cam_poses[int(index)] + return compute_mesh_scan_point_cloud(object_set_id, int(model_id), phi=phi, theta=theta, _mesh_cache=mesh_cache, **kw) + return processing.precompute_data(computer, pretty_identifiers, paths, page=page, force=force, debug=debug) + +def read_mesh_scan_point_cloud(object_set_id: str, model_id: int, *, identifier: str = None, phi: float = None, theta: float = None) -> SingleViewScan: + if identifier is None: + if phi is None or theta is None: + raise ValueError("Provide either phi+theta or an identifier!") + identifier = mesh_scan_identifier(phi=phi, theta=theta) + file = config.DATA_PATH / object_set_id / "uv_scan_clouds" / f"{model_id}_normalized_{identifier}.h5" + return SingleViewScan.from_h5_file(file) + +def list_mesh_scan_point_cloud_h5_fnames(models: Iterable[ModelUid], identifiers: Optional[Iterable[str]] = None, **kw): + if identifiers is None: + identifiers = list_mesh_scan_identifiers(**kw) + return [ + config.DATA_PATH / object_set_id / "uv_scan_clouds" / f"{model_id}_normalized_{identifier}.h5" + for object_set_id, model_id in models + for identifier in identifiers + ] + + +# === single-view UV scan clouds + +def compute_mesh_scan_uv( + object_set_id : str, + model_id : int, + phi : float, + theta : float, + *, + compute_miss_distances : bool = False, + fill_missing_points : bool = False, + compute_normals : bool = True, + convert_ok : bool = False, + **kw, + ) -> SingleViewUVScan: + + if convert_ok: + try: + return read_mesh_scan_point_cloud(object_set_id, model_id, phi=phi, theta=theta).to_uv_scan() + except FileNotFoundError: + pass + + mesh = read_mesh(object_set_id, model_id) + scan = SingleViewUVScan.from_mesh_single_view(mesh, + phi = phi, + theta = theta, + compute_normals = compute_normals, + **kw, + ) + if compute_miss_distances: + scan.compute_miss_distances() + if fill_missing_points: + scan.fill_missing_points() + + return scan + +def precompute_mesh_scan_uvs(models: Iterable[ModelUid], *, n_poses: int = 50, page: tuple[int, int] = (0, 1), force = False, debug = False, **kw): + "precomputes all single-view scan clouds and stores them as HDF5 datasets" + cam_poses = list_mesh_scan_sphere_coords(n_poses=n_poses) + pose_identifiers = list_mesh_scan_identifiers (n_poses=n_poses) + assert len(cam_poses) == len(pose_identifiers) + paths = list_mesh_scan_uv_h5_fnames(models, pose_identifiers, n_poses=n_poses) + mlen_syn = max(len(object_set_id) for object_set_id, model_id in models) + mlen_mod = max(len(str(model_id)) for object_set_id, model_id in models) + pretty_identifiers = [ + f"{object_set_id.ljust(mlen_syn)} @ {str(model_id).ljust(mlen_mod)} @ {i:>5} @ ({itentifier}: {theta:.2f}, {phi:.2f})" + for object_set_id, model_id in models + for i, (itentifier, (theta, phi)) in enumerate(zip(pose_identifiers, cam_poses)) + ] + mesh_cache = [] + def computer(pretty_identifier: str) -> SingleViewUVScan: + object_set_id, model_id, index, _ = map(str.strip, pretty_identifier.split("@")) + theta, phi = cam_poses[int(index)] + return compute_mesh_scan_uv(object_set_id, int(model_id), phi=phi, theta=theta, _mesh_cache=mesh_cache, **kw) + return processing.precompute_data(computer, pretty_identifiers, paths, page=page, force=force, debug=debug) + +def read_mesh_scan_uv(object_set_id: str, model_id: int, *, identifier: str = None, phi: float = None, theta: float = None) -> SingleViewUVScan: + if identifier is None: + if phi is None or theta is None: + raise ValueError("Provide either phi+theta or an identifier!") + identifier = mesh_scan_identifier(phi=phi, theta=theta) + file = config.DATA_PATH / object_set_id / "uv_scan_clouds" / f"{model_id}_normalized_{identifier}.h5" + + return SingleViewUVScan.from_h5_file(file) + +def list_mesh_scan_uv_h5_fnames(models: Iterable[ModelUid], identifiers: Optional[Iterable[str]] = None, **kw): + if identifiers is None: + identifiers = list_mesh_scan_identifiers(**kw) + return [ + config.DATA_PATH / object_set_id / "uv_scan_clouds" / f"{model_id}_normalized_{identifier}.h5" + for object_set_id, model_id in models + for identifier in identifiers + ] + + +# === sphere-view (UV) scan clouds + +def compute_mesh_sphere_scan( + object_set_id : str, + model_id : int, + *, + compute_normals : bool = True, + **kw, + ) -> SingleViewUVScan: + mesh = read_mesh(object_set_id, model_id) + scan = SingleViewUVScan.from_mesh_sphere_view(mesh, + compute_normals = compute_normals, + **kw, + ) + return scan + +def precompute_mesh_sphere_scan(models: Iterable[ModelUid], *, page: tuple[int, int] = (0, 1), force: bool = False, debug: bool = False, n_points: int = 4000, **kw): + "precomputes all sphere scan clouds and stores them as HDF5 datasets" + paths = list_mesh_sphere_scan_h5_fnames(models) + identifiers = [model_uid_to_string(*i) for i in models] + def computer(identifier: str) -> SingleViewScan: + object_set_id, model_id = model_id_string_to_uid(identifier) + return compute_mesh_sphere_scan(object_set_id, model_id, **kw) + return processing.precompute_data(computer, identifiers, paths, page=page, force=force, debug=debug) + +def read_mesh_mesh_sphere_scan(object_set_id: str, model_id: int) -> SingleViewUVScan: + file = config.DATA_PATH / object_set_id / "sphere_scan_clouds" / f"{model_id}_normalized.h5" + return SingleViewUVScan.from_h5_file(file) + +def list_mesh_sphere_scan_h5_fnames(models: Iterable[ModelUid]) -> list[str]: + return [ + config.DATA_PATH / object_set_id / "sphere_scan_clouds" / f"{model_id}_normalized.h5" + for object_set_id, model_id in models + ] diff --git a/ifield/data/stanford/__init__.py b/ifield/data/stanford/__init__.py new file mode 100644 index 0000000..56ddf54 --- /dev/null +++ b/ifield/data/stanford/__init__.py @@ -0,0 +1,76 @@ +from ..config import data_path_get, data_path_persist +from collections import namedtuple +import os + + +# Data source: +# http://graphics.stanford.edu/data/3Dscanrep/ + +__ALL__ = ["config", "Model", "MODELS"] + +@(lambda x: x()) # singleton +class config: + DATA_PATH = property( + doc = """ + Path to the dataset. The following envvars override it: + ${IFIELD_DATA_MODELS}/stanford + ${IFIELD_DATA_MODELS_STANFORD} + """, + fget = lambda self: data_path_get ("stanford"), + fset = lambda self, path: data_path_persist("stanford", path), + ) + + @property + def IS_DOWNLOADED_DB(self) -> list[os.PathLike]: + return [ + self.DATA_PATH / "downloaded.json", + ] + + Model = namedtuple("Model", "url mesh_fname download_size_str") + MODELS: dict[str, Model] = { + "bunny": Model( + "http://graphics.stanford.edu/pub/3Dscanrep/bunny.tar.gz", + "bunny/reconstruction/bun_zipper.ply", + "4.89M", + ), + "drill_bit": Model( + "http://graphics.stanford.edu/pub/3Dscanrep/drill.tar.gz", + "drill/reconstruction/drill_shaft_vrip.ply", + "555k", + ), + "happy_buddha": Model( + # religious symbol + "http://graphics.stanford.edu/pub/3Dscanrep/happy/happy_recon.tar.gz", + "happy_recon/happy_vrip.ply", + "14.5M", + ), + "dragon": Model( + # symbol of Chinese culture + "http://graphics.stanford.edu/pub/3Dscanrep/dragon/dragon_recon.tar.gz", + "dragon_recon/dragon_vrip.ply", + "11.2M", + ), + "armadillo": Model( + "http://graphics.stanford.edu/pub/3Dscanrep/armadillo/Armadillo.ply.gz", + "armadillo.ply.gz", + "3.87M", + ), + "lucy": Model( + # Christian angel + "http://graphics.stanford.edu/data/3Dscanrep/lucy.tar.gz", + "lucy.ply", + "322M", + ), + "asian_dragon": Model( + # symbol of Chinese culture + "http://graphics.stanford.edu/data/3Dscanrep/xyzrgb/xyzrgb_dragon.ply.gz", + "xyzrgb_dragon.ply.gz", + "70.5M", + ), + "thai_statue": Model( + # Hindu religious significance + "http://graphics.stanford.edu/data/3Dscanrep/xyzrgb/xyzrgb_statuette.ply.gz", + "xyzrgb_statuette.ply.gz", + "106M", + ), + } diff --git a/ifield/data/stanford/download.py b/ifield/data/stanford/download.py new file mode 100644 index 0000000..5307ec0 --- /dev/null +++ b/ifield/data/stanford/download.py @@ -0,0 +1,129 @@ +#!/usr/bin/env python3 +from . import config +from ...utils.helpers import make_relative +from ..common import download +from pathlib import Path +from textwrap import dedent +from typing import Iterable +import argparse +import io +import tarfile + + +def is_downloaded(*a, **kw): + return download.is_downloaded(*a, dbfiles=config.IS_DOWNLOADED_DB, **kw) + +def download_and_extract(target_dir: Path, url_list: Iterable[str], *, force=False, silent=False) -> bool: + target_dir.mkdir(parents=True, exist_ok=True) + + ret = False + for url in url_list: + if not force: + if is_downloaded(target_dir, url): continue + if not download.check_url(url): + print("ERROR:", url) + continue + ret = True + + data = download.download_data(url, silent=silent, label=str(Path(url).name)) + + print("extracting...") + if url.endswith(".ply.gz"): + fname = target_dir / "meshes" / url.split("/")[-1].lower() + fname.parent.mkdir(parents=True, exist_ok=True) + with fname.open("wb") as f: + f.write(data) + elif url.endswith(".tar.gz"): + with tarfile.open(fileobj=io.BytesIO(data)) as tar: + for member in tar.getmembers(): + if not member.isfile(): continue + if member.name.startswith("/"): continue + if member.name.startswith("."): continue + if Path(member.name).name.startswith("."): continue + tar.extract(member, target_dir / "meshes") + del tar + else: + raise NotImplementedError(f"Extraction for {str(Path(url).name)} unknown") + + is_downloaded(target_dir, url, add=True) + del data + + return ret + +def make_parser() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser(description=dedent(""" + Download The Stanford 3D Scanning Repository models. + More info: http://graphics.stanford.edu/data/3Dscanrep/ + + Example: + + download-stanford bunny + """), formatter_class=argparse.RawTextHelpFormatter) + + arg = parser.add_argument + + arg("objects", nargs="*", default=[], + help="Which objects to download, defaults to none.") + arg("--all", action="store_true", + help="Download all objects") + arg("--dir", default=str(config.DATA_PATH), + help=f"The target directory. Default is {make_relative(config.DATA_PATH, Path.cwd()).__str__()!r}") + + arg("--list", action="store_true", + help="Lists all the objects") + arg("--list-urls", action="store_true", + help="Lists the urls to download") + arg("--list-sizes", action="store_true", + help="Lists the download size of each model") + arg("--silent", action="store_true", + help="") + arg("--force", action="store_true", + help="Download again even if already downloaded") + + return parser + +# entrypoint +def cli(parser=make_parser()): + args = parser.parse_args() + + obj_names = sorted(set(args.objects)) + if args.all: + assert not obj_names + obj_names = sorted(config.MODELS.keys()) + if not obj_names and args.list_urls: config.MODELS.keys() + + if args.list: + print(*config.MODELS.keys(), sep="\n") + exit() + + if args.list_sizes: + print(*(f"{obj_name:<15}{config.MODELS[obj_name].download_size_str}" for obj_name in (obj_names or config.MODELS.keys())), sep="\n") + exit() + + try: + url_list = [config.MODELS[obj_name].url for obj_name in obj_names] + except KeyError: + print("Error: unrecognized object name:", *set(obj_names).difference(config.MODELS.keys()), sep="\n") + exit(1) + + if not url_list: + print("Error: No object set was selected for download!") + exit(1) + + if args.list_urls: + print(*url_list, sep="\n") + exit() + + + print("Download start") + any_downloaded = download_and_extract( + target_dir = Path(args.dir), + url_list = url_list, + force = args.force, + silent = args.silent, + ) + if not any_downloaded: + print("Everything has already been downloaded, skipping.") + +if __name__ == "__main__": + cli() diff --git a/ifield/data/stanford/preprocess.py b/ifield/data/stanford/preprocess.py new file mode 100644 index 0000000..b7363f7 --- /dev/null +++ b/ifield/data/stanford/preprocess.py @@ -0,0 +1,118 @@ +#!/usr/bin/env python3 +import os; os.environ.setdefault("PYOPENGL_PLATFORM", "egl") +from . import config, read +from ...utils.helpers import make_relative +from pathlib import Path +from textwrap import dedent +import argparse + + + +def make_parser() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser(description=dedent(""" + Preprocess the Stanford models. Depends on `download-stanford` having been run. + """), formatter_class=argparse.RawTextHelpFormatter) + + arg = parser.add_argument # brevity + + arg("objects", nargs="*", default=[], + help="Which objects to process, defaults to all downloaded") + arg("--dir", default=str(config.DATA_PATH), + help=f"The target directory. Default is {make_relative(config.DATA_PATH, Path.cwd()).__str__()!r}") + arg("--force", action="store_true", + help="Overwrite existing files") + arg("--list", action="store_true", + help="List the downloaded models available for preprocessing") + arg("--list-pages", type=int, default=None, + help="List the downloaded models available for preprocessing, paginated into N pages.") + arg("--page", nargs=2, type=int, default=[0, 1], + help="Subset of parts to compute. Use to parallelize. (page, total), page is 0 indexed") + + arg2 = parser.add_argument_group("preprocessing targets").add_argument # brevity + arg2("--precompute-mesh-sv-scan-clouds", action="store_true", + help="Compute single-view hit+miss point clouds from 100 synthetic scans.") + arg2("--precompute-mesh-sv-scan-uvs", action="store_true", + help="Compute single-view hit+miss UV clouds from 100 synthetic scans.") + arg2("--precompute-mesh-sphere-scan", action="store_true", + help="Compute a sphere-view hit+miss cloud cast from n to n unit sphere points.") + + arg3 = parser.add_argument_group("ray-scan modifiers").add_argument # brevity + arg3("--n-sphere-points", type=int, default=4000, + help="The number of unit-sphere points to sample rays from. Final result: n*(n-1).") + arg3("--compute-miss-distances", action="store_true", + help="Compute the distance to the nearest hit for each miss in the hit+miss clouds.") + arg3("--fill-missing-uv-points", action="store_true", + help="TODO") + arg3("--no-filter-backhits", action="store_true", + help="Do not filter scan hits on backside of mesh faces.") + arg3("--no-unit-sphere", action="store_true", + help="Do not center the objects to the unit sphere.") + arg3("--convert-ok", action="store_true", + help="Allow reusing point clouds for uv clouds and vice versa. (does not account for other hparams)") + arg3("--debug", action="store_true", + help="Abort on failiure.") + + arg5 = parser.add_argument_group("Shared modifiers").add_argument # brevity + arg5("--scan-resolution", type=int, default=400, + help="The resolution of the depth map rendered to sample points. Becomes x*x") + + return parser + +# entrypoint +def cli(parser: argparse.ArgumentParser = make_parser()): + args = parser.parse_args() + if not any(getattr(args, k) for k in dir(args) if k.startswith("precompute_")) and not (args.list or args.list_pages): + parser.error("no preprocessing target selected") # exits + + config.DATA_PATH = Path(args.dir) + obj_names = args.objects or read.list_object_names() + + if args.list: + print(*obj_names, sep="\n") + parser.exit() + + if args.list_pages is not None: + print(*( + f"--page {i} {args.list_pages} {obj_name}" + for obj_name in obj_names + for i in range(args.list_pages) + ), sep="\n") + parser.exit() + + if args.precompute_mesh_sv_scan_clouds: + read.precompute_mesh_scan_point_clouds( + obj_names, + compute_miss_distances = args.compute_miss_distances, + no_filter_backhits = args.no_filter_backhits, + no_unit_sphere = args.no_unit_sphere, + convert_ok = args.convert_ok, + page = args.page, + force = args.force, + debug = args.debug, + ) + if args.precompute_mesh_sv_scan_uvs: + read.precompute_mesh_scan_uvs( + obj_names, + compute_miss_distances = args.compute_miss_distances, + fill_missing_points = args.fill_missing_uv_points, + no_filter_backhits = args.no_filter_backhits, + no_unit_sphere = args.no_unit_sphere, + convert_ok = args.convert_ok, + page = args.page, + force = args.force, + debug = args.debug, + ) + if args.precompute_mesh_sphere_scan: + read.precompute_mesh_sphere_scan( + obj_names, + sphere_points = args.n_sphere_points, + compute_miss_distances = args.compute_miss_distances, + no_filter_backhits = args.no_filter_backhits, + no_unit_sphere = args.no_unit_sphere, + page = args.page, + force = args.force, + debug = args.debug, + ) + +if __name__ == "__main__": + cli() diff --git a/ifield/data/stanford/read.py b/ifield/data/stanford/read.py new file mode 100644 index 0000000..ae6c67b --- /dev/null +++ b/ifield/data/stanford/read.py @@ -0,0 +1,251 @@ +from . import config +from ..common import points +from ..common import processing +from ..common.scan import SingleViewScan, SingleViewUVScan +from ..common.types import MalformedMesh +from functools import lru_cache, wraps +from typing import Optional, Iterable +from pathlib import Path +import gzip +import numpy as np +import trimesh +import trimesh.transformations as T + +__doc__ = """ +Here are functions for reading and preprocessing shapenet benchmark data + +There are essentially a few sets per object: + "img" - meaning the RGBD images (none found in stanford) + "mesh_scans" - meaning synthetic scans of a mesh +""" + +MESH_TRANSFORM_SKYWARD = T.rotation_matrix(np.pi/2, (1, 0, 0)) +MESH_TRANSFORM_CANONICAL = { # to gain a shared canonical orientation + "armadillo" : T.rotation_matrix(np.pi, (0, 0, 1)) @ MESH_TRANSFORM_SKYWARD, + "asian_dragon" : T.rotation_matrix(-np.pi/2, (0, 0, 1)) @ MESH_TRANSFORM_SKYWARD, + "bunny" : MESH_TRANSFORM_SKYWARD, + "dragon" : MESH_TRANSFORM_SKYWARD, + "drill_bit" : MESH_TRANSFORM_SKYWARD, + "happy_buddha" : MESH_TRANSFORM_SKYWARD, + "lucy" : T.rotation_matrix(np.pi, (0, 0, 1)), + "thai_statue" : MESH_TRANSFORM_SKYWARD, +} + +def list_object_names() -> list[str]: + # downloaded only: + return [ + i for i, v in config.MODELS.items() + if (config.DATA_PATH / "meshes" / v.mesh_fname).is_file() + ] + +@lru_cache(maxsize=1) +def list_mesh_scan_sphere_coords(n_poses: int = 50) -> list[tuple[float, float]]: # (theta, phi) + return points.generate_equidistant_sphere_points(n_poses, compute_sphere_coordinates=True)#, shift_theta=True + +def mesh_scan_identifier(*, phi: float, theta: float) -> str: + return ( + f"{'np'[theta>=0]}{abs(theta):.2f}" + f"{'np'[phi >=0]}{abs(phi) :.2f}" + ).replace(".", "d") + +@lru_cache(maxsize=1) +def list_mesh_scan_identifiers(n_poses: int = 50) -> list[str]: + out = [ + mesh_scan_identifier(phi=phi, theta=theta) + for theta, phi in list_mesh_scan_sphere_coords(n_poses) + ] + assert len(out) == len(set(out)) + return out + +# === + +@lru_cache(maxsize=1) +def read_mesh(obj_name: str) -> trimesh.Trimesh: + path = config.DATA_PATH / "meshes" / config.MODELS[obj_name].mesh_fname + if not path.exists(): + raise FileNotFoundError(f"{obj_name = } -> {str(path) = }") + try: + if path.suffixes[-1] == ".gz": + with gzip.open(path, "r") as f: + mesh = trimesh.load(f, file_type="".join(path.suffixes[:-1])[1:]) + else: + mesh = trimesh.load(path) + except Exception as e: + raise MalformedMesh(f"Trimesh raised: {e.__class__.__name__}: {e}") from e + + # rotate to be upright in pyrender + mesh.apply_transform(MESH_TRANSFORM_CANONICAL.get(obj_name, MESH_TRANSFORM_SKYWARD)) + + return mesh + +# === single-view scan clouds + +def compute_mesh_scan_point_cloud( + obj_name : str, + *, + phi : float, + theta : float, + compute_miss_distances : bool = False, + compute_normals : bool = True, + convert_ok : bool = False, # this does not respect the other hparams + **kw, + ) -> SingleViewScan: + + if convert_ok: + try: + return read_mesh_scan_uv(obj_name, phi=phi, theta=theta).to_scan() + except FileNotFoundError: + pass + + mesh = read_mesh(obj_name) + return SingleViewScan.from_mesh_single_view(mesh, + phi = phi, + theta = theta, + compute_normals = compute_normals, + compute_miss_distances = compute_miss_distances, + **kw, + ) + +def precompute_mesh_scan_point_clouds(obj_names, *, page: tuple[int, int] = (0, 1), force: bool = False, debug: bool = False, n_poses: int = 50, **kw): + "precomputes all single-view scan clouds and stores them as HDF5 datasets" + cam_poses = list_mesh_scan_sphere_coords(n_poses) + pose_identifiers = list_mesh_scan_identifiers (n_poses) + assert len(cam_poses) == len(pose_identifiers) + paths = list_mesh_scan_point_cloud_h5_fnames(obj_names, pose_identifiers) + mlen = max(map(len, config.MODELS.keys())) + pretty_identifiers = [ + f"{obj_name.ljust(mlen)} @ {i:>5} @ ({itentifier}: {theta:.2f}, {phi:.2f})" + for obj_name in obj_names + for i, (itentifier, (theta, phi)) in enumerate(zip(pose_identifiers, cam_poses)) + ] + mesh_cache = [] + @wraps(compute_mesh_scan_point_cloud) + def computer(pretty_identifier: str) -> SingleViewScan: + obj_name, index, _ = map(str.strip, pretty_identifier.split("@")) + theta, phi = cam_poses[int(index)] + return compute_mesh_scan_point_cloud(obj_name, phi=phi, theta=theta, _mesh_cache=mesh_cache, **kw) + return processing.precompute_data(computer, pretty_identifiers, paths, page=page, force=force, debug=debug) + +def read_mesh_scan_point_cloud(obj_name, *, identifier: str = None, phi: float = None, theta: float = None) -> SingleViewScan: + if identifier is None: + if phi is None or theta is None: + raise ValueError("Provide either phi+theta or an identifier!") + identifier = mesh_scan_identifier(phi=phi, theta=theta) + file = config.DATA_PATH / "clouds" / obj_name / f"mesh_scan_{identifier}_clouds.h5" + if not file.exists(): raise FileNotFoundError(str(file)) + return SingleViewScan.from_h5_file(file) + +def list_mesh_scan_point_cloud_h5_fnames(obj_names: Iterable[str], identifiers: Optional[Iterable[str]] = None, **kw) -> list[Path]: + if identifiers is None: + identifiers = list_mesh_scan_identifiers(**kw) + return [ + config.DATA_PATH / "clouds" / obj_name / f"mesh_scan_{identifier}_clouds.h5" + for obj_name in obj_names + for identifier in identifiers + ] + +# === single-view UV scan clouds + +def compute_mesh_scan_uv( + obj_name : str, + *, + phi : float, + theta : float, + compute_miss_distances : bool = False, + fill_missing_points : bool = False, + compute_normals : bool = True, + convert_ok : bool = False, + **kw, + ) -> SingleViewUVScan: + + if convert_ok: + try: + return read_mesh_scan_point_cloud(obj_name, phi=phi, theta=theta).to_uv_scan() + except FileNotFoundError: + pass + + mesh = read_mesh(obj_name) + scan = SingleViewUVScan.from_mesh_single_view(mesh, + phi = phi, + theta = theta, + compute_normals = compute_normals, + **kw, + ) + if compute_miss_distances: + scan.compute_miss_distances() + if fill_missing_points: + scan.fill_missing_points() + + return scan + +def precompute_mesh_scan_uvs(obj_names, *, page: tuple[int, int] = (0, 1), force: bool = False, debug: bool = False, n_poses: int = 50, **kw): + "precomputes all single-view scan clouds and stores them as HDF5 datasets" + cam_poses = list_mesh_scan_sphere_coords(n_poses) + pose_identifiers = list_mesh_scan_identifiers (n_poses) + assert len(cam_poses) == len(pose_identifiers) + paths = list_mesh_scan_uv_h5_fnames(obj_names, pose_identifiers) + mlen = max(map(len, config.MODELS.keys())) + pretty_identifiers = [ + f"{obj_name.ljust(mlen)} @ {i:>5} @ ({itentifier}: {theta:.2f}, {phi:.2f})" + for obj_name in obj_names + for i, (itentifier, (theta, phi)) in enumerate(zip(pose_identifiers, cam_poses)) + ] + mesh_cache = [] + @wraps(compute_mesh_scan_uv) + def computer(pretty_identifier: str) -> SingleViewScan: + obj_name, index, _ = map(str.strip, pretty_identifier.split("@")) + theta, phi = cam_poses[int(index)] + return compute_mesh_scan_uv(obj_name, phi=phi, theta=theta, _mesh_cache=mesh_cache, **kw) + return processing.precompute_data(computer, pretty_identifiers, paths, page=page, force=force, debug=debug) + +def read_mesh_scan_uv(obj_name, *, identifier: str = None, phi: float = None, theta: float = None) -> SingleViewUVScan: + if identifier is None: + if phi is None or theta is None: + raise ValueError("Provide either phi+theta or an identifier!") + identifier = mesh_scan_identifier(phi=phi, theta=theta) + file = config.DATA_PATH / "clouds" / obj_name / f"mesh_scan_{identifier}_uv.h5" + if not file.exists(): raise FileNotFoundError(str(file)) + return SingleViewUVScan.from_h5_file(file) + +def list_mesh_scan_uv_h5_fnames(obj_names: Iterable[str], identifiers: Optional[Iterable[str]] = None, **kw) -> list[Path]: + if identifiers is None: + identifiers = list_mesh_scan_identifiers(**kw) + return [ + config.DATA_PATH / "clouds" / obj_name / f"mesh_scan_{identifier}_uv.h5" + for obj_name in obj_names + for identifier in identifiers + ] + +# === sphere-view (UV) scan clouds + +def compute_mesh_sphere_scan( + obj_name : str, + *, + compute_normals : bool = True, + **kw, + ) -> SingleViewUVScan: + mesh = read_mesh(obj_name) + scan = SingleViewUVScan.from_mesh_sphere_view(mesh, + compute_normals = compute_normals, + **kw, + ) + return scan + +def precompute_mesh_sphere_scan(obj_names, *, page: tuple[int, int] = (0, 1), force: bool = False, debug: bool = False, n_points: int = 4000, **kw): + "precomputes all single-view scan clouds and stores them as HDF5 datasets" + paths = list_mesh_sphere_scan_h5_fnames(obj_names) + @wraps(compute_mesh_sphere_scan) + def computer(obj_name: str) -> SingleViewScan: + return compute_mesh_sphere_scan(obj_name, **kw) + return processing.precompute_data(computer, obj_names, paths, page=page, force=force, debug=debug) + +def read_mesh_mesh_sphere_scan(obj_name) -> SingleViewUVScan: + file = config.DATA_PATH / "clouds" / obj_name / "mesh_sphere_scan.h5" + if not file.exists(): raise FileNotFoundError(str(file)) + return SingleViewUVScan.from_h5_file(file) + +def list_mesh_sphere_scan_h5_fnames(obj_names: Iterable[str]) -> list[Path]: + return [ + config.DATA_PATH / "clouds" / obj_name / "mesh_sphere_scan.h5" + for obj_name in obj_names + ] diff --git a/ifield/datasets/__init__.py b/ifield/datasets/__init__.py new file mode 100644 index 0000000..f9c3db4 --- /dev/null +++ b/ifield/datasets/__init__.py @@ -0,0 +1,3 @@ +__doc__ = """ +Submodules defining various `torch.utils.data.Dataset` +""" diff --git a/ifield/datasets/common.py b/ifield/datasets/common.py new file mode 100644 index 0000000..0e60162 --- /dev/null +++ b/ifield/datasets/common.py @@ -0,0 +1,196 @@ +from ..data.common.h5_dataclasses import H5Dataclass, PathLike +from torch.utils.data import Dataset, IterableDataset +from typing import Any, Iterable, Hashable, TypeVar, Iterator, Callable +from functools import partial, lru_cache +import inspect + + +T = TypeVar("T") +T_H5 = TypeVar("T_H5", bound=H5Dataclass) + + +class TransformableDatasetMixin: + def __init_subclass__(cls): + if getattr(cls, "_transformable_mixin_no_override_getitem", False): + pass + elif issubclass(cls, Dataset): + if cls.__getitem__ is not cls._transformable_mixin_getitem_wrapper: + cls._transformable_mixin_inner_getitem = cls.__getitem__ + cls.__getitem__ = cls._transformable_mixin_getitem_wrapper + elif issubclass(cls, IterableDataset): + if cls.__iter__ is not cls._transformable_mixin_iter_wrapper: + cls._transformable_mixin_inner_iter = cls.__iter__ + cls.__iter__ = cls._transformable_mixin_iter_wrapper + else: + raise TypeError(f"{cls.__name__!r} is neither a Dataset nor a IterableDataset!") + + def __init__(self, *a, **kw): + super().__init__(*a, **kw) + self._transforms = [] + + # works as a decorator + def map(self: T, func: callable = None, /, args=[], **kw) -> T: + def wrapper(func) -> T: + if args or kw: + func = partial(func, *args, **kw) + self._transforms.append(func) + return self + + if func is None: + return wrapper + else: + return wrapper(func) + + + def _transformable_mixin_getitem_wrapper(self, index: int): + if not self._transforms: + out = self._transformable_mixin_inner_getitem(index) # (TransformableDatasetMixin, no transforms) + else: + out = self._transformable_mixin_inner_getitem(index) # (TransformableDatasetMixin, has transforms) + for f in self._transforms: + out = f(out) # (TransformableDatasetMixin) + return out + + def _transformable_mixin_iter_wrapper(self): + if not self._transforms: + out = self._transformable_mixin_inner_iter() # (TransformableDatasetMixin, no transforms) + else: + out = self._transformable_mixin_inner_iter() # (TransformableDatasetMixin, has transforms) + for f in self._transforms: + out = map(f, out) # (TransformableDatasetMixin) + return out + + +class TransformedDataset(Dataset, TransformableDatasetMixin): + # used to wrap an another dataset + def __init__(self, dataset: Dataset, transforms: Iterable[callable]): + super().__init__() + self.dataset = dataset + for i in transforms: + self.map(i) + + def __len__(self): + return len(self.dataset) + + def __getitem__(self, index: int): + return self.dataset[index] # (TransformedDataset) + + +class TransformExtendedDataset(Dataset, TransformableDatasetMixin): + _transformable_mixin_no_override_getitem = True + def __init__(self, dataset: Dataset): + super().__init__() + self.dataset = dataset + + def __len__(self): + return len(self.dataset) * len(self._transforms) + + def __getitem__(self, index: int): + n = len(self._transforms) + assert n > 0, f"{len(self._transforms) = }" + + item = index // n + transform = self._transforms[index % n] + return transform(self.dataset[item]) + + +class CachedDataset(Dataset): + # used to wrap an another dataset + def __init__(self, dataset: Dataset, cache_size: int | None): + super().__init__() + self.dataset = dataset + if cache_size is not None and cache_size > 0: + self.cached_getter = lru_cache(cache_size, self.dataset.__getitem__) + else: + self.cached_getter = self.dataset.__getitem__ + + def __len__(self): + return len(self.dataset) + + def __getitem__(self, index: int): + return self.cached_getter(index) + + +class AutodecoderDataset(Dataset, TransformableDatasetMixin): + def __init__(self, + keys : Iterable[Hashable], + dataset : Dataset, + ): + super().__init__() + self.ad_mapping = list(keys) + self.dataset = dataset + if len(self.ad_mapping) != len(dataset): + raise ValueError(f"__len__ mismatch between keys and dataset: {len(self.ad_mapping)} != {len(dataset)}") + + def __len__(self) -> int: + return len(self.dataset) + + def __getitem__(self, index: int) -> tuple[Hashable, Any]: + return self.ad_mapping[index], self.dataset[index] # (AutodecoderDataset) + + def keys(self) -> list[Hashable]: + return self.ad_mapping + + def values(self) -> Iterator: + return iter(self.dataset) + + def items(self) -> Iterable[tuple[Hashable, Any]]: + return zip(self.ad_mapping, self.dataset) + + +class FunctionDataset(Dataset, TransformableDatasetMixin): + def __init__(self, + getter : Callable[[Hashable], T], + keys : list[Hashable], + cache_size : int | None = None, + ): + super().__init__() + if cache_size is not None and cache_size > 0: + getter = lru_cache(cache_size)(getter) + self.getter = getter + self.keys = keys + + def __len__(self) -> int: + return len(self.keys) + + def __getitem__(self, index: int) -> T: + return self.getter(self.keys[index]) + +class H5Dataset(FunctionDataset): + def __init__(self, + h5_dataclass_cls : type[T_H5], + fnames : list[PathLike], + **kw, + ): + super().__init__( + getter = h5_dataclass_cls.from_h5_file, + keys = fnames, + **kw, + ) + +class PaginatedH5Dataset(Dataset, TransformableDatasetMixin): + def __init__(self, + h5_dataclass_cls : type[T_H5], + fnames : list[PathLike], + n_pages : int = 10, + require_even_pages : bool = True, + ): + super().__init__() + self.h5_dataclass_cls = h5_dataclass_cls + self.fnames = fnames + self.n_pages = n_pages + self.require_even_pages = require_even_pages + + def __len__(self) -> int: + return len(self.fnames) * self.n_pages + + def __getitem__(self, index: int) -> T_H5: + item = index // self.n_pages + page = index % self.n_pages + + return self.h5_dataclass_cls.from_h5_file( # (PaginatedH5Dataset) + fname = self.fname[item], + page = page, + n_pages = self.n_pages, + require_even_pages = self.require_even_pages, + ) diff --git a/ifield/datasets/coseg.py b/ifield/datasets/coseg.py new file mode 100644 index 0000000..a5d76f9 --- /dev/null +++ b/ifield/datasets/coseg.py @@ -0,0 +1,40 @@ +from . import common +from ..data.coseg import config +from ..data.coseg import read +from ..data.common import scan +from typing import Iterable, Optional, Union +import os + + +class SingleViewUVScanDataset(common.H5Dataset): + def __init__(self, + object_sets : tuple[str], + identifiers : Optional[Iterable[str]] = None, + data_path : Union[str, os.PathLike, None] = None, + ): + if not object_sets: + raise ValueError("'object_sets' cannot be empty!") + if identifiers is None: + identifiers = read.list_mesh_scan_identifiers() + if data_path is not None: + config.DATA_PATH = data_path + models = read.list_model_ids(object_sets) + fnames = read.list_mesh_scan_uv_h5_fnames(models, identifiers) + super().__init__( + h5_dataclass_cls = scan.SingleViewUVScan, + fnames = fnames, + ) + +class AutodecoderSingleViewUVScanDataset(common.AutodecoderDataset): + def __init__(self, + object_sets : tuple[str], + identifiers : Optional[Iterable[str]] = None, + data_path : Union[str, os.PathLike, None] = None, + ): + if identifiers is None: + identifiers = read.list_mesh_scan_identifiers() + # here do this step first, such that all the duplicate strings reference the same object + super().__init__( + keys = [key for key in read.list_model_id_strings(object_sets) for _ in range(len(identifiers))], + dataset = SingleViewUVScanDataset(object_sets, identifiers, data_path=data_path), + ) diff --git a/ifield/datasets/stanford.py b/ifield/datasets/stanford.py new file mode 100644 index 0000000..2b6f92b --- /dev/null +++ b/ifield/datasets/stanford.py @@ -0,0 +1,64 @@ +from . import common +from ..data.stanford import config +from ..data.stanford import read +from ..data.common import scan +from typing import Iterable, Optional, Union +import os + + +class SingleViewUVScanDataset(common.H5Dataset): + def __init__(self, + obj_names : Iterable[str], + identifiers : Optional[Iterable[str]] = None, + data_path : Union[str, os.PathLike, None] = None, + ): + if not obj_names: + raise ValueError("'obj_names' cannot be empty!") + if identifiers is None: + identifiers = read.list_mesh_scan_identifiers() + if data_path is not None: + config.DATA_PATH = data_path + fnames = read.list_mesh_scan_uv_h5_fnames(obj_names, identifiers) + super().__init__( + h5_dataclass_cls = scan.SingleViewUVScan, + fnames = fnames, + ) + +class AutodecoderSingleViewUVScanDataset(common.AutodecoderDataset): + def __init__(self, + obj_names : Iterable[str], + identifiers : Optional[Iterable[str]] = None, + data_path : Union[str, os.PathLike, None] = None, + ): + if identifiers is None: + identifiers = read.list_mesh_scan_identifiers() + super().__init__( + keys = [obj_name for obj_name in obj_names for _ in range(len(identifiers))], + dataset = SingleViewUVScanDataset(obj_names, identifiers, data_path=data_path), + ) + + +class SphereScanDataset(common.H5Dataset): + def __init__(self, + obj_names : Iterable[str], + data_path : Union[str, os.PathLike, None] = None, + ): + if not obj_names: + raise ValueError("'obj_names' cannot be empty!") + if data_path is not None: + config.DATA_PATH = data_path + fnames = read.list_mesh_sphere_scan_h5_fnames(obj_names) + super().__init__( + h5_dataclass_cls = scan.SingleViewUVScan, + fnames = fnames, + ) + +class AutodecoderSphereScanDataset(common.AutodecoderDataset): + def __init__(self, + obj_names : Iterable[str], + data_path : Union[str, os.PathLike, None] = None, + ): + super().__init__( + keys = obj_names, + dataset = SphereScanDataset(obj_names, data_path=data_path), + ) diff --git a/ifield/logging.py b/ifield/logging.py new file mode 100644 index 0000000..fcf2c41 --- /dev/null +++ b/ifield/logging.py @@ -0,0 +1,258 @@ +from . import param +from dataclasses import dataclass +from pathlib import Path +from pytorch_lightning.utilities import rank_zero_only +from pytorch_lightning.utilities.exceptions import MisconfigurationException +from typing import Union, Literal, Optional, TypeVar +import concurrent.futures +import psutil +import pytorch_lightning as pl +import statistics +import threading +import time +import torch +import yaml + +# from https://github.com/yaml/pyyaml/issues/240#issuecomment-1018712495 +def str_presenter(dumper, data): + """configures yaml for dumping multiline strings + Ref: https://stackoverflow.com/questions/8640959/how-can-i-control-what-scalar-form-pyyaml-uses-for-my-data""" + if len(data.splitlines()) > 1: # check for multiline string + return dumper.represent_scalar('tag:yaml.org,2002:str', data, style='|') + return dumper.represent_scalar('tag:yaml.org,2002:str', data) +yaml.add_representer(str, str_presenter) + + +LoggerStr = Literal[ + #"csv", + "tensorboard", + #"mlflow", + #"comet", + #"neptune", + #"wandb", + None] +try: + Logger = TypeVar("L", bound=pl.loggers.Logger) +except AttributeError: + Logger = TypeVar("L", bound=pl.loggers.base.LightningLoggerBase) + +def make_logger( + experiment_name : str, + default_root_dir : Union[str, Path], # from pl.Trainer + save_dir : Union[str, Path], + type : LoggerStr = "tensorboard", + project : str = "ifield", + ) -> Optional[Logger]: + if type is None: + return None + elif type == "tensorboard": + return pl.loggers.TensorBoardLogger( + name = "tensorboard", + save_dir = Path(default_root_dir) / save_dir, + version = experiment_name, + log_graph = True, + ) + raise ValueError(f"make_logger({type=})") + +def make_jinja_template(*, save_dir: Union[None, str, Path], **kw) -> str: + return param.make_jinja_template(make_logger, + defaults = dict( + save_dir = save_dir, + ), + exclude_list = { + "experiment_name", + "default_root_dir", + }, + **({"name": "logging"} | kw), + ) + +def get_checkpoints(experiment_name, default_root_dir, save_dir, type, project) -> list[Path]: + if type is None: + return None + if type == "tensorboard": + folder = Path(default_root_dir) / save_dir / "tensorboard" / experiment_name + return folder.glob("*.ckpt") + if type == "mlflow": + raise NotImplementedError(f"{type=}") + if type == "wandb": + raise NotImplementedError(f"{type=}") + raise ValueError(f"get_checkpoint({type=})") + + +def log_config(_logger: Logger, **kwargs: Union[str, dict, list, int, float]): + assert isinstance(_logger, pl.loggers.Logger) \ + or isinstance(_logger, pl.loggers.base.LightningLoggerBase), _logger + + _logger: pl.loggers.TensorBoardLogger + _logger.log_hyperparams(params=kwargs) + +@dataclass +class ModelOutputMonitor(pl.callbacks.Callback): + log_training : bool = True + log_validation : bool = True + + def setup(self, trainer: pl.Trainer, pl_module: pl.LightningModule, stage: Optional[str] = None) -> None: + if not trainer.loggers: + raise MisconfigurationException(f"Cannot use {self._class__.__name__} callback with Trainer that has no logger.") + + @staticmethod + def _log_outputs(trainer: pl.Trainer, pl_module: pl.LightningModule, outputs, fname: str): + if outputs is None: + return + elif isinstance(outputs, list) or isinstance(outputs, tuple): + outputs = { + f"loss[{i}]": v + for i, v in enumerate(outputs) + } + elif isinstance(outputs, torch.Tensor): + outputs = { + "loss": outputs, + } + elif isinstance(outputs, dict): + pass + else: + raise ValueError + sep = trainer.logger.group_separator + pl_module.log_dict({ + f"{pl_module.__class__.__qualname__}.{fname}{sep}{k}": + float(v.item()) if isinstance(v, torch.Tensor) else float(v) + for k, v in outputs.items() + }, sync_dist=True) + + def on_train_batch_end(self, trainer: pl.Trainer, pl_module: pl.LightningModule, outputs, batch, batch_idx, unused=0): + if self.log_training: + self._log_outputs(trainer, pl_module, outputs, "training_step") + + def on_validation_batch_end(self, trainer: pl.Trainer, pl_module: pl.LightningModule, outputs, batch, batch_idx, dataloader_idx=0): + if self.log_validation: + self._log_outputs(trainer, pl_module, outputs, "validation_step") + +class EpochTimeMonitor(pl.callbacks.Callback): + __slots__ = [ + "epoch_start", + "epoch_start_train", + "epoch_start_validation", + "epoch_start_test", + "epoch_start_predict", + ] + + def setup(self, trainer: pl.Trainer, pl_module: pl.LightningModule, stage: Optional[str] = None) -> None: + if not trainer.loggers: + raise MisconfigurationException(f"Cannot use {self._class__.__name__} callback with Trainer that has no logger.") + + + @rank_zero_only + def on_train_epoch_start(self, trainer: pl.Trainer, pl_module: pl.LightningModule): + self.epoch_start_train = time.time() + + @rank_zero_only + def on_validation_epoch_start(self, trainer: pl.Trainer, pl_module: pl.LightningModule): + self.epoch_start_validation = time.time() + + @rank_zero_only + def on_test_epoch_start(self, trainer: pl.Trainer, pl_module: pl.LightningModule): + self.epoch_start_test = time.time() + + @rank_zero_only + def on_predict_epoch_start(self, trainer: pl.Trainer, pl_module: pl.LightningModule): + self.epoch_start_predict = time.time() + + @rank_zero_only + def on_train_epoch_end(self, trainer: pl.Trainer, pl_module: pl.LightningModule): + t = time.time() - self.epoch_start_train + del self.epoch_start_train + sep = trainer.logger.group_separator + trainer.logger.log_metrics({f"{self.__class__.__qualname__}{sep}epoch_train_time" : t}, step=trainer.global_step) + + @rank_zero_only + def on_validation_epoch_end(self, trainer: pl.Trainer, pl_module: pl.LightningModule): + t = time.time() - self.epoch_start_validation + del self.epoch_start_validation + sep = trainer.logger.group_separator + trainer.logger.log_metrics({f"{self.__class__.__qualname__}{sep}epoch_validation_time" : t}, step=trainer.global_step) + + @rank_zero_only + def on_test_epoch_end(self, trainer: pl.Trainer, pl_module: pl.LightningModule): + t = time.time() - self.epoch_start_test + del self.epoch_start_validation + sep = trainer.logger.group_separator + trainer.logger.log_metrics({f"{self.__class__.__qualname__}{sep}epoch_test_time" : t}, step=trainer.global_step) + + @rank_zero_only + def on_predict_epoch_end(self, trainer: pl.Trainer, pl_module: pl.LightningModule): + t = time.time() - self.epoch_start_predict + del self.epoch_start_validation + sep = trainer.logger.group_separator + trainer.logger.log_metrics({f"{self.__class__.__qualname__}{sep}epoch_predict_time" : t}, step=trainer.global_step) + +@dataclass +class PsutilMonitor(pl.callbacks.Callback): + sample_rate : float = 0.2 # times per second + + _should_stop = False + + @rank_zero_only + def on_fit_start(self, trainer: pl.Trainer, pl_module: pl.LightningModule): + if not trainer.loggers: + raise MisconfigurationException(f"Cannot use {self._class__.__name__} callback with Trainer that has no logger.") + assert not hasattr(self, "_thread") + + self._should_stop = False + self._thread = threading.Thread( + target = self.thread_target, + name = self.thread_target.__qualname__, + args = [trainer], + daemon=True, + ) + self._thread.start() + + @rank_zero_only + def on_fit_end(self, trainer: pl.Trainer, pl_module: pl.LightningModule): + assert getattr(self, "_thread", None) is not None + self._should_stop = True + del self._thread + + def thread_target(self, trainer: pl.Trainer): + uses_gpu = isinstance(trainer.accelerator, (pl.accelerators.GPUAccelerator, pl.accelerators.CUDAAccelerator)) + gpu_ids = trainer.device_ids + + prefix = f"{self.__class__.__qualname__}{trainer.logger.group_separator}" + + while not self._should_stop: + step = trainer.global_step + p = psutil.Process() + + meminfo = p.memory_info() + rss_ram = meminfo.rss / 1024**2 # MB + vms_ram = meminfo.vms / 1024**2 # MB + + util_per_cpu = psutil.cpu_percent(percpu=True) + + util_per_cpu = [util_per_cpu[i] for i in p.cpu_affinity()] + util_total = statistics.mean(util_per_cpu) + + if uses_gpu: + with concurrent.futures.ThreadPoolExecutor() as e: + if hasattr(pl.accelerators, "cuda"): + gpu_stats = e.map(pl.accelerators.cuda.get_nvidia_gpu_stats, gpu_ids) + else: + gpu_stats = e.map(pl.accelerators.gpu.get_nvidia_gpu_stats, gpu_ids) + trainer.logger.log_metrics({ + f"{prefix}ram.rss" : rss_ram, + f"{prefix}ram.vms" : vms_ram, + f"{prefix}cpu.total" : util_total, + **{ f"{prefix}cpu.{i:03}.utilization" : stat for i, stat in enumerate(util_per_cpu) }, + **{ + f"{prefix}gpu.{gpu_idx:02}.{key.split(' ',1)[0]}" : stat + for gpu_idx, stats in zip(gpu_ids, gpu_stats) + for key, stat in stats.items() + }, + }, step = step) + else: + trainer.logger.log_metrics({ + f"{prefix}cpu.total" : util_total, + **{ f"{prefix}cpu.{i:03}.utilization" : stat for i, stat in enumerate(util_per_cpu) }, + }, step = step) + + time.sleep(1 / self.sample_rate) + print("DAEMON END") diff --git a/ifield/models/__init__.py b/ifield/models/__init__.py new file mode 100644 index 0000000..3771d5e --- /dev/null +++ b/ifield/models/__init__.py @@ -0,0 +1,3 @@ +__doc__ = """ +Contains Pytorch Models +""" diff --git a/ifield/models/conditioning.py b/ifield/models/conditioning.py new file mode 100644 index 0000000..d8821f2 --- /dev/null +++ b/ifield/models/conditioning.py @@ -0,0 +1,159 @@ +from abc import ABC, abstractmethod +from torch import nn, Tensor +from torch.nn.modules.module import _EXTRA_STATE_KEY_SUFFIX +from typing import Hashable, Union, Optional, KeysView, ValuesView, ItemsView, Any, Sequence +import torch + + +class RequiresConditioner(nn.Module, ABC): # mixin + + @property + @abstractmethod + def n_latent_features(self) -> int: + "This should provide the width of the conditioning feature vector" + ... + + @property + @abstractmethod + def latent_embeddings_init_std(self) -> float: + "This should provide the standard deviation to initialize the latent features with. DeepSDF uses 0.01." + ... + + @property + @abstractmethod + def latent_embeddings() -> Optional[Tensor]: + """This property should return a tensor cotnaining all stored embeddings, for use in computing auto-decoder losses""" + ... + + @abstractmethod + def encode(self, batch: Any, batch_idx: int, optimizer_idx: int) -> Tensor: + "This should, given a training batch, return the encoded conditioning vector" + ... + + +class AutoDecoderModuleMixin(RequiresConditioner, ABC): + """ + Populates dunder methods making it behave as a mapping. + The mapping indexes into a stored set of learnable embedding vectors. + + Based on the auto-decoder architecture of + J.J. Park, P. Florence, J. Straub, R. Newcombe, S. Lovegrove, DeepSDF: + Learning Continuous Signed Distance Functions for Shape Representation, in: + 2019 IEEE/CVF Conference on Computer Vision and Pattern Recognition (CVPR), + IEEE, Long Beach, CA, USA, 2019: pp. 165–174. + https://doi.org/10.1109/CVPR.2019.00025. + """ + + _autodecoder_mapping: dict[Hashable, int] + autodecoder_embeddings: nn.Parameter + + def __init__(self, *a, **kw): + super().__init__(*a, **kw) + + @self._register_load_state_dict_pre_hook + def hook(state_dict, prefix, local_metadata, strict, missing_keys, unexpected_keys, error_msgs): + if f"{prefix}_autodecoder_mapping" in state_dict: + state_dict[f"{prefix}{_EXTRA_STATE_KEY_SUFFIX}"] = state_dict.pop(f"{prefix}_autodecoder_mapping") + + class ICanBeLoadedFromCheckpointsAndChangeShapeStopBotheringMePyTorchAndSitInTheCornerIKnowWhatIAmDoing(nn.UninitializedParameter): + def copy_(self, other): + self.materialize(other.shape, other.device, other.dtype) + return self.copy_(other) + self.autodecoder_embeddings = ICanBeLoadedFromCheckpointsAndChangeShapeStopBotheringMePyTorchAndSitInTheCornerIKnowWhatIAmDoing() + + # nn.Module interface + + def get_extra_state(self): + return { + "ad_uids": getattr(self, "_autodecoder_mapping", {}), + } + + def set_extra_state(self, obj): + if "ad_uids" not in obj: # backward compat + self._autodecoder_mapping = obj + else: + self._autodecoder_mapping = obj["ad_uids"] + + # RequiresConditioner interface + + @property + def latent_embeddings(self) -> Tensor: + return self.autodecoder_embeddings + + # my interface + + def set_observation_ids(self, z_uids: set[Hashable]): + assert self.latent_embeddings_init_std is not None, f"{self.__module__}.{self.__class__.__qualname__}.latent_embeddings_init_std" + assert self.n_latent_features is not None, f"{self.__module__}.{self.__class__.__qualname__}.n_latent_features" + assert self.latent_embeddings_init_std > 0, self.latent_embeddings_init_std + assert self.n_latent_features > 0, self.n_latent_features + + self._autodecoder_mapping = { + k: i + for i, k in enumerate(sorted(set(z_uids))) + } + + if not len(z_uids) == len(self._autodecoder_mapping): + raise ValueError(f"Observation identifiers are not unique! {z_uids = }") + + self.autodecoder_embeddings = nn.Parameter( + torch.Tensor(len(self._autodecoder_mapping), self.n_latent_features) + .normal_(mean=0, std=self.latent_embeddings_init_std) + .to(self.device, self.dtype) + ) + + def add_key(self, z_uid: Hashable, z: Optional[Tensor] = None): + if z_uid in self._autodecoder_mapping: + raise ValueError(f"Observation identifier {z_uid!r} not unique!") + + self._autodecoder_mapping[z_uid] = len(self._autodecoder_mapping) + self.autodecoder_embeddings + raise NotImplementedError + + def __delitem__(self, z_uid: Hashable): + i = self._autodecoder_mapping.pop(z_uid) + for k, v in list(self._autodecoder_mapping.items()): + if v > i: + self._autodecoder_mapping[k] -= 1 + + with torch.no_grad(): + self.autodecoder_embeddings = nn.Parameter(torch.cat(( + self.autodecoder_embeddings.detach()[:i, :], + self.autodecoder_embeddings.detach()[i+1:, :], + ), dim=0)) + + def __contains__(self, z_uid: Hashable) -> bool: + return z_uid in self._autodecoder_mapping + + def __getitem__(self, z_uids: Union[Hashable, Sequence[Hashable]]) -> Tensor: + if isinstance(z_uids, tuple) or isinstance(z_uids, list): + key = tuple(map(self._autodecoder_mapping.__getitem__, z_uids)) + else: + key = self._autodecoder_mapping[z_uids] + return self.autodecoder_embeddings[key, :] + + def __iter__(self): + return self._autodecoder_mapping.keys() + + def keys(self) -> KeysView[Hashable]: + """ + lists the identifiers of each code + """ + return self._autodecoder_mapping.keys() + + def values(self) -> ValuesView[Tensor]: + return list(self.autodecoder_embeddings) + + def items(self) -> ItemsView[Hashable, Tensor]: + """ + lists all the learned codes / latent vectors with their identifiers as keys + """ + return { + k : self.autodecoder_embeddings[i] + for k, i in self._autodecoder_mapping.items() + }.items() + +class EncoderModuleMixin(RequiresConditioner, ABC): + @property + def latent_embeddings(self) -> None: + return None diff --git a/ifield/models/intersection_fields.py b/ifield/models/intersection_fields.py new file mode 100644 index 0000000..06a086d --- /dev/null +++ b/ifield/models/intersection_fields.py @@ -0,0 +1,589 @@ +from .. import param +from ..modules.dtype import DtypeMixin +from ..utils import geometry +from ..utils.helpers import compose +from ..utils.loss import Schedulable, ensure_schedulables, HParamSchedule, HParamScheduleBase, Linear +from ..utils.operators import diff +from .conditioning import RequiresConditioner, AutoDecoderModuleMixin +from .medial_atoms import MedialAtomNet +from .orthogonal_plane import OrthogonalPlaneNet +from pytorch_lightning.utilities.exceptions import MisconfigurationException +from torch import Tensor +from torch.nn import functional as F +from typing import TypedDict, Literal, Union, Hashable, Optional +import pytorch_lightning as pl +import torch +import os + +LOG_ALL_METRICS = bool(int(os.environ.get("IFIELD_LOG_ALL_METRICS", "1"))) + +if __debug__: + def broadcast_tensors(*tensors: torch.Tensor) -> list[torch.Tensor]: + try: + return torch.broadcast_tensors(*tensors) + except RuntimeError as e: + shapes = ", ".join(f"{chr(c)}.size={tuple(t.shape)}" for c, t in enumerate(tensors, ord("a"))) + raise ValueError(f"Could not broadcast tensors {shapes}.\n{str(e)}") +else: + broadcast_tensors = torch.broadcast_tensors + + +class ForwardDepthMapsBatch(TypedDict): + cam2world : Tensor # (B, 4, 4) + uv : Tensor # (B, H, W) + intrinsics : Tensor # (B, 3, 3) + +class ForwardScanRaysBatch(TypedDict): + origins : Tensor # (B, H, W, 3) or (B, 3) + dirs : Tensor # (B, H, W, 3) + +class LossBatch(TypedDict): + hits : Tensor # (B, H, W) dtype=bool + miss : Tensor # (B, H, W) dtype=bool + depths : Tensor # (B, H, W) + normals : Tensor # (B, H, W, 3) NaN if not hit + distances : Tensor # (B, H, W, 1) NaN if not miss + +class LabeledBatch(TypedDict): + z_uid : list[Hashable] + +ForwardBatch = Union[ForwardDepthMapsBatch, ForwardScanRaysBatch] +TrainingBatch = Union[ForwardBatch, LossBatch, LabeledBatch] + + +IntersectionMode = Literal[ + "medial_sphere", + "orthogonal_plane", +] + +class IntersectionFieldModel(pl.LightningModule, RequiresConditioner, DtypeMixin): + net: Union[MedialAtomNet, OrthogonalPlaneNet] + + @ensure_schedulables + def __init__(self, + # mode + input_mode : geometry.RayEmbedding = "plucker", + output_mode : IntersectionMode = "medial_sphere", + + # network + latent_features : int = 256, + hidden_features : int = 512, + hidden_layers : int = 8, + improve_miss_grads: bool = True, + normalize_ray_dirs: bool = False, # the dataset is usually already normalized, but this could still be important for backprop + + # orthogonal plane + loss_hit_cross_entropy : Schedulable = 1.0, + + # medial atoms + loss_intersection : Schedulable = 1, + loss_intersection_l2 : Schedulable = 0, + loss_intersection_proj : Schedulable = 0, + loss_intersection_proj_l2 : Schedulable = 0, + loss_normal_cossim : Schedulable = 0.25, # supervise target normal cosine similarity + loss_normal_euclid : Schedulable = 0, # supervise target normal l2 distance + loss_normal_cossim_proj : Schedulable = 0, # supervise target normal cosine similarity + loss_normal_euclid_proj : Schedulable = 0, # supervise target normal l2 distance + loss_hit_nodistance_l1 : Schedulable = 0, # constrain no miss distance for hits + loss_hit_nodistance_l2 : Schedulable = 32, # constrain no miss distance for hits + loss_miss_distance_l1 : Schedulable = 0, # supervise target miss distance for misses + loss_miss_distance_l2 : Schedulable = 0, # supervise target miss distance for misses + loss_inscription_hits : Schedulable = 0, # Penalize atom candidates using the supervision data of a different ray + loss_inscription_hits_l2: Schedulable = 0, # Penalize atom candidates using the supervision data of a different ray + loss_inscription_miss : Schedulable = 0, # Penalize atom candidates using the supervision data of a different ray + loss_inscription_miss_l2: Schedulable = 0, # Penalize atom candidates using the supervision data of a different ray + loss_sphere_grow_reg : Schedulable = 0, # maximialize sphere size + loss_sphere_grow_reg_hit: Schedulable = 0, # maximialize sphere size + loss_embedding_norm : Schedulable = "0.01**2 * Linear(15)", # DeepSDF schedules over 150 epochs. DeepSDF use 0.01**2, irobot uses 0.04**2 + loss_multi_view_reg : Schedulable = 0, # minimize gradient w.r.t. delta ray dir, when ray origin = intersection + loss_atom_centroid_norm_std_reg : Schedulable = 0, # minimize per-atom centroid std + + # optimization + opt_learning_rate : Schedulable = 1e-5, + opt_weight_decay : float = 0, + opt_warmup : float = 0, + **kw, + ): + super().__init__() + opt_warmup = Linear(opt_warmup) + opt_warmup._param_name = "opt_warmup" + self.save_hyperparameters() + + + if "half" in input_mode: + assert output_mode == "medial_sphere" and kw.get("n_atoms", 1) > 1 + + assert output_mode in ["medial_sphere", "orthogonal_plane"] + assert opt_weight_decay >= 0, opt_weight_decay + + if output_mode == "orthogonal_plane": + self.net = OrthogonalPlaneNet( + in_features = self.n_input_embedding_features, + hidden_layers = hidden_layers, + hidden_features = hidden_features, + latent_features = latent_features, + **kw, + ) + elif output_mode == "medial_sphere": + self.net = MedialAtomNet( + in_features = self.n_input_embedding_features, + hidden_layers = hidden_layers, + hidden_features = hidden_features, + latent_features = latent_features, + **kw, + ) + + def on_fit_start(self): + if __debug__: + for k, v in self.hparams.items(): + if isinstance(v, HParamScheduleBase): + v.assert_positive(self.trainer.max_epochs) + + @property + def n_input_embedding_features(self) -> int: + return geometry.ray_input_embedding_length(self.hparams.input_mode) + + @property + def n_latent_features(self) -> int: + return self.hparams.latent_features + + @property + def latent_embeddings_init_std(self) -> float: + return 0.01 + + @property + def is_conditioned(self): + return self.net.is_conditioned + + @property + def is_double_backprop(self) -> bool: + return self.is_double_backprop_origins or self.is_double_backprop_dirs + + @property + def is_double_backprop_origins(self) -> bool: + prif = self.hparams.output_mode == "orthogonal_plane" + return prif and self.hparams.loss_normal_cossim + + @property + def is_double_backprop_dirs(self) -> bool: + return self.hparams.loss_multi_view_reg + + @classmethod + @compose("\n".join) + def make_jinja_template(cls, *, exclude_list: set[str] = {}, top_level: bool = True, **kw) -> str: + yield param.make_jinja_template(cls, top_level=top_level, **kw) + yield MedialAtomNet.make_jinja_template(top_level=False, exclude_list={ + "in_features", + "hidden_layers", + "hidden_features", + "latent_features", + }) + + def batch2rays(self, batch: ForwardBatch) -> tuple[Tensor, Tensor]: + if "uv" in batch: + raise NotImplementedError + assert not (self.hparams.loss_multi_view_reg and self.training) + ray_origins, \ + ray_dirs, \ + = geometry.camera_uv_to_rays( + cam2world = batch["cam2world"], + uv = batch["uv"], + intrinsics = batch["intrinsics"], + ) + else: + ray_origins = batch["points" if self.hparams.loss_multi_view_reg and self.training else "origins"] + ray_dirs = batch["dirs"] + return ray_origins, ray_dirs + + def forward(self, + batch : ForwardBatch, + z : Optional[Tensor] = None, # latent code + *, + return_input : bool = False, + allow_nans : bool = False, # in output + **kw, + ) -> tuple[torch.Tensor, ...]: + ( + ray_origins, # (B, 3) + ray_dirs, # (B, H, W, 3) + ) = self.batch2rays(batch) + + # Ensure rays are normalized + # NOTICE: this is slow, make sure to train with optimizations! + assert ray_dirs.detach().norm(dim=-1).allclose(torch.ones(ray_dirs.shape[:-1], **self.device_and_dtype)),\ + ray_dirs.detach().norm(dim=-1) + + if ray_origins.ndim + 2 == ray_dirs.ndim: + ray_origins = ray_origins[..., None, None, :] + + ray_origins, ray_dirs = broadcast_tensors(ray_origins, ray_dirs) + + if self.is_double_backprop and self.training: + if self.is_double_backprop_dirs: + ray_dirs.requires_grad = True + if self.is_double_backprop_origins: + ray_origins.requires_grad = True + assert ray_origins.requires_grad or ray_dirs.requires_grad + + input = geometry.ray_input_embedding( + ray_origins, ray_dirs, + mode = self.hparams.input_mode, + normalize_dirs = self.hparams.normalize_ray_dirs, + is_training = self.training, + ) + assert not input.detach().isnan().any() + + predictions = self.net(input, z) + + intersections = self.net.compute_intersections( + ray_origins, ray_dirs, predictions, + allow_nans = allow_nans and not self.training, **kw + ) + if return_input: + return ray_origins, ray_dirs, input, intersections + else: + return intersections + + def training_step(self, batch: TrainingBatch, batch_idx: int, *, is_validation=False) -> Tensor: + z = self.encode(batch) if self.is_conditioned else None + assert self.is_conditioned or len(set(batch["z_uid"])) <= 1, \ + f"Network is unconditioned, but the batch has multiple uids: {set(batch['z_uid'])!r}" + + # unpack + target_hits = batch["hits"] # (B, H, W) dtype=bool + target_miss = batch["miss"] # (B, H, W) dtype=bool + target_points = batch["points"] # (B, H, W, 3) + target_normals = batch["normals"] # (B, H, W, 3) NaN if not hit + target_distances = batch["distances"] # (B, H, W) NaN if not miss + assert not target_normals [target_hits].isnan().any() + assert not target_distances[target_miss].isnan().any() + target_normals[target_normals.isnan()] = 0 + assert not target_normals .isnan().any() + + # make z fit batch scheme + if z is not None: + z = z[..., None, None, :] + + losses = {} + metrics = {} + zeros = torch.zeros_like(target_distances) + + if self.hparams.output_mode == "medial_sphere": + assert isinstance(self.net, MedialAtomNet) + ray_origins, ray_dirs, plucker, ( + depths, # (...) float, projection if not hit + silhouettes, # (...) float + intersections, # (..., 3) float, projection or NaN if not hit + intersection_normals, # (..., 3) float, rejection or NaN if not hit + is_intersecting, # (...) bool, true if hit + sphere_centers, # (..., 3) network output + sphere_radii, # (...) network output + + atom_indices, + all_intersections, # (..., N_ATOMS) float, projection or NaN if not hit + all_intersection_normals, # (..., N_ATOMS, 3) float, rejection or NaN if not hit + all_depths, # (..., N_ATOMS) float, projection if not hit + all_silhouettes, # (..., N_ATOMS, 3) float, projection or NaN if not hit + all_is_intersecting, # (..., N_ATOMS) bool, true if hit + all_sphere_centers, # (..., N_ATOMS, 3) network output + all_sphere_radii, # (..., N_ATOMS) network output + ) = self(batch, z, + intersections_only = False, + return_all_atoms = True, + allow_nans = False, + return_input = True, + improve_miss_grads = True, + ) + + # target hit supervision + if (__debug__ or LOG_ALL_METRICS) or self.hparams.loss_intersection: # scores true hits + losses["loss_intersection"] = ( + (target_points - intersections).norm(dim=-1) + ).where(target_hits & is_intersecting, zeros).mean() + if (__debug__ or LOG_ALL_METRICS) or self.hparams.loss_intersection_l2: # scores true hits + losses["loss_intersection_l2"] = ( + (target_points - intersections).pow(2).sum(dim=-1) + ).where(target_hits & is_intersecting, zeros).mean() + if (__debug__ or LOG_ALL_METRICS) or self.hparams.loss_intersection_proj: # scores misses as if they were hits, using the projection + losses["loss_intersection_proj"] = ( + (target_points - intersections).norm(dim=-1) + ).where(target_hits, zeros).mean() + if (__debug__ or LOG_ALL_METRICS) or self.hparams.loss_intersection_proj_l2: # scores misses as if they were hits, using the projection + losses["loss_intersection_proj_l2"] = ( + (target_points - intersections).pow(2).sum(dim=-1) + ).where(target_hits, zeros).mean() + + # target hit normal supervision + if (__debug__ or LOG_ALL_METRICS) or self.hparams.loss_normal_cossim: # scores true hits + losses["loss_normal_cossim"] = ( + 1 - torch.cosine_similarity(target_normals, intersection_normals, dim=-1) + ).where(target_hits & is_intersecting, zeros).mean() + if (__debug__ or LOG_ALL_METRICS) or self.hparams.loss_normal_euclid: # scores true hits + losses["loss_normal_euclid"] = ( + (target_normals - intersection_normals).norm(dim=-1) + ).where(target_hits & is_intersecting, zeros).mean() + if (__debug__ or LOG_ALL_METRICS) or self.hparams.loss_normal_cossim_proj: # scores misses as if they were hits + losses["loss_normal_cossim_proj"] = ( + 1 - torch.cosine_similarity(target_normals, intersection_normals, dim=-1) + ).where(target_hits, zeros).mean() + if (__debug__ or LOG_ALL_METRICS) or self.hparams.loss_normal_euclid_proj: # scores misses as if they were hits + losses["loss_normal_euclid_proj"] = ( + (target_normals - intersection_normals).norm(dim=-1) + ).where(target_hits, zeros).mean() + + # target sufficient hit radius + if (__debug__ or LOG_ALL_METRICS) or self.hparams.loss_hit_nodistance_l1: # ensures hits become hits, instead of relying on the projection being right + losses["loss_hit_nodistance_l1"] = ( + silhouettes + ).where(target_hits & (silhouettes > 0), zeros).mean() + if (__debug__ or LOG_ALL_METRICS) or self.hparams.loss_hit_nodistance_l2: # ensures hits become hits, instead of relying on the projection being right + losses["loss_hit_nodistance_l2"] = ( + silhouettes + ).where(target_hits & (silhouettes > 0), zeros).pow(2).mean() + + # target miss supervision + if (__debug__ or LOG_ALL_METRICS) or self.hparams.loss_miss_distance_l1: # only positive misses reinforcement + losses["loss_miss_distance_l1"] = ( + target_distances - silhouettes + ).where(target_miss, zeros).abs().mean() + if (__debug__ or LOG_ALL_METRICS) or self.hparams.loss_miss_distance_l2: # only positive misses reinforcement + losses["loss_miss_distance_l2"] = ( + target_distances - silhouettes + ).where(target_miss, zeros).pow(2).mean() + + # incentivise maximal spheres + if (__debug__ or LOG_ALL_METRICS) or self.hparams.loss_sphere_grow_reg: # all atoms + losses["loss_sphere_grow_reg"] = ((all_sphere_radii.detach() + 1) - all_sphere_radii).abs().mean() + if (__debug__ or LOG_ALL_METRICS) or self.hparams.loss_sphere_grow_reg_hit: # true hits only + losses["loss_sphere_grow_reg_hit"] = ((sphere_radii.detach() + 1) - sphere_radii).where(target_hits & is_intersecting, zeros).abs().mean() + + # spherical latent prior + if (__debug__ or LOG_ALL_METRICS) or self.hparams.loss_embedding_norm: + losses["loss_embedding_norm"] = self.latent_embeddings.norm(dim=-1).mean() + + + is_grad_enabled = torch.is_grad_enabled() + + # multi-view regularization: atom should not change when view changes + if self.hparams.loss_multi_view_reg and is_grad_enabled: + assert ray_dirs.requires_grad, ray_dirs + assert plucker.requires_grad, plucker + assert intersections.grad_fn is not None + assert intersection_normals.grad_fn is not None + + *center_grads, radii_grads = diff.gradients( + sphere_centers[..., 0], + sphere_centers[..., 1], + sphere_centers[..., 2], + sphere_radii, + wrt=ray_dirs, + ) + + losses["loss_multi_view_reg"] = ( + sum( + i.pow(2).sum(dim=-1) + for i in center_grads + ).where(target_hits & is_intersecting, zeros).mean() + + + radii_grads.pow(2).sum(dim=-1) + .where(target_hits & is_intersecting, zeros).mean() + ) + + # minimize the volume spanned by each atom + if self.hparams.loss_atom_centroid_norm_std_reg and self.net.n_atoms > 1: + assert len(all_sphere_centers.shape) == 5, all_sphere_centers.shape + losses["loss_atom_centroid_norm_std_reg"] \ + = (( + all_sphere_centers + - all_sphere_centers + .mean(dim=(1, 2), keepdim=True) + ).pow(2).sum(dim=-1) - 0.05**2).clamp(0, None).mean() + + # prif is l1, LSMAT is l2 + if (__debug__ or LOG_ALL_METRICS) or self.hparams.loss_inscription_hits or self.hparams.loss_inscription_miss or self.hparams.loss_inscription_hits_l2 or self.hparams.loss_inscription_miss_l2: + b = target_hits.shape[0] # number of objects + n = target_hits.shape[1:].numel() # rays per object + perm = torch.randperm(n, device=self.device) # ray2ray permutation + flatten = dict(start_dim=1, end_dim=len(target_hits.shape) - 1) + + ( + inscr_sphere_center_projs, # (b, n, n_atoms, 3) + inscr_intersections_near, # (b, n, n_atoms, 3) + inscr_intersections_far, # (b, n, n_atoms, 3) + inscr_is_intersecting, # (b, n, n_atoms) dtype=bool + ) = geometry.ray_sphere_intersect( + ray_origins.flatten(**flatten)[:, perm, None, :], + ray_dirs .flatten(**flatten)[:, perm, None, :], + all_sphere_centers.flatten(**flatten), + all_sphere_radii .flatten(**flatten), + return_parts = True, + allow_nans = False, + improve_miss_grads = self.hparams.improve_miss_grads, + ) + assert inscr_sphere_center_projs.shape == (b, n, self.net.n_atoms, 3), \ + (inscr_sphere_center_projs.shape, (b, n, self.net.n_atoms, 3)) + inscr_silhouettes = ( + inscr_sphere_center_projs - all_sphere_centers.flatten(**flatten) + ).norm(dim=-1) - all_sphere_radii.flatten(**flatten) + + loss_inscription_hits = ( + ( + (inscr_intersections_near - target_points.flatten(**flatten)[:, perm, None, :]) + * ray_dirs.flatten(**flatten)[:, perm, None, :] + ).sum(dim=-1) + ).where(target_hits.flatten(**flatten)[:, perm, None] & inscr_is_intersecting, + torch.zeros(inscr_intersections_near.shape[:-1], **self.device_and_dtype), + ).clamp(None, 0) + loss_inscription_miss = ( + inscr_silhouettes - target_distances.flatten(**flatten)[:, perm, None] + ).where(target_miss.flatten(**flatten)[:, perm, None], + torch.zeros_like(inscr_silhouettes) + ).clamp(None, 0) + + if (__debug__ or LOG_ALL_METRICS) or self.hparams.loss_inscription_hits: + losses["loss_inscription_hits"] = loss_inscription_hits.neg().mean() + if (__debug__ or LOG_ALL_METRICS) or self.hparams.loss_inscription_miss: + losses["loss_inscription_miss"] = loss_inscription_miss.neg().mean() + if (__debug__ or LOG_ALL_METRICS) or self.hparams.loss_inscription_hits_l2: + losses["loss_inscription_hits_l2"] = loss_inscription_hits.pow(2).mean() + if (__debug__ or LOG_ALL_METRICS) or self.hparams.loss_inscription_miss_l2: + losses["loss_inscription_miss_l2"] = loss_inscription_miss.pow(2).mean() + + # metrics + metrics["iou"] = ( + ((~target_miss) & is_intersecting.detach()).sum() / + ((~target_miss) | is_intersecting.detach()).sum() + ) + metrics["radii"] = sphere_radii.detach().mean() # with the constant applied pressure, we need to measure it this way instead + + elif self.hparams.output_mode == "orthogonal_plane": + assert isinstance(self.net, OrthogonalPlaneNet) + ray_origins, ray_dirs, input_embedding, ( + intersections, # (..., 3) dtype=float + is_intersecting, # (...) dtype=float + ) = self(batch, z, return_input=True, normalize_origins=True) + + if (__debug__ or LOG_ALL_METRICS) or self.hparams.loss_intersection: + losses["loss_intersection"] = ( + (intersections - target_points).norm(dim=-1) + ).where(target_hits, zeros).mean() + if (__debug__ or LOG_ALL_METRICS) or self.hparams.loss_intersection_l2: + losses["loss_intersection_l2"] = ( + (intersections - target_points).pow(2).sum(dim=-1) + ).where(target_hits, zeros).mean() + + if (__debug__ or LOG_ALL_METRICS) or self.hparams.loss_hit_cross_entropy: + losses["loss_hit_cross_entropy"] = ( + F.binary_cross_entropy_with_logits(is_intersecting, (~target_miss).to(self.dtype)) + ).mean() + + if self.hparams.loss_normal_cossim and torch.is_grad_enabled(): + jac = diff.jacobian(intersections, ray_origins) + intersection_normals = self.compute_normals_from_intersection_origin_jacobian(jac, ray_dirs) + losses["loss_normal_cossim"] = ( + 1 - torch.cosine_similarity(target_normals, intersection_normals, dim=-1) + ).where(target_hits, zeros).mean() + + if self.hparams.loss_normal_euclid and torch.is_grad_enabled(): + jac = diff.jacobian(intersections, ray_origins) + intersection_normals = self.compute_normals_from_intersection_origin_jacobian(jac, ray_dirs) + losses["loss_normal_euclid"] = ( + (target_normals - intersection_normals).norm(dim=-1) + ).where(target_hits, zeros).mean() + + if self.hparams.loss_multi_view_reg and torch.is_grad_enabled(): + assert ray_dirs .requires_grad, ray_dirs + assert intersections.grad_fn is not None + grads = diff.gradients( + intersections[..., 0], + intersections[..., 1], + intersections[..., 2], + wrt=ray_dirs, + ) + losses["loss_multi_view_reg"] = sum( + i.pow(2).sum(dim=-1) + for i in grads + ).where(target_hits, zeros).mean() + + metrics["iou"] = ( + ((~target_miss) & (is_intersecting>0.5).detach()).sum() / + ((~target_miss) | (is_intersecting>0.5).detach()).sum() + ) + else: + raise NotImplementedError(self.hparams.output_mode) + + # output losses and metrics + + # apply scaling: + losses_unscaled = losses.copy() # shallow copy + for k in list(losses.keys()): + assert losses[k].numel() == 1, f"losses[{k!r}] shape: {losses[k].shape}" + val_schedule: HParamSchedule = self.hparams[k] + val = val_schedule.get(self) + if val == 0: + if (__debug__ or LOG_ALL_METRICS) and val_schedule.is_const: + del losses[k] # it was only added for unscaled logging, do not backprop + else: + losses[k] = 0 + elif val != 1: + losses[k] = losses[k] * val + + if not losses: + raise MisconfigurationException("no loss was computed") + + losses["loss"] = sum(losses.values()) * self.hparams.opt_warmup.get(self) + losses.update({f"unscaled_{k}": v.detach() for k, v in losses_unscaled.items()}) + losses.update({f"metric_{k}": v.detach() for k, v in metrics.items()}) + return losses + + + # used by pl.callbacks.EarlyStopping, via cli.py + @property + def metric_early_stop(self): return ( + "unscaled_loss_intersection_proj" + if self.hparams.output_mode == "medial_sphere" else + "unscaled_loss_intersection" + ) + + def validation_step(self, batch: TrainingBatch, batch_idx: int) -> dict[str, Tensor]: + losses = self.training_step(batch, batch_idx, is_validation=True) + return losses + + def configure_optimizers(self): + adam = torch.optim.Adam(self.parameters(), + lr=1 if not self.hparams.opt_learning_rate.is_const else self.hparams.opt_learning_rate.get_train_value(0), + weight_decay=self.hparams.opt_weight_decay) + schedules = [] + if not self.hparams.opt_learning_rate.is_const: + schedules = [ + torch.optim.lr_scheduler.LambdaLR(adam, + lambda epoch: self.hparams.opt_learning_rate.get_train_value(epoch), + ), + ] + return [adam], schedules + + @property + def example_input_array(self) -> tuple[dict[str, Tensor], Tensor]: + return ( + { # see self.batch2rays + "origins" : torch.zeros(1, 3), # most commonly used + "points" : torch.zeros(1, 3), # used if self.training and self.hparams.loss_multi_view_reg + "dirs" : torch.ones(1, 3) * torch.rsqrt(torch.tensor(3)), + }, + torch.ones(1, self.hparams.latent_features), + ) + + @staticmethod + def compute_normals_from_intersection_origin_jacobian(origin_jac: Tensor, ray_dirs: Tensor) -> Tensor: + normals = sum(( + torch.cross(origin_jac[..., 0], origin_jac[..., 1], dim=-1) * -ray_dirs[..., [2]], + torch.cross(origin_jac[..., 1], origin_jac[..., 2], dim=-1) * -ray_dirs[..., [0]], + torch.cross(origin_jac[..., 2], origin_jac[..., 0], dim=-1) * -ray_dirs[..., [1]], + )) + return normals / normals.norm(dim=-1, keepdim=True) + + +class IntersectionFieldAutoDecoderModel(IntersectionFieldModel, AutoDecoderModuleMixin): + def encode(self, batch: LabeledBatch) -> Tensor: + assert not isinstance(self.trainer.strategy, pl.strategies.DataParallelStrategy) + return self[batch["z_uid"]] # [N, Z_n] diff --git a/ifield/models/medial_atoms.py b/ifield/models/medial_atoms.py new file mode 100644 index 0000000..3f9b8dc --- /dev/null +++ b/ifield/models/medial_atoms.py @@ -0,0 +1,186 @@ +from .. import param +from ..modules import fc +from ..data.common import points +from ..utils import geometry +from ..utils.helpers import compose +from textwrap import indent, dedent +from torch import nn, Tensor +from typing import Optional +import torch +import warnings + +# generalize this into a HypoHyperConcat net? ConditionedNet? +class MedialAtomNet(nn.Module): + def __init__(self, + in_features : int, + latent_features : int, + hidden_features : int, + hidden_layers : int, + n_atoms : int = 1, + final_init_wrr : tuple[float, float] | None = (0.05, 0.6, 0.1), + **kw, + ): + super().__init__() + assert n_atoms >= 1, n_atoms + self.n_atoms = n_atoms + + self.fc = fc.FCBlock( + in_features = in_features, + hidden_layers = hidden_layers, + hidden_features = hidden_features, + out_features = n_atoms * 4, # n_atoms * (x, y, z, r) + outermost_linear = True, + latent_features = latent_features, + **kw, + ) + + if final_init_wrr is not None: + with torch.no_grad(): + w, r1, r2 = final_init_wrr + if w != 1: self.fc[-1].linear.weight *= w + dtype = self.fc[-1].linear.bias.dtype + self.fc[-1].linear.bias[..., [4*n+i for n in range(n_atoms) for i in range(3)]] = torch.tensor(points.generate_random_sphere_points(n_atoms, radius=r1), dtype=dtype).flatten() + self.fc[-1].linear.bias[..., 3::4] = r2 + + @property + def is_conditioned(self): + return self.fc.is_conditioned + + @classmethod + @compose("\n".join) + def make_jinja_template(cls, *, exclude_list: set[str] = {}, top_level: bool = True, **kw) -> str: + yield param.make_jinja_template(cls, top_level=top_level, exclude_list=exclude_list, **kw) + yield fc.FCBlock.make_jinja_template(top_level=False, exclude_list={ + "in_features", + "hidden_layers", + "hidden_features", + "out_features", + "outermost_linear", + "latent_features", + }) + + def forward(self, x: Tensor, z: Optional[Tensor] = None): + if __debug__ and self.is_conditioned and z is None: + warnings.warn(f"{self.__class__.__qualname__} is conditioned, but the forward pass was not supplied with a conditioning tensor.") + return self.fc(x, z) + + def compute_intersections(self, + ray_origins : Tensor, # (..., 3) + ray_dirs : Tensor, # (..., 3) + medial_atoms : Tensor, # (..., 4*self.n_atoms) + *, + intersections_only : bool = True, + return_all_atoms : bool = False, # only applies if intersections_only=False + allow_nans : bool = True, + improve_miss_grads : bool = False, + ) -> tuple[(Tensor,)*5]: + assert ray_origins.shape[:-1] == ray_dirs.shape[:-1] == medial_atoms.shape[:-1], \ + (ray_origins.shape, ray_dirs.shape, medial_atoms.shape) + assert medial_atoms.shape[-1] % 4 == 0, \ + medial_atoms.shape + assert ray_origins.shape[-1] == ray_dirs.shape[-1] == 3, \ + (ray_origins.shape, ray_dirs.shape) + + #n_atoms = medial_atoms.shape[-1] // 4 + n_atoms = medial_atoms.shape[-1] >> 2 + + # reshape (..., n_atoms * d) to (..., n_atoms, d) + medial_atoms = medial_atoms.view(*medial_atoms.shape[:-1], n_atoms, 4) + ray_origins = ray_origins.unsqueeze(-2).broadcast_to([*ray_origins.shape[:-1], n_atoms, 3]) + ray_dirs = ray_dirs .unsqueeze(-2).broadcast_to([*ray_dirs .shape[:-1], n_atoms, 3]) + + # unpack atoms + sphere_centers = medial_atoms[..., :3] + sphere_radii = medial_atoms[..., 3].abs() + + assert not ray_origins .detach().isnan().any() + assert not ray_dirs .detach().isnan().any() + assert not sphere_centers.detach().isnan().any() + assert not sphere_radii .detach().isnan().any() + + # compute intersections + ( + sphere_center_projs, # (..., 3) + intersections_near, # (..., 3) + intersections_far, # (..., 3) + is_intersecting, # (...) bool + ) = geometry.ray_sphere_intersect( + ray_origins, + ray_dirs, + sphere_centers, + sphere_radii, + return_parts = True, + allow_nans = allow_nans, + improve_miss_grads = improve_miss_grads, + ) + + # early return + if intersections_only and n_atoms == 1: + return intersections_near.squeeze(-2), is_intersecting.squeeze(-1) + + # compute how close each hit and miss are + depths = ((intersections_near - ray_origins) * ray_dirs).sum(-1) + silhouettes = torch.linalg.norm(sphere_center_projs - sphere_centers, dim=-1) - sphere_radii + + if return_all_atoms: + intersections_near_all = intersections_near + depths_all = depths + silhouettes_all = silhouettes + is_intersecting_all = is_intersecting + sphere_centers_all = sphere_centers + sphere_radii_all = sphere_radii + + # collapse n_atoms + if n_atoms > 1: + atom_indices = torch.where(is_intersecting.any(dim=-1, keepdim=True), + torch.where(is_intersecting, depths.detach(), depths.detach()+100).argmin(dim=-1, keepdim=True), + silhouettes.detach().argmin(dim=-1, keepdim=True), + ) + + intersections_near = intersections_near.take_along_dim(atom_indices[..., None], -2).squeeze(-2) + depths = depths .take_along_dim(atom_indices, -1).squeeze(-1) + silhouettes = silhouettes .take_along_dim(atom_indices, -1).squeeze(-1) + is_intersecting = is_intersecting .take_along_dim(atom_indices, -1).squeeze(-1) + sphere_centers = sphere_centers .take_along_dim(atom_indices[..., None], -2).squeeze(-2) + sphere_radii = sphere_radii .take_along_dim(atom_indices, -1).squeeze(-1) + else: + atom_indices = None + intersections_near = intersections_near.squeeze(-2) + depths = depths .squeeze(-1) + silhouettes = silhouettes .squeeze(-1) + is_intersecting = is_intersecting .squeeze(-1) + sphere_centers = sphere_centers .squeeze(-2) + sphere_radii = sphere_radii .squeeze(-1) + + # early return + if intersections_only: + return intersections_near, is_intersecting + + # compute sphere normals + intersection_normals = intersections_near - sphere_centers + intersection_normals = intersection_normals / (intersection_normals.norm(dim=-1)[..., None] + 1e-9) + + if return_all_atoms: + intersection_normals_all = intersections_near_all - sphere_centers_all + intersection_normals_all = intersection_normals_all / (intersection_normals_all.norm(dim=-1)[..., None] + 1e-9) + + + return ( + depths, # (...) valid if hit, based on 'intersections' + silhouettes, # (...) always valid + intersections_near, # (..., 3) valid if hit, projection if not + intersection_normals, # (..., 3) valid if hit, rejection if not + is_intersecting, # (...) dtype=bool + sphere_centers, # (..., 3) network output + sphere_radii, # (...) network output + *(() if not return_all_atoms else ( + + atom_indices, + intersections_near_all, # (..., N_ATOMS) valid if hit, based on 'intersections' + intersection_normals_all, # (..., N_ATOMS, 3) valid if hit, rejection if not + depths_all, # (..., N_ATOMS) always valid + silhouettes_all, # (..., N_ATOMS, 3) valid if hit, projection if not + is_intersecting_all, # (..., N_ATOMS) dtype=bool + sphere_centers_all, # (..., N_ATOMS, 3) network output + sphere_radii_all, # (..., N_ATOMS) network output + ))) diff --git a/ifield/models/orthogonal_plane.py b/ifield/models/orthogonal_plane.py new file mode 100644 index 0000000..4c2b885 --- /dev/null +++ b/ifield/models/orthogonal_plane.py @@ -0,0 +1,101 @@ +from .. import param +from ..modules import fc +from ..utils import geometry +from ..utils.helpers import compose +from textwrap import indent, dedent +from torch import nn, Tensor +from typing import Optional +import warnings + +class OrthogonalPlaneNet(nn.Module): + """ + + """ + + def __init__(self, + in_features : int, + latent_features : int, + hidden_features : int, + hidden_layers : int, + **kw, + ): + super().__init__() + + self.fc = fc.FCBlock( + in_features = in_features, + hidden_layers = hidden_layers, + hidden_features = hidden_features, + out_features = 2, # (plane_offset, is_intersecting) + outermost_linear = True, + latent_features = latent_features, + **kw, + ) + + @property + def is_conditioned(self): + return self.fc.is_conditioned + + @classmethod + @compose("\n".join) + def make_jinja_template(cls, *, exclude_list: set[str] = {}, top_level: bool = True, **kw) -> str: + yield param.make_jinja_template(cls, top_level=top_level, exclude_list=exclude_list, **kw) + yield param.make_jinja_template(fc.FCBlock, top_level=False, exclude_list={ + "in_features", + "hidden_layers", + "hidden_features", + "out_features", + "outermost_linear", + }) + + def forward(self, x: Tensor, z: Optional[Tensor] = None) -> Tensor: + if __debug__ and self.is_conditioned and z is None: + warnings.warn(f"{self.__class__.__qualname__} is conditioned, but the forward pass was not supplied with a conditioning tensor.") + return self.fc(x, z) + + @staticmethod + def compute_intersections( + ray_origins : Tensor, # (..., 3) + ray_dirs : Tensor, # (..., 3) + predictions : Tensor, # (..., 2) + *, + normalize_origins = True, + return_signed_displacements = False, + allow_nans = False, # MARF compat + atom_random_prob = None, # MARF compat + atom_dropout_prob = None, # MARF compat + ) -> tuple[(Tensor,)*5]: + assert ray_origins.shape[:-1] == ray_dirs.shape[:-1] == predictions.shape[:-1], \ + (ray_origins.shape, ray_dirs.shape, predictions.shape) + assert predictions.shape[-1] == 2, \ + predictions.shape + + assert not allow_nans + + if normalize_origins: + ray_origins = geometry.project_point_on_ray(0, ray_origins, ray_dirs) + + # unpack predictions + signed_displacements = predictions[..., 0] + is_intersecting = predictions[..., 1] + + # compute intersections + intersections = ray_origins - signed_displacements[..., None] * ray_dirs + + return ( + intersections, + is_intersecting, + *((signed_displacements,) if return_signed_displacements else ()), + ) + + + + +OrthogonalPlaneNet.__doc__ = __doc__ = f""" +{dedent(OrthogonalPlaneNet.__doc__).strip()} + +# Config template: + +```yaml +{OrthogonalPlaneNet.make_jinja_template()} +``` +""" diff --git a/ifield/modules/__init__.py b/ifield/modules/__init__.py new file mode 100644 index 0000000..0ee5155 --- /dev/null +++ b/ifield/modules/__init__.py @@ -0,0 +1,3 @@ +__doc__ = """ +Contains Pytorch Modules +""" diff --git a/ifield/modules/dtype.py b/ifield/modules/dtype.py new file mode 100644 index 0000000..b109149 --- /dev/null +++ b/ifield/modules/dtype.py @@ -0,0 +1,22 @@ +import pytorch_lightning as pl + + +class DtypeMixin: + def __init_subclass__(cls): + assert issubclass(cls, pl.LightningModule), \ + f"{cls.__name__!r} is not a subclass of 'pytorch_lightning.LightningModule'!" + + @property + def device_and_dtype(self) -> dict: + """ + Examples: + ``` + torch.tensor(1337, **self.device_and_dtype) + some_tensor.to(**self.device_and_dtype) + ``` + """ + + return { + "dtype": self.dtype, + "device": self.device, + } diff --git a/ifield/modules/fc.py b/ifield/modules/fc.py new file mode 100644 index 0000000..73345aa --- /dev/null +++ b/ifield/modules/fc.py @@ -0,0 +1,424 @@ +from . import siren +from .. import param +from ..utils.helpers import compose, run_length_encode, MetaModuleProxy +from collections import OrderedDict +from pytorch_lightning.core.mixins import HyperparametersMixin +from torch import nn, Tensor +from torch.nn.utils.weight_norm import WeightNorm +from torchmeta.modules import MetaModule, MetaSequential +from typing import Iterable, Literal, Optional, Union, Callable +import itertools +import math +import torch + +__doc__ = """ +`fc` is short for "Fully Connected" +""" + +def broadcast_tensors_except(*tensors: Tensor, dim: int) -> list[Tensor]: + if dim == -1: + shapes = [ i.shape[:dim] for i in tensors ] + else: + shapes = [ (*i.shape[:dim], i.shape[dim+1:]) for i in tensors ] + target_shape = list(torch.broadcast_shapes(*shapes)) + if dim == -1: + target_shape.append(-1) + elif dim < 0: + target_shape.insert(dim+1, -1) + else: + target_shape.insert(dim, -1) + + return [ i.broadcast_to(target_shape) for i in tensors ] + + +EPS = 1e-8 + +Nonlinearity = Literal[ + None, + "relu", + "leaky_relu", + "silu", + "softplus", + "elu", + "selu", + "sine", + "sigmoid", + "tanh", +] + +Normalization = Literal[ + None, + "batchnorm", + "batchnorm_na", + "layernorm", + "layernorm_na", + "weightnorm", +] + +class ReprHyperparametersMixin(HyperparametersMixin): + def extra_repr(self): + this = ", ".join(f"{k}={v!r}" for k, v in self.hparams.items()) + rest = super().extra_repr() + if rest: + return f"{this}, {rest}" + else: + return this + +class MultilineReprHyperparametersMixin(HyperparametersMixin): + def extra_repr(self): + items = [f"{k}={v!r}" for k, v in self.hparams.items()] + this = "\n".join( + ", ".join(filter(bool, i)) + "," + for i in itertools.zip_longest(items[0::3], items[1::3], items[2::3]) + ) + rest = super().extra_repr() + if rest: + return f"{this}, {rest}" + else: + return this + + +class BatchLinear(nn.Linear): + """ + A linear (meta-)layer that can deal with batched weight matrices and biases, + as for instance output by a hypernetwork. + """ + __doc__ = nn.Linear.__doc__ + _meta_forward_pre_hooks = None + + def register_forward_pre_hook(self, hook: Callable) -> torch.utils.hooks.RemovableHandle: + if not isinstance(hook, WeightNorm): + return super().register_forward_pre_hook(hook) + + if self._meta_forward_pre_hooks is None: + self._meta_forward_pre_hooks = OrderedDict() + + handle = torch.utils.hooks.RemovableHandle(self._meta_forward_pre_hooks) + self._meta_forward_pre_hooks[handle.id] = hook + return handle + + def forward(self, input: Tensor, params: Optional[dict[str, Tensor]]=None): + if params is None or not isinstance(self, MetaModule): + params = OrderedDict(self.named_parameters()) + if self._meta_forward_pre_hooks is not None: + proxy = MetaModuleProxy(self, params) + for hook in self._meta_forward_pre_hooks.values(): + hook(proxy, [input]) + + weight = params["weight"] + bias = params.get("bias", None) + + # transpose weights + weight = weight.permute(*range(len(weight.shape) - 2), -1, -2) # does not jit + + output = input.unsqueeze(-2).matmul(weight).squeeze(-2) + + if bias is not None: + output = output + bias + + return output + + +class MetaBatchLinear(BatchLinear, MetaModule): + pass + + +class CallbackConcatLayer(nn.Module): + "A tricky way to enable skip connections in sequentials models" + def __init__(self, tensor_getter: Callable[[], tuple[Tensor, ...]]): + super().__init__() + self.tensor_getter = tensor_getter + + def forward(self, x): + ys = self.tensor_getter() + return torch.cat(broadcast_tensors_except(x, *ys, dim=-1), dim=-1) + + +class ResidualSkipConnectionEndLayer(nn.Module): + """ + Residual skip connections that can be added to a nn.Sequential + """ + + class ResidualSkipConnectionStartLayer(nn.Module): + def __init__(self): + super().__init__() + self._stored_tensor = None + + def forward(self, x): + assert self._stored_tensor is None + self._stored_tensor = x + return x + + def get(self): + assert self._stored_tensor is not None + x = self._stored_tensor + self._stored_tensor = None + return x + + def __init__(self): + super().__init__() + self._stored_tensor = None + self._start = self.ResidualSkipConnectionStartLayer() + + def forward(self, x): + skip = self._start.get() + return x + skip + + @property + def start(self) -> ResidualSkipConnectionStartLayer: + return self._start + + @property + def end(self) -> "ResidualSkipConnectionEndLayer": + return self + + +ResidualMode = Literal[ + None, + "identity", +] + +class FCLayer(MultilineReprHyperparametersMixin, MetaSequential): + """ + A single fully connected (FC) layer + """ + + def __init__(self, + in_features : int, + out_features : int, + *, + nonlinearity : Nonlinearity = "relu", + normalization : Normalization = None, + is_first : bool = False, # used for SIREN initialization + is_final : bool = False, # used for fan_out init + dropout_prob : float = 0.0, + negative_slope : float = 0.01, # only for nonlinearity="leaky_relu", default is normally 0.01 + omega_0 : float = 30, # only for nonlinearity="sine" + residual_mode : ResidualMode = None, + _no_meta : bool = False, # set to true in hypernetworks + **_ + ): + super().__init__() + self.save_hyperparameters() + + # improve repr + if nonlinearity != "leaky_relu": + self.hparams.pop("negative_slope") + if nonlinearity != "sine": + self.hparams.pop("omega_0") + + Linear = nn.Linear if _no_meta else MetaBatchLinear + + def make_layer() -> Iterable[nn.Module]: + # residual start + if residual_mode is not None: + residual_layer = ResidualSkipConnectionEndLayer() + yield "res_a", residual_layer.start + + linear = Linear(in_features, out_features) + + # initialize + if nonlinearity in {"relu", "leaky_relu", "silu", "softplus"}: + nn.init.kaiming_uniform_(linear.weight, a=negative_slope, nonlinearity=nonlinearity, mode="fan_in" if not is_final else "fan_out") + elif nonlinearity == "elu": + nn.init.normal_(linear.weight, std=math.sqrt(1.5505188080679277) / math.sqrt(linear.weight.size(-1))) + elif nonlinearity == "selu": + nn.init.normal_(linear.weight, std=1 / math.sqrt(linear.weight.size(-1))) + elif nonlinearity == "sine": + siren.init_weights_(linear, omega_0, is_first) + elif nonlinearity in {"sigmoid", "tanh"}: + nn.init.xavier_normal_(linear.weight) + elif nonlinearity is None: + pass # this is effectively uniform(-1/sqrt(in_features), 1/sqrt(in_features)) + else: + raise NotImplementedError(nonlinearity) + + # linear + normalize + if normalization is None: + yield "linear", linear + elif normalization == "batchnorm": + yield "linear", linear + yield "norm", nn.BatchNorm1d(out_features, affine=True) + elif normalization == "batchnorm_na": + yield "linear", linear + yield "norm", nn.BatchNorm1d(out_features, affine=False) + elif normalization == "layernorm": + yield "linear", linear + yield "norm", nn.LayerNorm([out_features], elementwise_affine=True) + elif normalization == "layernorm_na": + yield "linear", linear + yield "norm", nn.LayerNorm([out_features], elementwise_affine=False) + elif normalization == "weightnorm": + yield "linear", nn.utils.weight_norm(linear) + else: + raise NotImplementedError(normalization) + + # activation + inplace = False + if nonlinearity is None : pass + elif nonlinearity == "relu" : yield nonlinearity, nn.ReLU(inplace=inplace) + elif nonlinearity == "leaky_relu" : yield nonlinearity, nn.LeakyReLU(negative_slope=negative_slope, inplace=inplace) + elif nonlinearity == "silu" : yield nonlinearity, nn.SiLU(inplace=inplace) + elif nonlinearity == "softplus" : yield nonlinearity, nn.Softplus() + elif nonlinearity == "elu" : yield nonlinearity, nn.ELU(inplace=inplace) + elif nonlinearity == "selu" : yield nonlinearity, nn.SELU(inplace=inplace) + elif nonlinearity == "sine" : yield nonlinearity, siren.Sine(omega_0) + elif nonlinearity == "sigmoid" : yield nonlinearity, nn.Sigmoid() + elif nonlinearity == "tanh" : yield nonlinearity, nn.Tanh() + else : raise NotImplementedError(f"{nonlinearity=}") + + # dropout + if dropout_prob > 0: + if nonlinearity == "selu": + yield "adropout", nn.AlphaDropout(p=dropout_prob) + else: + yield "dropout", nn.Dropout(p=dropout_prob) + + # residual end + if residual_mode is not None: + yield "res_b", residual_layer.end + + for name, module in make_layer(): + self.add_module(name.replace("-", "_"), module) + + @property + def nonlinearity(self) -> Optional[nn.Module]: + "alias to the activation function submodule" + if self.hparams.nonlinearity is None: + return None + return getattr(self, self.hparams.nonlinearity.replace("-", "_")) + + def initialize_weights(): + raise NotImplementedError + + +class FCBlock(MultilineReprHyperparametersMixin, MetaSequential): + """ + A block of FC layers + """ + def __init__(self, + in_features : int, + hidden_features : int, + hidden_layers : int, + out_features : int, + normalization : Normalization = None, + nonlinearity : Nonlinearity = "relu", + dropout_prob : float = 0.0, + outermost_linear : bool = True, # whether last linear is nonlinear + latent_features : Optional[int] = None, + concat_skipped_layers : Union[list[int], bool] = [], + concat_conditioned_layers : Union[list[int], bool] = [], + **kw, + ): + super().__init__() + self.save_hyperparameters() + + if isinstance(concat_skipped_layers, bool): + concat_skipped_layers = list(range(hidden_layers+2)) if concat_skipped_layers else [] + if isinstance(concat_conditioned_layers, bool): + concat_conditioned_layers = list(range(hidden_layers+2)) if concat_conditioned_layers else [] + if len(concat_conditioned_layers) != 0 and latent_features is None: + raise ValueError("Layers marked to be conditioned without known number of latent features") + concat_skipped_layers = [i if i >= 0 else hidden_layers+2-abs(i) for i in concat_skipped_layers] + concat_conditioned_layers = [i if i >= 0 else hidden_layers+2-abs(i) for i in concat_conditioned_layers] + self._concat_x_layers: frozenset[int] = frozenset(concat_skipped_layers) + self._concat_z_layers: frozenset[int] = frozenset(concat_conditioned_layers) + if len(self._concat_x_layers) != len(concat_skipped_layers): + raise ValueError(f"Duplicates found in {concat_skipped_layers = }") + if len(self._concat_z_layers) != len(concat_conditioned_layers): + raise ValueError(f"Duplicates found in {concat_conditioned_layers = }") + if not all(isinstance(i, int) for i in self._concat_x_layers): + raise TypeError(f"Expected only integers in {concat_skipped_layers = }") + if not all(isinstance(i, int) for i in self._concat_z_layers): + raise TypeError(f"Expected only integers in {concat_conditioned_layers = }") + + def make_layers() -> Iterable[nn.Module]: + def make_concat_layer(*idxs: int) -> int: + x_condition_this_layer = any(idx in self._concat_x_layers for idx in idxs) + z_condition_this_layer = any(idx in self._concat_z_layers for idx in idxs) + if x_condition_this_layer and z_condition_this_layer: + yield CallbackConcatLayer(lambda: (self._current_x, self._current_z)) + elif x_condition_this_layer: + yield CallbackConcatLayer(lambda: (self._current_x,)) + elif z_condition_this_layer: + yield CallbackConcatLayer(lambda: (self._current_z,)) + + return in_features*x_condition_this_layer + (latent_features or 0)*z_condition_this_layer + + added = yield from make_concat_layer(0) + + yield FCLayer( + in_features = in_features + added, + out_features = hidden_features, + nonlinearity = nonlinearity, + normalization = normalization, + dropout_prob = dropout_prob, + is_first = True, + is_final = False, + **kw, + ) + + for i in range(hidden_layers): + added = yield from make_concat_layer(i+1) + + yield FCLayer( + in_features = hidden_features + added, + out_features = hidden_features, + nonlinearity = nonlinearity, + normalization = normalization, + dropout_prob = dropout_prob, + is_first = False, + is_final = False, + **kw, + ) + + added = yield from make_concat_layer(hidden_layers+1) + + nl = nonlinearity + + yield FCLayer( + in_features = hidden_features + added, + out_features = out_features, + nonlinearity = None if outermost_linear else nl, + normalization = None if outermost_linear else normalization, + dropout_prob = 0.0 if outermost_linear else dropout_prob, + is_first = False, + is_final = True, + **kw, + ) + + for i, module in enumerate(make_layers()): + self.add_module(str(i), module) + + @property + def is_conditioned(self) -> bool: + "Whether z is used or not" + return bool(self._concat_z_layers) + + @classmethod + @compose("\n".join) + def make_jinja_template(cls, *, exclude_list: set[str] = {}, top_level: bool = True, **kw) -> str: + @compose(" ".join) + def as_jexpr(values: Union[list[int]]): + yield "{{" + for val, count in run_length_encode(values): + yield f"[{val!r}]*{count!r}" + yield "}}" + yield param.make_jinja_template(cls, top_level=top_level, exclude_list=exclude_list) + yield param.make_jinja_template(FCLayer, top_level=False, exclude_list=exclude_list | { + "in_features", + "out_features", + "nonlinearity", + "normalization", + "dropout_prob", + "is_first", + "is_final", + }) + + def forward(self, input: Tensor, z: Optional[Tensor] = None, *, params: Optional[dict[str, Tensor]]=None): + assert not self.is_conditioned or z is not None + if z is not None and z.ndim < input.ndim: + z = z[(*(None,)*(input.ndim - z.ndim), ...)] + self._current_x = input + self._current_z = z + return super().forward(input, params=params) diff --git a/ifield/modules/siren.py b/ifield/modules/siren.py new file mode 100644 index 0000000..0df337e --- /dev/null +++ b/ifield/modules/siren.py @@ -0,0 +1,25 @@ +from math import sqrt +from torch import nn +import torch + +class Sine(nn.Module): + def __init__(self, omega_0: float): + super().__init__() + self.omega_0 = omega_0 + + def forward(self, input): + if self.omega_0 == 1: + return torch.sin(input) + else: + return torch.sin(input * self.omega_0) + + +def init_weights_(module: nn.Linear, omega_0: float, is_first: bool = True): + assert isinstance(module, nn.Linear), module + with torch.no_grad(): + mag = ( + 1 / module.in_features + if is_first else + sqrt(6 / module.in_features) / omega_0 + ) + module.weight.uniform_(-mag, mag) diff --git a/ifield/param.py b/ifield/param.py new file mode 100644 index 0000000..4298048 --- /dev/null +++ b/ifield/param.py @@ -0,0 +1,231 @@ +from .utils.helpers import compose, elementwise_max +from datetime import datetime +from torch import nn +from typing import Any, Literal, Iterable, Union, Callable, Optional +import inspect +import jinja2 +import json +import os +import random +import re +import shlex +import string +import sys +import time +import typing +import warnings +import yaml + +_UNDEFINED = " I AM UNDEFINED " + +def _yaml_encode_value(val) -> str: + if isinstance(val, tuple): + val = list(val) + elif isinstance(val, set): + val = list(val) + if isinstance(val, list): + return json.dumps(val) + elif isinstance(val, dict): + return json.dumps(val) + else: + return yaml.dump(val).removesuffix("\n...\n").rstrip("\n") + +def _raise(val: Union[Exception, str]): + if isinstance(val, str): + val = jinja2.TemplateError(val) + raise val + +def make_jinja_globals(*, enable_require_defined: bool) -> dict: + import builtins + import functools + import itertools + import operator + import json + + def require_defined(name, value, *defaults, failed: bool = False, strict: bool=False, exchaustive=False): + if not defaults: + raise ValueError("`require_defined` requires at least one valid value provided") + if jinja2.is_undefined(value): + assert value._undefined_name == name, \ + f"Name mismatch: {value._undefined_name=}, {name=}" + if failed or jinja2.is_undefined(value): + if enable_require_defined or strict: + raise ValueError( + f"Required variable {name!r} " + f"is {'incorrect' if failed else 'undefined'}! " + f"Try providing:\n" + "\n".join( + f"-O{shlex.quote(name)}={shlex.quote(str(default))}" + for default in defaults + ) + ) + else: + warnings.warn( + f"Required variable {name!r} " + f"is {'incorrect' if failed else 'undefined'}! " + f"Try providing:\n" + "\n".join( + f"-O{shlex.quote(name)}={shlex.quote(str(default))}" + for default in defaults + ) + ) + if exchaustive and not jinja2.is_undefined(value) and value not in defaults: + raise ValueError( + f"Variable {name!r} not in list of allowed values: {defaults!r}" + ) + + def gen_run_uid(n: int, _choice = random.Random(time.time_ns()).choice): + """ + generates a UID for the experiment run, nice for regexes, grepping and timekeeping. + """ + # we have _choice, since most likely, pl.seed_everything has been run by this point + # we store it as a default parameter to reuse it, on the off-chance of two calls to this function being run withion the same ns + code = ''.join(_choice(string.ascii_lowercase) for _ in range(n)) + return f"{datetime.now():%Y-%m-%d-%H%M}-{code}" + return f"{datetime.now():%Y%m%d-%H%M}-{code}" + + def cartesian_hparams(_map=None, **kw: dict[str, list]) -> Iterable[jinja2.utils.Namespace]: + "Use this to bypass the common error 'SyntaxError: too many statically nested blocks'" + if isinstance(_map, jinja2.utils.Namespace): + kw = _map._Namespace__attrs | kw + elif isinstance(_map, dict): + kw = _map._Namespace__attrs | kw + keys, vals = zip(*kw.items()) + for i in itertools.product(*vals): + yield jinja2.utils.Namespace(zip(keys, i)) + + def ablation_hparams(_map=None, *, caartesian_keys: list[str] = None, **kw: dict[str, list]) -> Iterable[jinja2.utils.Namespace]: + "Use this to bypass the common error 'SyntaxError: too many statically nested blocks'" + if isinstance(_map, jinja2.utils.Namespace): + kw = _map._Namespace__attrs | kw + elif isinstance(_map, dict): + kw = _map._Namespace__attrs | kw + keys = list(kw.keys()) + + caartesian_keys = [k for k in keys if k in caartesian_keys] if caartesian_keys else [] + ablation_keys = [k for k in keys if k not in caartesian_keys] + caartesian_vals = list(map(kw.__getitem__, caartesian_keys)) + ablation_vals = list(map(kw.__getitem__, ablation_keys)) + + for base_vals in itertools.product(*caartesian_vals): + base = list(itertools.chain(zip(caartesian_keys, base_vals), zip(ablation_keys, [i[0] for i in ablation_vals]))) + yield jinja2.utils.Namespace(base) + for ablation_key, ablation_val in zip(ablation_keys, ablation_vals): + for val in ablation_val[1:]: + yield jinja2.utils.Namespace(base, **{ablation_key: val}) # ablation variation + + return { + **locals(), + **vars(builtins), + "argv": sys.argv, + "raise": _raise, + } + +def make_jinja_env(globals = make_jinja_globals(enable_require_defined=True), allow_undef=False) -> jinja2.Environment: + env = jinja2.Environment( + loader = jinja2.FileSystemLoader([os.getcwd(), "/"], followlinks=True), + autoescape = False, + trim_blocks = True, + lstrip_blocks = True, + undefined = jinja2.Undefined if allow_undef else jinja2.StrictUndefined, + extensions = [ + "jinja2.ext.do", # statements with side-effects + "jinja2.ext.loopcontrols", # break and continue + ], + ) + env.globals.update(globals) + env.filters.update({ + "defined": lambda x: _raise(f"{x._undefined_name!r} is not defined!") if jinja2.is_undefined(x) else x, + "repr": repr, + "to_json": json.dumps, + "bool": lambda x: json.dumps(bool(x)), + "int": lambda x: json.dumps(int(x)), + "float": lambda x: json.dumps(float(x)), + "str": lambda x: json.dumps(str(x)), + }) + return env + +def list_func_params(func: callable, exclude_list: set[str], defaults: dict={}) -> Iterable[tuple[str, Any, str]]: + signature = inspect.signature(func) + for i, (k, v) in enumerate(signature.parameters.items()): + if not i and k in {"self", "cls"}: + continue + if k in exclude_list: + continue + if k.startswith("_"): + continue + if v.kind is v.VAR_POSITIONAL or v.kind is v.VAR_KEYWORD: + continue + has_default = not defaults.get(k, v.default) is v.empty + has_annotation = not v.annotation is v.empty + allowed_literals = f"{{{', '.join(map(_yaml_encode_value, typing.get_args(v.annotation)))}}}" \ + if typing.get_origin(v.annotation) is Literal else None + + assert has_annotation, f"param {k!r} has no type annotation" + yield ( + k, + defaults.get(k, v.default) if has_default else _UNDEFINED, + f"in {allowed_literals}" if allowed_literals else typing._type_repr(v.annotation), + ) + +@compose("\n".join) +def make_jinja_template( + network_cls: nn.Module, + *, + exclude_list: set[str] = set(), + defaults: dict[str, Any]={}, + top_level: bool = True, + commented: bool = False, + name=None, + comment: Optional[str] = None, + special_encoders: dict[str, Callable[[Any], str]]={}, + ) -> str: + c = "#" if commented else "" + if name is None: + name = network_cls.__name__ + + if comment is not None: + if "\n" in comment: + raise ValueError("newline in jinja template comment is not allowed") + + hparams = [*list_func_params(network_cls, exclude_list, defaults=defaults)] + if not hparams: + if top_level: + yield f"{name}:" + else: + yield f" # {name}:" + return + + + encoded_hparams = [ + (key, _yaml_encode_value(value) if value is not _UNDEFINED else "", comment) + if key not in special_encoders else + (key, special_encoders[key](value) if value is not _UNDEFINED else "", comment) + for key, value, comment in hparams + ] + + ml_key, ml_value = elementwise_max( + ( + len(key), + len(value), + ) + for key, value, comment in encoded_hparams + ) + + if top_level: + yield f"{name}:" if not comment else f"{name}: # {comment}" + else: + yield f" # {name}:" if not comment else f" # {name}: # {comment}" + + for key, value, comment in encoded_hparams: + if key in exclude_list: + continue + pad_key = ml_key - len(key) + pad_value = ml_value - len(value) + + yield f" {c}{key}{' '*pad_key} : {value}{' '*pad_value} # {comment}" + + yield "" + +# helpers: + +def squash_newlines(data: str) -> str: + return re.sub(r'\n\n\n+', '\n\n', data) diff --git a/ifield/utils/__init__.py b/ifield/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ifield/utils/geometry.py b/ifield/utils/geometry.py new file mode 100644 index 0000000..a3e1f69 --- /dev/null +++ b/ifield/utils/geometry.py @@ -0,0 +1,197 @@ +from torch import Tensor +from torch.nn import functional as F +from typing import Optional, Literal +import torch +from .helpers import compose + + +def get_ray_origins(cam2world: Tensor): + return cam2world[..., :3, 3] + +def camera_uv_to_rays( + cam2world : Tensor, + uv : Tensor, + intrinsics : Tensor, + ) -> tuple[Tensor, Tensor]: + """ + Computes rays and origins from batched cam2world & intrinsics matrices, as well as pixel coordinates + cam2world: (..., 4, 4) + intrinsics: (..., 3, 3) + uv: (..., n, 2) + """ + ray_dirs = get_ray_directions(uv, cam2world=cam2world, intrinsics=intrinsics) + ray_origins = get_ray_origins(cam2world) + ray_origins = ray_origins[..., None, :].expand([*uv.shape[:-1], 3]) + return ray_origins, ray_dirs + +RayEmbedding = Literal[ + "plucker", # LFN + "perp_foot", # PRIF + "both", +] + +@compose(torch.cat, dim=-1) +@compose(tuple) +def ray_input_embedding(ray_origins: Tensor, ray_dirs: Tensor, mode: RayEmbedding = "plucker", normalize_dirs=False, is_training=False): + """ + Computes the plucker coordinates / perpendicular foot from ray origins and directions, appending it to direction + """ + assert ray_origins.shape[-1] == ray_dirs.shape[-1] == 3, \ + f"{ray_dirs.shape = }, {ray_origins.shape = }" + + if normalize_dirs: + ray_dirs = ray_dirs / ray_dirs.norm(dim=-1, keepdim=True) + + yield ray_dirs + + do_moment = mode in ("plucker", "both") + do_perp_feet = mode in ("perp_foot", "both") + assert do_moment or do_perp_feet + + moment = torch.cross(ray_origins, ray_dirs, dim=-1) + if do_moment: + yield moment + + if do_perp_feet: + perp_feet = torch.cross(ray_dirs, moment, dim=-1) + yield perp_feet + +def ray_input_embedding_length(mode: RayEmbedding = "plucker") -> int: + do_moment = mode in ("plucker", "both") + do_perp_feet = mode in ("perp_foot", "both") + assert do_moment or do_perp_feet + + out = 3 # ray_dirs + if do_moment: + out += 3 # moment + if do_perp_feet: + out += 3 # perp foot + return out + +def parse_intrinsics(intrinsics, return_dict=False): + fx = intrinsics[..., 0, 0:1] + fy = intrinsics[..., 1, 1:2] + cx = intrinsics[..., 0, 2:3] + cy = intrinsics[..., 1, 2:3] + if return_dict: + return {"fx": fx, "fy": fy, "cx": cx, "cy": cy} + else: + return fx, fy, cx, cy + +def expand_as(x, y): + if len(x.shape) == len(y.shape): + return x + + for i in range(len(y.shape) - len(x.shape)): + x = x.unsqueeze(-1) + + return x + +def lift(x, y, z, intrinsics, homogeneous=False): + """ + + :param self: + :param x: Shape (batch_size, num_points) + :param y: + :param z: + :param intrinsics: + :return: + """ + fx, fy, cx, cy = parse_intrinsics(intrinsics) + + x_lift = (x - expand_as(cx, x)) / expand_as(fx, x) * z + y_lift = (y - expand_as(cy, y)) / expand_as(fy, y) * z + + if homogeneous: + return torch.stack((x_lift, y_lift, z, torch.ones_like(z).to(x.device)), dim=-1) + else: + return torch.stack((x_lift, y_lift, z), dim=-1) + +def project(x, y, z, intrinsics): + """ + + :param self: + :param x: Shape (batch_size, num_points) + :param y: + :param z: + :param intrinsics: + :return: + """ + fx, fy, cx, cy = parse_intrinsics(intrinsics) + + x_proj = expand_as(fx, x) * x / z + expand_as(cx, x) + y_proj = expand_as(fy, y) * y / z + expand_as(cy, y) + + return torch.stack((x_proj, y_proj, z), dim=-1) + +def world_from_xy_depth(xy, depth, cam2world, intrinsics): + batch_size, *_ = cam2world.shape + + x_cam = xy[..., 0] + y_cam = xy[..., 1] + z_cam = depth + + pixel_points_cam = lift(x_cam, y_cam, z_cam, intrinsics=intrinsics, homogeneous=True) + world_coords = torch.einsum("b...ij,b...kj->b...ki", cam2world, pixel_points_cam)[..., :3] + + return world_coords + +def project_point_on_ray(projection_point, ray_origin, ray_dir): + dot = torch.einsum("...j,...j", projection_point-ray_origin, ray_dir) + return ray_origin + dot[..., None] * ray_dir + +def get_ray_directions( + xy : Tensor, # (..., N, 2) + cam2world : Tensor, # (..., 4, 4) + intrinsics : Tensor, # (..., 3, 3) + ): + z_cam = torch.ones(xy.shape[:-1]).to(xy.device) + pixel_points = world_from_xy_depth(xy, z_cam, intrinsics=intrinsics, cam2world=cam2world) # (batch, num_samples, 3) + + cam_pos = cam2world[..., :3, 3] + ray_dirs = pixel_points - cam_pos[..., None, :] # (batch, num_samples, 3) + ray_dirs = F.normalize(ray_dirs, dim=-1) + return ray_dirs + +def ray_sphere_intersect( + ray_origins : Tensor, # (..., 3) + ray_dirs : Tensor, # (..., 3) + sphere_centers : Optional[Tensor] = None, # (..., 3) + sphere_radii : Optional[Tensor] = 1, # (...) + *, + return_parts : bool = False, + allow_nans : bool = True, + improve_miss_grads : bool = False, + ) -> tuple[Tensor, ...]: + if improve_miss_grads: assert not allow_nans, "improve_miss_grads does not work with allow_nans" + if sphere_centers is None: + ray_origins_centered = ray_origins #- torch.zeros_like(ray_origins) + else: + ray_origins_centered = ray_origins - sphere_centers + + ray_dir_dot_origins = (ray_dirs * ray_origins_centered).sum(dim=-1, keepdim=True) + discriminants2 = ray_dir_dot_origins**2 - ((ray_origins_centered * ray_origins_centered).sum(dim=-1) - sphere_radii**2)[..., None] + if not allow_nans or return_parts: + is_intersecting = discriminants2 > 0 + if allow_nans: + discriminants = torch.sqrt(discriminants2) + else: + discriminants = torch.sqrt(torch.where(is_intersecting, discriminants2, + discriminants2 - discriminants2.detach() + 0.001 + if improve_miss_grads else + torch.zeros_like(discriminants2) + )) + assert not discriminants.detach().isnan().any() # slow, use optimizations! + + if not return_parts: + return ( + ray_origins + ray_dirs * (- ray_dir_dot_origins - discriminants), + ray_origins + ray_dirs * (- ray_dir_dot_origins + discriminants), + ) + else: + return ( + ray_origins + ray_dirs * (- ray_dir_dot_origins), + ray_origins + ray_dirs * (- ray_dir_dot_origins - discriminants), + ray_origins + ray_dirs * (- ray_dir_dot_origins + discriminants), + is_intersecting.squeeze(-1), + ) diff --git a/ifield/utils/helpers.py b/ifield/utils/helpers.py new file mode 100644 index 0000000..0e119a9 --- /dev/null +++ b/ifield/utils/helpers.py @@ -0,0 +1,205 @@ +from functools import wraps, reduce, partial +from itertools import zip_longest, groupby +from pathlib import Path +from typing import Iterable, TypeVar, Callable, Union, Optional, Mapping, Hashable +import collections +import operator +import re + +Numeric = Union[int, float, complex] +T = TypeVar("T") +S = TypeVar("S") + +# decorator +def compose(outer_func: Callable[[..., S], T], *outer_a, **outer_kw) -> Callable[..., T]: + def wrapper(inner_func: Callable[..., S]): + @wraps(inner_func) + def wrapped(*a, **kw): + return outer_func(*outer_a, inner_func(*a, **kw), **outer_kw) + return wrapped + return wrapper + +def compose_star(outer_func: Callable[[..., S], T], *outer_a, **outer_kw) -> Callable[..., T]: + def wrapper(inner_func: Callable[..., S]): + @wraps(inner_func) + def wrapped(*a, **kw): + return outer_func(*outer_a, *inner_func(*a, **kw), **outer_kw) + return wrapped + return wrapper + + +# itertools + +def elementwise_max(iterable: Iterable[Iterable[T]]) -> Iterable[T]: + return reduce(lambda xs, ys: [*map(max, zip(xs, ys))], iterable) + +def prod(numbers: Iterable[T], initial: Optional[T] = None) -> T: + if initial is not None: + return reduce(operator.mul, numbers, initial) + else: + return reduce(operator.mul, numbers) + +def run_length_encode(data: Iterable[T]) -> Iterable[tuple[T, int]]: + return ( + (x, len(y)) + for x, y in groupby(data) + ) + + +# text conversion + +def camel_to_snake_case(text: str, sep: str = "_", join_abbreviations: bool = False) -> str: + parts = ( + part.lower() + for part in re.split(r'(?=[A-Z])', text) + if part + ) + if join_abbreviations: + parts = list(parts) + if len(parts) > 1: + for i, (a, b) in list(enumerate(zip(parts[:-1], parts[1:])))[::-1]: + if len(a) == len(b) == 1: + parts[i] = parts[i] + parts.pop(i+1) + return sep.join(parts) + +def snake_to_camel_case(text: str) -> str: + return "".join( + part.captialize() + for part in text.split("_") + if part + ) + + +# textwrap + +def columnize_dict(data: dict, n_columns=2, prefix="", sep=" ") -> str: + sub = (len(data) + 1) // n_columns + return reduce(partial(columnize, sep=sep), + ( + columnize( + "\n".join([f"{'' if n else prefix}{i!r}" for i in data.keys() ][n*sub : (n+1)*sub]), + "\n".join([f": {i!r}," for i in data.values()][n*sub : (n+1)*sub]), + ) + for n in range(n_columns) + ) + ) + +def columnize(left: str, right: str, prefix="", sep=" ") -> str: + left = left .split("\n") + right = right.split("\n") + width = max(map(len, left)) if left else 0 + return "\n".join( + f"{prefix}{a.ljust(width)}{sep}{b}" + if b else + f"{prefix}{a}" + for a, b in zip_longest(left, right, fillvalue="") + ) + + +# pathlib + +def make_relative(path: Union[Path, str], parent: Path = None) -> Path: + if isinstance(path, str): + path = Path(path) + if parent is None: + parent = Path.cwd() + try: + return path.relative_to(parent) + except ValueError: + pass + try: + return ".." / path.relative_to(parent.parent) + except ValueError: + pass + return path + + +# dictionaries + +def update_recursive(target: dict, source: dict): + """ Update two config dictionaries recursively. """ + for k, v in source.items(): + if isinstance(v, dict): + if k not in target: + target[k] = type(target)() + update_recursive(target[k], v) + else: + target[k] = v + +def map_tree(func: Callable[[T], S], val: Union[Mapping[Hashable, T], tuple[T, ...], list[T], T]) -> Union[Mapping[Hashable, S], tuple[S, ...], list[S], S]: + if isinstance(val, collections.abc.Mapping): + return { + k: map_tree(func, subval) + for k, subval in val.items() + } + elif isinstance(val, tuple): + return tuple( + map_tree(func, subval) + for subval in val + ) + elif isinstance(val, list): + return [ + map_tree(func, subval) + for subval in val + ] + else: + return func(val) + +def flatten_tree(val, *, sep=".", prefix=None): + if isinstance(val, collections.abc.Mapping): + return { + k: v + for subkey, subval in val.items() + for k, v in flatten_tree(subval, sep=sep, prefix=f"{prefix}{sep}{subkey}" if prefix else subkey).items() + } + elif isinstance(val, tuple) or isinstance(val, list): + return { + k: v + for index, subval in enumerate(val) + for k, v in flatten_tree(subval, sep=sep, prefix=f"{prefix}{sep}[{index}]" if prefix else f"[{index}]").items() + } + elif prefix: + return {prefix: val} + else: + return val + +# conversions + +def hex2tuple(data: str) -> tuple[int]: + data = data.removeprefix("#") + return (*( + int(data[i:i+2], 16) + for i in range(0, len(data), 2) + ),) + + +# repr shims + +class CustomRepr: + def __init__(self, repr_str: str): + self.repr_str = repr_str + def __str__(self): + return self.repr_str + def __repr__(self): + return self.repr_str + + +# Meta Params Module proxy + +class MetaModuleProxy: + def __init__(self, module, params): + self._module = module + self._params = params + + def __getattr__(self, name): + params = super().__getattribute__("_params") + if name in params: + return params[name] + else: + return getattr(super().__getattribute__("_module"), name) + + def __setattr__(self, name, value): + if name not in ("_params", "_module"): + super().__getattribute__("_params")[name] = value + else: + super().__setattr__(name, value) diff --git a/ifield/utils/loss.py b/ifield/utils/loss.py new file mode 100644 index 0000000..cdc2237 --- /dev/null +++ b/ifield/utils/loss.py @@ -0,0 +1,590 @@ +from abc import abstractmethod, ABC +from dataclasses import dataclass, field, fields, MISSING +from functools import wraps +from matplotlib import pyplot as plt +from matplotlib.artist import Artist +from tabulate import tabulate +from torch import nn +from typing import Optional, TypeVar, Union +import inspect +import math +import pytorch_lightning as pl +import typing +import warnings + + +HParamSchedule = TypeVar("HParamSchedule", bound="HParamScheduleBase") +Schedulable = Union[HParamSchedule, int, float, str] + +class HParamScheduleBase(ABC): + _subclasses = {} # shared reference intended + def __init_subclass__(cls): + if not cls.__name__.startswith("_"): + cls._subclasses[cls.__name__] = cls + + _infix : Optional[str] = field(init=False, repr=False, default=None) + _param_name : Optional[str] = field(init=False, repr=False, default=None) + _expr : Optional[str] = field(init=False, repr=False, default=None) + + def get(self, module: nn.Module, *, trainer: Optional[pl.Trainer] = None) -> float: + if module.training: + if trainer is None: + trainer = module.trainer # this assumes `module` is a pl.LightningModule + value = self.get_train_value( + epoch = trainer.current_epoch + (trainer.fit_loop.epoch_loop.batch_progress.current.processed / trainer.num_training_batches), + ) + if trainer.logger is not None and self._param_name is not None and self.__class__ is not Const and trainer.global_step % 15 == 0: + trainer.logger.log_metrics({ + f"HParamSchedule/{self._param_name}": value, + }, step=trainer.global_step) + return value + else: + return self.get_eval_value() + + def _gen_data(self, n_epochs, steps_per_epoch=1000): + global_steps = 0 + for epoch in range(n_epochs): + for step in range(steps_per_epoch): + yield ( + epoch + step/steps_per_epoch, + self.get_train_value(epoch + step/steps_per_epoch), + ) + global_steps += steps_per_epoch + + def plot(self, *a, ax: Optional[plt.Axes] = None, **kw) -> Artist: + if ax is None: ax = plt.gca() + out = ax.plot(*zip(*self._gen_data(*a, **kw)), label=self._expr) + ax.set_title(self._param_name) + ax.set_xlabel("Epoch") + ax.set_ylabel("Value") + ax.legend() + return out + + def assert_positive(self, *a, **kw): + for epoch, val in self._gen_data(*a, **kw): + assert val >= 0, f"{epoch=}, {val=}" + + @abstractmethod + def get_eval_value(self) -> float: + ... + + @abstractmethod + def get_train_value(self, epoch: float) -> float: + ... + + def __add__(self, rhs): + for cls in self._subclasses.values(): + if cls._infix == "+": + return cls(self, rhs) + return NotImplemented + + def __radd__(self, lhs): + for cls in self._subclasses.values(): + if cls._infix == "+": + return cls(lhs, self) + return NotImplemented + + def __sub__(self, rhs): + for cls in self._subclasses.values(): + if cls._infix == "-": + return cls(self, rhs) + return NotImplemented + + def __rsub__(self, lhs): + for cls in self._subclasses.values(): + if cls._infix == "-": + return cls(lhs, self) + return NotImplemented + + def __mul__(self, rhs): + for cls in self._subclasses.values(): + if cls._infix == "*": + return cls(self, rhs) + return NotImplemented + + def __rmul__(self, lhs): + for cls in self._subclasses.values(): + if cls._infix == "*": + return cls(lhs, self) + return NotImplemented + + def __matmul__(self, rhs): + for cls in self._subclasses.values(): + if cls._infix == "@": + return cls(self, rhs) + return NotImplemented + + def __rmatmul__(self, lhs): + for cls in self._subclasses.values(): + if cls._infix == "@": + return cls(lhs, self) + return NotImplemented + + def __truediv__(self, rhs): + for cls in self._subclasses.values(): + if cls._infix == "/": + return cls(self, rhs) + return NotImplemented + + def __rtruediv__(self, lhs): + for cls in self._subclasses.values(): + if cls._infix == "/": + return cls(lhs, self) + return NotImplemented + + def __floordiv__(self, rhs): + for cls in self._subclasses.values(): + if cls._infix == "//": + return cls(self, rhs) + return NotImplemented + + def __rfloordiv__(self, lhs): + for cls in self._subclasses.values(): + if cls._infix == "//": + return cls(lhs, self) + return NotImplemented + + def __mod__(self, rhs): + for cls in self._subclasses.values(): + if cls._infix == "%": + return cls(self, rhs) + return NotImplemented + + def __rmod__(self, lhs): + for cls in self._subclasses.values(): + if cls._infix == "%": + return cls(lhs, self) + return NotImplemented + + def __pow__(self, rhs): + for cls in self._subclasses.values(): + if cls._infix == "**": + return cls(self, rhs) + return NotImplemented + + def __rpow__(self, lhs): + for cls in self._subclasses.values(): + if cls._infix == "**": + return cls(lhs, self) + return NotImplemented + + def __lshift__(self, rhs): + for cls in self._subclasses.values(): + if cls._infix == "<<": + return cls(self, rhs) + return NotImplemented + + def __rlshift__(self, lhs): + for cls in self._subclasses.values(): + if cls._infix == "<<": + return cls(lhs, self) + return NotImplemented + + def __rshift__(self, rhs): + for cls in self._subclasses.values(): + if cls._infix == ">>": + return cls(self, rhs) + return NotImplemented + + def __rrshift__(self, lhs): + for cls in self._subclasses.values(): + if cls._infix == ">>": + return cls(lhs, self) + return NotImplemented + + def __and__(self, rhs): + for cls in self._subclasses.values(): + if cls._infix == "&": + return cls(self, rhs) + return NotImplemented + + def __rand__(self, lhs): + for cls in self._subclasses.values(): + if cls._infix == "&": + return cls(lhs, self) + return NotImplemented + + def __xor__(self, rhs): + for cls in self._subclasses.values(): + if cls._infix == "^": + return cls(self, rhs) + return NotImplemented + + def __rxor__(self, lhs): + for cls in self._subclasses.values(): + if cls._infix == "^": + return cls(lhs, self) + return NotImplemented + + def __or__(self, rhs): + for cls in self._subclasses.values(): + if cls._infix == "|": + return cls(self, rhs) + return NotImplemented + + def __ror__(self, lhs): + for cls in self._subclasses.values(): + if cls._infix == "|": + return cls(lhs, self) + return NotImplemented + + def __ge__(self, rhs): + for cls in self._subclasses.values(): + if cls._infix == ">=": + return cls(self, rhs) + return NotImplemented + + def __gt__(self, rhs): + for cls in self._subclasses.values(): + if cls._infix == ">": + return cls(self, rhs) + return NotImplemented + + def __le__(self, rhs): + for cls in self._subclasses.values(): + if cls._infix == "<=": + return cls(self, rhs) + return NotImplemented + + def __lt__(self, rhs): + for cls in self._subclasses.values(): + if cls._infix == "<": + return cls(self, rhs) + return NotImplemented + + def __bool__(self): + return True + + def __neg__(self): + for cls in self._subclasses.values(): + if cls._infix == "-": + return cls(0, self) + return NotImplemented + + @property + def is_const(self) -> bool: + return False + + + +def parse_dsl(config: Schedulable, name=None) -> HParamSchedule: + if isinstance(config, HParamScheduleBase): + return config + elif isinstance(config, str): + out = eval(config, {"__builtins__": {}, "lg": math.log10}, HParamScheduleBase._subclasses) + if not isinstance(out, HParamScheduleBase): + out = Const(out) + else: + out = Const(config) + out._expr = config + out._param_name = name + return out + + +# decorator +def ensure_schedulables(func): + signature = inspect.signature(func) + module_name = func.__qualname__.removesuffix(".__init__") + + @wraps(func) + def wrapper(*a, **kw): + bound_args = signature.bind(*a, **kw) + + for param_name, param in signature.parameters.items(): + type_origin = typing.get_origin(param.annotation) + type_args = typing.get_args (param.annotation) + + if type_origin is HParamSchedule or (type_origin is Union and (HParamSchedule in type_args or HParamScheduleBase in type_args)): + if param_name in bound_args.arguments: + bound_args.arguments[param_name] = parse_dsl(bound_args.arguments[param_name], name=f"{module_name}.{param_name}") + elif param.default is not param.empty: + bound_args.arguments[param_name] = parse_dsl(param.default, name=f"{module_name}.{param_name}") + + return func( + *bound_args.args, + **bound_args.kwargs, + ) + return wrapper + +# https://easings.net/ + +@dataclass +class _InfixBase(HParamScheduleBase): + l : Union[HParamSchedule, int, float] + r : Union[HParamSchedule, int, float] + + def _operation(self, l: float, r: float) -> float: + raise NotImplementedError + + def get_eval_value(self) -> float: + return self._operation( + self.l.get_eval_value() if isinstance(self.l, HParamScheduleBase) else self.l, + self.r.get_eval_value() if isinstance(self.r, HParamScheduleBase) else self.r, + ) + + def get_train_value(self, epoch: float) -> float: + return self._operation( + self.l.get_train_value(epoch) if isinstance(self.l, HParamScheduleBase) else self.l, + self.r.get_train_value(epoch) if isinstance(self.r, HParamScheduleBase) else self.r, + ) + + def __bool__(self): + if self.is_const: + return bool(self.get_eval_value()) + else: + return True + + @property + def is_const(self) -> bool: + return (self.l.is_const if isinstance(self.l, HParamScheduleBase) else True) \ + and (self.r.is_const if isinstance(self.r, HParamScheduleBase) else True) + +@dataclass +class Add(_InfixBase): + """ adds the results of two schedules """ + _infix : Optional[str] = field(init=False, repr=False, default="+") + def _operation(self, l: float, r: float) -> float: + return l + r + + +@dataclass +class Sub(_InfixBase): + """ subtracts the results of two schedules """ + _infix : Optional[str] = field(init=False, repr=False, default="-") + def _operation(self, l: float, r: float) -> float: + return l - r + + +@dataclass +class Prod(_InfixBase): + """ multiplies the results of two schedules """ + _infix : Optional[str] = field(init=False, repr=False, default="*") + def _operation(self, l: float, r: float) -> float: + return l * r + @property + def is_const(self) -> bool: # propagate identity + l = self.l.get_eval_value() if isinstance(self.l, HParamScheduleBase) and self.l.is_const else self.l + r = self.r.get_eval_value() if isinstance(self.r, HParamScheduleBase) and self.r.is_const else self.r + return l == 0 or r == 0 or super().is_const + + +@dataclass +class Div(_InfixBase): + """ divides the results of two schedules """ + _infix : Optional[str] = field(init=False, repr=False, default="/") + def _operation(self, l: float, r: float) -> float: + return l / r + + +@dataclass +class Pow(_InfixBase): + """ raises the results of one schedule to the other """ + _infix : Optional[str] = field(init=False, repr=False, default="**") + def _operation(self, l: float, r: float) -> float: + return l ** r + + +@dataclass +class Gt(_InfixBase): + """ compares the results of two schedules """ + _infix : Optional[str] = field(init=False, repr=False, default=">") + def _operation(self, l: float, r: float) -> float: + return l > r + + +@dataclass +class Lt(_InfixBase): + """ compares the results of two schedules """ + _infix : Optional[str] = field(init=False, repr=False, default="<") + def _operation(self, l: float, r: float) -> float: + return l < r + + +@dataclass +class Ge(_InfixBase): + """ compares the results of two schedules """ + _infix : Optional[str] = field(init=False, repr=False, default=">=") + def _operation(self, l: float, r: float) -> float: + return l >= r + + +@dataclass +class Le(_InfixBase): + """ compares the results of two schedules """ + _infix : Optional[str] = field(init=False, repr=False, default="<=") + def _operation(self, l: float, r: float) -> float: + return l <= r + + +@dataclass +class Const(HParamScheduleBase): + """ A way to ensure .get(...) exists """ + + c : Union[int, float] + + def get_eval_value(self) -> float: + return self.c + + def get_train_value(self, epoch: float) -> float: + return self.c + + def __bool__(self): + return bool(self.get_eval_value()) + + @property + def is_const(self) -> bool: + return True + +@dataclass +class Step(HParamScheduleBase): + """ steps from 0 to 1 at `epoch` """ + + epoch : float + + def get_eval_value(self) -> float: + return 1 + + def get_train_value(self, epoch: float) -> float: + return 1 if epoch >= self.epoch else 0 + +@dataclass +class Linear(HParamScheduleBase): + """ linear from 0 to 1 over `n_epochs`, delayed by `offset` """ + + n_epochs : float + offset : float = 0 + + def get_eval_value(self) -> float: + return 1 + + def get_train_value(self, epoch: float) -> float: + if self.n_epochs <= 0: return 1 + return min(max(epoch - self.offset, 0) / self.n_epochs, 1) + +@dataclass +class EaseSin(HParamScheduleBase): # effectively 1-CosineAnnealing + """ sinusoidal ease in-out from 0 to 1 over `n_epochs`, delayed by `offset` """ + + n_epochs : float + offset : float = 0 + + def get_eval_value(self) -> float: + return 1 + + def get_train_value(self, epoch: float) -> float: + x = min(max(epoch - self.offset, 0) / self.n_epochs, 1) + return -(math.cos(math.pi * x) - 1) / 2 + +@dataclass +class EaseExp(HParamScheduleBase): + """ exponential ease in-out from 0 to 1 over `n_epochs`, delayed by `offset` """ + + n_epochs : float + offset : float = 0 + + def get_eval_value(self) -> float: + return 1 + + def get_train_value(self, epoch: float) -> float: + if (epoch-self.offset) < 0: + return 0 + if (epoch-self.offset) > self.n_epochs: + return 1 + x = min(max(epoch - self.offset, 0) / self.n_epochs, 1) + return ( + 2**(20*x-10) / 2 + if x < 0.5 else + (2 - 2**(-20*x+10)) / 2 + ) + +@dataclass +class Steps(HParamScheduleBase): + """ Starts at 1, multiply by gamma every n epochs. Models StepLR in pytorch """ + step_size: float + gamma: float = 0.1 + + def get_eval_value(self) -> float: + return 1 + def get_train_value(self, epoch: float) -> float: + return self.gamma**int(epoch / self.step_size) + +@dataclass +class MultiStep(HParamScheduleBase): + """ Starts at 1, multiply by gamma every milstone epoch. Models MultiStepLR in pytorch """ + milestones: list[float] + gamma: float = 0.1 + + def get_eval_value(self) -> float: + return 1 + def get_train_value(self, epoch: float) -> float: + for i, m in list(enumerate(self.milestones))[::-1]: + if epoch >= m: + return self.gamma**(i+1) + return 1 + +@dataclass +class Epoch(HParamScheduleBase): + """ The current epoch, starting at 0 """ + + def get_eval_value(self) -> float: + return 0 + def get_train_value(self, epoch: float) -> float: + return epoch + +@dataclass +class Offset(HParamScheduleBase): + """ Offsets the epoch for the subexpression, clamped above 0. Positive offsets makes it happen later """ + expr : Union[HParamSchedule, int, float] + offset : float + + def get_eval_value(self) -> float: + return self.expr.get_eval_value() if isinstance(self.expr, HParamScheduleBase) else self.expr + def get_train_value(self, epoch: float) -> float: + return self.expr.get_train_value(max(epoch - self.offset, 0)) if isinstance(self.expr, HParamScheduleBase) else self.expr + +@dataclass +class Mod(HParamScheduleBase): + """ The epoch in the subexptression is subject to a modulus. Use for warm restarts """ + + modulus : float + expr : Union[HParamSchedule, int, float] + + def get_eval_value(self) -> float: + return self.expr.get_eval_value() if isinstance(self.expr, HParamScheduleBase) else self.expr + def get_train_value(self, epoch: float) -> float: + return self.expr.get_train_value(epoch % self.modulus) if isinstance(self.expr, HParamScheduleBase) else self.expr + + +def main(): + import sys, rich.pretty + if not sys.argv[2:]: + print(f"Usage: {sys.argv[0]} n_epochs 'expression'") + print("Available operations:") + def mk_ops(): + for name, cls in HParamScheduleBase._subclasses.items(): + if isinstance(cls._infix, str): + yield (cls._infix, f"(infix) {cls.__doc__.strip()}") + else: + yield ( + f"""{name}({', '.join( + i.name + if i.default is MISSING else + f"{i.name}={i.default!r}" + for i in fields(cls) + )})""", + cls.__doc__.strip(), + ) + rich.print(tabulate(sorted(mk_ops()), tablefmt="plain")) + else: + n_epochs = int(sys.argv[1]) + schedules = [parse_dsl(arg, name="cli arg") for arg in sys.argv[2:]] + ax = plt.gca() + print("[") + for schedule in schedules: + rich.print(f" {schedule}, # {schedule.is_const = }") + schedule.plot(n_epochs, ax=ax) + print("]") + plt.show() + +if __name__ == "__main__": + main() diff --git a/ifield/utils/operators/__init__.py b/ifield/utils/operators/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ifield/utils/operators/diff.py b/ifield/utils/operators/diff.py new file mode 100644 index 0000000..8f2b194 --- /dev/null +++ b/ifield/utils/operators/diff.py @@ -0,0 +1,96 @@ +import torch +from torch.autograd import grad + + +def hessian(y: torch.Tensor, x: torch.Tensor, check=False, detach=False) -> torch.Tensor: + """ + hessian of y wrt x + y: shape (..., Y) + x: shape (..., X) + return: shape (..., Y, X, X) + """ + assert x.requires_grad + assert y.grad_fn + + grad_y = torch.ones_like(y[..., 0]).to(y.device) # reuse -> less memory + + hess = torch.stack([ + # calculate hessian on y for each x value + torch.stack( + gradients( + *(dydx[..., j] for j in range(x.shape[-1])), + wrt=x, + grad_outputs=[grad_y]*x.shape[-1], + detach=detach, + ), + dim = -2, + ) + # calculate dydx over batches for each feature value of y + for dydx in gradients(*(y[..., i] for i in range(y.shape[-1])), wrt=x) + ], dim=-3) + + if check: + assert hess.isnan().any() + return hess + +def laplace(y: torch.Tensor, x: torch.Tensor) -> torch.Tensor: + return divergence(*gradients(y, wrt=x), x) + +def divergence(y: torch.Tensor, x: torch.Tensor) -> torch.Tensor: + assert x.requires_grad + assert y.grad_fn + return sum( + grad( + y[..., i], + x, + torch.ones_like(y[..., i]), + create_graph=True + )[0][..., i:i+1] + for i in range(y.shape[-1]) + ) + +def gradients(*ys, wrt, grad_outputs=None, detach=False) -> list[torch.Tensor]: + assert wrt.requires_grad + assert all(y.grad_fn for y in ys) + if grad_outputs is None: + grad_outputs = [torch.ones_like(y) for y in ys] + + grads = ( + grad( + [y], + [wrt], + grad_outputs=y_grad, + create_graph=True, + )[0] + for y, y_grad in zip(ys, grad_outputs) + ) + if detach: + grads = map(torch.detach, grads) + + return [*grads] + +def jacobian(y: torch.Tensor, x: torch.Tensor, check=False, detach=False) -> torch.Tensor: + """ + jacobian of `y` w.r.t. `x` + + y: shape (..., Y) + x: shape (..., X) + return: shape (..., Y, X) + """ + assert x.requires_grad + assert y.grad_fn + + y_grad = torch.ones_like(y[..., 0]) + jac = torch.stack( + gradients( + *(y[..., i] for i in range(y.shape[-1])), + wrt=x, + grad_outputs=[y_grad]*x.shape[-1], + detach=detach, + ), + dim=-2, + ) + + if check: + assert jac.isnan().any() + return jac diff --git a/ifield/viewer/__init__.py b/ifield/viewer/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ifield/viewer/assets/texturify_pano-1-4.jpg b/ifield/viewer/assets/texturify_pano-1-4.jpg new file mode 100644 index 0000000..c2abbc7 Binary files /dev/null and b/ifield/viewer/assets/texturify_pano-1-4.jpg differ diff --git a/ifield/viewer/common.py b/ifield/viewer/common.py new file mode 100644 index 0000000..b2f3bff --- /dev/null +++ b/ifield/viewer/common.py @@ -0,0 +1,430 @@ +from ..utils import geometry +from abc import ABC, abstractmethod +from datetime import datetime +from pathlib import Path +from pytorch3d.transforms import euler_angles_to_matrix +from tqdm import tqdm +from typing import Sequence, Callable, TypedDict +import imageio +import shlex +import json +import numpy as np +import os +import time +import torch +os.environ["PYGAME_HIDE_SUPPORT_PROMPT"] = "1" +import pygame + +IVec2 = tuple[int, int] +IVec3 = tuple[int, int, int] +Vec2 = tuple[float|int, float|int] +Vec3 = tuple[float|int, float|int, float|int] + +class CamState(TypedDict, total=False): + distance : float + pos_x : float + pos_y : float + pos_z : float + rot_x : float + rot_y : float + fov_y : float + + + +class InteractiveViewer(ABC): + constants = pygame.constants # saves an import + + # realtime + t : float # time since start + td : float # time delta since last frame + + # offline + is_headless : bool + fps : int + frame_idx : int + + fill_color = (255, 255, 255) + + def __init__(self, name: str, res: IVec2 = (640, 480), scale: int= 1, screenshot_dir: Path = "."): + self.name = name + self.res = res + self.scale = scale + self.screenshot_dir = Path(screenshot_dir) + + self.is_headless = False + + self.cam_distance = 2.0 + self.cam_pos_x = 0.0 # look-at and rotation pivot + self.cam_pos_y = 0.0 # look-at and rotation pivot + self.cam_pos_z = 0.0 # look-at and rotation pivot + self.cam_rot_x = 0.5 * torch.pi # radians + self.cam_rot_y = -0.5 * torch.pi # radians + self.cam_fov_y = 60.0 / 180.0 * 3.1415 # radians + self.keep_rotating = False + self.initial_camera_state = self.cam_state + self.fps_cap = None + + @property + def cam_state(self) -> CamState: + return dict( + distance = self.cam_distance, + pos_x = self.cam_pos_x, + pos_y = self.cam_pos_y, + pos_z = self.cam_pos_z, + rot_x = self.cam_rot_x, + rot_y = self.cam_rot_y, + fov_y = self.cam_fov_y, + ) + + @cam_state.setter + def cam_state(self, new_state: CamState): + self.cam_distance = new_state.get("distance", self.cam_distance) + self.cam_pos_x = new_state.get("pos_x", self.cam_pos_x) + self.cam_pos_y = new_state.get("pos_y", self.cam_pos_y) + self.cam_pos_z = new_state.get("pos_z", self.cam_pos_z) + self.cam_rot_x = new_state.get("rot_x", self.cam_rot_x) + self.cam_rot_y = new_state.get("rot_y", self.cam_rot_y) + self.cam_fov_y = new_state.get("fov_y", self.cam_fov_y) + + @property + def scaled_res(self) -> IVec2: + return ( + self.res[0] * self.scale, + self.res[1] * self.scale, + ) + + def setup(self): + pass + + def teardown(self): + pass + + @abstractmethod + def render_frame(self, pixel_view: np.ndarray): # (W, H, 3) dtype=uint8 + ... + + def handle_key_up(self, key: int, keys_pressed: Sequence[bool]): + pass + + def handle_key_down(self, key: int, keys_pressed: Sequence[bool]): + mod = keys_pressed[pygame.K_LSHIFT] or keys_pressed[pygame.K_RSHIFT] + mod2 = keys_pressed[pygame.K_LCTRL] or keys_pressed[pygame.K_RCTRL] + if key == pygame.K_r: + self.keep_rotating = True + self.cam_rot_x += self.td + if key == pygame.K_MINUS: + self.scale += 1 + if __debug__: print() + print(f"== Scale = {self.scale} ==") + if key == pygame.K_PLUS and self.scale > 1: + self.scale -= 1 + if __debug__: print() + print(f"== Scale = {self.scale} ==") + if key == pygame.K_RETURN: + self.cam_state = self.initial_camera_state + if key == pygame.K_h: + if mod2: + print(shlex.quote(json.dumps(self.cam_state))) + elif mod: + with (self.screenshot_dir / "camera.json").open("w") as f: + json.dump(self.cam_state, f) + print("Wrote", self.screenshot_dir / "camera.json") + else: + with (self.screenshot_dir / "camera.json").open("r") as f: + self.cam_state = json.load(f) + print("Read", self.screenshot_dir / "camera.json") + + def handle_keys_pressed(self, pressed: Sequence[bool]) -> float: + mod1 = pressed[pygame.K_LCTRL] or pressed[pygame.K_RCTRL] + mod2 = pressed[pygame.K_LSHIFT] or pressed[pygame.K_RSHIFT] + mod3 = pressed[pygame.K_LALT] or pressed[pygame.K_RALT] + td = self.td * (0.5 if mod2 else (6 if mod1 else 2)) + + if pressed[pygame.K_UP]: self.cam_rot_y += td + if pressed[pygame.K_DOWN]: self.cam_rot_y -= td + if pressed[pygame.K_LEFT]: self.cam_rot_x += td + if pressed[pygame.K_RIGHT]: self.cam_rot_x -= td + if pressed[pygame.K_PAGEUP] and mod3: self.cam_distance -= td + if pressed[pygame.K_PAGEDOWN] and mod3: self.cam_distance += td + + if any(pressed[i] for i in [pygame.K_UP, pygame.K_DOWN, pygame.K_LEFT, pygame.K_RIGHT]): + self.keep_rotating = False + if self.keep_rotating: self.cam_rot_x += self.td * 0.25 + + if pressed[pygame.K_w]: self.cam_pos_x -= td * np.cos(-self.cam_rot_x) + if pressed[pygame.K_w]: self.cam_pos_y += td * np.sin(-self.cam_rot_x) + if pressed[pygame.K_s]: self.cam_pos_x += td * np.cos(-self.cam_rot_x) + if pressed[pygame.K_s]: self.cam_pos_y -= td * np.sin(-self.cam_rot_x) + if pressed[pygame.K_a]: self.cam_pos_x += td * np.sin(self.cam_rot_x) + if pressed[pygame.K_a]: self.cam_pos_y -= td * np.cos(self.cam_rot_x) + if pressed[pygame.K_d]: self.cam_pos_x -= td * np.sin(self.cam_rot_x) + if pressed[pygame.K_d]: self.cam_pos_y += td * np.cos(self.cam_rot_x) + if pressed[pygame.K_PAGEUP] and not mod3: self.cam_pos_z -= td + if pressed[pygame.K_PAGEDOWN] and not mod3: self.cam_pos_z += td + + return td + + def handle_mouse_button_up(self, pos: IVec2, button: int, keys_pressed: Sequence[bool]): + pass + + def handle_mouse_button_down(self, pos: IVec2, button: int, keys_pressed: Sequence[bool]): + pass + + def handle_mouse_motion(self, pos: IVec2, rel: IVec2, buttons: Sequence[bool], keys_pressed: Sequence[bool]): + pass + + def handle_mousewheel(self, flipped: bool, x: int, y: int, keys_pressed: Sequence[bool]): + if keys_pressed[pygame.K_LALT] or keys_pressed[pygame.K_RALT]: + self.cam_fov_y -= y * 0.015 + else: + self.cam_distance -= y * 0.2 + + _current_caption = None + def set_caption(self, title: str, *a, **kw): + if self._current_caption != title and not self.is_headless: + print(f"set_caption: {title!r}") + self._current_caption = title + return pygame.display.set_caption(title, *a, **kw) + + @property + def mouse_position(self) -> IVec2: + mx, my = pygame.mouse.get_pos() if not self.is_headless else (0, 0) + return ( + mx // self.scale, + my // self.scale, + ) + + @property + def uvs(self) -> torch.Tensor: # (w, h, 2) dtype=float32 + res = tuple(self.res) + if not getattr(self, "_uvs_res", None) == res: + U, V = torch.meshgrid( + torch.arange(self.res[1]).to(torch.float32), + torch.arange(self.res[0]).to(torch.float32), + indexing="xy", + ) + self._uvs_res, self._uvs = res, torch.stack((U, V), dim=-1) + return self._uvs + + @property + def cam2world(self) -> torch.Tensor: # (4, 4) dtype=float32 + if getattr(self, "_cam2world_cam_rot_y", None) is not self.cam_rot_y \ + or getattr(self, "_cam2world_cam_rot_x", None) is not self.cam_rot_x \ + or getattr(self, "_cam2world_cam_pos_x", None) is not self.cam_pos_x \ + or getattr(self, "_cam2world_cam_pos_y", None) is not self.cam_pos_y \ + or getattr(self, "_cam2world_cam_pos_z", None) is not self.cam_pos_z \ + or getattr(self, "_cam2world_cam_distance", None) is not self.cam_distance: + self._cam2world_cam_rot_y = self.cam_rot_y + self._cam2world_cam_rot_x = self.cam_rot_x + self._cam2world_cam_pos_x = self.cam_pos_x + self._cam2world_cam_pos_y = self.cam_pos_y + self._cam2world_cam_pos_z = self.cam_pos_z + self._cam2world_cam_distance = self.cam_distance + + a = torch.eye(4) + a[2, 3] = self.cam_distance + b = torch.eye(4) + b[:3, :3] = euler_angles_to_matrix(torch.tensor((self.cam_rot_x, self.cam_rot_y, 0)), "ZYX") + b[0:3, 3] -= torch.tensor(( self.cam_pos_x, self.cam_pos_y, self.cam_pos_z, )) + self._cam2world = b @ a + + self._cam2world_inv = None + return self._cam2world + + @property + def cam2world_inv(self) -> torch.Tensor: # (4, 4) dtype=float32 + if getattr(self, "_cam2world_inv", None) is None: + self._cam2world_inv = torch.linalg.inv(self._cam2world) + return self._cam2world_inv + + @property + def intrinsics(self) -> torch.Tensor: # (3, 3) dtype=float32 + if getattr(self, "_intrinsics_res", None) is not self.res \ + or getattr(self, "_intrinsics_cam_fov_y", None) is not self.cam_fov_y: + self._intrinsics_res = res = self.res + self._intrinsics_cam_fov_y = cam_fov_y = self.cam_fov_y + + self._intrinsics = torch.eye(3) + p = torch.sin(torch.tensor(cam_fov_y / 2)) + s = (res[1] / 2) + self._intrinsics[0, 0] = s/p # fx - focal length x + self._intrinsics[1, 1] = s/p # fy - focal length y + self._intrinsics[0, 2] = (res[1] - 1) / 2 # cx - optical center x + self._intrinsics[1, 2] = (res[0] - 1) / 2 # cy - optical center y + return self._intrinsics + + @property + def raydirs_and_cam(self) -> tuple[torch.Tensor, torch.Tensor]: # (w, h, 3) and (3) dtype=float32 + if getattr(self, "_raydirs_and_cam_cam2world", None) is not self.cam2world \ + or getattr(self, "_raydirs_and_cam_intrinsics", None) is not self.intrinsics \ + or getattr(self, "_raydirs_and_cam_uvs", None) is not self.uvs: + self._raydirs_and_cam_cam2world = cam2world = self.cam2world + self._raydirs_and_cam_intrinsics = intrinsics = self.intrinsics + self._raydirs_and_cam_uvs = uvs = self.uvs + + #cam_pos = (cam2world @ torch.tensor([0, 0, 0, 1], dtype=torch.float32))[:3] + cam_pos = cam2world[:3, -1] + + dirs = -geometry.get_ray_directions(uvs, cam2world[None, ...], intrinsics[None, ...]).squeeze(-1) + + self._raydirs_and_cam = (dirs, cam_pos) + return ( + self._raydirs_and_cam[0], + self._raydirs_and_cam[1], + ) + + def run(self): + self.is_headless = False + pygame.display.init() # we do not use the mixer, which often hangs on quit + try: + window = pygame.display.set_mode(self.scaled_res, flags=pygame.RESIZABLE) + buffer = pygame.surface.Surface(self.res) + + window.fill(self.fill_color) + buffer.fill(self.fill_color) + pygame.display.flip() + + pixel_view = pygame.surfarray.pixels3d(buffer) # (W, H, 3) + + current_scale = self.scale + def remake_window_buffer(window_size: IVec2): + nonlocal buffer, pixel_view, current_scale + self.res = ( + window_size[0] // self.scale, + window_size[1] // self.scale, + ) + buffer = pygame.surface.Surface(self.res) + pixel_view = pygame.surfarray.pixels3d(buffer) + current_scale = self.scale + + print() + + self.setup() + + is_running = True + clock = pygame.time.Clock() + epoch = t_prev = time.time() + self.frame_idx = -1 + while is_running: + self.frame_idx += 1 + if not self.fps_cap is None: clock.tick(self.fps_cap) + t = time.time() + self.td = t - t_prev + t_prev = t + self.t = t - epoch + print("\rFPS:", 1/self.td, " "*10, end="") + + self.render_frame(pixel_view) + + pygame.transform.scale(buffer, window.get_size(), window) + pygame.display.flip() + + keys_pressed = pygame.key.get_pressed() + self.handle_keys_pressed(keys_pressed) + + for event in pygame.event.get(): + if event.type == pygame.VIDEORESIZE: + print() + print("== resize window ==") + remake_window_buffer(event.size) + elif event.type == pygame.QUIT: + is_running = False + elif event.type == pygame.KEYUP: + self.handle_key_up(event.key, keys_pressed) + elif event.type == pygame.KEYDOWN: + self.handle_key_down(event.key, keys_pressed) + if event.key == pygame.K_q: + is_running = False + elif event.key == pygame.K_y: + fname = self.mk_dump_fname("png") + fname.parent.mkdir(parents=True, exist_ok=True) + pygame.image.save(buffer.copy(), fname) + print() + print("Saved", fname) + elif event.type == pygame.MOUSEBUTTONUP: + self.handle_mouse_button_up(event.pos, event.button, keys_pressed) + elif event.type == pygame.MOUSEBUTTONDOWN: + self.handle_mouse_button_down(event.pos, event.button, keys_pressed) + elif event.type == pygame.MOUSEMOTION: + self.handle_mouse_motion(event.pos, event.rel, event.buttons, keys_pressed) + elif event.type == pygame.MOUSEWHEEL: + self.handle_mousewheel(event.flipped, event.x, event.y, keys_pressed) + + if current_scale != self.scale: + remake_window_buffer(window.get_size()) + + finally: + self.teardown() + print() + pygame.quit() + + def render_headless(self, output_path: str, *, n_frames: int, fps: int, state_callback: Callable[["InteractiveViewer", int], None] | None, resolution=None, bitrate=None, **kw): + self.is_headless = True + self.fps = fps + + buffer = pygame.surface.Surface(self.res if resolution is None else resolution) + pixel_view = pygame.surfarray.pixels3d(buffer) # (W, H, 3) + + def do(): + try: + self.setup() + for frame in tqdm(range(n_frames), **kw, disable=n_frames==1): + self.frame_idx = frame + if state_callback is not None: + state_callback(self, frame) + + self.render_frame(pixel_view) + + yield pixel_view.copy().swapaxes(0,1) + finally: + self.teardown() + + output_path = Path(output_path) + if output_path.suffix == ".png": + if n_frames > 1 and "%" not in output_path.name: raise ValueError + output_path.parent.mkdir(parents=True, exist_ok=True) + for i, framebuffer in enumerate(do()): + with imageio.get_writer(output_path.parent / output_path.name.replace("%", f"{i:04}")) as writer: + writer.append_data(framebuffer) + else: # ffmpeg - https://imageio.readthedocs.io/en/v2.9.0/format_ffmpeg.html#ffmpeg + with imageio.get_writer(output_path, fps=fps, bitrate=bitrate) as writer: + for framebuffer in do(): + writer.append_data(framebuffer) + + def load_sphere_map(self, fname): + self._sphere_surf = pygame.image.load(fname) + self._sphere_map = pygame.surfarray.pixels3d(self._sphere_surf) + + def lookup_sphere_map_dirs(self, dirs, origins): + near, far = geometry.ray_sphere_intersect( + torch.tensor(origins), + torch.tensor(dirs), + sphere_radii = torch.tensor(origins).norm(dim=-1) * 2, + ) + hits = far.detach() + + x = hits[..., 0] + y = hits[..., 1] + z = hits[..., 2] + theta = (z / hits.norm(dim=-1)).acos() + phi = (y/x).atan() + phi[(x<0) & (y>=0)] += 3.14 + phi[(x<0) & (y< 0)] -= 3.14 + + w, h = self._sphere_map.shape[:2] + + return self._sphere_map[ + ((phi / (2*torch.pi) * w).int() % w).cpu(), + ((theta / (1*torch.pi) * h).int() % h).cpu(), + ] + + def blit_sphere_map_mask(self, pixel_view, mask=None): + dirs, origin = self.raydirs_and_cam + if mask is None: mask = (slice(None), slice(None)) + pixel_view[mask] \ + = self.lookup_sphere_map_dirs(dirs, origin[None, None, :]) + + def mk_dump_fname(self, suffix: str, uid=None) -> Path: + name = self.name.split("-")[-1] if len(self.name) > 160 else self.name + if uid is not None: name = f"{name}-{uid}" + return self.screenshot_dir / f"pygame-viewer-{datetime.now():%Y%m%d-%H%M%S}-{name}.{suffix}" diff --git a/ifield/viewer/ray_field.py b/ifield/viewer/ray_field.py new file mode 100644 index 0000000..a0919df --- /dev/null +++ b/ifield/viewer/ray_field.py @@ -0,0 +1,792 @@ +from ..data.common.scan import SingleViewUVScan +import mesh_to_sdf.scan as sdf_scan +from ..models import intersection_fields +from ..utils import geometry, helpers +from ..utils.operators import diff +from .common import InteractiveViewer +from matplotlib import cm +import matplotlib.colors as mcolors +from concurrent.futures import ThreadPoolExecutor +from textwrap import dedent +from typing import Hashable, Optional, Callable +from munch import Munch +import functools +import itertools +import numpy as np +import random +from pathlib import Path +import shutil +import subprocess +import torch +from trimesh import Trimesh +import trimesh.transformations as T + + +class ModelViewer(InteractiveViewer): + lambertian_color = (1.0, 1.0, 1.0) + max_cols = 200 + max_cols = 32 + + def __init__(self, + model : intersection_fields.IntersectionFieldAutoDecoderModel, + start_uid : Hashable, + skyward : str = "+Z", + mesh_gt_getter: Callable[[Hashable], Trimesh] | None = None, + *a, **kw): + self.model = model + self.model.eval() + self.current_uid = self._prev_uid = start_uid + self.all_uids = list(model.keys()) + + self.mesh_gt_getter = mesh_gt_getter + self.current_gt_mesh: tuple[Hashable, Trimesh] = (None, None) + + self.display_mode_normals = self.vizmodes_normals .index("medial" if self.model.hparams.output_mode == "medial_sphere" else "analytical") + self.display_mode_shading = self.vizmodes_shading .index("lambertian") + self.display_mode_centroid = self.vizmodes_centroids.index("best-centroids-colored") + self.display_mode_spheres = self.vizmodes_spheres .index(None) + self.display_mode_variation = 0 + + self.display_sphere_map_bg = True + self.atom_radius_offset = 0 + self.atom_index_solo = None + self.export_medial_surface_mesh = False + + self.light_angle1 = 0 + self.light_angle2 = 0 + + self.obj_rot = { + "-X": torch.tensor(T.rotation_matrix(angle= np.pi/2, direction=(0, 1, 0))[:3, :3], **model.device_and_dtype).T, + "+X": torch.tensor(T.rotation_matrix(angle=-np.pi/2, direction=(0, 1, 0))[:3, :3], **model.device_and_dtype).T, + "-Y": torch.tensor(T.rotation_matrix(angle= np.pi/2, direction=(1, 0, 0))[:3, :3], **model.device_and_dtype).T, + "+Y": torch.tensor(T.rotation_matrix(angle=-np.pi/2, direction=(1, 0, 0))[:3, :3], **model.device_and_dtype).T, + "-Z": torch.tensor(T.rotation_matrix(angle= np.pi, direction=(1, 0, 0))[:3, :3], **model.device_and_dtype).T, + "+Z": torch.eye(3, **model.device_and_dtype), + }[str(skyward).upper()] + self.obj_rot_inv = torch.linalg.inv(self.obj_rot) + + super().__init__(*a, **kw) + + vizmodes_normals = ( + "medial", + "analytical", + "ground_truth", + ) + vizmodes_shading = ( + None, # just atoms or medial axis + "colored-lambertian", + "lambertian", + "shade-best-radii", + "shade-all-radii", + "translucent", + "normal", + "centroid-grad-norm", # backprop + "anisotropic", # backprop + "curvature", # backprop + "glass", + "double-glass", + ) + vizmodes_centroids = ( + None, + "best-centroids", + "all-centroids", + "best-centroids-colored", + "all-centroids-colored", + "miss-centroids-colored", + "all-miss-centroids-colored", + ) + vizmodes_spheres = ( + None, + "intersecting-sphere", + "intersecting-sphere-colored", + "best-sphere", + "best-sphere-colored", + "all-spheres-colored", + ) + + def get_display_mode(self) -> tuple[str, str, Optional[str], Optional[str]]: + MARF = self.model.hparams.output_mode == "medial_sphere" + if isinstance(self.display_mode_normals, str): self.display_mode_normals = self.vizmodes_shading .index(self.display_mode_normals) + if isinstance(self.display_mode_shading, str): self.display_mode_shading = self.vizmodes_shading .index(self.display_mode_shading) + if isinstance(self.display_mode_centroid, str): self.display_mode_centroid = self.vizmodes_centroids.index(self.display_mode_centroid) + if isinstance(self.display_mode_spheres, str): self.display_mode_spheres = self.vizmodes_spheres .index(self.display_mode_spheres) + out = ( + self.vizmodes_normals [self.display_mode_normals % len(self.vizmodes_normals)], + self.vizmodes_shading [self.display_mode_shading % len(self.vizmodes_shading)], + self.vizmodes_centroids[self.display_mode_centroid % len(self.vizmodes_centroids)] if MARF else None, + self.vizmodes_spheres [self.display_mode_spheres % len(self.vizmodes_spheres)] if MARF else None, + ) + self.set_caption(" & ".join(i for i in out if i is not None)) + return out + + @property + def cam_state(self): + return super().cam_state | { + "light_angle1" : self.light_angle1, + "light_angle2" : self.light_angle2, + } + + @cam_state.setter + def cam_state(self, new_state): + InteractiveViewer.cam_state.fset(self, new_state) + self.light_angle1 = new_state.get("light_angle1", self.light_angle1) + self.light_angle2 = new_state.get("light_angle2", self.light_angle2) + + def get_current_conditioning(self) -> Optional[torch.Tensor]: + if not self.model.is_conditioned: + return None + + prev_uid = self._prev_uid # to determine if target has changed + next_z = self.model[prev_uid].detach() # interpolation target + prev_z = getattr(self, "_prev_z", next_z) # interpolation source + epoch = getattr(self, "_prev_epoch", 0) # interpolation factor + + if not self.is_headless: + now = self.t + t = (now - epoch) / 1 # 1 second + else: + now = self.frame_idx + t = (now - epoch) / self.fps # 1 second + assert t >= 0 + + if t < 1: + next_z = next_z*t + prev_z*(1-t) + + if prev_uid != self.current_uid: + self._prev_uid = self.current_uid + self._prev_z = next_z + self._prev_epoch = now + + return next_z + + def get_current_ground_truth(self) -> Trimesh | None: + if self.mesh_gt_getter is None: + return None + uid, mesh = self.current_gt_mesh + try: + if uid != self.current_uid: + print("Loading ground truth mesh...") + mesh = self.mesh_gt_getter(self.current_uid) + self.current_gt_mesh = self.current_uid, mesh + except NotImplementedError: + self.current_gt_mesh = self.current_uid, None + return None + return mesh + + def handle_keys_pressed(self, pressed): + td = super().handle_keys_pressed(pressed) + mod = pressed[self.constants.K_LALT] or pressed[self.constants.K_RALT] + if not mod and pressed[self.constants.K_f]: self.light_angle1 -= td * 0.5 + if not mod and pressed[self.constants.K_g]: self.light_angle1 += td * 0.5 + if mod and pressed[self.constants.K_f]: self.light_angle2 += td * 0.5 + if mod and pressed[self.constants.K_g]: self.light_angle2 -= td * 0.5 + return td + + def handle_key_down(self, key, keys_pressed): + super().handle_key_down(key, keys_pressed) + shift = keys_pressed[self.constants.K_LSHIFT] or keys_pressed[self.constants.K_RSHIFT] + if key == self.constants.K_o: + i = self.all_uids.index(self.current_uid) + i = (i - 1) % len(self.all_uids) + self.current_uid = self.all_uids[i] + print(self.current_uid) + if key == self.constants.K_p: + i = self.all_uids.index(self.current_uid) + i = (i + 1) % len(self.all_uids) + self.current_uid = self.all_uids[i] + print(self.current_uid) + if key == self.constants.K_SPACE: + self.display_sphere_map_bg = { + True : 255, + 255 : 0, + 0 : True, + }.get(self.display_sphere_map_bg, True) + if key == self.constants.K_u: self.export_medial_surface_mesh = True + if key == self.constants.K_x: self.display_mode_normals += -1 if shift else 1 + if key == self.constants.K_c: self.display_mode_shading += -1 if shift else 1 + if key == self.constants.K_v: self.display_mode_centroid += -1 if shift else 1 + if key == self.constants.K_b: self.display_mode_spheres += -1 if shift else 1 + if key == self.constants.K_e: self.display_mode_variation+= -1 if shift else 1 + if key == self.constants.K_c: self.display_mode_variation = 0 + if key == self.constants.K_0: self.atom_index_solo = None + if key == self.constants.K_1: self.atom_index_solo = 0 if self.atom_index_solo != 0 else None + if key == self.constants.K_2: self.atom_index_solo = 1 if self.atom_index_solo != 1 else None + if key == self.constants.K_3: self.atom_index_solo = 2 if self.atom_index_solo != 2 else None + if key == self.constants.K_4: self.atom_index_solo = 3 if self.atom_index_solo != 3 else None + if key == self.constants.K_5: self.atom_index_solo = 4 if self.atom_index_solo != 4 else None + if key == self.constants.K_6: self.atom_index_solo = 5 if self.atom_index_solo != 5 else None + if key == self.constants.K_7: self.atom_index_solo = 6 if self.atom_index_solo != 6 else None + if key == self.constants.K_8: self.atom_index_solo = 7 if self.atom_index_solo != 7 else None + if key == self.constants.K_9: self.atom_index_solo = self.atom_index_solo + (-1 if shift else 1) if self.atom_index_solo is not None else 0 + + def handle_mouse_button_down(self, pos, button, keys_pressed): + super().handle_mouse_button_down(pos, button, keys_pressed) + if button in (1, 3): + self.display_mode_spheres += 1 if button == 1 else -1 + + def handle_mousewheel(self, flipped, x, y, keys_pressed): + shift = keys_pressed[self.constants.K_LSHIFT] or keys_pressed[self.constants.K_RSHIFT] + if not shift: + super().handle_mousewheel(flipped, x, y, keys_pressed) + else: + self.atom_radius_offset += 0.005 * y + print() + print("atom_radius_offset:", self.atom_radius_offset) + + def setup(self): + if not self.is_headless: + print(dedent(""" + WASD + PG Up/Down - translate + ARROWS - rotate + + (SHIFT+) C - Next/(Prev) shading mode + (SHIFT+) V - Next/(Prev) centroids mode + (SHIFT+) B - Next/(Prev) sphere mode + Mouse L/ R - Next/ Prev sphere mode + (SHIFT+) E - Next/(Prev) variation (for quick experimentation within a shading mode) + SHIFT + Scroll - Offset atom radius + ALT + Scroll - Modify FoV (_true_ zoom) + Mouse Scroll - Translate in/out ("zoom", moves camera to/from to point of focus) + Alt+PG Up/Down - Translate in/out ("zoom", moves camera to/from to point of focus) + + F / G - rotate light left / right + ALT+ F / G - rotate light up / down + CTRL / SHIFT - faster/slower rotation + O / P - prev/next object + 1-9 - solo atom + 0 - show all atoms + + / - - decrease/increase pixel scale + R - rotate continuously + H / SHIFT+H / CTRL+H - load/save/print camera state + Enter - reset camera state + Y - save screenshot + U - save mesh of centroids + Space - cycle sphere map background + Q - quit + """).strip()) + + fname = Path(__file__).parent.resolve() / "assets/texturify_pano-1-4.jpg" + self.load_sphere_map(fname) + + if self.model.hparams.output_mode == "medial_sphere": + @self.model.net.register_forward_hook + def atom_offset_radius_and_solo(model, input, output): + slice = (..., [i+3 for i in range(0, output.shape[-1], 4)]) + output[slice] += self.atom_radius_offset * output[slice].sign() + if self.atom_index_solo is not None: + x = self.atom_index_solo * 4 + x = x % output.shape[-1] + output = output[..., list(range(x, x+4))] + return output + self._atom_offset_radius_and_solo_hook = atom_offset_radius_and_solo + + def teardown(self): + if hasattr(self, "_atom_offset_radius_and_solo_hook"): + self._atom_offset_radius_and_solo_hook.remove() + del self._atom_offset_radius_and_solo_hook + + @torch.no_grad() + def render_frame(self, pixel_view: np.ndarray): # (W, H, 3) dtype=uint8 + MARF = self.model.hparams.output_mode == "medial_sphere" + PRIF = self.model.hparams.output_mode == "orthogonal_plane" + assert (MARF or PRIF) and MARF != PRIF + device_and_dtype = self.model.device_and_dtype + device = self.model.device + dtype = self.model.dtype + + ( + vizmode_normals, + vizmode_shading, + vizmode_centroids, + vizmode_spheres, + ) = self.get_display_mode() + + dirs, origins = self.raydirs_and_cam + origins = origins.detach().clone().to(**device_and_dtype) + dirs = dirs .detach().clone().to(**device_and_dtype) + + if vizmode_normals != "ground_truth" or self.get_current_ground_truth() is None: + + # enable grad or not + do_jac = PRIF or vizmode_normals == "analytical" + do_jac_medial = MARF and "centroid-grad-norm" in (vizmode_shading or "") + do_shape_operator = "anisotropic" in (vizmode_shading or "") or "curvature" in (vizmode_shading or "") + do_grad = do_jac or do_jac_medial or do_shape_operator + if do_grad: + origins = origins.broadcast_to(dirs.shape) + + self.model.eval() + latent = self.get_current_conditioning() + if self.max_cols is None or self.max_cols > dirs.shape[0]: + chunks = [slice(None)] + else: + chunks = [slice(col, col+self.max_cols) for col in range(0, dirs.shape[0], self.max_cols)] + forward_chunks = [] + for chunk in chunks: + self.model.zero_grad() + origins_chunk = origins[chunk if origins.ndim != 1 else slice(None)] @ self.obj_rot + dirs_chunk = dirs [chunk] @ self.obj_rot + if do_grad: + origins_chunk.requires_grad = dirs_chunk.requires_grad = True + + @forward_chunks.append + @(lambda f: f(origins_chunk, dirs_chunk)) + @torch.set_grad_enabled(do_grad) + def forward_chunk(origins, dirs) -> Munch: + if PRIF: + intersections, is_intersecting = self.model(dict(origins=origins, dirs=dirs), z=latent, normalize_origins=True) + is_intersecting = is_intersecting > 0.5 + elif MARF: + ( + depths, silhouettes, intersections, + intersection_normals, is_intersecting, + sphere_centers, sphere_radii, + + atom_indices, + all_intersections, all_intersection_normals, all_depths, all_silhouettes, all_is_intersecting, + all_sphere_centers, all_sphere_radii, + ) = self.model.forward(dict(origins=origins, dirs=dirs), z=latent, + intersections_only = False, + return_all_atoms = True, + ) + + if do_jac: + jac = diff.jacobian(intersections, origins, detach=not do_shape_operator) + intersection_normals = self.model.compute_normals_from_intersection_origin_jacobian(jac, dirs.detach()) + + if do_jac_medial: + sphere_centers_jac = diff.jacobian(sphere_centers, origins, detach=True) + + if do_shape_operator: + hess = diff.jacobian(intersection_normals, origins, detach=True)[is_intersecting, :, :] + N = intersection_normals.detach()[is_intersecting, :] + TM = (torch.eye(3, device=device) - N[..., None, :]*N[..., :, None]) # projection onto tangent plane + # shape operator, i.e. total derivative of the surface normal w.r.t. the tangent space + shape_operator = hess @ TM + + return Munch((k, v.detach()) for k, v in locals().items() if isinstance(v, torch.Tensor)) + + intersections = torch.cat([chunk.intersections for chunk in forward_chunks], dim=0) + is_intersecting = torch.cat([chunk.is_intersecting for chunk in forward_chunks], dim=0) + intersection_normals = torch.cat([chunk.intersection_normals for chunk in forward_chunks], dim=0) + if MARF: + all_sphere_centers = torch.cat([chunk.all_sphere_centers for chunk in forward_chunks], dim=0) + all_sphere_radii = torch.cat([chunk.all_sphere_radii for chunk in forward_chunks], dim=0) + atom_indices = torch.cat([chunk.atom_indices for chunk in forward_chunks], dim=0) + silhouettes = torch.cat([chunk.silhouettes for chunk in forward_chunks], dim=0) + sphere_centers = torch.cat([chunk.sphere_centers for chunk in forward_chunks], dim=0) + sphere_radii = torch.cat([chunk.sphere_radii for chunk in forward_chunks], dim=0) + if do_jac_medial: + sphere_centers_jac = torch.cat([chunk.sphere_centers_jac for chunk in forward_chunks], dim=0) + if do_shape_operator: + shape_operator = torch.cat([chunk.shape_operator for chunk in forward_chunks], dim=0) + + n_atoms = all_sphere_centers.shape[-2] if MARF else 1 + + intersections = intersections @ self.obj_rot_inv + intersection_normals = intersection_normals @ self.obj_rot_inv + sphere_centers = sphere_centers @ self.obj_rot_inv if sphere_centers is not None else None + all_sphere_centers = all_sphere_centers @ self.obj_rot_inv if all_sphere_centers is not None else None + + else: # render ground truth mesh + # HACK: we use a thread to not break the pygame opengl context + with ThreadPoolExecutor(max_workers=1) as p: + scan = p.submit(sdf_scan.Scan, self.get_current_ground_truth(), + camera_transform = self.cam2world.numpy(), + resolution = self.res[1], + calculate_normals = True, + fov = self.cam_fov_y, + z_near = 0.001, + z_far = 50, + no_flip_backfaced_normals = True + ).result() + n_atoms, MARF, PRIF = 1, False, True + is_intersecting = torch.zeros(self.res, dtype=bool) + is_intersecting[ (self.res[0]-self.res[1]) // 2 : (self.res[0]-self.res[1]) // 2 + self.res[1], : ] = torch.tensor(scan.depth_buffer != 0, dtype=bool) + intersections = torch.zeros((*is_intersecting.shape, 3), dtype=dtype) + intersection_normals = torch.zeros((*is_intersecting.shape, 3), dtype=dtype) + intersections [is_intersecting] = torch.tensor(scan.points, dtype=dtype) + intersection_normals[is_intersecting] = torch.tensor(scan.normals, dtype=dtype) + is_intersecting = is_intersecting .flip(1).to(device) + intersections = intersections .flip(1).to(device) + intersection_normals = intersection_normals.flip(1).to(device) + + mask = is_intersecting.cpu() + + mx, my = self.mouse_position + w, h = dirs.shape[:2] + + # fill white + if self.display_sphere_map_bg == True: + self.blit_sphere_map_mask(pixel_view) + else: + pixel_view[:] = self.display_sphere_map_bg + + # draw to buffer + + to_cam = -dirs.detach() + + # light direction + extra = np.pi if vizmode_shading == "translucent" else 0 + LM = torch.tensor(T.rotation_matrix(angle=self.light_angle2, direction=(0, 1, 0))[:3, :3], dtype=dtype) + LM = torch.tensor(T.rotation_matrix(angle=self.light_angle1 + extra, direction=(1, 0, 0))[:3, :3], dtype=dtype) @ LM + to_light = (self.cam2world[:3, :3] @ LM @ torch.tensor((1, 1, 3), dtype=dtype)).to(device)[None, :] + to_light = to_light / to_light.norm(dim=-1, keepdim=True) + + # used to color different atom candidates + color_set = tuple(map(helpers.hex2tuple, + itertools.chain( + mcolors.TABLEAU_COLORS.values(), + #list(mcolors.TABLEAU_COLORS.values())[::-1], + #['#f8481c', '#c20078', '#35530a', '#010844', '#a8ff04'], + mcolors.XKCD_COLORS.values(), + ) + )) + color_per_atom = (*zip(*zip(range(n_atoms), itertools.cycle(color_set))),)[1] + + + # shade hits + + if vizmode_shading is None: + pass + elif vizmode_shading == "colored-lambertian": + if n_atoms > 1: + color = torch.tensor(color_per_atom, device=device)[(*atom_indices[is_intersecting].T,)] + else: + color = torch.tensor(color_set[(0 if self.atom_index_solo is None else self.atom_index_solo) % len(color_set)], device=device) + lambertian = torch.einsum("id,id->i", + intersection_normals[is_intersecting, :], + to_light, + )[..., None] + + pixel_view[mask, :] = (color * + torch.einsum("id,id->i", + intersection_normals[is_intersecting, :], + to_cam[is_intersecting, :], + )[..., None]).int().cpu() + pixel_view[mask, :] = ( + 255 * lambertian.clamp(0, 1).pow(32) + + color * (lambertian + 0.25).clamp(0, 1) * (1-lambertian.clamp(0, 1).pow(32)) + ).cpu() + elif vizmode_shading == "lambertian": + lambertian = torch.einsum("id,id->i", + intersection_normals[is_intersecting, :], + to_light, + )[..., None].clamp(0, 1) + + if self.lambertian_color == (1.0, 1.0, 1.0): + pixel_view[mask, :] = (255 * lambertian).cpu() + else: + color = 255*torch.tensor(self.lambertian_color, device=device) + pixel_view[mask, :] = (color * + torch.einsum("id,id->i", + intersection_normals[is_intersecting, :], + to_cam[is_intersecting, :], + )[..., None]).int().cpu() + pixel_view[mask, :] = ( + 255 * lambertian.clamp(0, 1).pow(32) + + color * (lambertian + 0.25).clamp(0, 1) * (1-lambertian.clamp(0, 1).pow(32)) + ).cpu() + elif vizmode_shading == "translucent" and MARF: + lambertian = torch.einsum("id,id->i", + intersection_normals[is_intersecting, :], + to_light, + )[..., None].abs().clamp(0, 1) + + distortion = 0.08 + power = 16 + ambient = 0 + thickness = sphere_radii[is_intersecting].detach() + if self.display_mode_variation % 2: + thickness = thickness.mean() + + color1 = torch.tensor((1, 0.5, 0.5), **device_and_dtype) # subsurface + color2 = torch.tensor((0, 1, 1), **device_and_dtype) # diffuse + + l = to_light + intersection_normals[is_intersecting, :] * distortion + d = (to_cam[is_intersecting, :] * -l).sum(dim=-1).clamp(0, None).pow(power) + f = (d + ambient) * (1/(0.05 + thickness)) + + pixel_view[((dirs * to_light).sum(dim=-1) > 0.99).cpu(), :] = 255 # draw light source + + pixel_view[mask, :] = (255 * ( + color2 * (0.05 + lambertian*0.15) + + color1 * 0.3 * f[..., None] + ).clamp(0, 1)).cpu() + elif vizmode_shading == "anisotropic" and vizmode_normals != "ground_truth": + eigvals, eigvecs = torch.linalg.eig(shape_operator.mT) # slow, complex output, not sorted + eigvals, indices = eigvals.abs().sort(dim=-1) + eigvecs = (eigvecs.abs() * eigvecs.real.sign()).take_along_dim(indices[..., None, :], dim=-1) + eigvecs = eigvecs.mT + + s = self.display_mode_variation % 5 + if s in (0, 1): + # try to keep these below 0.2: + if s == 0: a1, a2 = 0.05, 0.3 + if s == 1: a1, a2 = 0.3, 0.05 + + # == Ward anisotropic specular reflectance == + + # G.J. Ward, Measuring and modeling anisotropic reflection, in: + # Proceedings of the 19th Annual Conference on Computer Graphics and + # Interactive Techniques, 1992: pp. 265–272. + + eigvecs /= eigvecs.norm(dim=-1, keepdim=True) + + N = intersection_normals[is_intersecting, :] + H = to_cam[is_intersecting, :] + to_light + H = H / H.norm(dim=-1, keepdim=True) + specular = (1/(4*torch.pi * a1*a2 * torch.sqrt(( + (N * to_cam[is_intersecting, :]).sum(dim=-1) * + (N * to_light ).sum(dim=-1) + )))) * torch.exp( + -2 * ( + ((H * eigvecs[..., 2, :]).sum(dim=-1) / a1).pow(2) + + + ((H * eigvecs[..., 1, :]).sum(dim=-1) / a2).pow(2) + ) / ( + 1 + (N * H).sum(dim=-1) + ) + ) + specular = specular.clamp(0, None).nan_to_num(0, 0, 0) + lambertian = torch.einsum("id,id->i", N, to_light ).clamp(0, None) + + color1 = 0.4 * torch.tensor((1, 1, 1), **device_and_dtype) # specular + color2 = 0.4 * torch.tensor((0, 1, 1), **device_and_dtype) # diffuse + pixel_view[mask, :] = (255 * ( + color1 * specular [..., None] + + color2 * lambertian[..., None] + ).clamp(0, 1)).int().cpu() + if s == 2: + pixel_view[mask, :] = (255 * ( + eigvecs[..., 2, :].abs().clamp(0, 1) # orientation only + )).int().cpu() + elif s == 3: + pixel_view[mask, :] = (255 * ( + eigvecs[..., 1, :].abs().clamp(0, 1) # orientation only + )).int().cpu() + elif s == 4: + pixel_view[mask, :] = (255 * ( + eigvecs[..., 0, :].abs().clamp(0, 1) # orientation only + )).int().cpu() + elif vizmode_shading == "shade-best-radii" and MARF: + lambertian = torch.einsum("id,id->i", + intersection_normals[is_intersecting, :], + to_light, + )[..., None] + + radii = sphere_radii[is_intersecting] + radii = radii - 0.04 + radii = radii / 0.4 + + colors = cm.plasma(radii.clamp(0, 1).cpu())[..., :3] + pixel_view[mask, :] = 255 * ( + lambertian.pow(32).clamp(0, 1).cpu().numpy() + + colors * (lambertian + 0.25).clamp(0, 1).cpu().numpy() * (1-lambertian.pow(32).clamp(0, 1)).cpu().numpy() + ) + elif vizmode_shading == "shade-all-radii" and MARF: + radii = sphere_radii[is_intersecting][..., None] + radii /= radii.max() + if n_atoms > 1: + color = torch.tensor(color_per_atom, device=device)[(*atom_indices[is_intersecting].T,)] + else: + color = torch.tensor(color_set[(0 if self.atom_index_solo is None else self.atom_index_solo) % len(color_set)], device=device) + pixel_view[mask, :] = (color * radii).int().cpu() + elif vizmode_shading == "normal": + normal = intersection_normals[is_intersecting, :] + pixel_view[mask, :] = (255 * (normal * 0.5 + 0.5) ).int().cpu() + elif vizmode_shading == "curvature" and vizmode_normals != "ground_truth": + eigvals = torch.linalg.eigvals(shape_operator.mT) # complex output, not sorted + + # we sort them by absolute magnitude, not the real component + _, indices = (eigvals.abs() * eigvals.real.sign()).sort(dim=-1) + eigvals = eigvals.real.take_along_dim(indices, dim=-1) + + s = self.display_mode_variation % (6 if MARF else 5) + if s==0: out = (eigvals[..., [0, 2]].mean(dim=-1, keepdim=True) / 25).tanh() # mean curvature + if s==1: out = (eigvals[..., [0, 2]].prod(dim=-1, keepdim=True) / 25).tanh() # gaussian curvature + if s==2: out = (eigvals[..., [2]] / 25).tanh() # maximum principal curvature - k1 + if s==3: out = (eigvals[..., [1]] / 25).tanh() # some curvature + if s==4: out = (eigvals[..., [0]] / 25).tanh() # minimum principal curvature - k2 + if s==5: out = ((sphere_radii[is_intersecting][..., None].detach() - 1 / eigvals[..., [2]].clamp(1e-8, None)) * 5).tanh().clamp(0, None) + + lambertian = torch.einsum("id,id->i", + intersection_normals[is_intersecting, :], + to_light, + )[..., None] + + pixel_view[mask, :] = (255 * (lambertian+0.5).clamp(0, 1) * torch.cat(( + 1+out.clamp(-1, 0), + 1-out.abs(), + 1-out.clamp(0, 1), + ), dim=-1)).int().cpu() + elif vizmode_shading == "centroid-grad-norm" and MARF: + asd = sphere_centers_jac[is_intersecting, :, :].norm(dim=-2).mean(dim=-1, keepdim=True) + asd -= asd.min() + asd /= asd.max() + pixel_view[mask, :] = (255 * asd).cpu() + elif "glass" in vizmode_shading: + normals = intersection_normals[is_intersecting, :] + to_cam_ = to_cam [is_intersecting, :] + # "Empiricial Approximation" of fresnel + # https://developer.download.nvidia.com/CgTutorial/cg_tutorial_chapter07.html via + # http://kylehalladay.com/blog/tutorial/2014/02/18/Fresnel-Shaders-From-The-Ground-Up.html + cos = torch.einsum("id,id->i", normals, to_cam_ )[..., None] + bias, scale, power = 0, 4, 3 + fresnel = (bias + scale*(1-cos)**power).clamp(0, 1) + + #reflection + reflection = -to_cam_ - 2*(-cos)*normals + + #refraction + r = 1 / 1.5 # refractive index, air -> glass + refraction = -r*to_cam_ + (r*cos - (1-r**2*(1-cos**2)).sqrt()) * normals + exit_point = intersections[is_intersecting, :] + + # reflect the refraction over the plane defined by the refraction direction and the sphere center, resulting in the second refraction + if vizmode_shading == "double-glass" and MARF: + cos2 = torch.einsum("id,id->i", refraction, -to_cam_ )[..., None] + pn = -to_cam_ - cos2*refraction + pn /= pn.norm(dim=-1, keepdim=True) + + refraction = -to_cam_ - 2*torch.einsum("id,id->i", pn, -to_cam_ )[..., None]*pn + + exit_point -= sphere_centers[is_intersecting, :] + exit_point = exit_point - 2*torch.einsum("id,id->i", pn, exit_point )[..., None]*pn + exit_point += sphere_centers[is_intersecting, :] + + fresnel = np.asanyarray(fresnel.cpu()) + pixel_view[mask, :] \ + = self.lookup_sphere_map_dirs(reflection, intersections[is_intersecting, :]) * fresnel \ + + self.lookup_sphere_map_dirs(refraction, exit_point) * (1-fresnel) + else: # flat + pixel_view[mask, :] = 80 + + if not MARF: return + + # overlay medial atoms + + if vizmode_spheres is not None: + # show miss distance in red + s = silhouettes.detach()[~is_intersecting].clamp(0, 1) + s /= s.max() + pixel_view[~mask, 1] = (s * 255).cpu() + pixel_view[:, 2] = pixel_view[:, 1] + + mouse_hits = 0 <= mx < w and 0 <= my < h and mask[mx, my] + draw_intersecting = "intersecting-sphere" in vizmode_spheres + draw_best = "best-sphere" in vizmode_spheres + draw_color = "-sphere-colored" in vizmode_spheres + draw_all = "all-spheres-colored" in vizmode_spheres + + def get_nears(): + if draw_all: + projected, near, far, is_intersecting = geometry.ray_sphere_intersect( + torch.tensor(origins), + torch.tensor(dirs[..., None, :]), + sphere_centers = all_sphere_centers[mx, my][None, None, ...], + sphere_radii = all_sphere_radii [mx, my][None, None, ...], + allow_nans = False, + return_parts = True, + ) + + depths = (near - origins).norm(dim=-1) + atom_indices_ = torch.where(is_intersecting, depths.detach(), depths.detach()+100).argmin(dim=-1, keepdim=True) + is_intersecting = is_intersecting.any(dim=-1) + projected = None + near = near.take_along_dim(atom_indices_[..., None], -2).squeeze(-2) + far = None + sphere_centers_ = all_sphere_centers[mx, my][None, None, ...].take_along_dim(atom_indices_[..., None], -2).squeeze(-2) + + normals = near[is_intersecting, :] - sphere_centers_[is_intersecting, :] + normals /= torch.linalg.norm(normals, dim=-1)[..., None] + + color = torch.tensor(color_per_atom, device=device)[(*atom_indices_[is_intersecting].T,)] + yield color, projected, near, far, is_intersecting, normals + + if (mouse_hits and draw_intersecting) or draw_best: + projected, near, far, is_intersecting = geometry.ray_sphere_intersect( + torch.tensor(origins), + torch.tensor(dirs), + # unit-sphere by default + sphere_centers = sphere_centers[mx, my][None, None, ...], + sphere_radii = sphere_radii [mx, my][None, None, ...], + return_parts = True, + ) + + normals = near[is_intersecting, :] - sphere_centers[mx, my][None, ...] + normals /= torch.linalg.norm(normals, dim=-1)[..., None] + color = (255, 255, 255) if not draw_color else color_per_atom[atom_indices[mx, my]] + yield torch.tensor(color, device=device), projected, near, far, is_intersecting, normals + + # draw sphere with lambertian shading + for color, projected, near, far, is_intersecting_2, normals in get_nears(): + lambertian = torch.einsum("...id,...id->...i", normals, to_light )[..., None] + pixel_view[is_intersecting_2.cpu(), :] = ( + 255*lambertian.pow(32).clamp(0, 1) + + color * (lambertian + 0.25).clamp(0, 1) * (1-lambertian.pow(32).clamp(0, 1)) + ).cpu() + + # overlay points / sphere centers + + if vizmode_centroids is not None: + cam2world_inv = torch.tensor(self.cam2world_inv, **device_and_dtype) + intrinsics = torch.tensor(self.intrinsics, **device_and_dtype) + + def get_coords(): + miss_centroid = "miss-centroids" in vizmode_centroids + mask = is_intersecting if not miss_centroid else ~is_intersecting + if vizmode_centroids in ("all-centroids-colored", "all-miss-centroids-colored"): + # we use temporal dithering to the show all overlapping centers + for color, atom_index in sorted(zip(itertools.chain(color_set), range(n_atoms)), key=lambda x: random.random()): + yield color, all_sphere_centers[..., atom_index, :][mask], mask + elif "all-centroids" in vizmode_centroids: + yield (80, 150, 80), all_sphere_centers[mask].reshape(-1, 3), mask # [:, 3] + + if "centroids-colored" in vizmode_centroids: + if n_atoms == 1: + color = color_set[(0 if self.atom_index_solo is None else self.atom_index_solo) % len(color_set)] + else: + color = torch.tensor(color_per_atom, device=device)[(*atom_indices[mask].T,)].cpu() + else: + color = (0, 0, 0) + yield color, sphere_centers[mask], mask + + for i, (color, coords, coord_mask) in enumerate(get_coords()): + if self.export_medial_surface_mesh: + fname = self.mk_dump_fname("ply", uid=i) + p = torch.zeros_like(sphere_centers) + c = torch.zeros_like(sphere_centers) + p[coord_mask, :] = coords + c[coord_mask, :] = torch.tensor(color, device=p.device) / 255 + SingleViewUVScan( + hits = ( mask).numpy(), + miss = (~mask).numpy(), + points = p.cpu().numpy(), + colors = c.cpu().numpy(), + normals=None, distances=None, cam_pos=None, + cam_mat4=None, proj_mat4=None, transforms=None, + ).to_mesh().export(str(fname), file_type="ply") + print("dumped", fname) + if shutil.which("f3d"): + subprocess.Popen(["f3d", "-gsy", "--up=+z", "--bg-color=1,1,1", fname], close_fds=True) + + coords = torch.cat((coords, torch.ones((*coords.shape[:-1], 1), **device_and_dtype)), dim=-1) + + coords = torch.einsum("...ij,...kj->...ki", cam2world_inv, coords)[..., :3] + coords = geometry.project(coords[..., 0], coords[..., 1], coords[..., 2], intrinsics) + + in_view = functools.reduce(torch.mul, ( + coords[:, 0] < pixel_view.shape[1], + coords[:, 0] >= 0, + coords[:, 1] < pixel_view.shape[0], + coords[:, 1] >= 0, + )).cpu() + + coords = coords[in_view, :] + if not isinstance(color, tuple): + color = color[in_view, :] + + pixel_view[(*coords[..., [1, 0]].int().T.cpu(),)] = color + + self.export_medial_surface_mesh = False diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 0000000..96a9658 --- /dev/null +++ b/poetry.lock @@ -0,0 +1,6369 @@ +# This file is automatically @generated by Poetry and should not be changed by hand. + +[[package]] +name = "absl-py" +version = "1.4.0" +description = "Abseil Python Common Libraries, see https://github.com/abseil/abseil-py." +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ + {file = "absl-py-1.4.0.tar.gz", hash = "sha256:d2c244d01048ba476e7c080bd2c6df5e141d211de80223460d5b3b8a2a58433d"}, + {file = "absl_py-1.4.0-py3-none-any.whl", hash = "sha256:0d3fe606adfa4f7db64792dd4c7aee4ee0c38ab75dfd353b7a83ed3e957fcb47"}, +] + +[[package]] +name = "aiofiles" +version = "22.1.0" +description = "File support for asyncio." +category = "dev" +optional = false +python-versions = ">=3.7,<4.0" +files = [ + {file = "aiofiles-22.1.0-py3-none-any.whl", hash = "sha256:1142fa8e80dbae46bb6339573ad4c8c0841358f79c6eb50a493dceca14621bad"}, + {file = "aiofiles-22.1.0.tar.gz", hash = "sha256:9107f1ca0b2a5553987a94a3c9959fe5b491fdf731389aa5b7b1bd0733e32de6"}, +] + +[[package]] +name = "aiohttp" +version = "3.8.4" +description = "Async http client/server framework (asyncio)" +category = "main" +optional = false +python-versions = ">=3.6" +files = [ + {file = "aiohttp-3.8.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5ce45967538fb747370308d3145aa68a074bdecb4f3a300869590f725ced69c1"}, + {file = "aiohttp-3.8.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b744c33b6f14ca26b7544e8d8aadff6b765a80ad6164fb1a430bbadd593dfb1a"}, + {file = "aiohttp-3.8.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1a45865451439eb320784918617ba54b7a377e3501fb70402ab84d38c2cd891b"}, + {file = "aiohttp-3.8.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a86d42d7cba1cec432d47ab13b6637bee393a10f664c425ea7b305d1301ca1a3"}, + {file = "aiohttp-3.8.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ee3c36df21b5714d49fc4580247947aa64bcbe2939d1b77b4c8dcb8f6c9faecc"}, + {file = "aiohttp-3.8.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:176a64b24c0935869d5bbc4c96e82f89f643bcdf08ec947701b9dbb3c956b7dd"}, + {file = "aiohttp-3.8.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c844fd628851c0bc309f3c801b3a3d58ce430b2ce5b359cd918a5a76d0b20cb5"}, + {file = "aiohttp-3.8.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5393fb786a9e23e4799fec788e7e735de18052f83682ce2dfcabaf1c00c2c08e"}, + {file = "aiohttp-3.8.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e4b09863aae0dc965c3ef36500d891a3ff495a2ea9ae9171e4519963c12ceefd"}, + {file = "aiohttp-3.8.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:adfbc22e87365a6e564c804c58fc44ff7727deea782d175c33602737b7feadb6"}, + {file = "aiohttp-3.8.4-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:147ae376f14b55f4f3c2b118b95be50a369b89b38a971e80a17c3fd623f280c9"}, + {file = "aiohttp-3.8.4-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:eafb3e874816ebe2a92f5e155f17260034c8c341dad1df25672fb710627c6949"}, + {file = "aiohttp-3.8.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c6cc15d58053c76eacac5fa9152d7d84b8d67b3fde92709195cb984cfb3475ea"}, + {file = "aiohttp-3.8.4-cp310-cp310-win32.whl", hash = "sha256:59f029a5f6e2d679296db7bee982bb3d20c088e52a2977e3175faf31d6fb75d1"}, + {file = "aiohttp-3.8.4-cp310-cp310-win_amd64.whl", hash = "sha256:fe7ba4a51f33ab275515f66b0a236bcde4fb5561498fe8f898d4e549b2e4509f"}, + {file = "aiohttp-3.8.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3d8ef1a630519a26d6760bc695842579cb09e373c5f227a21b67dc3eb16cfea4"}, + {file = "aiohttp-3.8.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5b3f2e06a512e94722886c0827bee9807c86a9f698fac6b3aee841fab49bbfb4"}, + {file = "aiohttp-3.8.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3a80464982d41b1fbfe3154e440ba4904b71c1a53e9cd584098cd41efdb188ef"}, + {file = "aiohttp-3.8.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b631e26df63e52f7cce0cce6507b7a7f1bc9b0c501fcde69742130b32e8782f"}, + {file = "aiohttp-3.8.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3f43255086fe25e36fd5ed8f2ee47477408a73ef00e804cb2b5cba4bf2ac7f5e"}, + {file = "aiohttp-3.8.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4d347a172f866cd1d93126d9b239fcbe682acb39b48ee0873c73c933dd23bd0f"}, + {file = "aiohttp-3.8.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a3fec6a4cb5551721cdd70473eb009d90935b4063acc5f40905d40ecfea23e05"}, + {file = "aiohttp-3.8.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:80a37fe8f7c1e6ce8f2d9c411676e4bc633a8462844e38f46156d07a7d401654"}, + {file = "aiohttp-3.8.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d1e6a862b76f34395a985b3cd39a0d949ca80a70b6ebdea37d3ab39ceea6698a"}, + {file = "aiohttp-3.8.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:cd468460eefef601ece4428d3cf4562459157c0f6523db89365202c31b6daebb"}, + {file = "aiohttp-3.8.4-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:618c901dd3aad4ace71dfa0f5e82e88b46ef57e3239fc7027773cb6d4ed53531"}, + {file = "aiohttp-3.8.4-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:652b1bff4f15f6287550b4670546a2947f2a4575b6c6dff7760eafb22eacbf0b"}, + {file = "aiohttp-3.8.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80575ba9377c5171407a06d0196b2310b679dc752d02a1fcaa2bc20b235dbf24"}, + {file = "aiohttp-3.8.4-cp311-cp311-win32.whl", hash = "sha256:bbcf1a76cf6f6dacf2c7f4d2ebd411438c275faa1dc0c68e46eb84eebd05dd7d"}, + {file = "aiohttp-3.8.4-cp311-cp311-win_amd64.whl", hash = "sha256:6e74dd54f7239fcffe07913ff8b964e28b712f09846e20de78676ce2a3dc0bfc"}, + {file = "aiohttp-3.8.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:880e15bb6dad90549b43f796b391cfffd7af373f4646784795e20d92606b7a51"}, + {file = "aiohttp-3.8.4-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb96fa6b56bb536c42d6a4a87dfca570ff8e52de2d63cabebfd6fb67049c34b6"}, + {file = "aiohttp-3.8.4-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4a6cadebe132e90cefa77e45f2d2f1a4b2ce5c6b1bfc1656c1ddafcfe4ba8131"}, + {file = "aiohttp-3.8.4-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f352b62b45dff37b55ddd7b9c0c8672c4dd2eb9c0f9c11d395075a84e2c40f75"}, + {file = "aiohttp-3.8.4-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ab43061a0c81198d88f39aaf90dae9a7744620978f7ef3e3708339b8ed2ef01"}, + {file = "aiohttp-3.8.4-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c9cb1565a7ad52e096a6988e2ee0397f72fe056dadf75d17fa6b5aebaea05622"}, + {file = "aiohttp-3.8.4-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:1b3ea7edd2d24538959c1c1abf97c744d879d4e541d38305f9bd7d9b10c9ec41"}, + {file = "aiohttp-3.8.4-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:7c7837fe8037e96b6dd5cfcf47263c1620a9d332a87ec06a6ca4564e56bd0f36"}, + {file = "aiohttp-3.8.4-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:3b90467ebc3d9fa5b0f9b6489dfb2c304a1db7b9946fa92aa76a831b9d587e99"}, + {file = "aiohttp-3.8.4-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:cab9401de3ea52b4b4c6971db5fb5c999bd4260898af972bf23de1c6b5dd9d71"}, + {file = "aiohttp-3.8.4-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:d1f9282c5f2b5e241034a009779e7b2a1aa045f667ff521e7948ea9b56e0c5ff"}, + {file = "aiohttp-3.8.4-cp36-cp36m-win32.whl", hash = "sha256:5e14f25765a578a0a634d5f0cd1e2c3f53964553a00347998dfdf96b8137f777"}, + {file = "aiohttp-3.8.4-cp36-cp36m-win_amd64.whl", hash = "sha256:4c745b109057e7e5f1848c689ee4fb3a016c8d4d92da52b312f8a509f83aa05e"}, + {file = "aiohttp-3.8.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:aede4df4eeb926c8fa70de46c340a1bc2c6079e1c40ccf7b0eae1313ffd33519"}, + {file = "aiohttp-3.8.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ddaae3f3d32fc2cb4c53fab020b69a05c8ab1f02e0e59665c6f7a0d3a5be54f"}, + {file = "aiohttp-3.8.4-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c4eb3b82ca349cf6fadcdc7abcc8b3a50ab74a62e9113ab7a8ebc268aad35bb9"}, + {file = "aiohttp-3.8.4-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9bcb89336efa095ea21b30f9e686763f2be4478f1b0a616969551982c4ee4c3b"}, + {file = "aiohttp-3.8.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c08e8ed6fa3d477e501ec9db169bfac8140e830aa372d77e4a43084d8dd91ab"}, + {file = "aiohttp-3.8.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c6cd05ea06daca6ad6a4ca3ba7fe7dc5b5de063ff4daec6170ec0f9979f6c332"}, + {file = "aiohttp-3.8.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b7a00a9ed8d6e725b55ef98b1b35c88013245f35f68b1b12c5cd4100dddac333"}, + {file = "aiohttp-3.8.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:de04b491d0e5007ee1b63a309956eaed959a49f5bb4e84b26c8f5d49de140fa9"}, + {file = "aiohttp-3.8.4-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:40653609b3bf50611356e6b6554e3a331f6879fa7116f3959b20e3528783e699"}, + {file = "aiohttp-3.8.4-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:dbf3a08a06b3f433013c143ebd72c15cac33d2914b8ea4bea7ac2c23578815d6"}, + {file = "aiohttp-3.8.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:854f422ac44af92bfe172d8e73229c270dc09b96535e8a548f99c84f82dde241"}, + {file = "aiohttp-3.8.4-cp37-cp37m-win32.whl", hash = "sha256:aeb29c84bb53a84b1a81c6c09d24cf33bb8432cc5c39979021cc0f98c1292a1a"}, + {file = "aiohttp-3.8.4-cp37-cp37m-win_amd64.whl", hash = "sha256:db3fc6120bce9f446d13b1b834ea5b15341ca9ff3f335e4a951a6ead31105480"}, + {file = "aiohttp-3.8.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:fabb87dd8850ef0f7fe2b366d44b77d7e6fa2ea87861ab3844da99291e81e60f"}, + {file = "aiohttp-3.8.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:91f6d540163f90bbaef9387e65f18f73ffd7c79f5225ac3d3f61df7b0d01ad15"}, + {file = "aiohttp-3.8.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:d265f09a75a79a788237d7f9054f929ced2e69eb0bb79de3798c468d8a90f945"}, + {file = "aiohttp-3.8.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3d89efa095ca7d442a6d0cbc755f9e08190ba40069b235c9886a8763b03785da"}, + {file = "aiohttp-3.8.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4dac314662f4e2aa5009977b652d9b8db7121b46c38f2073bfeed9f4049732cd"}, + {file = "aiohttp-3.8.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fe11310ae1e4cd560035598c3f29d86cef39a83d244c7466f95c27ae04850f10"}, + {file = "aiohttp-3.8.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6ddb2a2026c3f6a68c3998a6c47ab6795e4127315d2e35a09997da21865757f8"}, + {file = "aiohttp-3.8.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e75b89ac3bd27d2d043b234aa7b734c38ba1b0e43f07787130a0ecac1e12228a"}, + {file = "aiohttp-3.8.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6e601588f2b502c93c30cd5a45bfc665faaf37bbe835b7cfd461753068232074"}, + {file = "aiohttp-3.8.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a5d794d1ae64e7753e405ba58e08fcfa73e3fad93ef9b7e31112ef3c9a0efb52"}, + {file = "aiohttp-3.8.4-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:a1f4689c9a1462f3df0a1f7e797791cd6b124ddbee2b570d34e7f38ade0e2c71"}, + {file = "aiohttp-3.8.4-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:3032dcb1c35bc330134a5b8a5d4f68c1a87252dfc6e1262c65a7e30e62298275"}, + {file = "aiohttp-3.8.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8189c56eb0ddbb95bfadb8f60ea1b22fcfa659396ea36f6adcc521213cd7b44d"}, + {file = "aiohttp-3.8.4-cp38-cp38-win32.whl", hash = "sha256:33587f26dcee66efb2fff3c177547bd0449ab7edf1b73a7f5dea1e38609a0c54"}, + {file = "aiohttp-3.8.4-cp38-cp38-win_amd64.whl", hash = "sha256:e595432ac259af2d4630008bf638873d69346372d38255774c0e286951e8b79f"}, + {file = "aiohttp-3.8.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5a7bdf9e57126dc345b683c3632e8ba317c31d2a41acd5800c10640387d193ed"}, + {file = "aiohttp-3.8.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:22f6eab15b6db242499a16de87939a342f5a950ad0abaf1532038e2ce7d31567"}, + {file = "aiohttp-3.8.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7235604476a76ef249bd64cb8274ed24ccf6995c4a8b51a237005ee7a57e8643"}, + {file = "aiohttp-3.8.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ea9eb976ffdd79d0e893869cfe179a8f60f152d42cb64622fca418cd9b18dc2a"}, + {file = "aiohttp-3.8.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:92c0cea74a2a81c4c76b62ea1cac163ecb20fb3ba3a75c909b9fa71b4ad493cf"}, + {file = "aiohttp-3.8.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:493f5bc2f8307286b7799c6d899d388bbaa7dfa6c4caf4f97ef7521b9cb13719"}, + {file = "aiohttp-3.8.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0a63f03189a6fa7c900226e3ef5ba4d3bd047e18f445e69adbd65af433add5a2"}, + {file = "aiohttp-3.8.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:10c8cefcff98fd9168cdd86c4da8b84baaa90bf2da2269c6161984e6737bf23e"}, + {file = "aiohttp-3.8.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bca5f24726e2919de94f047739d0a4fc01372801a3672708260546aa2601bf57"}, + {file = "aiohttp-3.8.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:03baa76b730e4e15a45f81dfe29a8d910314143414e528737f8589ec60cf7391"}, + {file = "aiohttp-3.8.4-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:8c29c77cc57e40f84acef9bfb904373a4e89a4e8b74e71aa8075c021ec9078c2"}, + {file = "aiohttp-3.8.4-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:03543dcf98a6619254b409be2d22b51f21ec66272be4ebda7b04e6412e4b2e14"}, + {file = "aiohttp-3.8.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:17b79c2963db82086229012cff93ea55196ed31f6493bb1ccd2c62f1724324e4"}, + {file = "aiohttp-3.8.4-cp39-cp39-win32.whl", hash = "sha256:34ce9f93a4a68d1272d26030655dd1b58ff727b3ed2a33d80ec433561b03d67a"}, + {file = "aiohttp-3.8.4-cp39-cp39-win_amd64.whl", hash = "sha256:41a86a69bb63bb2fc3dc9ad5ea9f10f1c9c8e282b471931be0268ddd09430b04"}, + {file = "aiohttp-3.8.4.tar.gz", hash = "sha256:bf2e1a9162c1e441bf805a1fd166e249d574ca04e03b34f97e2928769e91ab5c"}, +] + +[package.dependencies] +aiosignal = ">=1.1.2" +async-timeout = ">=4.0.0a3,<5.0" +attrs = ">=17.3.0" +charset-normalizer = ">=2.0,<4.0" +frozenlist = ">=1.1.1" +multidict = ">=4.5,<7.0" +yarl = ">=1.0,<2.0" + +[package.extras] +speedups = ["Brotli", "aiodns", "cchardet"] + +[[package]] +name = "aiosignal" +version = "1.3.1" +description = "aiosignal: a list of registered asynchronous callbacks" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "aiosignal-1.3.1-py3-none-any.whl", hash = "sha256:f8376fb07dd1e86a584e4fcdec80b36b7f81aac666ebc724e2c090300dd83b17"}, + {file = "aiosignal-1.3.1.tar.gz", hash = "sha256:54cd96e15e1649b75d6c87526a6ff0b6c1b0dd3459f43d9ca11d48c339b68cfc"}, +] + +[package.dependencies] +frozenlist = ">=1.1.0" + +[[package]] +name = "aiosqlite" +version = "0.18.0" +description = "asyncio bridge to the standard sqlite3 module" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "aiosqlite-0.18.0-py3-none-any.whl", hash = "sha256:c3511b841e3a2c5614900ba1d179f366826857586f78abd75e7cbeb88e75a557"}, + {file = "aiosqlite-0.18.0.tar.gz", hash = "sha256:faa843ef5fb08bafe9a9b3859012d3d9d6f77ce3637899de20606b7fc39aa213"}, +] + +[[package]] +name = "ansiwrap" +version = "0.8.4" +description = "textwrap, but savvy to ANSI colors and styles" +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "ansiwrap-0.8.4-py2.py3-none-any.whl", hash = "sha256:7b053567c88e1ad9eed030d3ac41b722125e4c1271c8a99ade797faff1f49fb1"}, + {file = "ansiwrap-0.8.4.zip", hash = "sha256:ca0c740734cde59bf919f8ff2c386f74f9a369818cdc60efe94893d01ea8d9b7"}, +] + +[package.dependencies] +textwrap3 = ">=0.9.2" + +[[package]] +name = "anyio" +version = "3.6.2" +description = "High level compatibility layer for multiple asynchronous event loop implementations" +category = "dev" +optional = false +python-versions = ">=3.6.2" +files = [ + {file = "anyio-3.6.2-py3-none-any.whl", hash = "sha256:fbbe32bd270d2a2ef3ed1c5d45041250284e31fc0a4df4a5a6071842051a51e3"}, + {file = "anyio-3.6.2.tar.gz", hash = "sha256:25ea0d673ae30af41a0c442f81cf3b38c7e79fdc7b60335a4c14e05eb0947421"}, +] + +[package.dependencies] +idna = ">=2.8" +sniffio = ">=1.1" + +[package.extras] +doc = ["packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"] +test = ["contextlib2", "coverage[toml] (>=4.5)", "hypothesis (>=4.0)", "mock (>=4)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (<0.15)", "uvloop (>=0.15)"] +trio = ["trio (>=0.16,<0.22)"] + +[[package]] +name = "appdirs" +version = "1.4.4" +description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"}, + {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"}, +] + +[[package]] +name = "appnope" +version = "0.1.3" +description = "Disable App Nap on macOS >= 10.9" +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "appnope-0.1.3-py2.py3-none-any.whl", hash = "sha256:265a455292d0bd8a72453494fa24df5a11eb18373a60c7c0430889f22548605e"}, + {file = "appnope-0.1.3.tar.gz", hash = "sha256:02bd91c4de869fbb1e1c50aafc4098827a7a54ab2f39d9dcba6c9547ed920e24"}, +] + +[[package]] +name = "argon2-cffi" +version = "21.3.0" +description = "The secure Argon2 password hashing algorithm." +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ + {file = "argon2-cffi-21.3.0.tar.gz", hash = "sha256:d384164d944190a7dd7ef22c6aa3ff197da12962bd04b17f64d4e93d934dba5b"}, + {file = "argon2_cffi-21.3.0-py3-none-any.whl", hash = "sha256:8c976986f2c5c0e5000919e6de187906cfd81fb1c72bf9d88c01177e77da7f80"}, +] + +[package.dependencies] +argon2-cffi-bindings = "*" + +[package.extras] +dev = ["cogapp", "coverage[toml] (>=5.0.2)", "furo", "hypothesis", "pre-commit", "pytest", "sphinx", "sphinx-notfound-page", "tomli"] +docs = ["furo", "sphinx", "sphinx-notfound-page"] +tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pytest"] + +[[package]] +name = "argon2-cffi-bindings" +version = "21.2.0" +description = "Low-level CFFI bindings for Argon2" +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ + {file = "argon2-cffi-bindings-21.2.0.tar.gz", hash = "sha256:bb89ceffa6c791807d1305ceb77dbfacc5aa499891d2c55661c6459651fc39e3"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ccb949252cb2ab3a08c02024acb77cfb179492d5701c7cbdbfd776124d4d2367"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9524464572e12979364b7d600abf96181d3541da11e23ddf565a32e70bd4dc0d"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b746dba803a79238e925d9046a63aa26bf86ab2a2fe74ce6b009a1c3f5c8f2ae"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:58ed19212051f49a523abb1dbe954337dc82d947fb6e5a0da60f7c8471a8476c"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:bd46088725ef7f58b5a1ef7ca06647ebaf0eb4baff7d1d0d177c6cc8744abd86"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-musllinux_1_1_i686.whl", hash = "sha256:8cd69c07dd875537a824deec19f978e0f2078fdda07fd5c42ac29668dda5f40f"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:f1152ac548bd5b8bcecfb0b0371f082037e47128653df2e8ba6e914d384f3c3e"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-win32.whl", hash = "sha256:603ca0aba86b1349b147cab91ae970c63118a0f30444d4bc80355937c950c082"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-win_amd64.whl", hash = "sha256:b2ef1c30440dbbcba7a5dc3e319408b59676e2e039e2ae11a8775ecf482b192f"}, + {file = "argon2_cffi_bindings-21.2.0-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:e415e3f62c8d124ee16018e491a009937f8cf7ebf5eb430ffc5de21b900dad93"}, + {file = "argon2_cffi_bindings-21.2.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:3e385d1c39c520c08b53d63300c3ecc28622f076f4c2b0e6d7e796e9f6502194"}, + {file = "argon2_cffi_bindings-21.2.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c3e3cc67fdb7d82c4718f19b4e7a87123caf8a93fde7e23cf66ac0337d3cb3f"}, + {file = "argon2_cffi_bindings-21.2.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6a22ad9800121b71099d0fb0a65323810a15f2e292f2ba450810a7316e128ee5"}, + {file = "argon2_cffi_bindings-21.2.0-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f9f8b450ed0547e3d473fdc8612083fd08dd2120d6ac8f73828df9b7d45bb351"}, + {file = "argon2_cffi_bindings-21.2.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:93f9bf70084f97245ba10ee36575f0c3f1e7d7724d67d8e5b08e61787c320ed7"}, + {file = "argon2_cffi_bindings-21.2.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:3b9ef65804859d335dc6b31582cad2c5166f0c3e7975f324d9ffaa34ee7e6583"}, + {file = "argon2_cffi_bindings-21.2.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4966ef5848d820776f5f562a7d45fdd70c2f330c961d0d745b784034bd9f48d"}, + {file = "argon2_cffi_bindings-21.2.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:20ef543a89dee4db46a1a6e206cd015360e5a75822f76df533845c3cbaf72670"}, + {file = "argon2_cffi_bindings-21.2.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ed2937d286e2ad0cc79a7087d3c272832865f779430e0cc2b4f3718d3159b0cb"}, + {file = "argon2_cffi_bindings-21.2.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:5e00316dabdaea0b2dd82d141cc66889ced0cdcbfa599e8b471cf22c620c329a"}, +] + +[package.dependencies] +cffi = ">=1.0.1" + +[package.extras] +dev = ["cogapp", "pre-commit", "pytest", "wheel"] +tests = ["pytest"] + +[[package]] +name = "arrow" +version = "1.2.3" +description = "Better dates & times for Python" +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ + {file = "arrow-1.2.3-py3-none-any.whl", hash = "sha256:5a49ab92e3b7b71d96cd6bfcc4df14efefc9dfa96ea19045815914a6ab6b1fe2"}, + {file = "arrow-1.2.3.tar.gz", hash = "sha256:3934b30ca1b9f292376d9db15b19446088d12ec58629bc3f0da28fd55fb633a1"}, +] + +[package.dependencies] +python-dateutil = ">=2.7.0" + +[[package]] +name = "astroid" +version = "2.15.0" +description = "An abstract syntax tree for Python with inference support." +category = "dev" +optional = false +python-versions = ">=3.7.2" +files = [ + {file = "astroid-2.15.0-py3-none-any.whl", hash = "sha256:e3e4d0ffc2d15d954065579689c36aac57a339a4679a679579af6401db4d3fdb"}, + {file = "astroid-2.15.0.tar.gz", hash = "sha256:525f126d5dc1b8b0b6ee398b33159105615d92dc4a17f2cd064125d57f6186fa"}, +] + +[package.dependencies] +lazy-object-proxy = ">=1.4.0" +typing-extensions = {version = ">=4.0.0", markers = "python_version < \"3.11\""} +wrapt = {version = ">=1.11,<2", markers = "python_version < \"3.11\""} + +[[package]] +name = "asttokens" +version = "2.2.1" +description = "Annotate AST trees with source code positions" +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "asttokens-2.2.1-py2.py3-none-any.whl", hash = "sha256:6b0ac9e93fb0335014d382b8fa9b3afa7df546984258005da0b9e7095b3deb1c"}, + {file = "asttokens-2.2.1.tar.gz", hash = "sha256:4622110b2a6f30b77e1473affaa97e711bc2f07d3f10848420ff1898edbe94f3"}, +] + +[package.dependencies] +six = "*" + +[package.extras] +test = ["astroid", "pytest"] + +[[package]] +name = "async-timeout" +version = "4.0.2" +description = "Timeout context manager for asyncio programs" +category = "main" +optional = false +python-versions = ">=3.6" +files = [ + {file = "async-timeout-4.0.2.tar.gz", hash = "sha256:2163e1640ddb52b7a8c80d0a67a08587e5d245cc9c553a74a847056bc2976b15"}, + {file = "async_timeout-4.0.2-py3-none-any.whl", hash = "sha256:8ca1e4fcf50d07413d66d1a5e416e42cfdf5851c981d679a09851a6853383b3c"}, +] + +[[package]] +name = "attrs" +version = "22.2.0" +description = "Classes Without Boilerplate" +category = "main" +optional = false +python-versions = ">=3.6" +files = [ + {file = "attrs-22.2.0-py3-none-any.whl", hash = "sha256:29e95c7f6778868dbd49170f98f8818f78f3dc5e0e37c0b1f474e3561b240836"}, + {file = "attrs-22.2.0.tar.gz", hash = "sha256:c9227bfc2f01993c03f68db37d1d15c9690188323c067c641f1a35ca58185f99"}, +] + +[package.extras] +cov = ["attrs[tests]", "coverage-enable-subprocess", "coverage[toml] (>=5.3)"] +dev = ["attrs[docs,tests]"] +docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope.interface"] +tests = ["attrs[tests-no-zope]", "zope.interface"] +tests-no-zope = ["cloudpickle", "cloudpickle", "hypothesis", "hypothesis", "mypy (>=0.971,<0.990)", "mypy (>=0.971,<0.990)", "pympler", "pympler", "pytest (>=4.3.0)", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-mypy-plugins", "pytest-xdist[psutil]", "pytest-xdist[psutil]"] + +[[package]] +name = "autopep8" +version = "1.6.0" +description = "A tool that automatically formats Python code to conform to the PEP 8 style guide" +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "autopep8-1.6.0-py2.py3-none-any.whl", hash = "sha256:ed77137193bbac52d029a52c59bec1b0629b5a186c495f1eb21b126ac466083f"}, + {file = "autopep8-1.6.0.tar.gz", hash = "sha256:44f0932855039d2c15c4510d6df665e4730f2b8582704fa48f9c55bd3e17d979"}, +] + +[package.dependencies] +pycodestyle = ">=2.8.0" +toml = "*" + +[[package]] +name = "babel" +version = "2.12.1" +description = "Internationalization utilities" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "Babel-2.12.1-py3-none-any.whl", hash = "sha256:b4246fb7677d3b98f501a39d43396d3cafdc8eadb045f4a31be01863f655c610"}, + {file = "Babel-2.12.1.tar.gz", hash = "sha256:cc2d99999cd01d44420ae725a21c9e3711b3aadc7976d6147f622d8581963455"}, +] + +[[package]] +name = "backcall" +version = "0.2.0" +description = "Specifications for callback functions passed in to an API" +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "backcall-0.2.0-py2.py3-none-any.whl", hash = "sha256:fbbce6a29f263178a1f7915c1940bde0ec2b2a967566fe1c65c1dfb7422bd255"}, + {file = "backcall-0.2.0.tar.gz", hash = "sha256:5cbdbf27be5e7cfadb448baf0aa95508f91f2bbc6c6437cd9cd06e2a4c215e1e"}, +] + +[[package]] +name = "baron" +version = "0.10.1" +description = "Full Syntax Tree for python to make writing refactoring code a realist task" +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "baron-0.10.1-py2.py3-none-any.whl", hash = "sha256:befb33f4b9e832c7cd1e3cf0eafa6dd3cb6ed4cb2544245147c019936f4e0a8a"}, + {file = "baron-0.10.1.tar.gz", hash = "sha256:af822ad44d4eb425c8516df4239ac4fdba9fdb398ef77e4924cd7c9b4045bc2f"}, +] + +[package.dependencies] +rply = "*" + +[[package]] +name = "beautifulsoup4" +version = "4.12.0" +description = "Screen-scraping library" +category = "dev" +optional = false +python-versions = ">=3.6.0" +files = [ + {file = "beautifulsoup4-4.12.0-py3-none-any.whl", hash = "sha256:2130a5ad7f513200fae61a17abb5e338ca980fa28c439c0571014bc0217e9591"}, + {file = "beautifulsoup4-4.12.0.tar.gz", hash = "sha256:c5fceeaec29d09c84970e47c65f2f0efe57872f7cff494c9691a26ec0ff13234"}, +] + +[package.dependencies] +soupsieve = ">1.2" + +[package.extras] +html5lib = ["html5lib"] +lxml = ["lxml"] + +[[package]] +name = "bleach" +version = "6.0.0" +description = "An easy safelist-based HTML-sanitizing tool." +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "bleach-6.0.0-py3-none-any.whl", hash = "sha256:33c16e3353dbd13028ab4799a0f89a83f113405c766e9c122df8a06f5b85b3f4"}, + {file = "bleach-6.0.0.tar.gz", hash = "sha256:1a1a85c1595e07d8db14c5f09f09e6433502c51c595970edc090551f0db99414"}, +] + +[package.dependencies] +six = ">=1.9.0" +webencodings = "*" + +[package.extras] +css = ["tinycss2 (>=1.1.0,<1.2)"] + +[[package]] +name = "cachetools" +version = "5.3.0" +description = "Extensible memoizing collections and decorators" +category = "dev" +optional = false +python-versions = "~=3.7" +files = [ + {file = "cachetools-5.3.0-py3-none-any.whl", hash = "sha256:429e1a1e845c008ea6c85aa35d4b98b65d6a9763eeef3e37e92728a12d1de9d4"}, + {file = "cachetools-5.3.0.tar.gz", hash = "sha256:13dfddc7b8df938c21a940dfa6557ce6e94a2f1cdfa58eb90c805721d58f2c14"}, +] + +[[package]] +name = "certifi" +version = "2022.12.7" +description = "Python package for providing Mozilla's CA Bundle." +category = "main" +optional = false +python-versions = ">=3.6" +files = [ + {file = "certifi-2022.12.7-py3-none-any.whl", hash = "sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18"}, + {file = "certifi-2022.12.7.tar.gz", hash = "sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3"}, +] + +[[package]] +name = "cffi" +version = "1.15.1" +description = "Foreign Function Interface for Python calling C code." +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "cffi-1.15.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:a66d3508133af6e8548451b25058d5812812ec3798c886bf38ed24a98216fab2"}, + {file = "cffi-1.15.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:470c103ae716238bbe698d67ad020e1db9d9dba34fa5a899b5e21577e6d52ed2"}, + {file = "cffi-1.15.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:9ad5db27f9cabae298d151c85cf2bad1d359a1b9c686a275df03385758e2f914"}, + {file = "cffi-1.15.1-cp27-cp27m-win32.whl", hash = "sha256:b3bbeb01c2b273cca1e1e0c5df57f12dce9a4dd331b4fa1635b8bec26350bde3"}, + {file = "cffi-1.15.1-cp27-cp27m-win_amd64.whl", hash = "sha256:e00b098126fd45523dd056d2efba6c5a63b71ffe9f2bbe1a4fe1716e1d0c331e"}, + {file = "cffi-1.15.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:d61f4695e6c866a23a21acab0509af1cdfd2c013cf256bbf5b6b5e2695827162"}, + {file = "cffi-1.15.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:ed9cb427ba5504c1dc15ede7d516b84757c3e3d7868ccc85121d9310d27eed0b"}, + {file = "cffi-1.15.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:39d39875251ca8f612b6f33e6b1195af86d1b3e60086068be9cc053aa4376e21"}, + {file = "cffi-1.15.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:285d29981935eb726a4399badae8f0ffdff4f5050eaa6d0cfc3f64b857b77185"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3eb6971dcff08619f8d91607cfc726518b6fa2a9eba42856be181c6d0d9515fd"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21157295583fe8943475029ed5abdcf71eb3911894724e360acff1d61c1d54bc"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5635bd9cb9731e6d4a1132a498dd34f764034a8ce60cef4f5319c0541159392f"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2012c72d854c2d03e45d06ae57f40d78e5770d252f195b93f581acf3ba44496e"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd86c085fae2efd48ac91dd7ccffcfc0571387fe1193d33b6394db7ef31fe2a4"}, + {file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:fa6693661a4c91757f4412306191b6dc88c1703f780c8234035eac011922bc01"}, + {file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:59c0b02d0a6c384d453fece7566d1c7e6b7bae4fc5874ef2ef46d56776d61c9e"}, + {file = "cffi-1.15.1-cp310-cp310-win32.whl", hash = "sha256:cba9d6b9a7d64d4bd46167096fc9d2f835e25d7e4c121fb2ddfc6528fb0413b2"}, + {file = "cffi-1.15.1-cp310-cp310-win_amd64.whl", hash = "sha256:ce4bcc037df4fc5e3d184794f27bdaab018943698f4ca31630bc7f84a7b69c6d"}, + {file = "cffi-1.15.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3d08afd128ddaa624a48cf2b859afef385b720bb4b43df214f85616922e6a5ac"}, + {file = "cffi-1.15.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3799aecf2e17cf585d977b780ce79ff0dc9b78d799fc694221ce814c2c19db83"}, + {file = "cffi-1.15.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a591fe9e525846e4d154205572a029f653ada1a78b93697f3b5a8f1f2bc055b9"}, + {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3548db281cd7d2561c9ad9984681c95f7b0e38881201e157833a2342c30d5e8c"}, + {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91fc98adde3d7881af9b59ed0294046f3806221863722ba7d8d120c575314325"}, + {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94411f22c3985acaec6f83c6df553f2dbe17b698cc7f8ae751ff2237d96b9e3c"}, + {file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:03425bdae262c76aad70202debd780501fabeaca237cdfddc008987c0e0f59ef"}, + {file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cc4d65aeeaa04136a12677d3dd0b1c0c94dc43abac5860ab33cceb42b801c1e8"}, + {file = "cffi-1.15.1-cp311-cp311-win32.whl", hash = "sha256:a0f100c8912c114ff53e1202d0078b425bee3649ae34d7b070e9697f93c5d52d"}, + {file = "cffi-1.15.1-cp311-cp311-win_amd64.whl", hash = "sha256:04ed324bda3cda42b9b695d51bb7d54b680b9719cfab04227cdd1e04e5de3104"}, + {file = "cffi-1.15.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50a74364d85fd319352182ef59c5c790484a336f6db772c1a9231f1c3ed0cbd7"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e263d77ee3dd201c3a142934a086a4450861778baaeeb45db4591ef65550b0a6"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cec7d9412a9102bdc577382c3929b337320c4c4c4849f2c5cdd14d7368c5562d"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4289fc34b2f5316fbb762d75362931e351941fa95fa18789191b33fc4cf9504a"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:173379135477dc8cac4bc58f45db08ab45d228b3363adb7af79436135d028405"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6975a3fac6bc83c4a65c9f9fcab9e47019a11d3d2cf7f3c0d03431bf145a941e"}, + {file = "cffi-1.15.1-cp36-cp36m-win32.whl", hash = "sha256:2470043b93ff09bf8fb1d46d1cb756ce6132c54826661a32d4e4d132e1977adf"}, + {file = "cffi-1.15.1-cp36-cp36m-win_amd64.whl", hash = "sha256:30d78fbc8ebf9c92c9b7823ee18eb92f2e6ef79b45ac84db507f52fbe3ec4497"}, + {file = "cffi-1.15.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:198caafb44239b60e252492445da556afafc7d1e3ab7a1fb3f0584ef6d742375"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5ef34d190326c3b1f822a5b7a45f6c4535e2f47ed06fec77d3d799c450b2651e"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8102eaf27e1e448db915d08afa8b41d6c7ca7a04b7d73af6514df10a3e74bd82"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5df2768244d19ab7f60546d0c7c63ce1581f7af8b5de3eb3004b9b6fc8a9f84b"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8c4917bd7ad33e8eb21e9a5bbba979b49d9a97acb3a803092cbc1133e20343c"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2642fe3142e4cc4af0799748233ad6da94c62a8bec3a6648bf8ee68b1c7426"}, + {file = "cffi-1.15.1-cp37-cp37m-win32.whl", hash = "sha256:e229a521186c75c8ad9490854fd8bbdd9a0c9aa3a524326b55be83b54d4e0ad9"}, + {file = "cffi-1.15.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a0b71b1b8fbf2b96e41c4d990244165e2c9be83d54962a9a1d118fd8657d2045"}, + {file = "cffi-1.15.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:320dab6e7cb2eacdf0e658569d2575c4dad258c0fcc794f46215e1e39f90f2c3"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e74c6b51a9ed6589199c787bf5f9875612ca4a8a0785fb2d4a84429badaf22a"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5c84c68147988265e60416b57fc83425a78058853509c1b0629c180094904a5"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b926aa83d1edb5aa5b427b4053dc420ec295a08e40911296b9eb1b6170f6cca"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:87c450779d0914f2861b8526e035c5e6da0a3199d8f1add1a665e1cbc6fc6d02"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f2c9f67e9821cad2e5f480bc8d83b8742896f1242dba247911072d4fa94c192"}, + {file = "cffi-1.15.1-cp38-cp38-win32.whl", hash = "sha256:8b7ee99e510d7b66cdb6c593f21c043c248537a32e0bedf02e01e9553a172314"}, + {file = "cffi-1.15.1-cp38-cp38-win_amd64.whl", hash = "sha256:00a9ed42e88df81ffae7a8ab6d9356b371399b91dbdf0c3cb1e84c03a13aceb5"}, + {file = "cffi-1.15.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:54a2db7b78338edd780e7ef7f9f6c442500fb0d41a5a4ea24fff1c929d5af585"}, + {file = "cffi-1.15.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fcd131dd944808b5bdb38e6f5b53013c5aa4f334c5cad0c72742f6eba4b73db0"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7473e861101c9e72452f9bf8acb984947aa1661a7704553a9f6e4baa5ba64415"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c9a799e985904922a4d207a94eae35c78ebae90e128f0c4e521ce339396be9d"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3bcde07039e586f91b45c88f8583ea7cf7a0770df3a1649627bf598332cb6984"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33ab79603146aace82c2427da5ca6e58f2b3f2fb5da893ceac0c42218a40be35"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d598b938678ebf3c67377cdd45e09d431369c3b1a5b331058c338e201f12b27"}, + {file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:db0fbb9c62743ce59a9ff687eb5f4afbe77e5e8403d6697f7446e5f609976f76"}, + {file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:98d85c6a2bef81588d9227dde12db8a7f47f639f4a17c9ae08e773aa9c697bf3"}, + {file = "cffi-1.15.1-cp39-cp39-win32.whl", hash = "sha256:40f4774f5a9d4f5e344f31a32b5096977b5d48560c5592e2f3d2c4374bd543ee"}, + {file = "cffi-1.15.1-cp39-cp39-win_amd64.whl", hash = "sha256:70df4e3b545a17496c9b3f41f5115e69a4f2e77e94e1d2a8e1070bc0c38c8a3c"}, + {file = "cffi-1.15.1.tar.gz", hash = "sha256:d400bfb9a37b1351253cb402671cea7e89bdecc294e8016a707f6d1d8ac934f9"}, +] + +[package.dependencies] +pycparser = "*" + +[[package]] +name = "charset-normalizer" +version = "3.1.0" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +category = "main" +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "charset-normalizer-3.1.0.tar.gz", hash = "sha256:34e0a2f9c370eb95597aae63bf85eb5e96826d81e3dcf88b8886012906f509b5"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e0ac8959c929593fee38da1c2b64ee9778733cdf03c482c9ff1d508b6b593b2b"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d7fc3fca01da18fbabe4625d64bb612b533533ed10045a2ac3dd194bfa656b60"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:04eefcee095f58eaabe6dc3cc2262f3bcd776d2c67005880894f447b3f2cb9c1"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:20064ead0717cf9a73a6d1e779b23d149b53daf971169289ed2ed43a71e8d3b0"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1435ae15108b1cb6fffbcea2af3d468683b7afed0169ad718451f8db5d1aff6f"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c84132a54c750fda57729d1e2599bb598f5fa0344085dbde5003ba429a4798c0"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75f2568b4189dda1c567339b48cba4ac7384accb9c2a7ed655cd86b04055c795"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:11d3bcb7be35e7b1bba2c23beedac81ee893ac9871d0ba79effc7fc01167db6c"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:891cf9b48776b5c61c700b55a598621fdb7b1e301a550365571e9624f270c203"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:5f008525e02908b20e04707a4f704cd286d94718f48bb33edddc7d7b584dddc1"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:b06f0d3bf045158d2fb8837c5785fe9ff9b8c93358be64461a1089f5da983137"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:49919f8400b5e49e961f320c735388ee686a62327e773fa5b3ce6721f7e785ce"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:22908891a380d50738e1f978667536f6c6b526a2064156203d418f4856d6e86a"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-win32.whl", hash = "sha256:12d1a39aa6b8c6f6248bb54550efcc1c38ce0d8096a146638fd4738e42284448"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:65ed923f84a6844de5fd29726b888e58c62820e0769b76565480e1fdc3d062f8"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9a3267620866c9d17b959a84dd0bd2d45719b817245e49371ead79ed4f710d19"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6734e606355834f13445b6adc38b53c0fd45f1a56a9ba06c2058f86893ae8017"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f8303414c7b03f794347ad062c0516cee0e15f7a612abd0ce1e25caf6ceb47df"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aaf53a6cebad0eae578f062c7d462155eada9c172bd8c4d250b8c1d8eb7f916a"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3dc5b6a8ecfdc5748a7e429782598e4f17ef378e3e272eeb1340ea57c9109f41"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e1b25e3ad6c909f398df8921780d6a3d120d8c09466720226fc621605b6f92b1"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0ca564606d2caafb0abe6d1b5311c2649e8071eb241b2d64e75a0d0065107e62"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b82fab78e0b1329e183a65260581de4375f619167478dddab510c6c6fb04d9b6"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:bd7163182133c0c7701b25e604cf1611c0d87712e56e88e7ee5d72deab3e76b5"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:11d117e6c63e8f495412d37e7dc2e2fff09c34b2d09dbe2bee3c6229577818be"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:cf6511efa4801b9b38dc5546d7547d5b5c6ef4b081c60b23e4d941d0eba9cbeb"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:abc1185d79f47c0a7aaf7e2412a0eb2c03b724581139193d2d82b3ad8cbb00ac"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cb7b2ab0188829593b9de646545175547a70d9a6e2b63bf2cd87a0a391599324"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-win32.whl", hash = "sha256:c36bcbc0d5174a80d6cccf43a0ecaca44e81d25be4b7f90f0ed7bcfbb5a00909"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:cca4def576f47a09a943666b8f829606bcb17e2bc2d5911a46c8f8da45f56755"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0c95f12b74681e9ae127728f7e5409cbbef9cd914d5896ef238cc779b8152373"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fca62a8301b605b954ad2e9c3666f9d97f63872aa4efcae5492baca2056b74ab"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ac0aa6cd53ab9a31d397f8303f92c42f534693528fafbdb997c82bae6e477ad9"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c3af8e0f07399d3176b179f2e2634c3ce9c1301379a6b8c9c9aeecd481da494f"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a5fc78f9e3f501a1614a98f7c54d3969f3ad9bba8ba3d9b438c3bc5d047dd28"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:628c985afb2c7d27a4800bfb609e03985aaecb42f955049957814e0491d4006d"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:74db0052d985cf37fa111828d0dd230776ac99c740e1a758ad99094be4f1803d"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:1e8fcdd8f672a1c4fc8d0bd3a2b576b152d2a349782d1eb0f6b8e52e9954731d"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:04afa6387e2b282cf78ff3dbce20f0cc071c12dc8f685bd40960cc68644cfea6"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:dd5653e67b149503c68c4018bf07e42eeed6b4e956b24c00ccdf93ac79cdff84"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d2686f91611f9e17f4548dbf050e75b079bbc2a82be565832bc8ea9047b61c8c"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-win32.whl", hash = "sha256:4155b51ae05ed47199dc5b2a4e62abccb274cee6b01da5b895099b61b1982974"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:322102cdf1ab682ecc7d9b1c5eed4ec59657a65e1c146a0da342b78f4112db23"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e633940f28c1e913615fd624fcdd72fdba807bf53ea6925d6a588e84e1151531"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3a06f32c9634a8705f4ca9946d667609f52cf130d5548881401f1eb2c39b1e2c"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7381c66e0561c5757ffe616af869b916c8b4e42b367ab29fedc98481d1e74e14"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3573d376454d956553c356df45bb824262c397c6e26ce43e8203c4c540ee0acb"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e89df2958e5159b811af9ff0f92614dabf4ff617c03a4c1c6ff53bf1c399e0e1"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:78cacd03e79d009d95635e7d6ff12c21eb89b894c354bd2b2ed0b4763373693b"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:de5695a6f1d8340b12a5d6d4484290ee74d61e467c39ff03b39e30df62cf83a0"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1c60b9c202d00052183c9be85e5eaf18a4ada0a47d188a83c8f5c5b23252f649"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:f645caaf0008bacf349875a974220f1f1da349c5dbe7c4ec93048cdc785a3326"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:ea9f9c6034ea2d93d9147818f17c2a0860d41b71c38b9ce4d55f21b6f9165a11"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:80d1543d58bd3d6c271b66abf454d437a438dff01c3e62fdbcd68f2a11310d4b"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:73dc03a6a7e30b7edc5b01b601e53e7fc924b04e1835e8e407c12c037e81adbd"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6f5c2e7bc8a4bf7c426599765b1bd33217ec84023033672c1e9a8b35eaeaaaf8"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-win32.whl", hash = "sha256:12a2b561af122e3d94cdb97fe6fb2bb2b82cef0cdca131646fdb940a1eda04f0"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:3160a0fd9754aab7d47f95a6b63ab355388d890163eb03b2d2b87ab0a30cfa59"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:38e812a197bf8e71a59fe55b757a84c1f946d0ac114acafaafaf21667a7e169e"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6baf0baf0d5d265fa7944feb9f7451cc316bfe30e8df1a61b1bb08577c554f31"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8f25e17ab3039b05f762b0a55ae0b3632b2e073d9c8fc88e89aca31a6198e88f"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3747443b6a904001473370d7810aa19c3a180ccd52a7157aacc264a5ac79265e"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b116502087ce8a6b7a5f1814568ccbd0e9f6cfd99948aa59b0e241dc57cf739f"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d16fd5252f883eb074ca55cb622bc0bee49b979ae4e8639fff6ca3ff44f9f854"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21fa558996782fc226b529fdd2ed7866c2c6ec91cee82735c98a197fae39f706"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6f6c7a8a57e9405cad7485f4c9d3172ae486cfef1344b5ddd8e5239582d7355e"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ac3775e3311661d4adace3697a52ac0bab17edd166087d493b52d4f4f553f9f0"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:10c93628d7497c81686e8e5e557aafa78f230cd9e77dd0c40032ef90c18f2230"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:6f4f4668e1831850ebcc2fd0b1cd11721947b6dc7c00bf1c6bd3c929ae14f2c7"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:0be65ccf618c1e7ac9b849c315cc2e8a8751d9cfdaa43027d4f6624bd587ab7e"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:53d0a3fa5f8af98a1e261de6a3943ca631c526635eb5817a87a59d9a57ebf48f"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-win32.whl", hash = "sha256:a04f86f41a8916fe45ac5024ec477f41f886b3c435da2d4e3d2709b22ab02af1"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:830d2948a5ec37c386d3170c483063798d7879037492540f10a475e3fd6f244b"}, + {file = "charset_normalizer-3.1.0-py3-none-any.whl", hash = "sha256:3d9098b479e78c85080c98e1e35ff40b4a31d8953102bb0fd7d1b6f8a2111a3d"}, +] + +[[package]] +name = "click" +version = "8.1.3" +description = "Composable command line interface toolkit" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"}, + {file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +category = "main" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "comm" +version = "0.1.3" +description = "Jupyter Python Comm implementation, for usage in ipykernel, xeus-python etc." +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ + {file = "comm-0.1.3-py3-none-any.whl", hash = "sha256:16613c6211e20223f215fc6d3b266a247b6e2641bf4e0a3ad34cb1aff2aa3f37"}, + {file = "comm-0.1.3.tar.gz", hash = "sha256:a61efa9daffcfbe66fd643ba966f846a624e4e6d6767eda9cf6e993aadaab93e"}, +] + +[package.dependencies] +traitlets = ">=5.3" + +[package.extras] +lint = ["black (>=22.6.0)", "mdformat (>0.7)", "mdformat-gfm (>=0.3.5)", "ruff (>=0.0.156)"] +test = ["pytest"] +typing = ["mypy (>=0.990)"] + +[[package]] +name = "contourpy" +version = "1.0.7" +description = "Python library for calculating contours of 2D quadrilateral grids" +category = "main" +optional = false +python-versions = ">=3.8" +files = [ + {file = "contourpy-1.0.7-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:95c3acddf921944f241b6773b767f1cbce71d03307270e2d769fd584d5d1092d"}, + {file = "contourpy-1.0.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:fc1464c97579da9f3ab16763c32e5c5d5bb5fa1ec7ce509a4ca6108b61b84fab"}, + {file = "contourpy-1.0.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8acf74b5d383414401926c1598ed77825cd530ac7b463ebc2e4f46638f56cce6"}, + {file = "contourpy-1.0.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c71fdd8f1c0f84ffd58fca37d00ca4ebaa9e502fb49825484da075ac0b0b803"}, + {file = "contourpy-1.0.7-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f99e9486bf1bb979d95d5cffed40689cb595abb2b841f2991fc894b3452290e8"}, + {file = "contourpy-1.0.7-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87f4d8941a9564cda3f7fa6a6cd9b32ec575830780677932abdec7bcb61717b0"}, + {file = "contourpy-1.0.7-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:9e20e5a1908e18aaa60d9077a6d8753090e3f85ca25da6e25d30dc0a9e84c2c6"}, + {file = "contourpy-1.0.7-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a877ada905f7d69b2a31796c4b66e31a8068b37aa9b78832d41c82fc3e056ddd"}, + {file = "contourpy-1.0.7-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6381fa66866b0ea35e15d197fc06ac3840a9b2643a6475c8fff267db8b9f1e69"}, + {file = "contourpy-1.0.7-cp310-cp310-win32.whl", hash = "sha256:3c184ad2433635f216645fdf0493011a4667e8d46b34082f5a3de702b6ec42e3"}, + {file = "contourpy-1.0.7-cp310-cp310-win_amd64.whl", hash = "sha256:3caea6365b13119626ee996711ab63e0c9d7496f65641f4459c60a009a1f3e80"}, + {file = "contourpy-1.0.7-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ed33433fc3820263a6368e532f19ddb4c5990855e4886088ad84fd7c4e561c71"}, + {file = "contourpy-1.0.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:38e2e577f0f092b8e6774459317c05a69935a1755ecfb621c0a98f0e3c09c9a5"}, + {file = "contourpy-1.0.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ae90d5a8590e5310c32a7630b4b8618cef7563cebf649011da80874d0aa8f414"}, + {file = "contourpy-1.0.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:130230b7e49825c98edf0b428b7aa1125503d91732735ef897786fe5452b1ec2"}, + {file = "contourpy-1.0.7-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:58569c491e7f7e874f11519ef46737cea1d6eda1b514e4eb5ac7dab6aa864d02"}, + {file = "contourpy-1.0.7-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:54d43960d809c4c12508a60b66cb936e7ed57d51fb5e30b513934a4a23874fae"}, + {file = "contourpy-1.0.7-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:152fd8f730c31fd67fe0ffebe1df38ab6a669403da93df218801a893645c6ccc"}, + {file = "contourpy-1.0.7-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:9056c5310eb1daa33fc234ef39ebfb8c8e2533f088bbf0bc7350f70a29bde1ac"}, + {file = "contourpy-1.0.7-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a9d7587d2fdc820cc9177139b56795c39fb8560f540bba9ceea215f1f66e1566"}, + {file = "contourpy-1.0.7-cp311-cp311-win32.whl", hash = "sha256:4ee3ee247f795a69e53cd91d927146fb16c4e803c7ac86c84104940c7d2cabf0"}, + {file = "contourpy-1.0.7-cp311-cp311-win_amd64.whl", hash = "sha256:5caeacc68642e5f19d707471890f037a13007feba8427eb7f2a60811a1fc1350"}, + {file = "contourpy-1.0.7-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:fd7dc0e6812b799a34f6d12fcb1000539098c249c8da54f3566c6a6461d0dbad"}, + {file = "contourpy-1.0.7-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0f9d350b639db6c2c233d92c7f213d94d2e444d8e8fc5ca44c9706cf72193772"}, + {file = "contourpy-1.0.7-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e96a08b62bb8de960d3a6afbc5ed8421bf1a2d9c85cc4ea73f4bc81b4910500f"}, + {file = "contourpy-1.0.7-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:031154ed61f7328ad7f97662e48660a150ef84ee1bc8876b6472af88bf5a9b98"}, + {file = "contourpy-1.0.7-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2e9ebb4425fc1b658e13bace354c48a933b842d53c458f02c86f371cecbedecc"}, + {file = "contourpy-1.0.7-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:efb8f6d08ca7998cf59eaf50c9d60717f29a1a0a09caa46460d33b2924839dbd"}, + {file = "contourpy-1.0.7-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6c180d89a28787e4b73b07e9b0e2dac7741261dbdca95f2b489c4f8f887dd810"}, + {file = "contourpy-1.0.7-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:b8d587cc39057d0afd4166083d289bdeff221ac6d3ee5046aef2d480dc4b503c"}, + {file = "contourpy-1.0.7-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:769eef00437edf115e24d87f8926955f00f7704bede656ce605097584f9966dc"}, + {file = "contourpy-1.0.7-cp38-cp38-win32.whl", hash = "sha256:62398c80ef57589bdbe1eb8537127321c1abcfdf8c5f14f479dbbe27d0322e66"}, + {file = "contourpy-1.0.7-cp38-cp38-win_amd64.whl", hash = "sha256:57119b0116e3f408acbdccf9eb6ef19d7fe7baf0d1e9aaa5381489bc1aa56556"}, + {file = "contourpy-1.0.7-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:30676ca45084ee61e9c3da589042c24a57592e375d4b138bd84d8709893a1ba4"}, + {file = "contourpy-1.0.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3e927b3868bd1e12acee7cc8f3747d815b4ab3e445a28d2e5373a7f4a6e76ba1"}, + {file = "contourpy-1.0.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:366a0cf0fc079af5204801786ad7a1c007714ee3909e364dbac1729f5b0849e5"}, + {file = "contourpy-1.0.7-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:89ba9bb365446a22411f0673abf6ee1fea3b2cf47b37533b970904880ceb72f3"}, + {file = "contourpy-1.0.7-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:71b0bf0c30d432278793d2141362ac853859e87de0a7dee24a1cea35231f0d50"}, + {file = "contourpy-1.0.7-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e7281244c99fd7c6f27c1c6bfafba878517b0b62925a09b586d88ce750a016d2"}, + {file = "contourpy-1.0.7-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b6d0f9e1d39dbfb3977f9dd79f156c86eb03e57a7face96f199e02b18e58d32a"}, + {file = "contourpy-1.0.7-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7f6979d20ee5693a1057ab53e043adffa1e7418d734c1532e2d9e915b08d8ec2"}, + {file = "contourpy-1.0.7-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5dd34c1ae752515318224cba7fc62b53130c45ac6a1040c8b7c1a223c46e8967"}, + {file = "contourpy-1.0.7-cp39-cp39-win32.whl", hash = "sha256:c5210e5d5117e9aec8c47d9156d1d3835570dd909a899171b9535cb4a3f32693"}, + {file = "contourpy-1.0.7-cp39-cp39-win_amd64.whl", hash = "sha256:60835badb5ed5f4e194a6f21c09283dd6e007664a86101431bf870d9e86266c4"}, + {file = "contourpy-1.0.7-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ce41676b3d0dd16dbcfabcc1dc46090aaf4688fd6e819ef343dbda5a57ef0161"}, + {file = "contourpy-1.0.7-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5a011cf354107b47c58ea932d13b04d93c6d1d69b8b6dce885e642531f847566"}, + {file = "contourpy-1.0.7-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:31a55dccc8426e71817e3fe09b37d6d48ae40aae4ecbc8c7ad59d6893569c436"}, + {file = "contourpy-1.0.7-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:69f8ff4db108815addd900a74df665e135dbbd6547a8a69333a68e1f6e368ac2"}, + {file = "contourpy-1.0.7-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:efe99298ba37e37787f6a2ea868265465410822f7bea163edcc1bd3903354ea9"}, + {file = "contourpy-1.0.7-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a1e97b86f73715e8670ef45292d7cc033548266f07d54e2183ecb3c87598888f"}, + {file = "contourpy-1.0.7-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc331c13902d0f50845099434cd936d49d7a2ca76cb654b39691974cb1e4812d"}, + {file = "contourpy-1.0.7-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:24847601071f740837aefb730e01bd169fbcaa610209779a78db7ebb6e6a7051"}, + {file = "contourpy-1.0.7-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:abf298af1e7ad44eeb93501e40eb5a67abbf93b5d90e468d01fc0c4451971afa"}, + {file = "contourpy-1.0.7-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:64757f6460fc55d7e16ed4f1de193f362104285c667c112b50a804d482777edd"}, + {file = "contourpy-1.0.7.tar.gz", hash = "sha256:d8165a088d31798b59e91117d1f5fc3df8168d8b48c4acc10fc0df0d0bdbcc5e"}, +] + +[package.dependencies] +numpy = ">=1.16" + +[package.extras] +bokeh = ["bokeh", "chromedriver", "selenium"] +docs = ["furo", "sphinx-copybutton"] +mypy = ["contourpy[bokeh]", "docutils-stubs", "mypy (==0.991)", "types-Pillow"] +test = ["Pillow", "matplotlib", "pytest"] +test-no-images = ["pytest"] + +[[package]] +name = "cycler" +version = "0.11.0" +description = "Composable style cycles" +category = "main" +optional = false +python-versions = ">=3.6" +files = [ + {file = "cycler-0.11.0-py3-none-any.whl", hash = "sha256:3a27e95f763a428a739d2add979fa7494c912a32c17c4c38c4d5f082cad165a3"}, + {file = "cycler-0.11.0.tar.gz", hash = "sha256:9c87405839a19696e837b3b818fed3f5f69f16f1eec1a1ad77e043dcea9c772f"}, +] + +[[package]] +name = "cython" +version = "0.29.33" +description = "The Cython compiler for writing C extensions for the Python language." +category = "main" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ + {file = "Cython-0.29.33-cp27-cp27m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:286cdfb193e23799e113b7bd5ac74f58da5e9a77c70e3b645b078836b896b165"}, + {file = "Cython-0.29.33-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:8507279a4f86ed8365b96603d5ad155888d4d01b72a9bbf0615880feda5a11d4"}, + {file = "Cython-0.29.33-cp27-cp27mu-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5bf5ffd96957a595441cca2fc78470d93fdc40dfe5449881b812ea6045d7e9be"}, + {file = "Cython-0.29.33-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:d2019a7e54ba8b253f44411863b8f8c0b6cd623f7a92dc0ccb83892358c4283a"}, + {file = "Cython-0.29.33-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:190e60b7505d3b9b60130bcc2251c01b9ef52603420829c19d3c3ede4ac2763a"}, + {file = "Cython-0.29.33-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:0168482495b75fea1c97a9641a95bac991f313e85f378003f9a4909fdeb3d454"}, + {file = "Cython-0.29.33-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:090556e41f2b30427dd3a1628d3613177083f47567a30148b6b7b8c7a5862187"}, + {file = "Cython-0.29.33-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:19c9913e9304bf97f1d2c357438895466f99aa2707d3c7a5e9de60c259e1ca1d"}, + {file = "Cython-0.29.33-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:afc9b6ab20889676c76e700ae6967aa6886a7efe5b05ef6d5b744a6ca793cc43"}, + {file = "Cython-0.29.33-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:49fb45b2bf12d6e2060bbd64506c06ac90e254f3a4bceb32c717f4964a1ae812"}, + {file = "Cython-0.29.33-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:5430f38d3d01c4715ec2aef5c41e02a2441c1c3a0149359c7a498e4c605b8e6c"}, + {file = "Cython-0.29.33-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c4d315443c7f4c61180b6c3ea9a9717ee7c901cc9db8d1d46fdf6556613840ed"}, + {file = "Cython-0.29.33-cp35-cp35m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6b4e6481e3e7e4d345640fe2fdc6dc57c94369b467f3dc280949daa8e9fd13b9"}, + {file = "Cython-0.29.33-cp35-cp35m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:060a2568ef80116a0a9dcaf3218a61c6007be0e0b77c5752c094ce5187a4d63c"}, + {file = "Cython-0.29.33-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:b67ddd32eaa2932a66bf8121accc36a7b3078593805519b0f00040f2b10a6a52"}, + {file = "Cython-0.29.33-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:1b507236ba3ca94170ce0a504dd03acf77307d4bfbc5a010a8031673f6b213a9"}, + {file = "Cython-0.29.33-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:581efc0622a9be05714222f2b4ac96a5419de58d5949517282d8df38155c8b9d"}, + {file = "Cython-0.29.33-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6b8bcbf8f1c3c46d6184be1e559e3a3fb8cdf27c6d507d8bc8ae04cfcbfd75f5"}, + {file = "Cython-0.29.33-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:1ca93bbe584aee92094fd4fb6acc5cb6500acf98d4f57cc59244f0a598b0fcf6"}, + {file = "Cython-0.29.33-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:da490129e1e4ffaf3f88bfb46d338549a2150f60f809a63d385b83e00960d11a"}, + {file = "Cython-0.29.33-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:4cadf5250eda0c5cdaf4c3a29b52be3e0695f4a2bf1ccd49b638d239752ea513"}, + {file = "Cython-0.29.33-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:bcb1a84fd2bd7885d572adc180e24fd8a7d4b0c104c144e33ccf84a1ab4eb2b8"}, + {file = "Cython-0.29.33-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:d78147ad8a3417ae6b371bbc5bfc6512f6ad4ad3fb71f5eef42e136e4ed14970"}, + {file = "Cython-0.29.33-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:dd96b06b93c0e5fa4fc526c5be37c13a93e2fe7c372b5f358277ebe9e1620957"}, + {file = "Cython-0.29.33-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:959f0092d58e7fa00fd3434f7ff32fb78be7c2fa9f8e0096326343159477fe45"}, + {file = "Cython-0.29.33-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:0455d5b92f461218bcf173a149a88b7396c3a109066274ccab5eff58db0eae32"}, + {file = "Cython-0.29.33-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:a9b0b890656e9d18a18e1efe26ea3d2d0f3e525a07a2a853592b0afc56a15c89"}, + {file = "Cython-0.29.33-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:b5e8ce3039ff64000d58cd45b3f6f83e13f032dde7f27bb1ab96070d9213550b"}, + {file = "Cython-0.29.33-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:e8922fa3d7e76b7186bbd0810e170ca61f83661ab1b29dc75e88ff2327aaf49d"}, + {file = "Cython-0.29.33-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f67b7306fd00d55f271009335cecadc506d144205c7891070aad889928d85750"}, + {file = "Cython-0.29.33-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:f271f90005064c49b47a93f456dc6cf0a21d21ef835bd33ac1e0db10ad51f84f"}, + {file = "Cython-0.29.33-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d4457d417ffbb94abc42adcd63a03b24ff39cf090f3e9eca5e10cfb90766cbe3"}, + {file = "Cython-0.29.33-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:0b53e017522feb8dcc2189cf1d2d344bab473c5bba5234390b5666d822992c7c"}, + {file = "Cython-0.29.33-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:4f88c2dc0653eef6468848eb8022faf64115b39734f750a1c01a7ba7eb04d89f"}, + {file = "Cython-0.29.33-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:1900d862a4a537d2125706740e9f3b016e80f7bbf7b54db6b3cc3d0bdf0f5c3a"}, + {file = "Cython-0.29.33-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:37bfca4f9f26361343d8c678f8178321e4ae5b919523eed05d2cd8ddbe6b06ec"}, + {file = "Cython-0.29.33-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:a9863f8238642c0b1ef8069d99da5ade03bfe2225a64b00c5ae006d95f142a73"}, + {file = "Cython-0.29.33-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:1dd503408924723b0bb10c0013b76e324eeee42db6deced9b02b648f1415d94c"}, + {file = "Cython-0.29.33-py2.py3-none-any.whl", hash = "sha256:8b99252bde8ff51cd06a3fe4aeacd3af9b4ff4a4e6b701ac71bddc54f5da61d6"}, + {file = "Cython-0.29.33.tar.gz", hash = "sha256:5040764c4a4d2ce964a395da24f0d1ae58144995dab92c6b96f44c3f4d72286a"}, +] + +[[package]] +name = "debugpy" +version = "1.6.6" +description = "An implementation of the Debug Adapter Protocol for Python" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "debugpy-1.6.6-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:0ea1011e94416e90fb3598cc3ef5e08b0a4dd6ce6b9b33ccd436c1dffc8cd664"}, + {file = "debugpy-1.6.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dff595686178b0e75580c24d316aa45a8f4d56e2418063865c114eef651a982e"}, + {file = "debugpy-1.6.6-cp310-cp310-win32.whl", hash = "sha256:87755e173fcf2ec45f584bb9d61aa7686bb665d861b81faa366d59808bbd3494"}, + {file = "debugpy-1.6.6-cp310-cp310-win_amd64.whl", hash = "sha256:72687b62a54d9d9e3fb85e7a37ea67f0e803aaa31be700e61d2f3742a5683917"}, + {file = "debugpy-1.6.6-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:78739f77c58048ec006e2b3eb2e0cd5a06d5f48c915e2fc7911a337354508110"}, + {file = "debugpy-1.6.6-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:23c29e40e39ad7d869d408ded414f6d46d82f8a93b5857ac3ac1e915893139ca"}, + {file = "debugpy-1.6.6-cp37-cp37m-win32.whl", hash = "sha256:7aa7e103610e5867d19a7d069e02e72eb2b3045b124d051cfd1538f1d8832d1b"}, + {file = "debugpy-1.6.6-cp37-cp37m-win_amd64.whl", hash = "sha256:f6383c29e796203a0bba74a250615ad262c4279d398e89d895a69d3069498305"}, + {file = "debugpy-1.6.6-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:23363e6d2a04d726bbc1400bd4e9898d54419b36b2cdf7020e3e215e1dcd0f8e"}, + {file = "debugpy-1.6.6-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9b5d1b13d7c7bf5d7cf700e33c0b8ddb7baf030fcf502f76fc061ddd9405d16c"}, + {file = "debugpy-1.6.6-cp38-cp38-win32.whl", hash = "sha256:70ab53918fd907a3ade01909b3ed783287ede362c80c75f41e79596d5ccacd32"}, + {file = "debugpy-1.6.6-cp38-cp38-win_amd64.whl", hash = "sha256:c05349890804d846eca32ce0623ab66c06f8800db881af7a876dc073ac1c2225"}, + {file = "debugpy-1.6.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a771739902b1ae22a120dbbb6bd91b2cae6696c0e318b5007c5348519a4211c6"}, + {file = "debugpy-1.6.6-cp39-cp39-win32.whl", hash = "sha256:549ae0cb2d34fc09d1675f9b01942499751d174381b6082279cf19cdb3c47cbe"}, + {file = "debugpy-1.6.6-cp39-cp39-win_amd64.whl", hash = "sha256:de4a045fbf388e120bb6ec66501458d3134f4729faed26ff95de52a754abddb1"}, + {file = "debugpy-1.6.6-py2.py3-none-any.whl", hash = "sha256:be596b44448aac14eb3614248c91586e2bc1728e020e82ef3197189aae556115"}, + {file = "debugpy-1.6.6.zip", hash = "sha256:b9c2130e1c632540fbf9c2c88341493797ddf58016e7cba02e311de9b0a96b67"}, +] + +[[package]] +name = "decorator" +version = "5.1.1" +description = "Decorators for Humans" +category = "dev" +optional = false +python-versions = ">=3.5" +files = [ + {file = "decorator-5.1.1-py3-none-any.whl", hash = "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186"}, + {file = "decorator-5.1.1.tar.gz", hash = "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330"}, +] + +[[package]] +name = "defusedxml" +version = "0.7.1" +description = "XML bomb protection for Python stdlib modules" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +files = [ + {file = "defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61"}, + {file = "defusedxml-0.7.1.tar.gz", hash = "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69"}, +] + +[[package]] +name = "dill" +version = "0.3.6" +description = "serialize all of python" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "dill-0.3.6-py3-none-any.whl", hash = "sha256:a07ffd2351b8c678dfc4a856a3005f8067aea51d6ba6c700796a4d9e280f39f0"}, + {file = "dill-0.3.6.tar.gz", hash = "sha256:e5db55f3687856d8fbdab002ed78544e1c4559a130302693d839dfe8f93f2373"}, +] + +[package.extras] +graph = ["objgraph (>=1.7.2)"] + +[[package]] +name = "docstring-to-markdown" +version = "0.12" +description = "On the fly conversion of Python docstrings to markdown" +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ + {file = "docstring-to-markdown-0.12.tar.gz", hash = "sha256:40004224b412bd6f64c0f3b85bb357a41341afd66c4b4896709efa56827fb2bb"}, + {file = "docstring_to_markdown-0.12-py3-none-any.whl", hash = "sha256:7df6311a887dccf9e770f51242ec002b19f0591994c4783be49d24cdc1df3737"}, +] + +[[package]] +name = "entrypoints" +version = "0.4" +description = "Discover and load entry points from installed packages." +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ + {file = "entrypoints-0.4-py3-none-any.whl", hash = "sha256:f174b5ff827504fd3cd97cc3f8649f3693f51538c7e4bdf3ef002c8429d42f9f"}, + {file = "entrypoints-0.4.tar.gz", hash = "sha256:b706eddaa9218a19ebcd67b56818f05bb27589b1ca9e8d797b74affad4ccacd4"}, +] + +[[package]] +name = "executing" +version = "1.2.0" +description = "Get the currently executing AST node of a frame, and other information" +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "executing-1.2.0-py2.py3-none-any.whl", hash = "sha256:0314a69e37426e3608aada02473b4161d4caf5a4b244d1d0c48072b8fee7bacc"}, + {file = "executing-1.2.0.tar.gz", hash = "sha256:19da64c18d2d851112f09c287f8d3dbbdf725ab0e569077efb6cdcbd3497c107"}, +] + +[package.extras] +tests = ["asttokens", "littleutils", "pytest", "rich"] + +[[package]] +name = "faiss-cpu" +version = "1.7.3" +description = "A library for efficient similarity search and clustering of dense vectors." +category = "main" +optional = false +python-versions = "*" +files = [ + {file = "faiss-cpu-1.7.3.tar.gz", hash = "sha256:cb71fe3f2934732d157d9d8cfb6ed2dd4020a0065571c84842ff6a3f0beab310"}, + {file = "faiss_cpu-1.7.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:343f025e0846239d987d0c719772387ad685b74e5ef62b2e5616cabef9062729"}, + {file = "faiss_cpu-1.7.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8b7b1cf693d7c24b5a633ff024717bd715fec501af4854357da0805b4899bcec"}, + {file = "faiss_cpu-1.7.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c37e5fc0a266839844798a53dd42dd6afbee0c5905611f3f278297053fccbd7"}, + {file = "faiss_cpu-1.7.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0628f7b0c6263ef4431995bb4f5f39833f999e96e6663935cbf0a1f2243dc4ac"}, + {file = "faiss_cpu-1.7.3-cp310-cp310-win_amd64.whl", hash = "sha256:e22d1887c617156a673665c913ee82a30bfc1a3bc939ba8500b61328bce5a625"}, + {file = "faiss_cpu-1.7.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6d411449a5f3c3abfcafadaac3190ab1ab206023fc9110da86649506dcbe8a27"}, + {file = "faiss_cpu-1.7.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a10ea8622908f9f9ca4003e66da809dfad4af5c7d9fb7f582722d703bbc6c8bd"}, + {file = "faiss_cpu-1.7.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7c5ced43ae058a62f63b12194ec9aa4c34066b0ea813ecbd936c65b7d52848c8"}, + {file = "faiss_cpu-1.7.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3df6371012248dea8e9509949e2d2c6d73dea7c1bdaa4ba4563eb1c3cd8021a6"}, + {file = "faiss_cpu-1.7.3-cp311-cp311-win_amd64.whl", hash = "sha256:8b6ff7854c3f46104718c6b34e81cd48c156d970dd87703c5122ca90217bb8dc"}, + {file = "faiss_cpu-1.7.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:ab6314a8fbcce11dc3ecb6f48dda8c4ec274ed11c1f336f599f480bf0561442c"}, + {file = "faiss_cpu-1.7.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:877c0bbf4c4a1806d88e091aba4c91ff3fa35c3ede5663b7fafc5b39247a369e"}, + {file = "faiss_cpu-1.7.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6f199be10d30ecc6ed65350931006eca01b7bb8faa27d63069318eea0f6a0c1"}, + {file = "faiss_cpu-1.7.3-cp37-cp37m-win_amd64.whl", hash = "sha256:1ca2b7cdbfdcc6a2e8fa75a09594916b50ec8260913ca48334dc3ce797179b5f"}, + {file = "faiss_cpu-1.7.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7b3f91856c19cfb8464178bab7e8ea94a391f6947b556be6754f9fc10b3c25fb"}, + {file = "faiss_cpu-1.7.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7a238a0ef4d36c614d6f60e1ea308288b3920091638a3687f708de6071d007c1"}, + {file = "faiss_cpu-1.7.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:af53bee502c629eaaaf8b5ec648484a726be0fd2768ad4ef2bd4b829384b2682"}, + {file = "faiss_cpu-1.7.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:441d1c305595d925138f2cde63dabe8c10ee05fc8ad66bf750e278a7e8c409bd"}, + {file = "faiss_cpu-1.7.3-cp38-cp38-win_amd64.whl", hash = "sha256:2766cc14b9004c1aae3b3943e693c3a9566eb1a25168b681981f9048276fe1e7"}, + {file = "faiss_cpu-1.7.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:20ef191bb6164c8e794b11d20427568a75d15980b6d66732071e9aa57ea06e2d"}, + {file = "faiss_cpu-1.7.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c57c293c4682066955626c2a2956be9a3b92594f69ed1a33abd72260a6911b69"}, + {file = "faiss_cpu-1.7.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd128170446ff3c3e28d89e813d32cd04f17fa3025794778a01a0d81524275dc"}, + {file = "faiss_cpu-1.7.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a14d832b5361ce9af21977eb1dcdebe23b9edcc12aad40316df7ca1bd86bc6b5"}, + {file = "faiss_cpu-1.7.3-cp39-cp39-win_amd64.whl", hash = "sha256:52df8895c5e59d1c9eda368a63790381a6f7fceddb22bed08f9c90a706d8a148"}, +] + +[[package]] +name = "fastjsonschema" +version = "2.16.3" +description = "Fastest Python implementation of JSON schema" +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "fastjsonschema-2.16.3-py3-none-any.whl", hash = "sha256:04fbecc94300436f628517b05741b7ea009506ce8f946d40996567c669318490"}, + {file = "fastjsonschema-2.16.3.tar.gz", hash = "sha256:4a30d6315a68c253cfa8f963b9697246315aa3db89f98b97235e345dedfb0b8e"}, +] + +[package.extras] +devel = ["colorama", "json-spec", "jsonschema", "pylint", "pytest", "pytest-benchmark", "pytest-cache", "validictory"] + +[[package]] +name = "fix-my-functions" +version = "0.1.3" +description = "" +category = "dev" +optional = false +python-versions = ">=3.9" +files = [ + {file = "fix-my-functions-0.1.3.tar.gz", hash = "sha256:3668905cf84f76f6e3f72059881456b47af4ae3763da0cd2a37200c87ad75adf"}, + {file = "fix_my_functions-0.1.3-py3-none-any.whl", hash = "sha256:ace77267430050e979615c944f69adae6f80542e138e1be03deddfd4143ff9c9"}, +] + +[package.dependencies] +colorama = ">=0.4.4" +redbaron = ">=0.9.2" + +[[package]] +name = "flake8" +version = "6.0.0" +description = "the modular source code checker: pep8 pyflakes and co" +category = "dev" +optional = false +python-versions = ">=3.8.1" +files = [ + {file = "flake8-6.0.0-py2.py3-none-any.whl", hash = "sha256:3833794e27ff64ea4e9cf5d410082a8b97ff1a06c16aa3d2027339cd0f1195c7"}, + {file = "flake8-6.0.0.tar.gz", hash = "sha256:c61007e76655af75e6785a931f452915b371dc48f56efd765247c8fe68f2b181"}, +] + +[package.dependencies] +mccabe = ">=0.7.0,<0.8.0" +pycodestyle = ">=2.10.0,<2.11.0" +pyflakes = ">=3.0.0,<3.1.0" + +[[package]] +name = "fonttools" +version = "4.39.2" +description = "Tools to manipulate font files" +category = "main" +optional = false +python-versions = ">=3.8" +files = [ + {file = "fonttools-4.39.2-py3-none-any.whl", hash = "sha256:85245aa2fd4cf502a643c9a9a2b5a393703e150a6eaacc3e0e84bb448053f061"}, + {file = "fonttools-4.39.2.zip", hash = "sha256:e2d9f10337c9e3b17f9bce17a60a16a885a7d23b59b7f45ce07ea643e5580439"}, +] + +[package.extras] +all = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "fs (>=2.2.0,<3)", "lxml (>=4.0,<5)", "lz4 (>=1.7.4.2)", "matplotlib", "munkres", "scipy", "skia-pathops (>=0.5.0)", "sympy", "uharfbuzz (>=0.23.0)", "unicodedata2 (>=15.0.0)", "xattr", "zopfli (>=0.1.4)"] +graphite = ["lz4 (>=1.7.4.2)"] +interpolatable = ["munkres", "scipy"] +lxml = ["lxml (>=4.0,<5)"] +pathops = ["skia-pathops (>=0.5.0)"] +plot = ["matplotlib"] +repacker = ["uharfbuzz (>=0.23.0)"] +symfont = ["sympy"] +type1 = ["xattr"] +ufo = ["fs (>=2.2.0,<3)"] +unicode = ["unicodedata2 (>=15.0.0)"] +woff = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "zopfli (>=0.1.4)"] + +[[package]] +name = "fqdn" +version = "1.5.1" +description = "Validates fully-qualified domain names against RFC 1123, so that they are acceptable to modern bowsers" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0, !=3.1, !=3.2, !=3.3, !=3.4, <4" +files = [ + {file = "fqdn-1.5.1-py3-none-any.whl", hash = "sha256:3a179af3761e4df6eb2e026ff9e1a3033d3587bf980a0b1b2e1e5d08d7358014"}, + {file = "fqdn-1.5.1.tar.gz", hash = "sha256:105ed3677e767fb5ca086a0c1f4bb66ebc3c100be518f0e0d755d9eae164d89f"}, +] + +[[package]] +name = "freetype-py" +version = "2.3.0" +description = "Freetype python bindings" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "freetype-py-2.3.0.zip", hash = "sha256:f9b64ce3272a5c358dcee824800a32d70997fb872a0965a557adca20fce7a5d0"}, + {file = "freetype_py-2.3.0-py3-none-macosx_10_9_universal2.whl", hash = "sha256:ca7155de937af6f26bfd9f9089a6e9b01fa8f9d3040a3ddc0aeb3a53cf88f428"}, + {file = "freetype_py-2.3.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ccdb1616794a8ad48beaa9e29d3494e6643d24d8e925cc39263de21c062ea5a7"}, + {file = "freetype_py-2.3.0-py3-none-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c8f17c3ac35dc7cc9571ac37a00a6daa428a1a6d0fe6926a77d16066865ed5ef"}, + {file = "freetype_py-2.3.0-py3-none-musllinux_1_1_aarch64.whl", hash = "sha256:89cee8f4e7cf0a37b73a43a08c88703d84e3b9f9243fc665d8dc0b72a5d206a8"}, + {file = "freetype_py-2.3.0-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:b95ccd52ff7e9bef34505f8af724cee114a3c3cc9cf13e0fd406fa0cc92b988a"}, + {file = "freetype_py-2.3.0-py3-none-win_amd64.whl", hash = "sha256:3a552265b06c2cb3fa54f86ed6fcbf045d8dc8176f9475bedddf9a1b31f5402f"}, +] + +[[package]] +name = "frozenlist" +version = "1.3.3" +description = "A list-like structure which implements collections.abc.MutableSequence" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "frozenlist-1.3.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ff8bf625fe85e119553b5383ba0fb6aa3d0ec2ae980295aaefa552374926b3f4"}, + {file = "frozenlist-1.3.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dfbac4c2dfcc082fcf8d942d1e49b6aa0766c19d3358bd86e2000bf0fa4a9cf0"}, + {file = "frozenlist-1.3.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b1c63e8d377d039ac769cd0926558bb7068a1f7abb0f003e3717ee003ad85530"}, + {file = "frozenlist-1.3.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7fdfc24dcfce5b48109867c13b4cb15e4660e7bd7661741a391f821f23dfdca7"}, + {file = "frozenlist-1.3.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2c926450857408e42f0bbc295e84395722ce74bae69a3b2aa2a65fe22cb14b99"}, + {file = "frozenlist-1.3.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1841e200fdafc3d51f974d9d377c079a0694a8f06de2e67b48150328d66d5483"}, + {file = "frozenlist-1.3.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f470c92737afa7d4c3aacc001e335062d582053d4dbe73cda126f2d7031068dd"}, + {file = "frozenlist-1.3.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:783263a4eaad7c49983fe4b2e7b53fa9770c136c270d2d4bbb6d2192bf4d9caf"}, + {file = "frozenlist-1.3.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:924620eef691990dfb56dc4709f280f40baee568c794b5c1885800c3ecc69816"}, + {file = "frozenlist-1.3.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:ae4dc05c465a08a866b7a1baf360747078b362e6a6dbeb0c57f234db0ef88ae0"}, + {file = "frozenlist-1.3.3-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:bed331fe18f58d844d39ceb398b77d6ac0b010d571cba8267c2e7165806b00ce"}, + {file = "frozenlist-1.3.3-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:02c9ac843e3390826a265e331105efeab489ffaf4dd86384595ee8ce6d35ae7f"}, + {file = "frozenlist-1.3.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:9545a33965d0d377b0bc823dcabf26980e77f1b6a7caa368a365a9497fb09420"}, + {file = "frozenlist-1.3.3-cp310-cp310-win32.whl", hash = "sha256:d5cd3ab21acbdb414bb6c31958d7b06b85eeb40f66463c264a9b343a4e238642"}, + {file = "frozenlist-1.3.3-cp310-cp310-win_amd64.whl", hash = "sha256:b756072364347cb6aa5b60f9bc18e94b2f79632de3b0190253ad770c5df17db1"}, + {file = "frozenlist-1.3.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:b4395e2f8d83fbe0c627b2b696acce67868793d7d9750e90e39592b3626691b7"}, + {file = "frozenlist-1.3.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:14143ae966a6229350021384870458e4777d1eae4c28d1a7aa47f24d030e6678"}, + {file = "frozenlist-1.3.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5d8860749e813a6f65bad8285a0520607c9500caa23fea6ee407e63debcdbef6"}, + {file = "frozenlist-1.3.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23d16d9f477bb55b6154654e0e74557040575d9d19fe78a161bd33d7d76808e8"}, + {file = "frozenlist-1.3.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eb82dbba47a8318e75f679690190c10a5e1f447fbf9df41cbc4c3afd726d88cb"}, + {file = "frozenlist-1.3.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9309869032abb23d196cb4e4db574232abe8b8be1339026f489eeb34a4acfd91"}, + {file = "frozenlist-1.3.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a97b4fe50b5890d36300820abd305694cb865ddb7885049587a5678215782a6b"}, + {file = "frozenlist-1.3.3-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c188512b43542b1e91cadc3c6c915a82a5eb95929134faf7fd109f14f9892ce4"}, + {file = "frozenlist-1.3.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:303e04d422e9b911a09ad499b0368dc551e8c3cd15293c99160c7f1f07b59a48"}, + {file = "frozenlist-1.3.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:0771aed7f596c7d73444c847a1c16288937ef988dc04fb9f7be4b2aa91db609d"}, + {file = "frozenlist-1.3.3-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:66080ec69883597e4d026f2f71a231a1ee9887835902dbe6b6467d5a89216cf6"}, + {file = "frozenlist-1.3.3-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:41fe21dc74ad3a779c3d73a2786bdf622ea81234bdd4faf90b8b03cad0c2c0b4"}, + {file = "frozenlist-1.3.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f20380df709d91525e4bee04746ba612a4df0972c1b8f8e1e8af997e678c7b81"}, + {file = "frozenlist-1.3.3-cp311-cp311-win32.whl", hash = "sha256:f30f1928162e189091cf4d9da2eac617bfe78ef907a761614ff577ef4edfb3c8"}, + {file = "frozenlist-1.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:a6394d7dadd3cfe3f4b3b186e54d5d8504d44f2d58dcc89d693698e8b7132b32"}, + {file = "frozenlist-1.3.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8df3de3a9ab8325f94f646609a66cbeeede263910c5c0de0101079ad541af332"}, + {file = "frozenlist-1.3.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0693c609e9742c66ba4870bcee1ad5ff35462d5ffec18710b4ac89337ff16e27"}, + {file = "frozenlist-1.3.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cd4210baef299717db0a600d7a3cac81d46ef0e007f88c9335db79f8979c0d3d"}, + {file = "frozenlist-1.3.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:394c9c242113bfb4b9aa36e2b80a05ffa163a30691c7b5a29eba82e937895d5e"}, + {file = "frozenlist-1.3.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6327eb8e419f7d9c38f333cde41b9ae348bec26d840927332f17e887a8dcb70d"}, + {file = "frozenlist-1.3.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2e24900aa13212e75e5b366cb9065e78bbf3893d4baab6052d1aca10d46d944c"}, + {file = "frozenlist-1.3.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:3843f84a6c465a36559161e6c59dce2f2ac10943040c2fd021cfb70d58c4ad56"}, + {file = "frozenlist-1.3.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:84610c1502b2461255b4c9b7d5e9c48052601a8957cd0aea6ec7a7a1e1fb9420"}, + {file = "frozenlist-1.3.3-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:c21b9aa40e08e4f63a2f92ff3748e6b6c84d717d033c7b3438dd3123ee18f70e"}, + {file = "frozenlist-1.3.3-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:efce6ae830831ab6a22b9b4091d411698145cb9b8fc869e1397ccf4b4b6455cb"}, + {file = "frozenlist-1.3.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:40de71985e9042ca00b7953c4f41eabc3dc514a2d1ff534027f091bc74416401"}, + {file = "frozenlist-1.3.3-cp37-cp37m-win32.whl", hash = "sha256:180c00c66bde6146a860cbb81b54ee0df350d2daf13ca85b275123bbf85de18a"}, + {file = "frozenlist-1.3.3-cp37-cp37m-win_amd64.whl", hash = "sha256:9bbbcedd75acdfecf2159663b87f1bb5cfc80e7cd99f7ddd9d66eb98b14a8411"}, + {file = "frozenlist-1.3.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:034a5c08d36649591be1cbb10e09da9f531034acfe29275fc5454a3b101ce41a"}, + {file = "frozenlist-1.3.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ba64dc2b3b7b158c6660d49cdb1d872d1d0bf4e42043ad8d5006099479a194e5"}, + {file = "frozenlist-1.3.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:47df36a9fe24054b950bbc2db630d508cca3aa27ed0566c0baf661225e52c18e"}, + {file = "frozenlist-1.3.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:008a054b75d77c995ea26629ab3a0c0d7281341f2fa7e1e85fa6153ae29ae99c"}, + {file = "frozenlist-1.3.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:841ea19b43d438a80b4de62ac6ab21cfe6827bb8a9dc62b896acc88eaf9cecba"}, + {file = "frozenlist-1.3.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e235688f42b36be2b6b06fc37ac2126a73b75fb8d6bc66dd632aa35286238703"}, + {file = "frozenlist-1.3.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca713d4af15bae6e5d79b15c10c8522859a9a89d3b361a50b817c98c2fb402a2"}, + {file = "frozenlist-1.3.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ac5995f2b408017b0be26d4a1d7c61bce106ff3d9e3324374d66b5964325448"}, + {file = "frozenlist-1.3.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:a4ae8135b11652b08a8baf07631d3ebfe65a4c87909dbef5fa0cdde440444ee4"}, + {file = "frozenlist-1.3.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:4ea42116ceb6bb16dbb7d526e242cb6747b08b7710d9782aa3d6732bd8d27649"}, + {file = "frozenlist-1.3.3-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:810860bb4bdce7557bc0febb84bbd88198b9dbc2022d8eebe5b3590b2ad6c842"}, + {file = "frozenlist-1.3.3-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:ee78feb9d293c323b59a6f2dd441b63339a30edf35abcb51187d2fc26e696d13"}, + {file = "frozenlist-1.3.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:0af2e7c87d35b38732e810befb9d797a99279cbb85374d42ea61c1e9d23094b3"}, + {file = "frozenlist-1.3.3-cp38-cp38-win32.whl", hash = "sha256:899c5e1928eec13fd6f6d8dc51be23f0d09c5281e40d9cf4273d188d9feeaf9b"}, + {file = "frozenlist-1.3.3-cp38-cp38-win_amd64.whl", hash = "sha256:7f44e24fa70f6fbc74aeec3e971f60a14dde85da364aa87f15d1be94ae75aeef"}, + {file = "frozenlist-1.3.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2b07ae0c1edaa0a36339ec6cce700f51b14a3fc6545fdd32930d2c83917332cf"}, + {file = "frozenlist-1.3.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ebb86518203e12e96af765ee89034a1dbb0c3c65052d1b0c19bbbd6af8a145e1"}, + {file = "frozenlist-1.3.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5cf820485f1b4c91e0417ea0afd41ce5cf5965011b3c22c400f6d144296ccbc0"}, + {file = "frozenlist-1.3.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c11e43016b9024240212d2a65043b70ed8dfd3b52678a1271972702d990ac6d"}, + {file = "frozenlist-1.3.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8fa3c6e3305aa1146b59a09b32b2e04074945ffcfb2f0931836d103a2c38f936"}, + {file = "frozenlist-1.3.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:352bd4c8c72d508778cf05ab491f6ef36149f4d0cb3c56b1b4302852255d05d5"}, + {file = "frozenlist-1.3.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:65a5e4d3aa679610ac6e3569e865425b23b372277f89b5ef06cf2cdaf1ebf22b"}, + {file = "frozenlist-1.3.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1e2c1185858d7e10ff045c496bbf90ae752c28b365fef2c09cf0fa309291669"}, + {file = "frozenlist-1.3.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f163d2fd041c630fed01bc48d28c3ed4a3b003c00acd396900e11ee5316b56bb"}, + {file = "frozenlist-1.3.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:05cdb16d09a0832eedf770cb7bd1fe57d8cf4eaf5aced29c4e41e3f20b30a784"}, + {file = "frozenlist-1.3.3-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:8bae29d60768bfa8fb92244b74502b18fae55a80eac13c88eb0b496d4268fd2d"}, + {file = "frozenlist-1.3.3-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:eedab4c310c0299961ac285591acd53dc6723a1ebd90a57207c71f6e0c2153ab"}, + {file = "frozenlist-1.3.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3bbdf44855ed8f0fbcd102ef05ec3012d6a4fd7c7562403f76ce6a52aeffb2b1"}, + {file = "frozenlist-1.3.3-cp39-cp39-win32.whl", hash = "sha256:efa568b885bca461f7c7b9e032655c0c143d305bf01c30caf6db2854a4532b38"}, + {file = "frozenlist-1.3.3-cp39-cp39-win_amd64.whl", hash = "sha256:cfe33efc9cb900a4c46f91a5ceba26d6df370ffddd9ca386eb1d4f0ad97b9ea9"}, + {file = "frozenlist-1.3.3.tar.gz", hash = "sha256:58bcc55721e8a90b88332d6cd441261ebb22342e238296bb330968952fbb3a6a"}, +] + +[[package]] +name = "fsspec" +version = "2023.3.0" +description = "File-system specification" +category = "main" +optional = false +python-versions = ">=3.8" +files = [ + {file = "fsspec-2023.3.0-py3-none-any.whl", hash = "sha256:bf57215e19dbfa4fe7edae53040cc1deef825e3b1605cca9a8d2c2fadd2328a0"}, + {file = "fsspec-2023.3.0.tar.gz", hash = "sha256:24e635549a590d74c6c18274ddd3ffab4753341753e923408b1904eaabafe04d"}, +] + +[package.dependencies] +aiohttp = {version = "<4.0.0a0 || >4.0.0a0,<4.0.0a1 || >4.0.0a1", optional = true, markers = "extra == \"http\""} +requests = {version = "*", optional = true, markers = "extra == \"http\""} + +[package.extras] +abfs = ["adlfs"] +adl = ["adlfs"] +arrow = ["pyarrow (>=1)"] +dask = ["dask", "distributed"] +dropbox = ["dropbox", "dropboxdrivefs", "requests"] +fuse = ["fusepy"] +gcs = ["gcsfs"] +git = ["pygit2"] +github = ["requests"] +gs = ["gcsfs"] +gui = ["panel"] +hdfs = ["pyarrow (>=1)"] +http = ["aiohttp (!=4.0.0a0,!=4.0.0a1)", "requests"] +libarchive = ["libarchive-c"] +oci = ["ocifs"] +s3 = ["s3fs"] +sftp = ["paramiko"] +smb = ["smbprotocol"] +ssh = ["paramiko"] +tqdm = ["tqdm"] + +[[package]] +name = "fvcore" +version = "0.1.5.post20221221" +description = "Collection of common code shared among different research projects in FAIR computer vision team" +category = "main" +optional = false +python-versions = ">=3.6" +files = [ + {file = "fvcore-0.1.5.post20221221.tar.gz", hash = "sha256:f2fb0bb90572ae651c11c78e20493ed19b2240550a7e4bbb2d6de87bdd037860"}, +] + +[package.dependencies] +iopath = ">=0.1.7" +numpy = "*" +Pillow = "*" +pyyaml = ">=5.1" +tabulate = "*" +termcolor = ">=1.1" +tqdm = "*" +yacs = ">=0.1.6" + +[package.extras] +all = ["shapely"] + +[[package]] +name = "geomloss" +version = "0.2.4" +description = "Geometric loss functions between point clouds, images and volumes." +category = "main" +optional = false +python-versions = ">=3" +files = [ + {file = "geomloss-0.2.4-py3-none-any.whl", hash = "sha256:1e4b4a53d1798f7927f2a15f6fb533675d8f172d9de4f0eaf959eb120ab8c9f5"}, + {file = "geomloss-0.2.4.tar.gz", hash = "sha256:3d6cc5a358b854429619fc180f1e7a3ab31a0b50742d7196042adf5134065dfa"}, +] + +[package.dependencies] +numpy = "*" + +[package.extras] +full = ["cmake (>=3.18)", "pykeops[full]"] + +[[package]] +name = "google-auth" +version = "2.16.2" +description = "Google Authentication Library" +category = "dev" +optional = false +python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*" +files = [ + {file = "google-auth-2.16.2.tar.gz", hash = "sha256:07e14f34ec288e3f33e00e2e3cc40c8942aa5d4ceac06256a28cd8e786591420"}, + {file = "google_auth-2.16.2-py2.py3-none-any.whl", hash = "sha256:2fef3cf94876d1a0e204afece58bb4d83fb57228aaa366c64045039fda6770a2"}, +] + +[package.dependencies] +cachetools = ">=2.0.0,<6.0" +pyasn1-modules = ">=0.2.1" +rsa = {version = ">=3.1.4,<5", markers = "python_version >= \"3.6\""} +six = ">=1.9.0" + +[package.extras] +aiohttp = ["aiohttp (>=3.6.2,<4.0.0dev)", "requests (>=2.20.0,<3.0.0dev)"] +enterprise-cert = ["cryptography (==36.0.2)", "pyopenssl (==22.0.0)"] +pyopenssl = ["cryptography (>=38.0.3)", "pyopenssl (>=20.0.0)"] +reauth = ["pyu2f (>=0.1.5)"] +requests = ["requests (>=2.20.0,<3.0.0dev)"] + +[[package]] +name = "google-auth-oauthlib" +version = "0.4.6" +description = "Google Authentication Library" +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ + {file = "google-auth-oauthlib-0.4.6.tar.gz", hash = "sha256:a90a072f6993f2c327067bf65270046384cda5a8ecb20b94ea9a687f1f233a7a"}, + {file = "google_auth_oauthlib-0.4.6-py2.py3-none-any.whl", hash = "sha256:3f2a6e802eebbb6fb736a370fbf3b055edcb6b52878bf2f26330b5e041316c73"}, +] + +[package.dependencies] +google-auth = ">=1.0.0" +requests-oauthlib = ">=0.7.0" + +[package.extras] +tool = ["click (>=6.0.0)"] + +[[package]] +name = "graphviz" +version = "0.20.1" +description = "Simple Python interface for Graphviz" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "graphviz-0.20.1-py3-none-any.whl", hash = "sha256:587c58a223b51611c0cf461132da386edd896a029524ca61a1462b880bf97977"}, + {file = "graphviz-0.20.1.zip", hash = "sha256:8c58f14adaa3b947daf26c19bc1e98c4e0702cdc31cf99153e6f06904d492bf8"}, +] + +[package.extras] +dev = ["flake8", "pep8-naming", "tox (>=3)", "twine", "wheel"] +docs = ["sphinx (>=5)", "sphinx-autodoc-typehints", "sphinx-rtd-theme"] +test = ["coverage", "mock (>=4)", "pytest (>=7)", "pytest-cov", "pytest-mock (>=3)"] + +[[package]] +name = "grpcio" +version = "1.51.3" +description = "HTTP/2-based RPC framework" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "grpcio-1.51.3-cp310-cp310-linux_armv7l.whl", hash = "sha256:f601aaeae18dab81930fb8d4f916b0da21e89bb4b5f7367ef793f46b4a76b7b0"}, + {file = "grpcio-1.51.3-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:eef0450a4b5ed11feab639bf3eb1b6e23d0efa9b911bf7b06fb60e14f5f8a585"}, + {file = "grpcio-1.51.3-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:82b0ad8ac825d4bb31bff9f638557c045f4a6d824d84b21e893968286f88246b"}, + {file = "grpcio-1.51.3-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3667c06e37d6cd461afdd51cefe6537702f3d1dc5ff4cac07e88d8b4795dc16f"}, + {file = "grpcio-1.51.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3709048fe0aa23dda09b3e69849a12055790171dab9e399a72ea8f9dfbf9ac80"}, + {file = "grpcio-1.51.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:200d69857f9910f7458b39b9bcf83ee4a180591b40146ba9e49314e3a7419313"}, + {file = "grpcio-1.51.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:cd9a5e68e79c5f031500e67793048a90209711e0854a9ddee8a3ce51728de4e5"}, + {file = "grpcio-1.51.3-cp310-cp310-win32.whl", hash = "sha256:6604f614016127ae10969176bbf12eb0e03d2fb3d643f050b3b69e160d144fb4"}, + {file = "grpcio-1.51.3-cp310-cp310-win_amd64.whl", hash = "sha256:e95c7ccd4c5807adef1602005513bf7c7d14e5a41daebcf9d8d30d8bf51b8f81"}, + {file = "grpcio-1.51.3-cp311-cp311-linux_armv7l.whl", hash = "sha256:5e77ee138100f0bb55cbd147840f87ee6241dbd25f09ea7cd8afe7efff323449"}, + {file = "grpcio-1.51.3-cp311-cp311-macosx_10_10_universal2.whl", hash = "sha256:68a7514b754e38e8de9075f7bb4dee919919515ec68628c43a894027e40ddec4"}, + {file = "grpcio-1.51.3-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c1b9f8afa62ff265d86a4747a2990ec5a96e4efce5d5888f245a682d66eca47"}, + {file = "grpcio-1.51.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8de30f0b417744288cec65ec8cf84b8a57995cf7f1e84ccad2704d93f05d0aae"}, + {file = "grpcio-1.51.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:b69c7adc7ed60da1cb1b502853db61f453fc745f940cbcc25eb97c99965d8f41"}, + {file = "grpcio-1.51.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d81528ffe0e973dc840ec73a4132fd18b8203ad129d7410155d951a0a7e4f5d0"}, + {file = "grpcio-1.51.3-cp311-cp311-win32.whl", hash = "sha256:040eb421613b57c696063abde405916dd830203c184c9000fc8c3b3b3c950325"}, + {file = "grpcio-1.51.3-cp311-cp311-win_amd64.whl", hash = "sha256:2a8e17286c4240137d933b8ca506465472248b4ce0fe46f3404459e708b65b68"}, + {file = "grpcio-1.51.3-cp37-cp37m-linux_armv7l.whl", hash = "sha256:d5cd1389669a847555df54177b911d9ff6f17345b2a6f19388707b7a9f724c88"}, + {file = "grpcio-1.51.3-cp37-cp37m-macosx_10_10_universal2.whl", hash = "sha256:be1bf35ce82cdbcac14e39d5102d8de4079a1c1a6a06b68e41fcd9ef64f9dd28"}, + {file = "grpcio-1.51.3-cp37-cp37m-manylinux_2_17_aarch64.whl", hash = "sha256:5eed34994c095e2bf7194ffac7381c6068b057ef1e69f8f08db77771350a7566"}, + {file = "grpcio-1.51.3-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3f9a7d88082b2a17ae7bd3c2354d13bab0453899e0851733f6afa6918373f476"}, + {file = "grpcio-1.51.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:36c8abbc5f837111e7bd619612eedc223c290b0903b952ce0c7b00840ea70f14"}, + {file = "grpcio-1.51.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:165b05af77e6aecb4210ae7663e25acf234ba78a7c1c157fa5f2efeb0d6ec53c"}, + {file = "grpcio-1.51.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:54e36c2ee304ff15f2bfbdc43d2b56c63331c52d818c364e5b5214e5bc2ad9f6"}, + {file = "grpcio-1.51.3-cp37-cp37m-win32.whl", hash = "sha256:cd0daac21d9ef5e033a5100c1d3aa055bbed28bfcf070b12d8058045c4e821b1"}, + {file = "grpcio-1.51.3-cp37-cp37m-win_amd64.whl", hash = "sha256:2fdd6333ce96435408565a9dbbd446212cd5d62e4d26f6a3c0feb1e3c35f1cc8"}, + {file = "grpcio-1.51.3-cp38-cp38-linux_armv7l.whl", hash = "sha256:54b0c29bdd9a3b1e1b61443ab152f060fc719f1c083127ab08d03fac5efd51be"}, + {file = "grpcio-1.51.3-cp38-cp38-macosx_10_10_universal2.whl", hash = "sha256:ffaaf7e93fcb437356b5a4b23bf36e8a3d0221399ff77fd057e4bc77776a24be"}, + {file = "grpcio-1.51.3-cp38-cp38-manylinux_2_17_aarch64.whl", hash = "sha256:eafbe7501a3268d05f2e450e1ddaffb950d842a8620c13ec328b501d25d2e2c3"}, + {file = "grpcio-1.51.3-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:881ecb34feabf31c6b3b9bbbddd1a5b57e69f805041e5a2c6c562a28574f71c4"}, + {file = "grpcio-1.51.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e860a3222139b41d430939bbec2ec9c3f6c740938bf7a04471a9a8caaa965a2e"}, + {file = "grpcio-1.51.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:49ede0528e9dac7e8a9fe30b16c73b630ddd9a576bf4b675eb6b0c53ee5ca00f"}, + {file = "grpcio-1.51.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6972b009638b40a448d10e1bc18e2223143b8a7aa20d7def0d78dd4af4126d12"}, + {file = "grpcio-1.51.3-cp38-cp38-win32.whl", hash = "sha256:5694448256e3cdfe5bd358f1574a3f2f51afa20cc834713c4b9788d60b7cc646"}, + {file = "grpcio-1.51.3-cp38-cp38-win_amd64.whl", hash = "sha256:3ea4341efe603b049e8c9a5f13c696ca37fcdf8a23ca35f650428ad3606381d9"}, + {file = "grpcio-1.51.3-cp39-cp39-linux_armv7l.whl", hash = "sha256:6c677581ce129f5fa228b8f418cee10bd28dd449f3a544ea73c8ba590ee49d0b"}, + {file = "grpcio-1.51.3-cp39-cp39-macosx_10_10_universal2.whl", hash = "sha256:30e09b5e0531685e176f49679b6a3b190762cc225f4565e55a899f5e14b3aa62"}, + {file = "grpcio-1.51.3-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:c831f31336e81243f85b6daff3e5e8a123302ce0ea1f2726ad752fd7a59f3aee"}, + {file = "grpcio-1.51.3-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2cd2e4cefb724cab1ba2df4b7535a9980531b9ec51b4dbb5f137a1f3a3754ef0"}, + {file = "grpcio-1.51.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f7a0d0bf44438869d307f85a54f25a896ad6b4b0ca12370f76892ad732928d87"}, + {file = "grpcio-1.51.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:c02abd55409bfb293371554adf6a4401197ec2133dd97727c01180889014ba4d"}, + {file = "grpcio-1.51.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2f8ff75e61e1227ba7a3f16b2eadbcc11d0a54096d52ab75a6b88cfbe56f55d1"}, + {file = "grpcio-1.51.3-cp39-cp39-win32.whl", hash = "sha256:6c99a73a6260bdf844b2e5ddad02dcd530310f80e1fa72c300fa19c1c7496962"}, + {file = "grpcio-1.51.3-cp39-cp39-win_amd64.whl", hash = "sha256:22bdfac4f7f27acdd4da359b5e7e1973dc74bf1ed406729b07d0759fde2f064b"}, + {file = "grpcio-1.51.3.tar.gz", hash = "sha256:be7b2265b7527bb12109a7727581e274170766d5b3c9258d4e466f4872522d7a"}, +] + +[package.extras] +protobuf = ["grpcio-tools (>=1.51.3)"] + +[[package]] +name = "h5py" +version = "3.8.0" +description = "Read and write HDF5 files from Python" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "h5py-3.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:533d7dad466ddb7e3b30af274b630eb7c1a6e4ddf01d1c373a0334dc2152110a"}, + {file = "h5py-3.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c873ba9fd4fa875ad62ce0e4891725e257a8fe7f5abdbc17e51a5d54819be55c"}, + {file = "h5py-3.8.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:98a240cd4c1bfd568aaa52ec42d263131a2582dab82d74d3d42a0d954cac12be"}, + {file = "h5py-3.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c3389b63222b1c7a158bb7fe69d11ca00066740ec5574596d47a2fe5317f563a"}, + {file = "h5py-3.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:7f3350fc0a8407d668b13247861c2acd23f7f5fe7d060a3ad9b0820f5fcbcae0"}, + {file = "h5py-3.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:db03e3f2c716205fbdabb34d0848459840585225eb97b4f08998c743821ca323"}, + {file = "h5py-3.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:36761693efbe53df179627a775476dcbc37727d6e920958277a7efbc18f1fb73"}, + {file = "h5py-3.8.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a506fc223def428f4329e7e1f9fe1c8c593eab226e7c0942c8d75308ad49950"}, + {file = "h5py-3.8.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:33b15aae79e9147aebe1d0e54099cbcde8d65e3e227cd5b59e49b1272aa0e09d"}, + {file = "h5py-3.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:9f6f6ffadd6bfa9b2c5b334805eb4b19ca0a5620433659d8f7fb86692c40a359"}, + {file = "h5py-3.8.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8f55d9c6c84d7d09c79fb85979e97b81ec6071cc776a97eb6b96f8f6ec767323"}, + {file = "h5py-3.8.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b685453e538b2b5934c58a644ac3f3b3d0cec1a01b6fb26d57388e9f9b674ad0"}, + {file = "h5py-3.8.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:377865821fe80ad984d003723d6f8890bd54ceeb5981b43c0313b9df95411b30"}, + {file = "h5py-3.8.0-cp37-cp37m-win_amd64.whl", hash = "sha256:0fef76e10b9216657fa37e7edff6d8be0709b25bd5066474c229b56cf0098df9"}, + {file = "h5py-3.8.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:26ffc344ec9984d2cd3ca0265007299a8bac8d85c1ad48f4639d8d3aed2af171"}, + {file = "h5py-3.8.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:bacaa1c16810dd2b3e4417f8e730971b7c4d53d234de61fe4a918db78e80e1e4"}, + {file = "h5py-3.8.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bae730580ae928de409d63cbe4fdca4c82c3ad2bed30511d19d34e995d63c77e"}, + {file = "h5py-3.8.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f47f757d1b76f0ecb8aa0508ec8d1b390df67a8b67ee2515dc1b046f3a1596ea"}, + {file = "h5py-3.8.0-cp38-cp38-win_amd64.whl", hash = "sha256:f891b17e3a3e974e93f9e34e7cca9f530806543571ce078998676a555837d91d"}, + {file = "h5py-3.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:290e00fa2de74a10688d1bac98d5a9cdd43f14f58e562c580b5b3dfbd358ecae"}, + {file = "h5py-3.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:03890b1c123d024fb0239a3279737d5432498c1901c354f8b10d8221d1d16235"}, + {file = "h5py-3.8.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b7865de06779b14d98068da387333ad9bf2756b5b579cc887fac169bc08f87c3"}, + {file = "h5py-3.8.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49bc857635f935fa30e92e61ac1e87496df8f260a6945a3235e43a9890426866"}, + {file = "h5py-3.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:5fd2252d1fc364ba0e93dd0b7089f4906b66805cb4e6aca7fa8874ac08649647"}, + {file = "h5py-3.8.0.tar.gz", hash = "sha256:6fead82f0c4000cf38d53f9c030780d81bfa0220218aee13b90b7701c937d95f"}, +] + +[package.dependencies] +numpy = ">=1.14.5" + +[[package]] +name = "hdf5plugin" +version = "4.1.1" +description = "HDF5 Plugins for Windows, MacOS, and Linux" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "hdf5plugin-4.1.1-py3-none-macosx_10_9_universal2.whl", hash = "sha256:02fa82fbe5b6608b8c5371bd2a2dbe780abdafe30f2830e0ca658b6b59da6225"}, + {file = "hdf5plugin-4.1.1-py3-none-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a23463b1f70c3bfe3654299a397e97d074f1f8122d60fc4fc72c0005abf3900f"}, + {file = "hdf5plugin-4.1.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0a6906054df13341999c56f2301ae55f86499aebeec286612b6479453868d335"}, + {file = "hdf5plugin-4.1.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:16a8936f38890ce3a4dc10375adfb19dbf315e44cc7f099aafd1232e7ec75854"}, + {file = "hdf5plugin-4.1.1-py3-none-win_amd64.whl", hash = "sha256:11c3fffe14aaf8ebd84729c2f110a3f2e4965b164484c0ef67005dc376f82b10"}, + {file = "hdf5plugin-4.1.1.tar.gz", hash = "sha256:96a989679f1f38251e0dcae363180d382ba402f6c89aab73ca351a391ac23b36"}, +] + +[package.dependencies] +h5py = "*" + +[package.extras] +dev = ["sphinx", "sphinx-rtd-theme"] + +[[package]] +name = "idna" +version = "3.4" +description = "Internationalized Domain Names in Applications (IDNA)" +category = "main" +optional = false +python-versions = ">=3.5" +files = [ + {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, + {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, +] + +[[package]] +name = "imageio" +version = "2.26.1" +description = "Library for reading and writing a wide range of image, video, scientific, and volumetric data formats." +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "imageio-2.26.1-py3-none-any.whl", hash = "sha256:df56ade8a9b43476ce020be0202b09a3a4cc0c7a079f8fe5ee4b87105eff4237"}, + {file = "imageio-2.26.1.tar.gz", hash = "sha256:7f7bc13254a311f298bc64d60c2690dd3460fd412532717e2c51715daed17fc5"}, +] + +[package.dependencies] +numpy = "*" +pillow = ">=8.3.2" + +[package.extras] +all-plugins = ["astropy", "av", "imageio-ffmpeg", "psutil", "tifffile"] +all-plugins-pypy = ["av", "imageio-ffmpeg", "psutil", "tifffile"] +build = ["wheel"] +dev = ["black", "flake8", "fsspec[github]", "invoke", "pytest", "pytest-cov"] +docs = ["numpydoc", "pydata-sphinx-theme", "sphinx (<6)"] +ffmpeg = ["imageio-ffmpeg", "psutil"] +fits = ["astropy"] +full = ["astropy", "av", "black", "flake8", "fsspec[github]", "gdal", "imageio-ffmpeg", "invoke", "itk", "numpydoc", "psutil", "pydata-sphinx-theme", "pytest", "pytest-cov", "sphinx (<6)", "tifffile", "wheel"] +gdal = ["gdal"] +itk = ["itk"] +linting = ["black", "flake8"] +pyav = ["av"] +test = ["fsspec[github]", "invoke", "pytest", "pytest-cov"] +tifffile = ["tifffile"] + +[[package]] +name = "imageio-ffmpeg" +version = "0.4.8" +description = "FFMPEG wrapper for Python" +category = "dev" +optional = false +python-versions = ">=3.5" +files = [ + {file = "imageio-ffmpeg-0.4.8.tar.gz", hash = "sha256:fdaa05ad10fe070b7fa8e5f615cb0d28f3b9b791d00af6d2a11e694158d10aa9"}, + {file = "imageio_ffmpeg-0.4.8-py3-none-macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:dba439a303d65061aef17d2ee9324ecfa9c6b4752bd0953b309fdbb79b38451e"}, + {file = "imageio_ffmpeg-0.4.8-py3-none-manylinux2010_x86_64.whl", hash = "sha256:7caa9ce9fc0d7e2f3160ce8cb70a115e5211e0f048e5c1509163d8f89d1080df"}, + {file = "imageio_ffmpeg-0.4.8-py3-none-manylinux2014_aarch64.whl", hash = "sha256:dd3ef9835df91570a1cbd9e36dfbc7d228fca42dbb11636e20df75d719de2949"}, + {file = "imageio_ffmpeg-0.4.8-py3-none-win32.whl", hash = "sha256:0e2688120b3bdb367897450d07c1b1300e96a0bace03ba7de2eb8d738237ea9a"}, + {file = "imageio_ffmpeg-0.4.8-py3-none-win_amd64.whl", hash = "sha256:120d70e6448617cad6213e47dee3a3310117c230f532dd614ed3059a78acf13a"}, +] + +[[package]] +name = "importlib-metadata" +version = "6.1.0" +description = "Read metadata from Python packages" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "importlib_metadata-6.1.0-py3-none-any.whl", hash = "sha256:ff80f3b5394912eb1b108fcfd444dc78b7f1f3e16b16188054bd01cb9cb86f09"}, + {file = "importlib_metadata-6.1.0.tar.gz", hash = "sha256:43ce9281e097583d758c2c708c4376371261a02c34682491a8e98352365aad20"}, +] + +[package.dependencies] +zipp = ">=0.5" + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +perf = ["ipython"] +testing = ["flake8 (<5)", "flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)"] + +[[package]] +name = "iopath" +version = "0.1.10" +description = "A library for providing I/O abstraction." +category = "main" +optional = false +python-versions = ">=3.6" +files = [ + {file = "iopath-0.1.10.tar.gz", hash = "sha256:3311c16a4d9137223e20f141655759933e1eda24f8bff166af834af3c645ef01"}, +] + +[package.dependencies] +portalocker = "*" +tqdm = "*" +typing_extensions = "*" + +[package.extras] +aws = ["boto3"] + +[[package]] +name = "ipykernel" +version = "6.22.0" +description = "IPython Kernel for Jupyter" +category = "dev" +optional = false +python-versions = ">=3.8" +files = [ + {file = "ipykernel-6.22.0-py3-none-any.whl", hash = "sha256:1ae6047c1277508933078163721bbb479c3e7292778a04b4bacf0874550977d6"}, + {file = "ipykernel-6.22.0.tar.gz", hash = "sha256:302558b81f1bc22dc259fb2a0c5c7cf2f4c0bdb21b50484348f7bafe7fb71421"}, +] + +[package.dependencies] +appnope = {version = "*", markers = "platform_system == \"Darwin\""} +comm = ">=0.1.1" +debugpy = ">=1.6.5" +ipython = ">=7.23.1" +jupyter-client = ">=6.1.12" +jupyter-core = ">=4.12,<5.0.0 || >=5.1.0" +matplotlib-inline = ">=0.1" +nest-asyncio = "*" +packaging = "*" +psutil = "*" +pyzmq = ">=20" +tornado = ">=6.1" +traitlets = ">=5.4.0" + +[package.extras] +cov = ["coverage[toml]", "curio", "matplotlib", "pytest-cov", "trio"] +docs = ["myst-parser", "pydata-sphinx-theme", "sphinx", "sphinx-autodoc-typehints", "sphinxcontrib-github-alt", "sphinxcontrib-spelling", "trio"] +pyqt5 = ["pyqt5"] +pyside6 = ["pyside6"] +test = ["flaky", "ipyparallel", "pre-commit", "pytest (>=7.0)", "pytest-asyncio", "pytest-cov", "pytest-timeout"] + +[[package]] +name = "ipython" +version = "8.11.0" +description = "IPython: Productive Interactive Computing" +category = "dev" +optional = false +python-versions = ">=3.8" +files = [ + {file = "ipython-8.11.0-py3-none-any.whl", hash = "sha256:5b54478e459155a326bf5f42ee4f29df76258c0279c36f21d71ddb560f88b156"}, + {file = "ipython-8.11.0.tar.gz", hash = "sha256:735cede4099dbc903ee540307b9171fbfef4aa75cfcacc5a273b2cda2f02be04"}, +] + +[package.dependencies] +appnope = {version = "*", markers = "sys_platform == \"darwin\""} +backcall = "*" +colorama = {version = "*", markers = "sys_platform == \"win32\""} +decorator = "*" +jedi = ">=0.16" +matplotlib-inline = "*" +pexpect = {version = ">4.3", markers = "sys_platform != \"win32\""} +pickleshare = "*" +prompt-toolkit = ">=3.0.30,<3.0.37 || >3.0.37,<3.1.0" +pygments = ">=2.4.0" +stack-data = "*" +traitlets = ">=5" + +[package.extras] +all = ["black", "curio", "docrepr", "ipykernel", "ipyparallel", "ipywidgets", "matplotlib", "matplotlib (!=3.2.0)", "nbconvert", "nbformat", "notebook", "numpy (>=1.21)", "pandas", "pytest (<7)", "pytest (<7.1)", "pytest-asyncio", "qtconsole", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "stack-data", "testpath", "trio", "typing-extensions"] +black = ["black"] +doc = ["docrepr", "ipykernel", "matplotlib", "pytest (<7)", "pytest (<7.1)", "pytest-asyncio", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "stack-data", "testpath", "typing-extensions"] +kernel = ["ipykernel"] +nbconvert = ["nbconvert"] +nbformat = ["nbformat"] +notebook = ["ipywidgets", "notebook"] +parallel = ["ipyparallel"] +qtconsole = ["qtconsole"] +test = ["pytest (<7.1)", "pytest-asyncio", "testpath"] +test-extra = ["curio", "matplotlib (!=3.2.0)", "nbformat", "numpy (>=1.21)", "pandas", "pytest (<7.1)", "pytest-asyncio", "testpath", "trio"] + +[[package]] +name = "ipython-genutils" +version = "0.2.0" +description = "Vestigial utilities from IPython" +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "ipython_genutils-0.2.0-py2.py3-none-any.whl", hash = "sha256:72dd37233799e619666c9f639a9da83c34013a73e8bbc79a7a6348d93c61fab8"}, + {file = "ipython_genutils-0.2.0.tar.gz", hash = "sha256:eb2e116e75ecef9d4d228fdc66af54269afa26ab4463042e33785b887c628ba8"}, +] + +[[package]] +name = "ipywidgets" +version = "8.0.5" +description = "Jupyter interactive widgets" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "ipywidgets-8.0.5-py3-none-any.whl", hash = "sha256:a6e5c0392f86207fae304688a670afb26b2fd819592cfc0812777c2fdf22dbad"}, + {file = "ipywidgets-8.0.5.tar.gz", hash = "sha256:89a1930b9ef255838571a2415cc4a15e824e4316b8f067805d1d03b98b6a8c5f"}, +] + +[package.dependencies] +ipython = ">=6.1.0" +jupyterlab-widgets = ">=3.0,<4.0" +traitlets = ">=4.3.1" +widgetsnbextension = ">=4.0,<5.0" + +[package.extras] +test = ["ipykernel", "jsonschema", "pytest (>=3.6.0)", "pytest-cov", "pytz"] + +[[package]] +name = "isoduration" +version = "20.11.0" +description = "Operations with ISO 8601 durations" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "isoduration-20.11.0-py3-none-any.whl", hash = "sha256:b2904c2a4228c3d44f409c8ae8e2370eb21a26f7ac2ec5446df141dde3452042"}, + {file = "isoduration-20.11.0.tar.gz", hash = "sha256:ac2f9015137935279eac671f94f89eb00584f940f5dc49462a0c4ee692ba1bd9"}, +] + +[package.dependencies] +arrow = ">=0.15.0" + +[[package]] +name = "isort" +version = "5.12.0" +description = "A Python utility / library to sort Python imports." +category = "dev" +optional = false +python-versions = ">=3.8.0" +files = [ + {file = "isort-5.12.0-py3-none-any.whl", hash = "sha256:f84c2818376e66cf843d497486ea8fed8700b340f308f076c6fb1229dff318b6"}, + {file = "isort-5.12.0.tar.gz", hash = "sha256:8bef7dde241278824a6d83f44a544709b065191b95b6e50894bdc722fcba0504"}, +] + +[package.extras] +colors = ["colorama (>=0.4.3)"] +pipfile-deprecated-finder = ["pip-shims (>=0.5.2)", "pipreqs", "requirementslib"] +plugins = ["setuptools"] +requirements-deprecated-finder = ["pip-api", "pipreqs"] + +[[package]] +name = "jedi" +version = "0.18.2" +description = "An autocompletion tool for Python that can be used for text editors." +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ + {file = "jedi-0.18.2-py2.py3-none-any.whl", hash = "sha256:203c1fd9d969ab8f2119ec0a3342e0b49910045abe6af0a3ae83a5764d54639e"}, + {file = "jedi-0.18.2.tar.gz", hash = "sha256:bae794c30d07f6d910d32a7048af09b5a39ed740918da923c6b780790ebac612"}, +] + +[package.dependencies] +parso = ">=0.8.0,<0.9.0" + +[package.extras] +docs = ["Jinja2 (==2.11.3)", "MarkupSafe (==1.1.1)", "Pygments (==2.8.1)", "alabaster (==0.7.12)", "babel (==2.9.1)", "chardet (==4.0.0)", "commonmark (==0.8.1)", "docutils (==0.17.1)", "future (==0.18.2)", "idna (==2.10)", "imagesize (==1.2.0)", "mock (==1.0.1)", "packaging (==20.9)", "pyparsing (==2.4.7)", "pytz (==2021.1)", "readthedocs-sphinx-ext (==2.1.4)", "recommonmark (==0.5.0)", "requests (==2.25.1)", "six (==1.15.0)", "snowballstemmer (==2.1.0)", "sphinx (==1.8.5)", "sphinx-rtd-theme (==0.4.3)", "sphinxcontrib-serializinghtml (==1.1.4)", "sphinxcontrib-websupport (==1.2.4)", "urllib3 (==1.26.4)"] +qa = ["flake8 (==3.8.3)", "mypy (==0.782)"] +testing = ["Django (<3.1)", "attrs", "colorama", "docopt", "pytest (<7.0.0)"] + +[[package]] +name = "jinja2" +version = "3.1.2" +description = "A very fast and expressive template engine." +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"}, + {file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"}, +] + +[package.dependencies] +MarkupSafe = ">=2.0" + +[package.extras] +i18n = ["Babel (>=2.7)"] + +[[package]] +name = "joblib" +version = "1.2.0" +description = "Lightweight pipelining with Python functions" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "joblib-1.2.0-py3-none-any.whl", hash = "sha256:091138ed78f800342968c523bdde947e7a305b8594b910a0fea2ab83c3c6d385"}, + {file = "joblib-1.2.0.tar.gz", hash = "sha256:e1cee4a79e4af22881164f218d4311f60074197fb707e082e803b61f6d137018"}, +] + +[[package]] +name = "json5" +version = "0.9.11" +description = "A Python implementation of the JSON5 data format." +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "json5-0.9.11-py2.py3-none-any.whl", hash = "sha256:1aa54b80b5e507dfe31d12b7743a642e2ffa6f70bf73b8e3d7d1d5fba83d99bd"}, + {file = "json5-0.9.11.tar.gz", hash = "sha256:4f1e196acc55b83985a51318489f345963c7ba84aa37607e49073066c562e99b"}, +] + +[package.extras] +dev = ["hypothesis"] + +[[package]] +name = "jsonpointer" +version = "2.3" +description = "Identify specific nodes in a JSON document (RFC 6901)" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "jsonpointer-2.3-py2.py3-none-any.whl", hash = "sha256:51801e558539b4e9cd268638c078c6c5746c9ac96bc38152d443400e4f3793e9"}, + {file = "jsonpointer-2.3.tar.gz", hash = "sha256:97cba51526c829282218feb99dab1b1e6bdf8efd1c43dc9d57be093c0d69c99a"}, +] + +[[package]] +name = "jsonschema" +version = "4.17.3" +description = "An implementation of JSON Schema validation for Python" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "jsonschema-4.17.3-py3-none-any.whl", hash = "sha256:a870ad254da1a8ca84b6a2905cac29d265f805acc57af304784962a2aa6508f6"}, + {file = "jsonschema-4.17.3.tar.gz", hash = "sha256:0f864437ab8b6076ba6707453ef8f98a6a0d512a80e93f8abdb676f737ecb60d"}, +] + +[package.dependencies] +attrs = ">=17.4.0" +fqdn = {version = "*", optional = true, markers = "extra == \"format-nongpl\""} +idna = {version = "*", optional = true, markers = "extra == \"format-nongpl\""} +isoduration = {version = "*", optional = true, markers = "extra == \"format-nongpl\""} +jsonpointer = {version = ">1.13", optional = true, markers = "extra == \"format-nongpl\""} +pyrsistent = ">=0.14.0,<0.17.0 || >0.17.0,<0.17.1 || >0.17.1,<0.17.2 || >0.17.2" +rfc3339-validator = {version = "*", optional = true, markers = "extra == \"format-nongpl\""} +rfc3986-validator = {version = ">0.1.0", optional = true, markers = "extra == \"format-nongpl\""} +uri-template = {version = "*", optional = true, markers = "extra == \"format-nongpl\""} +webcolors = {version = ">=1.11", optional = true, markers = "extra == \"format-nongpl\""} + +[package.extras] +format = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3987", "uri-template", "webcolors (>=1.11)"] +format-nongpl = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3986-validator (>0.1.0)", "uri-template", "webcolors (>=1.11)"] + +[[package]] +name = "jupyter" +version = "1.0.0" +description = "Jupyter metapackage. Install all the Jupyter components in one go." +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "jupyter-1.0.0-py2.py3-none-any.whl", hash = "sha256:5b290f93b98ffbc21c0c7e749f054b3267782166d72fa5e3ed1ed4eaf34a2b78"}, + {file = "jupyter-1.0.0.tar.gz", hash = "sha256:d9dc4b3318f310e34c82951ea5d6683f67bed7def4b259fafbfe4f1beb1d8e5f"}, + {file = "jupyter-1.0.0.zip", hash = "sha256:3e1f86076bbb7c8c207829390305a2b1fe836d471ed54be66a3b8c41e7f46cc7"}, +] + +[package.dependencies] +ipykernel = "*" +ipywidgets = "*" +jupyter-console = "*" +nbconvert = "*" +notebook = "*" +qtconsole = "*" + +[[package]] +name = "jupyter-client" +version = "8.1.0" +description = "Jupyter protocol implementation and client libraries" +category = "dev" +optional = false +python-versions = ">=3.8" +files = [ + {file = "jupyter_client-8.1.0-py3-none-any.whl", hash = "sha256:d5b8e739d7816944be50f81121a109788a3d92732ecf1ad1e4dadebc948818fe"}, + {file = "jupyter_client-8.1.0.tar.gz", hash = "sha256:3fbab64100a0dcac7701b1e0f1a4412f1ccb45546ff2ad9bc4fcbe4e19804811"}, +] + +[package.dependencies] +jupyter-core = ">=4.12,<5.0.0 || >=5.1.0" +python-dateutil = ">=2.8.2" +pyzmq = ">=23.0" +tornado = ">=6.2" +traitlets = ">=5.3" + +[package.extras] +docs = ["ipykernel", "myst-parser", "pydata-sphinx-theme", "sphinx (>=4)", "sphinx-autodoc-typehints", "sphinxcontrib-github-alt", "sphinxcontrib-spelling"] +test = ["codecov", "coverage", "ipykernel (>=6.14)", "mypy", "paramiko", "pre-commit", "pytest", "pytest-cov", "pytest-jupyter[client] (>=0.4.1)", "pytest-timeout"] + +[[package]] +name = "jupyter-console" +version = "6.6.3" +description = "Jupyter terminal console" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "jupyter_console-6.6.3-py3-none-any.whl", hash = "sha256:309d33409fcc92ffdad25f0bcdf9a4a9daa61b6f341177570fdac03de5352485"}, + {file = "jupyter_console-6.6.3.tar.gz", hash = "sha256:566a4bf31c87adbfadf22cdf846e3069b59a71ed5da71d6ba4d8aaad14a53539"}, +] + +[package.dependencies] +ipykernel = ">=6.14" +ipython = "*" +jupyter-client = ">=7.0.0" +jupyter-core = ">=4.12,<5.0.0 || >=5.1.0" +prompt-toolkit = ">=3.0.30" +pygments = "*" +pyzmq = ">=17" +traitlets = ">=5.4" + +[package.extras] +test = ["flaky", "pexpect", "pytest"] + +[[package]] +name = "jupyter-contrib-core" +version = "0.4.2" +description = "Common utilities for jupyter-contrib projects." +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "jupyter_contrib_core-0.4.2.tar.gz", hash = "sha256:1887212f3ca9d4487d624c0705c20dfdf03d5a0b9ea2557d3aaeeb4c38bdcabb"}, +] + +[package.dependencies] +jupyter_core = "*" +notebook = ">=4.0" +setuptools = "*" +tornado = "*" +traitlets = "*" + +[package.extras] +testing-utils = ["mock", "nose"] + +[[package]] +name = "jupyter-contrib-nbextensions" +version = "0.7.0" +description = "A collection of Jupyter nbextensions." +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "jupyter_contrib_nbextensions-0.7.0.tar.gz", hash = "sha256:06e33f005885eb92f89cbe82711e921278201298d08ab0d886d1ba09e8c3e9ca"}, +] + +[package.dependencies] +ipython_genutils = "*" +jupyter_contrib_core = ">=0.3.3" +jupyter_core = "*" +jupyter_highlight_selected_word = ">=0.1.1" +jupyter_nbextensions_configurator = ">=0.4.0" +lxml = "*" +nbconvert = ">=6.0" +notebook = ">=6.0" +tornado = "*" +traitlets = ">=4.1" + +[package.extras] +test = ["mock", "nbformat", "nose", "pip", "requests"] + +[[package]] +name = "jupyter-core" +version = "5.3.0" +description = "Jupyter core package. A base package on which Jupyter projects rely." +category = "dev" +optional = false +python-versions = ">=3.8" +files = [ + {file = "jupyter_core-5.3.0-py3-none-any.whl", hash = "sha256:d4201af84559bc8c70cead287e1ab94aeef3c512848dde077b7684b54d67730d"}, + {file = "jupyter_core-5.3.0.tar.gz", hash = "sha256:6db75be0c83edbf1b7c9f91ec266a9a24ef945da630f3120e1a0046dc13713fc"}, +] + +[package.dependencies] +platformdirs = ">=2.5" +pywin32 = {version = ">=300", markers = "sys_platform == \"win32\" and platform_python_implementation != \"PyPy\""} +traitlets = ">=5.3" + +[package.extras] +docs = ["myst-parser", "sphinx-autodoc-typehints", "sphinxcontrib-github-alt", "sphinxcontrib-spelling", "traitlets"] +test = ["ipykernel", "pre-commit", "pytest", "pytest-cov", "pytest-timeout"] + +[[package]] +name = "jupyter-events" +version = "0.6.3" +description = "Jupyter Event System library" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "jupyter_events-0.6.3-py3-none-any.whl", hash = "sha256:57a2749f87ba387cd1bfd9b22a0875b889237dbf2edc2121ebb22bde47036c17"}, + {file = "jupyter_events-0.6.3.tar.gz", hash = "sha256:9a6e9995f75d1b7146b436ea24d696ce3a35bfa8bfe45e0c33c334c79464d0b3"}, +] + +[package.dependencies] +jsonschema = {version = ">=3.2.0", extras = ["format-nongpl"]} +python-json-logger = ">=2.0.4" +pyyaml = ">=5.3" +rfc3339-validator = "*" +rfc3986-validator = ">=0.1.1" +traitlets = ">=5.3" + +[package.extras] +cli = ["click", "rich"] +docs = ["jupyterlite-sphinx", "myst-parser", "pydata-sphinx-theme", "sphinxcontrib-spelling"] +test = ["click", "coverage", "pre-commit", "pytest (>=7.0)", "pytest-asyncio (>=0.19.0)", "pytest-console-scripts", "pytest-cov", "rich"] + +[[package]] +name = "jupyter-highlight-selected-word" +version = "0.2.0" +description = "Jupyter notebook extension that enables highlighting every instance of the current word in the notebook." +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "jupyter_highlight_selected_word-0.2.0-py2.py3-none-any.whl", hash = "sha256:9545dfa9cb057eebe3a5795604dcd3a5294ea18637e553f61a0b67c1b5903c58"}, + {file = "jupyter_highlight_selected_word-0.2.0.tar.gz", hash = "sha256:9fa740424859a807950ca08d2bfd28a35154cd32dd6d50ac4e0950022adc0e7b"}, +] + +[[package]] +name = "jupyter-nbextensions-configurator" +version = "0.6.1" +description = "jupyter serverextension providing configuration interfaces for nbextensions." +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "jupyter_nbextensions_configurator-0.6.1.tar.gz", hash = "sha256:4b9e1270ccc1f8e0a421efb8979a737f586813023a4855b9453f61c3ca599b82"}, +] + +[package.dependencies] +jupyter_contrib_core = ">=0.3.3" +jupyter_core = "*" +notebook = ">=6.0" +pyyaml = "*" +tornado = "*" +traitlets = "*" + +[package.extras] +test = ["jupyter_contrib_core[testing-utils]", "mock", "nose", "requests", "selenium"] + +[[package]] +name = "jupyter-server" +version = "2.5.0" +description = "The backend—i.e. core services, APIs, and REST endpoints—to Jupyter web applications." +category = "dev" +optional = false +python-versions = ">=3.8" +files = [ + {file = "jupyter_server-2.5.0-py3-none-any.whl", hash = "sha256:e6bc1e9e96d7c55b9ce9699ff6cb9a910581fe7349e27c40389acb67632e24c0"}, + {file = "jupyter_server-2.5.0.tar.gz", hash = "sha256:9fde612791f716fd34d610cd939704a9639643744751ba66e7ee8fdc9cead07e"}, +] + +[package.dependencies] +anyio = ">=3.1.0" +argon2-cffi = "*" +jinja2 = "*" +jupyter-client = ">=7.4.4" +jupyter-core = ">=4.12,<5.0.0 || >=5.1.0" +jupyter-events = ">=0.4.0" +jupyter-server-terminals = "*" +nbconvert = ">=6.4.4" +nbformat = ">=5.3.0" +packaging = "*" +prometheus-client = "*" +pywinpty = {version = "*", markers = "os_name == \"nt\""} +pyzmq = ">=24" +send2trash = "*" +terminado = ">=0.8.3" +tornado = ">=6.2.0" +traitlets = ">=5.6.0" +websocket-client = "*" + +[package.extras] +docs = ["docutils (<0.20)", "ipykernel", "jinja2", "jupyter-client", "jupyter-server", "mistune (<1.0.0)", "myst-parser", "nbformat", "prometheus-client", "pydata-sphinx-theme", "send2trash", "sphinx-autodoc-typehints", "sphinxcontrib-github-alt", "sphinxcontrib-openapi", "sphinxcontrib-spelling", "sphinxemoji", "tornado", "typing-extensions"] +test = ["ipykernel", "pre-commit", "pytest (>=7.0)", "pytest-console-scripts", "pytest-jupyter[server] (>=0.4)", "pytest-timeout", "requests"] + +[[package]] +name = "jupyter-server-fileid" +version = "0.8.0" +description = "" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "jupyter_server_fileid-0.8.0-py3-none-any.whl", hash = "sha256:6092ef114eddccf6cba69c0f0feb612c2f476f2e9467828809edb854c18806bb"}, + {file = "jupyter_server_fileid-0.8.0.tar.gz", hash = "sha256:1e0816d0857f490fadea11348570f0cba03f70f315c9842225aecfa45882b6af"}, +] + +[package.dependencies] +jupyter-events = ">=0.5.0" +jupyter-server = ">=1.15,<3" + +[package.extras] +cli = ["click"] +test = ["jupyter-server[test] (>=1.15,<3)", "pytest", "pytest-cov"] + +[[package]] +name = "jupyter-server-terminals" +version = "0.4.4" +description = "A Jupyter Server Extension Providing Terminals." +category = "dev" +optional = false +python-versions = ">=3.8" +files = [ + {file = "jupyter_server_terminals-0.4.4-py3-none-any.whl", hash = "sha256:75779164661cec02a8758a5311e18bb8eb70c4e86c6b699403100f1585a12a36"}, + {file = "jupyter_server_terminals-0.4.4.tar.gz", hash = "sha256:57ab779797c25a7ba68e97bcfb5d7740f2b5e8a83b5e8102b10438041a7eac5d"}, +] + +[package.dependencies] +pywinpty = {version = ">=2.0.3", markers = "os_name == \"nt\""} +terminado = ">=0.8.3" + +[package.extras] +docs = ["jinja2", "jupyter-server", "mistune (<3.0)", "myst-parser", "nbformat", "packaging", "pydata-sphinx-theme", "sphinxcontrib-github-alt", "sphinxcontrib-openapi", "sphinxcontrib-spelling", "sphinxemoji", "tornado"] +test = ["coverage", "jupyter-server (>=2.0.0)", "pytest (>=7.0)", "pytest-cov", "pytest-jupyter[server] (>=0.5.3)", "pytest-timeout"] + +[[package]] +name = "jupyter-server-ydoc" +version = "0.8.0" +description = "A Jupyter Server Extension Providing Y Documents." +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "jupyter_server_ydoc-0.8.0-py3-none-any.whl", hash = "sha256:969a3a1a77ed4e99487d60a74048dc9fa7d3b0dcd32e60885d835bbf7ba7be11"}, + {file = "jupyter_server_ydoc-0.8.0.tar.gz", hash = "sha256:a6fe125091792d16c962cc3720c950c2b87fcc8c3ecf0c54c84e9a20b814526c"}, +] + +[package.dependencies] +jupyter-server-fileid = ">=0.6.0,<1" +jupyter-ydoc = ">=0.2.0,<0.4.0" +ypy-websocket = ">=0.8.2,<0.9.0" + +[package.extras] +test = ["coverage", "jupyter-server[test] (>=2.0.0a0)", "pytest (>=7.0)", "pytest-cov", "pytest-timeout", "pytest-tornasync"] + +[[package]] +name = "jupyter-ydoc" +version = "0.2.3" +description = "Document structures for collaborative editing using Ypy" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "jupyter_ydoc-0.2.3-py3-none-any.whl", hash = "sha256:3ac51abfe378c6aeb62a449e8f0241bede1205f0199b0d27429140cbba950f79"}, + {file = "jupyter_ydoc-0.2.3.tar.gz", hash = "sha256:98db7785215873c64d7dfcb1b741f41df11994c4b3d7e2957e004b392d6f11ea"}, +] + +[package.dependencies] +y-py = ">=0.5.3,<0.6.0" + +[package.extras] +dev = ["click", "jupyter-releaser"] +test = ["pre-commit", "pytest", "pytest-asyncio", "websockets (>=10.0)", "ypy-websocket (>=0.3.1,<0.4.0)"] + +[[package]] +name = "jupyterlab" +version = "3.6.2" +description = "JupyterLab computational environment" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "jupyterlab-3.6.2-py3-none-any.whl", hash = "sha256:6c87c5910f886a14009352bc63f640961b206f73ed650dcf94d65f9dfcb30f95"}, + {file = "jupyterlab-3.6.2.tar.gz", hash = "sha256:e55bc40c36c2a52b76cf301138507a5488eb769137dd39d9f31a6259a00c6b03"}, +] + +[package.dependencies] +ipython = "*" +jinja2 = ">=2.1" +jupyter-core = "*" +jupyter-server = ">=1.16.0,<3" +jupyter-server-ydoc = ">=0.8.0,<0.9.0" +jupyter-ydoc = ">=0.2.3,<0.3.0" +jupyterlab-server = ">=2.19,<3.0" +nbclassic = "*" +notebook = "<7" +packaging = "*" +tomli = {version = "*", markers = "python_version < \"3.11\""} +tornado = ">=6.1.0" + +[package.extras] +test = ["check-manifest", "coverage", "jupyterlab-server[test]", "pre-commit", "pytest (>=6.0)", "pytest-check-links (>=0.5)", "pytest-console-scripts", "pytest-cov", "pytest-jupyter (>=0.5.3)", "requests", "requests-cache", "virtualenv"] + +[[package]] +name = "jupyterlab-pygments" +version = "0.2.2" +description = "Pygments theme using JupyterLab CSS variables" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "jupyterlab_pygments-0.2.2-py2.py3-none-any.whl", hash = "sha256:2405800db07c9f770863bcf8049a529c3dd4d3e28536638bd7c1c01d2748309f"}, + {file = "jupyterlab_pygments-0.2.2.tar.gz", hash = "sha256:7405d7fde60819d905a9fa8ce89e4cd830e318cdad22a0030f7a901da705585d"}, +] + +[[package]] +name = "jupyterlab-server" +version = "2.21.0" +description = "A set of server components for JupyterLab and JupyterLab like applications." +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "jupyterlab_server-2.21.0-py3-none-any.whl", hash = "sha256:ff1e7a81deb2dcb433215d469000988590fd5a5733574aa2698d643a6c9b3ace"}, + {file = "jupyterlab_server-2.21.0.tar.gz", hash = "sha256:b4f5b48eaae1be83e2fd6fb77ac49d9b639be4ca4bd2e05b5368d29632a93725"}, +] + +[package.dependencies] +babel = ">=2.10" +jinja2 = ">=3.0.3" +json5 = ">=0.9.0" +jsonschema = ">=4.17.3" +jupyter-server = ">=1.21,<3" +packaging = ">=21.3" +requests = ">=2.28" + +[package.extras] +docs = ["autodoc-traits", "docutils (<0.20)", "jinja2 (<3.2.0)", "mistune (<3)", "myst-parser", "pydata-sphinx-theme", "sphinx", "sphinx-copybutton", "sphinxcontrib-openapi"] +openapi = ["openapi-core (>=0.16.1,<0.17.0)", "ruamel-yaml"] +test = ["codecov", "hatch", "ipykernel", "jupyterlab-server[openapi]", "openapi-spec-validator (>=0.5.1,<0.6.0)", "pytest (>=7.0)", "pytest-console-scripts", "pytest-cov", "pytest-jupyter[server] (>=0.6.2)", "pytest-timeout", "requests-mock", "sphinxcontrib-spelling", "strict-rfc3339", "werkzeug"] + +[[package]] +name = "jupyterlab-widgets" +version = "3.0.6" +description = "Jupyter interactive widgets for JupyterLab" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "jupyterlab_widgets-3.0.6-py3-none-any.whl", hash = "sha256:e95d08adf4f9c37a57da5fff8a65d00480199885fd2ecd2583fd9560b594b4e9"}, + {file = "jupyterlab_widgets-3.0.6.tar.gz", hash = "sha256:a464d68a7b9ebabdc135196389381412a39503d89302be0867d0ff3b2428ebb8"}, +] + +[[package]] +name = "jupyterthemes" +version = "0.20.0" +description = "Select and install a Jupyter notebook theme" +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "jupyterthemes-0.20.0-py2.py3-none-any.whl", hash = "sha256:4bd42fc88a06e3afabbe70c2ee25e6467147512993a3cbd9bec57ae3fd2e2fb1"}, + {file = "jupyterthemes-0.20.0.tar.gz", hash = "sha256:2a8ebc0c84b212ab99b9f1757fc0582a3f53930d3a75b2492d91a7c8b36ab41e"}, +] + +[package.dependencies] +ipython = ">=5.4.1" +jupyter-core = "*" +lesscpy = ">=0.11.2" +matplotlib = ">=1.4.3" +notebook = ">=5.6.0" + +[[package]] +name = "keopscore" +version = "2.1.1" +description = "keopscore is the KeOps meta programming engine. This python module should be used through a binder (e.g. pykeops or rkeops)" +category = "main" +optional = false +python-versions = ">=3" +files = [ + {file = "keopscore-2.1.1.tar.gz", hash = "sha256:07b4d254a28a9d4a43153663856677263dd7112912efacbad83c2a76ea0836f0"}, +] + +[[package]] +name = "kiwisolver" +version = "1.4.4" +description = "A fast implementation of the Cassowary constraint solver" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "kiwisolver-1.4.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:2f5e60fabb7343a836360c4f0919b8cd0d6dbf08ad2ca6b9cf90bf0c76a3c4f6"}, + {file = "kiwisolver-1.4.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:10ee06759482c78bdb864f4109886dff7b8a56529bc1609d4f1112b93fe6423c"}, + {file = "kiwisolver-1.4.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c79ebe8f3676a4c6630fd3f777f3cfecf9289666c84e775a67d1d358578dc2e3"}, + {file = "kiwisolver-1.4.4-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:abbe9fa13da955feb8202e215c4018f4bb57469b1b78c7a4c5c7b93001699938"}, + {file = "kiwisolver-1.4.4-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:7577c1987baa3adc4b3c62c33bd1118c3ef5c8ddef36f0f2c950ae0b199e100d"}, + {file = "kiwisolver-1.4.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f8ad8285b01b0d4695102546b342b493b3ccc6781fc28c8c6a1bb63e95d22f09"}, + {file = "kiwisolver-1.4.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8ed58b8acf29798b036d347791141767ccf65eee7f26bde03a71c944449e53de"}, + {file = "kiwisolver-1.4.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a68b62a02953b9841730db7797422f983935aeefceb1679f0fc85cbfbd311c32"}, + {file = "kiwisolver-1.4.4-cp310-cp310-win32.whl", hash = "sha256:e92a513161077b53447160b9bd8f522edfbed4bd9759e4c18ab05d7ef7e49408"}, + {file = "kiwisolver-1.4.4-cp310-cp310-win_amd64.whl", hash = "sha256:3fe20f63c9ecee44560d0e7f116b3a747a5d7203376abeea292ab3152334d004"}, + {file = "kiwisolver-1.4.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e0ea21f66820452a3f5d1655f8704a60d66ba1191359b96541eaf457710a5fc6"}, + {file = "kiwisolver-1.4.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:bc9db8a3efb3e403e4ecc6cd9489ea2bac94244f80c78e27c31dcc00d2790ac2"}, + {file = "kiwisolver-1.4.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d5b61785a9ce44e5a4b880272baa7cf6c8f48a5180c3e81c59553ba0cb0821ca"}, + {file = "kiwisolver-1.4.4-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c2dbb44c3f7e6c4d3487b31037b1bdbf424d97687c1747ce4ff2895795c9bf69"}, + {file = "kiwisolver-1.4.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6295ecd49304dcf3bfbfa45d9a081c96509e95f4b9d0eb7ee4ec0530c4a96514"}, + {file = "kiwisolver-1.4.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4bd472dbe5e136f96a4b18f295d159d7f26fd399136f5b17b08c4e5f498cd494"}, + {file = "kiwisolver-1.4.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bf7d9fce9bcc4752ca4a1b80aabd38f6d19009ea5cbda0e0856983cf6d0023f5"}, + {file = "kiwisolver-1.4.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78d6601aed50c74e0ef02f4204da1816147a6d3fbdc8b3872d263338a9052c51"}, + {file = "kiwisolver-1.4.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:877272cf6b4b7e94c9614f9b10140e198d2186363728ed0f701c6eee1baec1da"}, + {file = "kiwisolver-1.4.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:db608a6757adabb32f1cfe6066e39b3706d8c3aa69bbc353a5b61edad36a5cb4"}, + {file = "kiwisolver-1.4.4-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:5853eb494c71e267912275e5586fe281444eb5e722de4e131cddf9d442615626"}, + {file = "kiwisolver-1.4.4-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:f0a1dbdb5ecbef0d34eb77e56fcb3e95bbd7e50835d9782a45df81cc46949750"}, + {file = "kiwisolver-1.4.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:283dffbf061a4ec60391d51e6155e372a1f7a4f5b15d59c8505339454f8989e4"}, + {file = "kiwisolver-1.4.4-cp311-cp311-win32.whl", hash = "sha256:d06adcfa62a4431d404c31216f0f8ac97397d799cd53800e9d3efc2fbb3cf14e"}, + {file = "kiwisolver-1.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:e7da3fec7408813a7cebc9e4ec55afed2d0fd65c4754bc376bf03498d4e92686"}, + {file = "kiwisolver-1.4.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:62ac9cc684da4cf1778d07a89bf5f81b35834cb96ca523d3a7fb32509380cbf6"}, + {file = "kiwisolver-1.4.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41dae968a94b1ef1897cb322b39360a0812661dba7c682aa45098eb8e193dbdf"}, + {file = "kiwisolver-1.4.4-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:02f79693ec433cb4b5f51694e8477ae83b3205768a6fb48ffba60549080e295b"}, + {file = "kiwisolver-1.4.4-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d0611a0a2a518464c05ddd5a3a1a0e856ccc10e67079bb17f265ad19ab3c7597"}, + {file = "kiwisolver-1.4.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:db5283d90da4174865d520e7366801a93777201e91e79bacbac6e6927cbceede"}, + {file = "kiwisolver-1.4.4-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:1041feb4cda8708ce73bb4dcb9ce1ccf49d553bf87c3954bdfa46f0c3f77252c"}, + {file = "kiwisolver-1.4.4-cp37-cp37m-win32.whl", hash = "sha256:a553dadda40fef6bfa1456dc4be49b113aa92c2a9a9e8711e955618cd69622e3"}, + {file = "kiwisolver-1.4.4-cp37-cp37m-win_amd64.whl", hash = "sha256:03baab2d6b4a54ddbb43bba1a3a2d1627e82d205c5cf8f4c924dc49284b87166"}, + {file = "kiwisolver-1.4.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:841293b17ad704d70c578f1f0013c890e219952169ce8a24ebc063eecf775454"}, + {file = "kiwisolver-1.4.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f4f270de01dd3e129a72efad823da90cc4d6aafb64c410c9033aba70db9f1ff0"}, + {file = "kiwisolver-1.4.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f9f39e2f049db33a908319cf46624a569b36983c7c78318e9726a4cb8923b26c"}, + {file = "kiwisolver-1.4.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c97528e64cb9ebeff9701e7938653a9951922f2a38bd847787d4a8e498cc83ae"}, + {file = "kiwisolver-1.4.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d1573129aa0fd901076e2bfb4275a35f5b7aa60fbfb984499d661ec950320b0"}, + {file = "kiwisolver-1.4.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ad881edc7ccb9d65b0224f4e4d05a1e85cf62d73aab798943df6d48ab0cd79a1"}, + {file = "kiwisolver-1.4.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b428ef021242344340460fa4c9185d0b1f66fbdbfecc6c63eff4b7c29fad429d"}, + {file = "kiwisolver-1.4.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:2e407cb4bd5a13984a6c2c0fe1845e4e41e96f183e5e5cd4d77a857d9693494c"}, + {file = "kiwisolver-1.4.4-cp38-cp38-win32.whl", hash = "sha256:75facbe9606748f43428fc91a43edb46c7ff68889b91fa31f53b58894503a191"}, + {file = "kiwisolver-1.4.4-cp38-cp38-win_amd64.whl", hash = "sha256:5bce61af018b0cb2055e0e72e7d65290d822d3feee430b7b8203d8a855e78766"}, + {file = "kiwisolver-1.4.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8c808594c88a025d4e322d5bb549282c93c8e1ba71b790f539567932722d7bd8"}, + {file = "kiwisolver-1.4.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f0a71d85ecdd570ded8ac3d1c0f480842f49a40beb423bb8014539a9f32a5897"}, + {file = "kiwisolver-1.4.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b533558eae785e33e8c148a8d9921692a9fe5aa516efbdff8606e7d87b9d5824"}, + {file = "kiwisolver-1.4.4-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:efda5fc8cc1c61e4f639b8067d118e742b812c930f708e6667a5ce0d13499e29"}, + {file = "kiwisolver-1.4.4-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:7c43e1e1206cd421cd92e6b3280d4385d41d7166b3ed577ac20444b6995a445f"}, + {file = "kiwisolver-1.4.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bc8d3bd6c72b2dd9decf16ce70e20abcb3274ba01b4e1c96031e0c4067d1e7cd"}, + {file = "kiwisolver-1.4.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4ea39b0ccc4f5d803e3337dd46bcce60b702be4d86fd0b3d7531ef10fd99a1ac"}, + {file = "kiwisolver-1.4.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:968f44fdbf6dd757d12920d63b566eeb4d5b395fd2d00d29d7ef00a00582aac9"}, + {file = "kiwisolver-1.4.4-cp39-cp39-win32.whl", hash = "sha256:da7e547706e69e45d95e116e6939488d62174e033b763ab1496b4c29b76fabea"}, + {file = "kiwisolver-1.4.4-cp39-cp39-win_amd64.whl", hash = "sha256:ba59c92039ec0a66103b1d5fe588fa546373587a7d68f5c96f743c3396afc04b"}, + {file = "kiwisolver-1.4.4-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:91672bacaa030f92fc2f43b620d7b337fd9a5af28b0d6ed3f77afc43c4a64b5a"}, + {file = "kiwisolver-1.4.4-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:787518a6789009c159453da4d6b683f468ef7a65bbde796bcea803ccf191058d"}, + {file = "kiwisolver-1.4.4-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da152d8cdcab0e56e4f45eb08b9aea6455845ec83172092f09b0e077ece2cf7a"}, + {file = "kiwisolver-1.4.4-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:ecb1fa0db7bf4cff9dac752abb19505a233c7f16684c5826d1f11ebd9472b871"}, + {file = "kiwisolver-1.4.4-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:28bc5b299f48150b5f822ce68624e445040595a4ac3d59251703779836eceff9"}, + {file = "kiwisolver-1.4.4-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:81e38381b782cc7e1e46c4e14cd997ee6040768101aefc8fa3c24a4cc58e98f8"}, + {file = "kiwisolver-1.4.4-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2a66fdfb34e05b705620dd567f5a03f239a088d5a3f321e7b6ac3239d22aa286"}, + {file = "kiwisolver-1.4.4-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:872b8ca05c40d309ed13eb2e582cab0c5a05e81e987ab9c521bf05ad1d5cf5cb"}, + {file = "kiwisolver-1.4.4-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:70e7c2e7b750585569564e2e5ca9845acfaa5da56ac46df68414f29fea97be9f"}, + {file = "kiwisolver-1.4.4-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:9f85003f5dfa867e86d53fac6f7e6f30c045673fa27b603c397753bebadc3008"}, + {file = "kiwisolver-1.4.4-pp39-pypy39_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2e307eb9bd99801f82789b44bb45e9f541961831c7311521b13a6c85afc09767"}, + {file = "kiwisolver-1.4.4-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b1792d939ec70abe76f5054d3f36ed5656021dcad1322d1cc996d4e54165cef9"}, + {file = "kiwisolver-1.4.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6cb459eea32a4e2cf18ba5fcece2dbdf496384413bc1bae15583f19e567f3b2"}, + {file = "kiwisolver-1.4.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:36dafec3d6d6088d34e2de6b85f9d8e2324eb734162fba59d2ba9ed7a2043d5b"}, + {file = "kiwisolver-1.4.4.tar.gz", hash = "sha256:d41997519fcba4a1e46eb4a2fe31bc12f0ff957b2b81bac28db24744f333e955"}, +] + +[[package]] +name = "lazy-object-proxy" +version = "1.9.0" +description = "A fast and thorough lazy object proxy." +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "lazy-object-proxy-1.9.0.tar.gz", hash = "sha256:659fb5809fa4629b8a1ac5106f669cfc7bef26fbb389dda53b3e010d1ac4ebae"}, + {file = "lazy_object_proxy-1.9.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b40387277b0ed2d0602b8293b94d7257e17d1479e257b4de114ea11a8cb7f2d7"}, + {file = "lazy_object_proxy-1.9.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8c6cfb338b133fbdbc5cfaa10fe3c6aeea827db80c978dbd13bc9dd8526b7d4"}, + {file = "lazy_object_proxy-1.9.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:721532711daa7db0d8b779b0bb0318fa87af1c10d7fe5e52ef30f8eff254d0cd"}, + {file = "lazy_object_proxy-1.9.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:66a3de4a3ec06cd8af3f61b8e1ec67614fbb7c995d02fa224813cb7afefee701"}, + {file = "lazy_object_proxy-1.9.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:1aa3de4088c89a1b69f8ec0dcc169aa725b0ff017899ac568fe44ddc1396df46"}, + {file = "lazy_object_proxy-1.9.0-cp310-cp310-win32.whl", hash = "sha256:f0705c376533ed2a9e5e97aacdbfe04cecd71e0aa84c7c0595d02ef93b6e4455"}, + {file = "lazy_object_proxy-1.9.0-cp310-cp310-win_amd64.whl", hash = "sha256:ea806fd4c37bf7e7ad82537b0757999264d5f70c45468447bb2b91afdbe73a6e"}, + {file = "lazy_object_proxy-1.9.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:946d27deaff6cf8452ed0dba83ba38839a87f4f7a9732e8f9fd4107b21e6ff07"}, + {file = "lazy_object_proxy-1.9.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79a31b086e7e68b24b99b23d57723ef7e2c6d81ed21007b6281ebcd1688acb0a"}, + {file = "lazy_object_proxy-1.9.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f699ac1c768270c9e384e4cbd268d6e67aebcfae6cd623b4d7c3bfde5a35db59"}, + {file = "lazy_object_proxy-1.9.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:bfb38f9ffb53b942f2b5954e0f610f1e721ccebe9cce9025a38c8ccf4a5183a4"}, + {file = "lazy_object_proxy-1.9.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:189bbd5d41ae7a498397287c408617fe5c48633e7755287b21d741f7db2706a9"}, + {file = "lazy_object_proxy-1.9.0-cp311-cp311-win32.whl", hash = "sha256:81fc4d08b062b535d95c9ea70dbe8a335c45c04029878e62d744bdced5141586"}, + {file = "lazy_object_proxy-1.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:f2457189d8257dd41ae9b434ba33298aec198e30adf2dcdaaa3a28b9994f6adb"}, + {file = "lazy_object_proxy-1.9.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d9e25ef10a39e8afe59a5c348a4dbf29b4868ab76269f81ce1674494e2565a6e"}, + {file = "lazy_object_proxy-1.9.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cbf9b082426036e19c6924a9ce90c740a9861e2bdc27a4834fd0a910742ac1e8"}, + {file = "lazy_object_proxy-1.9.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f5fa4a61ce2438267163891961cfd5e32ec97a2c444e5b842d574251ade27d2"}, + {file = "lazy_object_proxy-1.9.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:8fa02eaab317b1e9e03f69aab1f91e120e7899b392c4fc19807a8278a07a97e8"}, + {file = "lazy_object_proxy-1.9.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e7c21c95cae3c05c14aafffe2865bbd5e377cfc1348c4f7751d9dc9a48ca4bda"}, + {file = "lazy_object_proxy-1.9.0-cp37-cp37m-win32.whl", hash = "sha256:f12ad7126ae0c98d601a7ee504c1122bcef553d1d5e0c3bfa77b16b3968d2734"}, + {file = "lazy_object_proxy-1.9.0-cp37-cp37m-win_amd64.whl", hash = "sha256:edd20c5a55acb67c7ed471fa2b5fb66cb17f61430b7a6b9c3b4a1e40293b1671"}, + {file = "lazy_object_proxy-1.9.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2d0daa332786cf3bb49e10dc6a17a52f6a8f9601b4cf5c295a4f85854d61de63"}, + {file = "lazy_object_proxy-1.9.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cd077f3d04a58e83d04b20e334f678c2b0ff9879b9375ed107d5d07ff160171"}, + {file = "lazy_object_proxy-1.9.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:660c94ea760b3ce47d1855a30984c78327500493d396eac4dfd8bd82041b22be"}, + {file = "lazy_object_proxy-1.9.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:212774e4dfa851e74d393a2370871e174d7ff0ebc980907723bb67d25c8a7c30"}, + {file = "lazy_object_proxy-1.9.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:f0117049dd1d5635bbff65444496c90e0baa48ea405125c088e93d9cf4525b11"}, + {file = "lazy_object_proxy-1.9.0-cp38-cp38-win32.whl", hash = "sha256:0a891e4e41b54fd5b8313b96399f8b0e173bbbfc03c7631f01efbe29bb0bcf82"}, + {file = "lazy_object_proxy-1.9.0-cp38-cp38-win_amd64.whl", hash = "sha256:9990d8e71b9f6488e91ad25f322898c136b008d87bf852ff65391b004da5e17b"}, + {file = "lazy_object_proxy-1.9.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9e7551208b2aded9c1447453ee366f1c4070602b3d932ace044715d89666899b"}, + {file = "lazy_object_proxy-1.9.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f83ac4d83ef0ab017683d715ed356e30dd48a93746309c8f3517e1287523ef4"}, + {file = "lazy_object_proxy-1.9.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7322c3d6f1766d4ef1e51a465f47955f1e8123caee67dd641e67d539a534d006"}, + {file = "lazy_object_proxy-1.9.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:18b78ec83edbbeb69efdc0e9c1cb41a3b1b1ed11ddd8ded602464c3fc6020494"}, + {file = "lazy_object_proxy-1.9.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:09763491ce220c0299688940f8dc2c5d05fd1f45af1e42e636b2e8b2303e4382"}, + {file = "lazy_object_proxy-1.9.0-cp39-cp39-win32.whl", hash = "sha256:9090d8e53235aa280fc9239a86ae3ea8ac58eff66a705fa6aa2ec4968b95c821"}, + {file = "lazy_object_proxy-1.9.0-cp39-cp39-win_amd64.whl", hash = "sha256:db1c1722726f47e10e0b5fdbf15ac3b8adb58c091d12b3ab713965795036985f"}, +] + +[[package]] +name = "lesscpy" +version = "0.15.1" +description = "Python LESS compiler" +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "lesscpy-0.15.1-py2.py3-none-any.whl", hash = "sha256:8d26e58ed4812b345c2896daea435a28cb3182f87ae3391157085255d4c37dff"}, + {file = "lesscpy-0.15.1.tar.gz", hash = "sha256:1045d17a98f688646ca758dff254e6e9c03745648e051a081b0395c3b77c824c"}, +] + +[package.dependencies] +ply = "*" + +[[package]] +name = "lightning-utilities" +version = "0.8.0" +description = "PyTorch Lightning Sample project." +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "lightning-utilities-0.8.0.tar.gz", hash = "sha256:8e5d95c7c57f026cdfed7c154303e88c93a7a5e868c9944cb02cf71f1db29720"}, + {file = "lightning_utilities-0.8.0-py3-none-any.whl", hash = "sha256:22aa107b51c8f50ccef54d08885eb370903eb04148cddb2891b9c65c59de2a6e"}, +] + +[package.dependencies] +packaging = ">=17.1" +typing-extensions = "*" + +[package.extras] +cli = ["fire"] +docs = ["sphinx (>=4.0,<5.0)"] +test = ["coverage (==6.5.0)"] +typing = ["mypy (>=1.0.0)"] + +[[package]] +name = "llvmlite" +version = "0.39.1" +description = "lightweight wrapper around basic LLVM functionality" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "llvmlite-0.39.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6717c7a6e93c9d2c3d07c07113ec80ae24af45cde536b34363d4bcd9188091d9"}, + {file = "llvmlite-0.39.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ddab526c5a2c4ccb8c9ec4821fcea7606933dc53f510e2a6eebb45a418d3488a"}, + {file = "llvmlite-0.39.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a3f331a323d0f0ada6b10d60182ef06c20a2f01be21699999d204c5750ffd0b4"}, + {file = "llvmlite-0.39.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e2c00ff204afa721b0bb9835b5bf1ba7fba210eefcec5552a9e05a63219ba0dc"}, + {file = "llvmlite-0.39.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:16f56eb1eec3cda3a5c526bc3f63594fc24e0c8d219375afeb336f289764c6c7"}, + {file = "llvmlite-0.39.1-cp310-cp310-win32.whl", hash = "sha256:d0bfd18c324549c0fec2c5dc610fd024689de6f27c6cc67e4e24a07541d6e49b"}, + {file = "llvmlite-0.39.1-cp310-cp310-win_amd64.whl", hash = "sha256:7ebf1eb9badc2a397d4f6a6c8717447c81ac011db00064a00408bc83c923c0e4"}, + {file = "llvmlite-0.39.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:6546bed4e02a1c3d53a22a0bced254b3b6894693318b16c16c8e43e29d6befb6"}, + {file = "llvmlite-0.39.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1578f5000fdce513712e99543c50e93758a954297575610f48cb1fd71b27c08a"}, + {file = "llvmlite-0.39.1-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3803f11ad5f6f6c3d2b545a303d68d9fabb1d50e06a8d6418e6fcd2d0df00959"}, + {file = "llvmlite-0.39.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50aea09a2b933dab7c9df92361b1844ad3145bfb8dd2deb9cd8b8917d59306fb"}, + {file = "llvmlite-0.39.1-cp37-cp37m-win32.whl", hash = "sha256:b1a0bbdb274fb683f993198775b957d29a6f07b45d184c571ef2a721ce4388cf"}, + {file = "llvmlite-0.39.1-cp37-cp37m-win_amd64.whl", hash = "sha256:e172c73fccf7d6db4bd6f7de963dedded900d1a5c6778733241d878ba613980e"}, + {file = "llvmlite-0.39.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e31f4b799d530255aaf0566e3da2df5bfc35d3cd9d6d5a3dcc251663656c27b1"}, + {file = "llvmlite-0.39.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:62c0ea22e0b9dffb020601bb65cb11dd967a095a488be73f07d8867f4e327ca5"}, + {file = "llvmlite-0.39.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9ffc84ade195abd4abcf0bd3b827b9140ae9ef90999429b9ea84d5df69c9058c"}, + {file = "llvmlite-0.39.1-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c0f158e4708dda6367d21cf15afc58de4ebce979c7a1aa2f6b977aae737e2a54"}, + {file = "llvmlite-0.39.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22d36591cd5d02038912321d9ab8e4668e53ae2211da5523f454e992b5e13c36"}, + {file = "llvmlite-0.39.1-cp38-cp38-win32.whl", hash = "sha256:4c6ebace910410daf0bebda09c1859504fc2f33d122e9a971c4c349c89cca630"}, + {file = "llvmlite-0.39.1-cp38-cp38-win_amd64.whl", hash = "sha256:fb62fc7016b592435d3e3a8f680e3ea8897c3c9e62e6e6cc58011e7a4801439e"}, + {file = "llvmlite-0.39.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fa9b26939ae553bf30a9f5c4c754db0fb2d2677327f2511e674aa2f5df941789"}, + {file = "llvmlite-0.39.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e4f212c018db951da3e1dc25c2651abc688221934739721f2dad5ff1dd5f90e7"}, + {file = "llvmlite-0.39.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:39dc2160aed36e989610fc403487f11b8764b6650017ff367e45384dff88ffbf"}, + {file = "llvmlite-0.39.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1ec3d70b3e507515936e475d9811305f52d049281eaa6c8273448a61c9b5b7e2"}, + {file = "llvmlite-0.39.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:60f8dd1e76f47b3dbdee4b38d9189f3e020d22a173c00f930b52131001d801f9"}, + {file = "llvmlite-0.39.1-cp39-cp39-win32.whl", hash = "sha256:03aee0ccd81735696474dc4f8b6be60774892a2929d6c05d093d17392c237f32"}, + {file = "llvmlite-0.39.1-cp39-cp39-win_amd64.whl", hash = "sha256:3fc14e757bc07a919221f0cbaacb512704ce5774d7fcada793f1996d6bc75f2a"}, + {file = "llvmlite-0.39.1.tar.gz", hash = "sha256:b43abd7c82e805261c425d50335be9a6c4f84264e34d6d6e475207300005d572"}, +] + +[[package]] +name = "lxml" +version = "4.9.2" +description = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API." +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, != 3.4.*" +files = [ + {file = "lxml-4.9.2-cp27-cp27m-macosx_10_15_x86_64.whl", hash = "sha256:76cf573e5a365e790396a5cc2b909812633409306c6531a6877c59061e42c4f2"}, + {file = "lxml-4.9.2-cp27-cp27m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b1f42b6921d0e81b1bcb5e395bc091a70f41c4d4e55ba99c6da2b31626c44892"}, + {file = "lxml-4.9.2-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:9f102706d0ca011de571de32c3247c6476b55bb6bc65a20f682f000b07a4852a"}, + {file = "lxml-4.9.2-cp27-cp27m-win32.whl", hash = "sha256:8d0b4612b66ff5d62d03bcaa043bb018f74dfea51184e53f067e6fdcba4bd8de"}, + {file = "lxml-4.9.2-cp27-cp27m-win_amd64.whl", hash = "sha256:4c8f293f14abc8fd3e8e01c5bd86e6ed0b6ef71936ded5bf10fe7a5efefbaca3"}, + {file = "lxml-4.9.2-cp27-cp27mu-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2899456259589aa38bfb018c364d6ae7b53c5c22d8e27d0ec7609c2a1ff78b50"}, + {file = "lxml-4.9.2-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6749649eecd6a9871cae297bffa4ee76f90b4504a2a2ab528d9ebe912b101975"}, + {file = "lxml-4.9.2-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:a08cff61517ee26cb56f1e949cca38caabe9ea9fbb4b1e10a805dc39844b7d5c"}, + {file = "lxml-4.9.2-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:85cabf64adec449132e55616e7ca3e1000ab449d1d0f9d7f83146ed5bdcb6d8a"}, + {file = "lxml-4.9.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:8340225bd5e7a701c0fa98284c849c9b9fc9238abf53a0ebd90900f25d39a4e4"}, + {file = "lxml-4.9.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:1ab8f1f932e8f82355e75dda5413a57612c6ea448069d4fb2e217e9a4bed13d4"}, + {file = "lxml-4.9.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:699a9af7dffaf67deeae27b2112aa06b41c370d5e7633e0ee0aea2e0b6c211f7"}, + {file = "lxml-4.9.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b9cc34af337a97d470040f99ba4282f6e6bac88407d021688a5d585e44a23184"}, + {file = "lxml-4.9.2-cp310-cp310-win32.whl", hash = "sha256:d02a5399126a53492415d4906ab0ad0375a5456cc05c3fc0fc4ca11771745cda"}, + {file = "lxml-4.9.2-cp310-cp310-win_amd64.whl", hash = "sha256:a38486985ca49cfa574a507e7a2215c0c780fd1778bb6290c21193b7211702ab"}, + {file = "lxml-4.9.2-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:c83203addf554215463b59f6399835201999b5e48019dc17f182ed5ad87205c9"}, + {file = "lxml-4.9.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:2a87fa548561d2f4643c99cd13131acb607ddabb70682dcf1dff5f71f781a4bf"}, + {file = "lxml-4.9.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:d6b430a9938a5a5d85fc107d852262ddcd48602c120e3dbb02137c83d212b380"}, + {file = "lxml-4.9.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:3efea981d956a6f7173b4659849f55081867cf897e719f57383698af6f618a92"}, + {file = "lxml-4.9.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:df0623dcf9668ad0445e0558a21211d4e9a149ea8f5666917c8eeec515f0a6d1"}, + {file = "lxml-4.9.2-cp311-cp311-win32.whl", hash = "sha256:da248f93f0418a9e9d94b0080d7ebc407a9a5e6d0b57bb30db9b5cc28de1ad33"}, + {file = "lxml-4.9.2-cp311-cp311-win_amd64.whl", hash = "sha256:3818b8e2c4b5148567e1b09ce739006acfaa44ce3156f8cbbc11062994b8e8dd"}, + {file = "lxml-4.9.2-cp35-cp35m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ca989b91cf3a3ba28930a9fc1e9aeafc2a395448641df1f387a2d394638943b0"}, + {file = "lxml-4.9.2-cp35-cp35m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:822068f85e12a6e292803e112ab876bc03ed1f03dddb80154c395f891ca6b31e"}, + {file = "lxml-4.9.2-cp35-cp35m-win32.whl", hash = "sha256:be7292c55101e22f2a3d4d8913944cbea71eea90792bf914add27454a13905df"}, + {file = "lxml-4.9.2-cp35-cp35m-win_amd64.whl", hash = "sha256:998c7c41910666d2976928c38ea96a70d1aa43be6fe502f21a651e17483a43c5"}, + {file = "lxml-4.9.2-cp36-cp36m-macosx_10_15_x86_64.whl", hash = "sha256:b26a29f0b7fc6f0897f043ca366142d2b609dc60756ee6e4e90b5f762c6adc53"}, + {file = "lxml-4.9.2-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:ab323679b8b3030000f2be63e22cdeea5b47ee0abd2d6a1dc0c8103ddaa56cd7"}, + {file = "lxml-4.9.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:689bb688a1db722485e4610a503e3e9210dcc20c520b45ac8f7533c837be76fe"}, + {file = "lxml-4.9.2-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:f49e52d174375a7def9915c9f06ec4e569d235ad428f70751765f48d5926678c"}, + {file = "lxml-4.9.2-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:36c3c175d34652a35475a73762b545f4527aec044910a651d2bf50de9c3352b1"}, + {file = "lxml-4.9.2-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:a35f8b7fa99f90dd2f5dc5a9fa12332642f087a7641289ca6c40d6e1a2637d8e"}, + {file = "lxml-4.9.2-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:58bfa3aa19ca4c0f28c5dde0ff56c520fbac6f0daf4fac66ed4c8d2fb7f22e74"}, + {file = "lxml-4.9.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:bc718cd47b765e790eecb74d044cc8d37d58562f6c314ee9484df26276d36a38"}, + {file = "lxml-4.9.2-cp36-cp36m-win32.whl", hash = "sha256:d5bf6545cd27aaa8a13033ce56354ed9e25ab0e4ac3b5392b763d8d04b08e0c5"}, + {file = "lxml-4.9.2-cp36-cp36m-win_amd64.whl", hash = "sha256:3ab9fa9d6dc2a7f29d7affdf3edebf6ece6fb28a6d80b14c3b2fb9d39b9322c3"}, + {file = "lxml-4.9.2-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:05ca3f6abf5cf78fe053da9b1166e062ade3fa5d4f92b4ed688127ea7d7b1d03"}, + {file = "lxml-4.9.2-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:a5da296eb617d18e497bcf0a5c528f5d3b18dadb3619fbdadf4ed2356ef8d941"}, + {file = "lxml-4.9.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:04876580c050a8c5341d706dd464ff04fd597095cc8c023252566a8826505726"}, + {file = "lxml-4.9.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:c9ec3eaf616d67db0764b3bb983962b4f385a1f08304fd30c7283954e6a7869b"}, + {file = "lxml-4.9.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2a29ba94d065945944016b6b74e538bdb1751a1db6ffb80c9d3c2e40d6fa9894"}, + {file = "lxml-4.9.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:a82d05da00a58b8e4c0008edbc8a4b6ec5a4bc1e2ee0fb6ed157cf634ed7fa45"}, + {file = "lxml-4.9.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:223f4232855ade399bd409331e6ca70fb5578efef22cf4069a6090acc0f53c0e"}, + {file = "lxml-4.9.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d17bc7c2ccf49c478c5bdd447594e82692c74222698cfc9b5daae7ae7e90743b"}, + {file = "lxml-4.9.2-cp37-cp37m-win32.whl", hash = "sha256:b64d891da92e232c36976c80ed7ebb383e3f148489796d8d31a5b6a677825efe"}, + {file = "lxml-4.9.2-cp37-cp37m-win_amd64.whl", hash = "sha256:a0a336d6d3e8b234a3aae3c674873d8f0e720b76bc1d9416866c41cd9500ffb9"}, + {file = "lxml-4.9.2-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:da4dd7c9c50c059aba52b3524f84d7de956f7fef88f0bafcf4ad7dde94a064e8"}, + {file = "lxml-4.9.2-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:821b7f59b99551c69c85a6039c65b75f5683bdc63270fec660f75da67469ca24"}, + {file = "lxml-4.9.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:e5168986b90a8d1f2f9dc1b841467c74221bd752537b99761a93d2d981e04889"}, + {file = "lxml-4.9.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:8e20cb5a47247e383cf4ff523205060991021233ebd6f924bca927fcf25cf86f"}, + {file = "lxml-4.9.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:13598ecfbd2e86ea7ae45ec28a2a54fb87ee9b9fdb0f6d343297d8e548392c03"}, + {file = "lxml-4.9.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:880bbbcbe2fca64e2f4d8e04db47bcdf504936fa2b33933efd945e1b429bea8c"}, + {file = "lxml-4.9.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:7d2278d59425777cfcb19735018d897ca8303abe67cc735f9f97177ceff8027f"}, + {file = "lxml-4.9.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5344a43228767f53a9df6e5b253f8cdca7dfc7b7aeae52551958192f56d98457"}, + {file = "lxml-4.9.2-cp38-cp38-win32.whl", hash = "sha256:925073b2fe14ab9b87e73f9a5fde6ce6392da430f3004d8b72cc86f746f5163b"}, + {file = "lxml-4.9.2-cp38-cp38-win_amd64.whl", hash = "sha256:9b22c5c66f67ae00c0199f6055705bc3eb3fcb08d03d2ec4059a2b1b25ed48d7"}, + {file = "lxml-4.9.2-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:5f50a1c177e2fa3ee0667a5ab79fdc6b23086bc8b589d90b93b4bd17eb0e64d1"}, + {file = "lxml-4.9.2-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:090c6543d3696cbe15b4ac6e175e576bcc3f1ccfbba970061b7300b0c15a2140"}, + {file = "lxml-4.9.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:63da2ccc0857c311d764e7d3d90f429c252e83b52d1f8f1d1fe55be26827d1f4"}, + {file = "lxml-4.9.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:5b4545b8a40478183ac06c073e81a5ce4cf01bf1734962577cf2bb569a5b3bbf"}, + {file = "lxml-4.9.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2e430cd2824f05f2d4f687701144556646bae8f249fd60aa1e4c768ba7018947"}, + {file = "lxml-4.9.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6804daeb7ef69e7b36f76caddb85cccd63d0c56dedb47555d2fc969e2af6a1a5"}, + {file = "lxml-4.9.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:a6e441a86553c310258aca15d1c05903aaf4965b23f3bc2d55f200804e005ee5"}, + {file = "lxml-4.9.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ca34efc80a29351897e18888c71c6aca4a359247c87e0b1c7ada14f0ab0c0fb2"}, + {file = "lxml-4.9.2-cp39-cp39-win32.whl", hash = "sha256:6b418afe5df18233fc6b6093deb82a32895b6bb0b1155c2cdb05203f583053f1"}, + {file = "lxml-4.9.2-cp39-cp39-win_amd64.whl", hash = "sha256:f1496ea22ca2c830cbcbd473de8f114a320da308438ae65abad6bab7867fe38f"}, + {file = "lxml-4.9.2-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:b264171e3143d842ded311b7dccd46ff9ef34247129ff5bf5066123c55c2431c"}, + {file = "lxml-4.9.2-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:0dc313ef231edf866912e9d8f5a042ddab56c752619e92dfd3a2c277e6a7299a"}, + {file = "lxml-4.9.2-pp38-pypy38_pp73-macosx_10_15_x86_64.whl", hash = "sha256:16efd54337136e8cd72fb9485c368d91d77a47ee2d42b057564aae201257d419"}, + {file = "lxml-4.9.2-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:0f2b1e0d79180f344ff9f321327b005ca043a50ece8713de61d1cb383fb8ac05"}, + {file = "lxml-4.9.2-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:7b770ed79542ed52c519119473898198761d78beb24b107acf3ad65deae61f1f"}, + {file = "lxml-4.9.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:efa29c2fe6b4fdd32e8ef81c1528506895eca86e1d8c4657fda04c9b3786ddf9"}, + {file = "lxml-4.9.2-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7e91ee82f4199af8c43d8158024cbdff3d931df350252288f0d4ce656df7f3b5"}, + {file = "lxml-4.9.2-pp39-pypy39_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:b23e19989c355ca854276178a0463951a653309fb8e57ce674497f2d9f208746"}, + {file = "lxml-4.9.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:01d36c05f4afb8f7c20fd9ed5badca32a2029b93b1750f571ccc0b142531caf7"}, + {file = "lxml-4.9.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7b515674acfdcadb0eb5d00d8a709868173acece5cb0be3dd165950cbfdf5409"}, + {file = "lxml-4.9.2.tar.gz", hash = "sha256:2455cfaeb7ac70338b3257f41e21f0724f4b5b0c0e7702da67ee6c3640835b67"}, +] + +[package.extras] +cssselect = ["cssselect (>=0.7)"] +html5 = ["html5lib"] +htmlsoup = ["BeautifulSoup4"] +source = ["Cython (>=0.29.7)"] + +[[package]] +name = "mako" +version = "1.2.4" +description = "A super-fast templating language that borrows the best ideas from the existing templating languages." +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "Mako-1.2.4-py3-none-any.whl", hash = "sha256:c97c79c018b9165ac9922ae4f32da095ffd3c4e6872b45eded42926deea46818"}, + {file = "Mako-1.2.4.tar.gz", hash = "sha256:d60a3903dc3bb01a18ad6a89cdbe2e4eadc69c0bc8ef1e3773ba53d44c3f7a34"}, +] + +[package.dependencies] +MarkupSafe = ">=0.9.2" + +[package.extras] +babel = ["Babel"] +lingua = ["lingua"] +testing = ["pytest"] + +[[package]] +name = "markdown" +version = "3.4.3" +description = "Python implementation of John Gruber's Markdown." +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "Markdown-3.4.3-py3-none-any.whl", hash = "sha256:065fd4df22da73a625f14890dd77eb8040edcbd68794bcd35943be14490608b2"}, + {file = "Markdown-3.4.3.tar.gz", hash = "sha256:8bf101198e004dc93e84a12a7395e31aac6a9c9942848ae1d99b9d72cf9b3520"}, +] + +[package.extras] +testing = ["coverage", "pyyaml"] + +[[package]] +name = "markdown-it-py" +version = "2.2.0" +description = "Python port of markdown-it. Markdown parsing, done right!" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "markdown-it-py-2.2.0.tar.gz", hash = "sha256:7c9a5e412688bc771c67432cbfebcdd686c93ce6484913dccf06cb5a0bea35a1"}, + {file = "markdown_it_py-2.2.0-py3-none-any.whl", hash = "sha256:5a35f8d1870171d9acc47b99612dc146129b631baf04970128b568f190d0cc30"}, +] + +[package.dependencies] +mdurl = ">=0.1,<1.0" + +[package.extras] +benchmarking = ["psutil", "pytest", "pytest-benchmark"] +code-style = ["pre-commit (>=3.0,<4.0)"] +compare = ["commonmark (>=0.9,<1.0)", "markdown (>=3.4,<4.0)", "mistletoe (>=1.0,<2.0)", "mistune (>=2.0,<3.0)", "panflute (>=2.3,<3.0)"] +linkify = ["linkify-it-py (>=1,<3)"] +plugins = ["mdit-py-plugins"] +profiling = ["gprof2dot"] +rtd = ["attrs", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"] +testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] + +[[package]] +name = "markupsafe" +version = "2.1.2" +description = "Safely add untrusted strings to HTML/XML markup." +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "MarkupSafe-2.1.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:665a36ae6f8f20a4676b53224e33d456a6f5a72657d9c83c2aa00765072f31f7"}, + {file = "MarkupSafe-2.1.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:340bea174e9761308703ae988e982005aedf427de816d1afe98147668cc03036"}, + {file = "MarkupSafe-2.1.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22152d00bf4a9c7c83960521fc558f55a1adbc0631fbb00a9471e097b19d72e1"}, + {file = "MarkupSafe-2.1.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28057e985dace2f478e042eaa15606c7efccb700797660629da387eb289b9323"}, + {file = "MarkupSafe-2.1.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca244fa73f50a800cf8c3ebf7fd93149ec37f5cb9596aa8873ae2c1d23498601"}, + {file = "MarkupSafe-2.1.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d9d971ec1e79906046aa3ca266de79eac42f1dbf3612a05dc9368125952bd1a1"}, + {file = "MarkupSafe-2.1.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:7e007132af78ea9df29495dbf7b5824cb71648d7133cf7848a2a5dd00d36f9ff"}, + {file = "MarkupSafe-2.1.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7313ce6a199651c4ed9d7e4cfb4aa56fe923b1adf9af3b420ee14e6d9a73df65"}, + {file = "MarkupSafe-2.1.2-cp310-cp310-win32.whl", hash = "sha256:c4a549890a45f57f1ebf99c067a4ad0cb423a05544accaf2b065246827ed9603"}, + {file = "MarkupSafe-2.1.2-cp310-cp310-win_amd64.whl", hash = "sha256:835fb5e38fd89328e9c81067fd642b3593c33e1e17e2fdbf77f5676abb14a156"}, + {file = "MarkupSafe-2.1.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2ec4f2d48ae59bbb9d1f9d7efb9236ab81429a764dedca114f5fdabbc3788013"}, + {file = "MarkupSafe-2.1.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:608e7073dfa9e38a85d38474c082d4281f4ce276ac0010224eaba11e929dd53a"}, + {file = "MarkupSafe-2.1.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:65608c35bfb8a76763f37036547f7adfd09270fbdbf96608be2bead319728fcd"}, + {file = "MarkupSafe-2.1.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2bfb563d0211ce16b63c7cb9395d2c682a23187f54c3d79bfec33e6705473c6"}, + {file = "MarkupSafe-2.1.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:da25303d91526aac3672ee6d49a2f3db2d9502a4a60b55519feb1a4c7714e07d"}, + {file = "MarkupSafe-2.1.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:9cad97ab29dfc3f0249b483412c85c8ef4766d96cdf9dcf5a1e3caa3f3661cf1"}, + {file = "MarkupSafe-2.1.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:085fd3201e7b12809f9e6e9bc1e5c96a368c8523fad5afb02afe3c051ae4afcc"}, + {file = "MarkupSafe-2.1.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1bea30e9bf331f3fef67e0a3877b2288593c98a21ccb2cf29b74c581a4eb3af0"}, + {file = "MarkupSafe-2.1.2-cp311-cp311-win32.whl", hash = "sha256:7df70907e00c970c60b9ef2938d894a9381f38e6b9db73c5be35e59d92e06625"}, + {file = "MarkupSafe-2.1.2-cp311-cp311-win_amd64.whl", hash = "sha256:e55e40ff0cc8cc5c07996915ad367fa47da6b3fc091fdadca7f5403239c5fec3"}, + {file = "MarkupSafe-2.1.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a6e40afa7f45939ca356f348c8e23048e02cb109ced1eb8420961b2f40fb373a"}, + {file = "MarkupSafe-2.1.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf877ab4ed6e302ec1d04952ca358b381a882fbd9d1b07cccbfd61783561f98a"}, + {file = "MarkupSafe-2.1.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63ba06c9941e46fa389d389644e2d8225e0e3e5ebcc4ff1ea8506dce646f8c8a"}, + {file = "MarkupSafe-2.1.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f1cd098434e83e656abf198f103a8207a8187c0fc110306691a2e94a78d0abb2"}, + {file = "MarkupSafe-2.1.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:55f44b440d491028addb3b88f72207d71eeebfb7b5dbf0643f7c023ae1fba619"}, + {file = "MarkupSafe-2.1.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:a6f2fcca746e8d5910e18782f976489939d54a91f9411c32051b4aab2bd7c513"}, + {file = "MarkupSafe-2.1.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:0b462104ba25f1ac006fdab8b6a01ebbfbce9ed37fd37fd4acd70c67c973e460"}, + {file = "MarkupSafe-2.1.2-cp37-cp37m-win32.whl", hash = "sha256:7668b52e102d0ed87cb082380a7e2e1e78737ddecdde129acadb0eccc5423859"}, + {file = "MarkupSafe-2.1.2-cp37-cp37m-win_amd64.whl", hash = "sha256:6d6607f98fcf17e534162f0709aaad3ab7a96032723d8ac8750ffe17ae5a0666"}, + {file = "MarkupSafe-2.1.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:a806db027852538d2ad7555b203300173dd1b77ba116de92da9afbc3a3be3eed"}, + {file = "MarkupSafe-2.1.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a4abaec6ca3ad8660690236d11bfe28dfd707778e2442b45addd2f086d6ef094"}, + {file = "MarkupSafe-2.1.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f03a532d7dee1bed20bc4884194a16160a2de9ffc6354b3878ec9682bb623c54"}, + {file = "MarkupSafe-2.1.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4cf06cdc1dda95223e9d2d3c58d3b178aa5dacb35ee7e3bbac10e4e1faacb419"}, + {file = "MarkupSafe-2.1.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:22731d79ed2eb25059ae3df1dfc9cb1546691cc41f4e3130fe6bfbc3ecbbecfa"}, + {file = "MarkupSafe-2.1.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:f8ffb705ffcf5ddd0e80b65ddf7bed7ee4f5a441ea7d3419e861a12eaf41af58"}, + {file = "MarkupSafe-2.1.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8db032bf0ce9022a8e41a22598eefc802314e81b879ae093f36ce9ddf39ab1ba"}, + {file = "MarkupSafe-2.1.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2298c859cfc5463f1b64bd55cb3e602528db6fa0f3cfd568d3605c50678f8f03"}, + {file = "MarkupSafe-2.1.2-cp38-cp38-win32.whl", hash = "sha256:50c42830a633fa0cf9e7d27664637532791bfc31c731a87b202d2d8ac40c3ea2"}, + {file = "MarkupSafe-2.1.2-cp38-cp38-win_amd64.whl", hash = "sha256:bb06feb762bade6bf3c8b844462274db0c76acc95c52abe8dbed28ae3d44a147"}, + {file = "MarkupSafe-2.1.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:99625a92da8229df6d44335e6fcc558a5037dd0a760e11d84be2260e6f37002f"}, + {file = "MarkupSafe-2.1.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8bca7e26c1dd751236cfb0c6c72d4ad61d986e9a41bbf76cb445f69488b2a2bd"}, + {file = "MarkupSafe-2.1.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40627dcf047dadb22cd25ea7ecfe9cbf3bbbad0482ee5920b582f3809c97654f"}, + {file = "MarkupSafe-2.1.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40dfd3fefbef579ee058f139733ac336312663c6706d1163b82b3003fb1925c4"}, + {file = "MarkupSafe-2.1.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:090376d812fb6ac5f171e5938e82e7f2d7adc2b629101cec0db8b267815c85e2"}, + {file = "MarkupSafe-2.1.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:2e7821bffe00aa6bd07a23913b7f4e01328c3d5cc0b40b36c0bd81d362faeb65"}, + {file = "MarkupSafe-2.1.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:c0a33bc9f02c2b17c3ea382f91b4db0e6cde90b63b296422a939886a7a80de1c"}, + {file = "MarkupSafe-2.1.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:b8526c6d437855442cdd3d87eede9c425c4445ea011ca38d937db299382e6fa3"}, + {file = "MarkupSafe-2.1.2-cp39-cp39-win32.whl", hash = "sha256:137678c63c977754abe9086a3ec011e8fd985ab90631145dfb9294ad09c102a7"}, + {file = "MarkupSafe-2.1.2-cp39-cp39-win_amd64.whl", hash = "sha256:0576fe974b40a400449768941d5d0858cc624e3249dfd1e0c33674e5c7ca7aed"}, + {file = "MarkupSafe-2.1.2.tar.gz", hash = "sha256:abcabc8c2b26036d62d4c746381a6f7cf60aafcc653198ad678306986b09450d"}, +] + +[[package]] +name = "matplotlib" +version = "3.7.1" +description = "Python plotting package" +category = "main" +optional = false +python-versions = ">=3.8" +files = [ + {file = "matplotlib-3.7.1-cp310-cp310-macosx_10_12_universal2.whl", hash = "sha256:95cbc13c1fc6844ab8812a525bbc237fa1470863ff3dace7352e910519e194b1"}, + {file = "matplotlib-3.7.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:08308bae9e91aca1ec6fd6dda66237eef9f6294ddb17f0d0b3c863169bf82353"}, + {file = "matplotlib-3.7.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:544764ba51900da4639c0f983b323d288f94f65f4024dc40ecb1542d74dc0500"}, + {file = "matplotlib-3.7.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:56d94989191de3fcc4e002f93f7f1be5da476385dde410ddafbb70686acf00ea"}, + {file = "matplotlib-3.7.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e99bc9e65901bb9a7ce5e7bb24af03675cbd7c70b30ac670aa263240635999a4"}, + {file = "matplotlib-3.7.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eb7d248c34a341cd4c31a06fd34d64306624c8cd8d0def7abb08792a5abfd556"}, + {file = "matplotlib-3.7.1-cp310-cp310-win32.whl", hash = "sha256:ce463ce590f3825b52e9fe5c19a3c6a69fd7675a39d589e8b5fbe772272b3a24"}, + {file = "matplotlib-3.7.1-cp310-cp310-win_amd64.whl", hash = "sha256:3d7bc90727351fb841e4d8ae620d2d86d8ed92b50473cd2b42ce9186104ecbba"}, + {file = "matplotlib-3.7.1-cp311-cp311-macosx_10_12_universal2.whl", hash = "sha256:770a205966d641627fd5cf9d3cb4b6280a716522cd36b8b284a8eb1581310f61"}, + {file = "matplotlib-3.7.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:f67bfdb83a8232cb7a92b869f9355d677bce24485c460b19d01970b64b2ed476"}, + {file = "matplotlib-3.7.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2bf092f9210e105f414a043b92af583c98f50050559616930d884387d0772aba"}, + {file = "matplotlib-3.7.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:89768d84187f31717349c6bfadc0e0d8c321e8eb34522acec8a67b1236a66332"}, + {file = "matplotlib-3.7.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:83111e6388dec67822e2534e13b243cc644c7494a4bb60584edbff91585a83c6"}, + {file = "matplotlib-3.7.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a867bf73a7eb808ef2afbca03bcdb785dae09595fbe550e1bab0cd023eba3de0"}, + {file = "matplotlib-3.7.1-cp311-cp311-win32.whl", hash = "sha256:fbdeeb58c0cf0595efe89c05c224e0a502d1aa6a8696e68a73c3efc6bc354304"}, + {file = "matplotlib-3.7.1-cp311-cp311-win_amd64.whl", hash = "sha256:c0bd19c72ae53e6ab979f0ac6a3fafceb02d2ecafa023c5cca47acd934d10be7"}, + {file = "matplotlib-3.7.1-cp38-cp38-macosx_10_12_universal2.whl", hash = "sha256:6eb88d87cb2c49af00d3bbc33a003f89fd9f78d318848da029383bfc08ecfbfb"}, + {file = "matplotlib-3.7.1-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:cf0e4f727534b7b1457898c4f4ae838af1ef87c359b76dcd5330fa31893a3ac7"}, + {file = "matplotlib-3.7.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:46a561d23b91f30bccfd25429c3c706afe7d73a5cc64ef2dfaf2b2ac47c1a5dc"}, + {file = "matplotlib-3.7.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:8704726d33e9aa8a6d5215044b8d00804561971163563e6e6591f9dcf64340cc"}, + {file = "matplotlib-3.7.1-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4cf327e98ecf08fcbb82685acaf1939d3338548620ab8dfa02828706402c34de"}, + {file = "matplotlib-3.7.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:617f14ae9d53292ece33f45cba8503494ee199a75b44de7717964f70637a36aa"}, + {file = "matplotlib-3.7.1-cp38-cp38-win32.whl", hash = "sha256:7c9a4b2da6fac77bcc41b1ea95fadb314e92508bf5493ceff058e727e7ecf5b0"}, + {file = "matplotlib-3.7.1-cp38-cp38-win_amd64.whl", hash = "sha256:14645aad967684e92fc349493fa10c08a6da514b3d03a5931a1bac26e6792bd1"}, + {file = "matplotlib-3.7.1-cp39-cp39-macosx_10_12_universal2.whl", hash = "sha256:81a6b377ea444336538638d31fdb39af6be1a043ca5e343fe18d0f17e098770b"}, + {file = "matplotlib-3.7.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:28506a03bd7f3fe59cd3cd4ceb2a8d8a2b1db41afede01f66c42561b9be7b4b7"}, + {file = "matplotlib-3.7.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8c587963b85ce41e0a8af53b9b2de8dddbf5ece4c34553f7bd9d066148dc719c"}, + {file = "matplotlib-3.7.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8bf26ade3ff0f27668989d98c8435ce9327d24cffb7f07d24ef609e33d582439"}, + {file = "matplotlib-3.7.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:def58098f96a05f90af7e92fd127d21a287068202aa43b2a93476170ebd99e87"}, + {file = "matplotlib-3.7.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f883a22a56a84dba3b588696a2b8a1ab0d2c3d41be53264115c71b0a942d8fdb"}, + {file = "matplotlib-3.7.1-cp39-cp39-win32.whl", hash = "sha256:4f99e1b234c30c1e9714610eb0c6d2f11809c9c78c984a613ae539ea2ad2eb4b"}, + {file = "matplotlib-3.7.1-cp39-cp39-win_amd64.whl", hash = "sha256:3ba2af245e36990facf67fde840a760128ddd71210b2ab6406e640188d69d136"}, + {file = "matplotlib-3.7.1-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:3032884084f541163f295db8a6536e0abb0db464008fadca6c98aaf84ccf4717"}, + {file = "matplotlib-3.7.1-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3a2cb34336110e0ed8bb4f650e817eed61fa064acbefeb3591f1b33e3a84fd96"}, + {file = "matplotlib-3.7.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b867e2f952ed592237a1828f027d332d8ee219ad722345b79a001f49df0936eb"}, + {file = "matplotlib-3.7.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:57bfb8c8ea253be947ccb2bc2d1bb3862c2bccc662ad1b4626e1f5e004557042"}, + {file = "matplotlib-3.7.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:438196cdf5dc8d39b50a45cb6e3f6274edbcf2254f85fa9b895bf85851c3a613"}, + {file = "matplotlib-3.7.1-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:21e9cff1a58d42e74d01153360de92b326708fb205250150018a52c70f43c290"}, + {file = "matplotlib-3.7.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75d4725d70b7c03e082bbb8a34639ede17f333d7247f56caceb3801cb6ff703d"}, + {file = "matplotlib-3.7.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:97cc368a7268141afb5690760921765ed34867ffb9655dd325ed207af85c7529"}, + {file = "matplotlib-3.7.1.tar.gz", hash = "sha256:7b73305f25eab4541bd7ee0b96d87e53ae9c9f1823be5659b806cd85786fe882"}, +] + +[package.dependencies] +contourpy = ">=1.0.1" +cycler = ">=0.10" +fonttools = ">=4.22.0" +kiwisolver = ">=1.0.1" +numpy = ">=1.20" +packaging = ">=20.0" +pillow = ">=6.2.0" +pyparsing = ">=2.3.1" +python-dateutil = ">=2.7" + +[[package]] +name = "matplotlib-inline" +version = "0.1.6" +description = "Inline Matplotlib backend for Jupyter" +category = "dev" +optional = false +python-versions = ">=3.5" +files = [ + {file = "matplotlib-inline-0.1.6.tar.gz", hash = "sha256:f887e5f10ba98e8d2b150ddcf4702c1e5f8b3a20005eb0f74bfdbd360ee6f304"}, + {file = "matplotlib_inline-0.1.6-py3-none-any.whl", hash = "sha256:f1f41aab5328aa5aaea9b16d083b128102f8712542f819fe7e6a420ff581b311"}, +] + +[package.dependencies] +traitlets = "*" + +[[package]] +name = "mccabe" +version = "0.7.0" +description = "McCabe checker, plugin for flake8" +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ + {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, + {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, +] + +[[package]] +name = "mdurl" +version = "0.1.2" +description = "Markdown URL utilities" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, + {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, +] + +[[package]] +name = "mesh-to-sdf" +version = "0.0.14" +description = "Calculate signed distance fields for arbitrary meshes" +category = "main" +optional = false +python-versions = ">=3.5" +files = [] +develop = false + +[package.dependencies] +pyopengl = "*" +pyrender = "*" +scikit-image = "*" +scikit-learn = "*" + +[package.source] +type = "git" +url = "https://github.com/pbsds/mesh_to_sdf" +reference = "no_flip_normals" +resolved_reference = "c5e9a53425108008065c66f927ffe68d9c01453e" + +[[package]] +name = "methodtools" +version = "0.4.7" +description = "Expand standard functools to methods" +category = "main" +optional = false +python-versions = "*" +files = [ + {file = "methodtools-0.4.7.tar.gz", hash = "sha256:e213439dd64cfe60213f7015da6efe5dd4003fd89376db3baa09fe13ec2bb0ba"}, +] + +[package.dependencies] +wirerope = ">=0.4.7" + +[package.extras] +doc = ["sphinx"] +test = ["functools32 (>=3.2.3-2)", "pytest (>=4.6.7)", "pytest-cov (>=2.6.1)"] + +[[package]] +name = "mistune" +version = "0.8.4" +description = "The fastest markdown parser in pure Python" +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "mistune-0.8.4-py2.py3-none-any.whl", hash = "sha256:88a1051873018da288eee8538d476dffe1262495144b33ecb586c4ab266bb8d4"}, + {file = "mistune-0.8.4.tar.gz", hash = "sha256:59a3429db53c50b5c6bcc8a07f8848cb00d7dc8bdb431a4ab41920d201d4756e"}, +] + +[[package]] +name = "more-itertools" +version = "9.1.0" +description = "More routines for operating on iterables, beyond itertools" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "more-itertools-9.1.0.tar.gz", hash = "sha256:cabaa341ad0389ea83c17a94566a53ae4c9d07349861ecb14dc6d0345cf9ac5d"}, + {file = "more_itertools-9.1.0-py3-none-any.whl", hash = "sha256:d2bc7f02446e86a68911e58ded76d6561eea00cddfb2a91e7019bbb586c799f3"}, +] + +[[package]] +name = "mpmath" +version = "1.3.0" +description = "Python library for arbitrary-precision floating-point arithmetic" +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "mpmath-1.3.0-py3-none-any.whl", hash = "sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c"}, + {file = "mpmath-1.3.0.tar.gz", hash = "sha256:7a28eb2a9774d00c7bc92411c19a89209d5da7c4c9a9e227be8330a23a25b91f"}, +] + +[package.extras] +develop = ["codecov", "pycodestyle", "pytest (>=4.6)", "pytest-cov", "wheel"] +docs = ["sphinx"] +gmpy = ["gmpy2 (>=2.1.0a4)"] +tests = ["pytest (>=4.6)"] + +[[package]] +name = "multidict" +version = "6.0.4" +description = "multidict implementation" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "multidict-6.0.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0b1a97283e0c85772d613878028fec909f003993e1007eafa715b24b377cb9b8"}, + {file = "multidict-6.0.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:eeb6dcc05e911516ae3d1f207d4b0520d07f54484c49dfc294d6e7d63b734171"}, + {file = "multidict-6.0.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d6d635d5209b82a3492508cf5b365f3446afb65ae7ebd755e70e18f287b0adf7"}, + {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c048099e4c9e9d615545e2001d3d8a4380bd403e1a0578734e0d31703d1b0c0b"}, + {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ea20853c6dbbb53ed34cb4d080382169b6f4554d394015f1bef35e881bf83547"}, + {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:16d232d4e5396c2efbbf4f6d4df89bfa905eb0d4dc5b3549d872ab898451f569"}, + {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:36c63aaa167f6c6b04ef2c85704e93af16c11d20de1d133e39de6a0e84582a93"}, + {file = "multidict-6.0.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:64bdf1086b6043bf519869678f5f2757f473dee970d7abf6da91ec00acb9cb98"}, + {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:43644e38f42e3af682690876cff722d301ac585c5b9e1eacc013b7a3f7b696a0"}, + {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:7582a1d1030e15422262de9f58711774e02fa80df0d1578995c76214f6954988"}, + {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:ddff9c4e225a63a5afab9dd15590432c22e8057e1a9a13d28ed128ecf047bbdc"}, + {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:ee2a1ece51b9b9e7752e742cfb661d2a29e7bcdba2d27e66e28a99f1890e4fa0"}, + {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a2e4369eb3d47d2034032a26c7a80fcb21a2cb22e1173d761a162f11e562caa5"}, + {file = "multidict-6.0.4-cp310-cp310-win32.whl", hash = "sha256:574b7eae1ab267e5f8285f0fe881f17efe4b98c39a40858247720935b893bba8"}, + {file = "multidict-6.0.4-cp310-cp310-win_amd64.whl", hash = "sha256:4dcbb0906e38440fa3e325df2359ac6cb043df8e58c965bb45f4e406ecb162cc"}, + {file = "multidict-6.0.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0dfad7a5a1e39c53ed00d2dd0c2e36aed4650936dc18fd9a1826a5ae1cad6f03"}, + {file = "multidict-6.0.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:64da238a09d6039e3bd39bb3aee9c21a5e34f28bfa5aa22518581f910ff94af3"}, + {file = "multidict-6.0.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ff959bee35038c4624250473988b24f846cbeb2c6639de3602c073f10410ceba"}, + {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:01a3a55bd90018c9c080fbb0b9f4891db37d148a0a18722b42f94694f8b6d4c9"}, + {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c5cb09abb18c1ea940fb99360ea0396f34d46566f157122c92dfa069d3e0e982"}, + {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:666daae833559deb2d609afa4490b85830ab0dfca811a98b70a205621a6109fe"}, + {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11bdf3f5e1518b24530b8241529d2050014c884cf18b6fc69c0c2b30ca248710"}, + {file = "multidict-6.0.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7d18748f2d30f94f498e852c67d61261c643b349b9d2a581131725595c45ec6c"}, + {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:458f37be2d9e4c95e2d8866a851663cbc76e865b78395090786f6cd9b3bbf4f4"}, + {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:b1a2eeedcead3a41694130495593a559a668f382eee0727352b9a41e1c45759a"}, + {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:7d6ae9d593ef8641544d6263c7fa6408cc90370c8cb2bbb65f8d43e5b0351d9c"}, + {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:5979b5632c3e3534e42ca6ff856bb24b2e3071b37861c2c727ce220d80eee9ed"}, + {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:dcfe792765fab89c365123c81046ad4103fcabbc4f56d1c1997e6715e8015461"}, + {file = "multidict-6.0.4-cp311-cp311-win32.whl", hash = "sha256:3601a3cece3819534b11d4efc1eb76047488fddd0c85a3948099d5da4d504636"}, + {file = "multidict-6.0.4-cp311-cp311-win_amd64.whl", hash = "sha256:81a4f0b34bd92df3da93315c6a59034df95866014ac08535fc819f043bfd51f0"}, + {file = "multidict-6.0.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:67040058f37a2a51ed8ea8f6b0e6ee5bd78ca67f169ce6122f3e2ec80dfe9b78"}, + {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:853888594621e6604c978ce2a0444a1e6e70c8d253ab65ba11657659dcc9100f"}, + {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:39ff62e7d0f26c248b15e364517a72932a611a9b75f35b45be078d81bdb86603"}, + {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:af048912e045a2dc732847d33821a9d84ba553f5c5f028adbd364dd4765092ac"}, + {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1e8b901e607795ec06c9e42530788c45ac21ef3aaa11dbd0c69de543bfb79a9"}, + {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:62501642008a8b9871ddfccbf83e4222cf8ac0d5aeedf73da36153ef2ec222d2"}, + {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:99b76c052e9f1bc0721f7541e5e8c05db3941eb9ebe7b8553c625ef88d6eefde"}, + {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:509eac6cf09c794aa27bcacfd4d62c885cce62bef7b2c3e8b2e49d365b5003fe"}, + {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:21a12c4eb6ddc9952c415f24eef97e3e55ba3af61f67c7bc388dcdec1404a067"}, + {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:5cad9430ab3e2e4fa4a2ef4450f548768400a2ac635841bc2a56a2052cdbeb87"}, + {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ab55edc2e84460694295f401215f4a58597f8f7c9466faec545093045476327d"}, + {file = "multidict-6.0.4-cp37-cp37m-win32.whl", hash = "sha256:5a4dcf02b908c3b8b17a45fb0f15b695bf117a67b76b7ad18b73cf8e92608775"}, + {file = "multidict-6.0.4-cp37-cp37m-win_amd64.whl", hash = "sha256:6ed5f161328b7df384d71b07317f4d8656434e34591f20552c7bcef27b0ab88e"}, + {file = "multidict-6.0.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5fc1b16f586f049820c5c5b17bb4ee7583092fa0d1c4e28b5239181ff9532e0c"}, + {file = "multidict-6.0.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1502e24330eb681bdaa3eb70d6358e818e8e8f908a22a1851dfd4e15bc2f8161"}, + {file = "multidict-6.0.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b692f419760c0e65d060959df05f2a531945af31fda0c8a3b3195d4efd06de11"}, + {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45e1ecb0379bfaab5eef059f50115b54571acfbe422a14f668fc8c27ba410e7e"}, + {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ddd3915998d93fbcd2566ddf9cf62cdb35c9e093075f862935573d265cf8f65d"}, + {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:59d43b61c59d82f2effb39a93c48b845efe23a3852d201ed2d24ba830d0b4cf2"}, + {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc8e1d0c705233c5dd0c5e6460fbad7827d5d36f310a0fadfd45cc3029762258"}, + {file = "multidict-6.0.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d6aa0418fcc838522256761b3415822626f866758ee0bc6632c9486b179d0b52"}, + {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6748717bb10339c4760c1e63da040f5f29f5ed6e59d76daee30305894069a660"}, + {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:4d1a3d7ef5e96b1c9e92f973e43aa5e5b96c659c9bc3124acbbd81b0b9c8a951"}, + {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4372381634485bec7e46718edc71528024fcdc6f835baefe517b34a33c731d60"}, + {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:fc35cb4676846ef752816d5be2193a1e8367b4c1397b74a565a9d0389c433a1d"}, + {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:4b9d9e4e2b37daddb5c23ea33a3417901fa7c7b3dee2d855f63ee67a0b21e5b1"}, + {file = "multidict-6.0.4-cp38-cp38-win32.whl", hash = "sha256:e41b7e2b59679edfa309e8db64fdf22399eec4b0b24694e1b2104fb789207779"}, + {file = "multidict-6.0.4-cp38-cp38-win_amd64.whl", hash = "sha256:d6c254ba6e45d8e72739281ebc46ea5eb5f101234f3ce171f0e9f5cc86991480"}, + {file = "multidict-6.0.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:16ab77bbeb596e14212e7bab8429f24c1579234a3a462105cda4a66904998664"}, + {file = "multidict-6.0.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bc779e9e6f7fda81b3f9aa58e3a6091d49ad528b11ed19f6621408806204ad35"}, + {file = "multidict-6.0.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4ceef517eca3e03c1cceb22030a3e39cb399ac86bff4e426d4fc6ae49052cc60"}, + {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:281af09f488903fde97923c7744bb001a9b23b039a909460d0f14edc7bf59706"}, + {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:52f2dffc8acaba9a2f27174c41c9e57f60b907bb9f096b36b1a1f3be71c6284d"}, + {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b41156839806aecb3641f3208c0dafd3ac7775b9c4c422d82ee2a45c34ba81ca"}, + {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d5e3fc56f88cc98ef8139255cf8cd63eb2c586531e43310ff859d6bb3a6b51f1"}, + {file = "multidict-6.0.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8316a77808c501004802f9beebde51c9f857054a0c871bd6da8280e718444449"}, + {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f70b98cd94886b49d91170ef23ec5c0e8ebb6f242d734ed7ed677b24d50c82cf"}, + {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bf6774e60d67a9efe02b3616fee22441d86fab4c6d335f9d2051d19d90a40063"}, + {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:e69924bfcdda39b722ef4d9aa762b2dd38e4632b3641b1d9a57ca9cd18f2f83a"}, + {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:6b181d8c23da913d4ff585afd1155a0e1194c0b50c54fcfe286f70cdaf2b7176"}, + {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:52509b5be062d9eafc8170e53026fbc54cf3b32759a23d07fd935fb04fc22d95"}, + {file = "multidict-6.0.4-cp39-cp39-win32.whl", hash = "sha256:27c523fbfbdfd19c6867af7346332b62b586eed663887392cff78d614f9ec313"}, + {file = "multidict-6.0.4-cp39-cp39-win_amd64.whl", hash = "sha256:33029f5734336aa0d4c0384525da0387ef89148dc7191aae00ca5fb23d7aafc2"}, + {file = "multidict-6.0.4.tar.gz", hash = "sha256:3666906492efb76453c0e7b97f2cf459b0682e7402c0489a95484965dbc1da49"}, +] + +[[package]] +name = "munch" +version = "2.5.0" +description = "A dot-accessible dictionary (a la JavaScript objects)" +category = "main" +optional = false +python-versions = "*" +files = [ + {file = "munch-2.5.0-py2.py3-none-any.whl", hash = "sha256:6f44af89a2ce4ed04ff8de41f70b226b984db10a91dcc7b9ac2efc1c77022fdd"}, + {file = "munch-2.5.0.tar.gz", hash = "sha256:2d735f6f24d4dba3417fa448cae40c6e896ec1fdab6cdb5e6510999758a4dbd2"}, +] + +[package.dependencies] +six = "*" + +[package.extras] +testing = ["astroid (>=1.5.3,<1.6.0)", "astroid (>=2.0)", "coverage", "pylint (>=1.7.2,<1.8.0)", "pylint (>=2.3.1,<2.4.0)", "pytest"] +yaml = ["PyYAML (>=5.1.0)"] + +[[package]] +name = "nbclassic" +version = "0.5.3" +description = "Jupyter Notebook as a Jupyter Server extension." +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "nbclassic-0.5.3-py3-none-any.whl", hash = "sha256:e849277872d9ffd8fe4b39a8038d01ba82d6a1def9ce11b1b3c26c9546ed5131"}, + {file = "nbclassic-0.5.3.tar.gz", hash = "sha256:889772a7ba524eb781d2901f396540bcad41151e1f7e043f12ebc14a6540d342"}, +] + +[package.dependencies] +argon2-cffi = "*" +ipykernel = "*" +ipython-genutils = "*" +jinja2 = "*" +jupyter-client = ">=6.1.1" +jupyter-core = ">=4.6.1" +jupyter-server = ">=1.8" +nbconvert = ">=5" +nbformat = "*" +nest-asyncio = ">=1.5" +notebook-shim = ">=0.1.0" +prometheus-client = "*" +pyzmq = ">=17" +Send2Trash = ">=1.8.0" +terminado = ">=0.8.3" +tornado = ">=6.1" +traitlets = ">=4.2.1" + +[package.extras] +docs = ["myst-parser", "nbsphinx", "sphinx", "sphinx-rtd-theme", "sphinxcontrib-github-alt"] +json-logging = ["json-logging"] +test = ["coverage", "nbval", "pytest", "pytest-cov", "pytest-jupyter", "pytest-playwright", "pytest-tornasync", "requests", "requests-unixsocket", "testpath"] + +[[package]] +name = "nbclient" +version = "0.7.2" +description = "A client library for executing notebooks. Formerly nbconvert's ExecutePreprocessor." +category = "dev" +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "nbclient-0.7.2-py3-none-any.whl", hash = "sha256:d97ac6257de2794f5397609df754fcbca1a603e94e924eb9b99787c031ae2e7c"}, + {file = "nbclient-0.7.2.tar.gz", hash = "sha256:884a3f4a8c4fc24bb9302f263e0af47d97f0d01fe11ba714171b320c8ac09547"}, +] + +[package.dependencies] +jupyter-client = ">=6.1.12" +jupyter-core = ">=4.12,<5.0.0 || >=5.1.0" +nbformat = ">=5.1" +traitlets = ">=5.3" + +[package.extras] +dev = ["pre-commit"] +docs = ["autodoc-traits", "mock", "moto", "myst-parser", "nbclient[test]", "sphinx (>=1.7)", "sphinx-book-theme"] +test = ["ipykernel", "ipython", "ipywidgets", "nbconvert (>=7.0.0)", "pytest (>=7.0)", "pytest-asyncio", "pytest-cov (>=4.0)", "testpath", "xmltodict"] + +[[package]] +name = "nbconvert" +version = "6.5.0" +description = "Converting Jupyter Notebooks" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "nbconvert-6.5.0-py3-none-any.whl", hash = "sha256:c56dd0b8978a1811a5654f74c727ff16ca87dd5a43abd435a1c49b840fcd8360"}, + {file = "nbconvert-6.5.0.tar.gz", hash = "sha256:223e46e27abe8596b8aed54301fadbba433b7ffea8196a68fd7b1ff509eee99d"}, +] + +[package.dependencies] +beautifulsoup4 = "*" +bleach = "*" +defusedxml = "*" +entrypoints = ">=0.2.2" +jinja2 = ">=3.0" +jupyter-core = ">=4.7" +jupyterlab-pygments = "*" +MarkupSafe = ">=2.0" +mistune = ">=0.8.1,<2" +nbclient = ">=0.5.0" +nbformat = ">=5.1" +packaging = "*" +pandocfilters = ">=1.4.1" +pygments = ">=2.4.1" +tinycss2 = "*" +traitlets = ">=5.0" + +[package.extras] +all = ["ipykernel", "ipython", "ipywidgets (>=7)", "nbsphinx (>=0.2.12)", "pre-commit", "pyppeteer (>=1,<1.1)", "pytest", "pytest-cov", "pytest-dependency", "sphinx (>=1.5.1)", "sphinx-rtd-theme", "tornado (>=6.1)"] +docs = ["ipython", "nbsphinx (>=0.2.12)", "sphinx (>=1.5.1)", "sphinx-rtd-theme"] +serve = ["tornado (>=6.1)"] +test = ["ipykernel", "ipywidgets (>=7)", "pre-commit", "pyppeteer (>=1,<1.1)", "pytest", "pytest-cov", "pytest-dependency"] +webpdf = ["pyppeteer (>=1,<1.1)"] + +[[package]] +name = "nbformat" +version = "5.8.0" +description = "The Jupyter Notebook format" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "nbformat-5.8.0-py3-none-any.whl", hash = "sha256:d910082bd3e0bffcf07eabf3683ed7dda0727a326c446eeb2922abe102e65162"}, + {file = "nbformat-5.8.0.tar.gz", hash = "sha256:46dac64c781f1c34dfd8acba16547024110348f9fc7eab0f31981c2a3dc48d1f"}, +] + +[package.dependencies] +fastjsonschema = "*" +jsonschema = ">=2.6" +jupyter-core = "*" +traitlets = ">=5.1" + +[package.extras] +docs = ["myst-parser", "pydata-sphinx-theme", "sphinx", "sphinxcontrib-github-alt", "sphinxcontrib-spelling"] +test = ["pep440", "pre-commit", "pytest", "testpath"] + +[[package]] +name = "nest-asyncio" +version = "1.5.6" +description = "Patch asyncio to allow nested event loops" +category = "dev" +optional = false +python-versions = ">=3.5" +files = [ + {file = "nest_asyncio-1.5.6-py3-none-any.whl", hash = "sha256:b9a953fb40dceaa587d109609098db21900182b16440652454a146cffb06e8b8"}, + {file = "nest_asyncio-1.5.6.tar.gz", hash = "sha256:d267cc1ff794403f7df692964d1d2a3fa9418ffea2a3f6859a439ff482fef290"}, +] + +[[package]] +name = "networkx" +version = "3.0" +description = "Python package for creating and manipulating graphs and networks" +category = "main" +optional = false +python-versions = ">=3.8" +files = [ + {file = "networkx-3.0-py3-none-any.whl", hash = "sha256:58058d66b1818043527244fab9d41a51fcd7dcc271748015f3c181b8a90c8e2e"}, + {file = "networkx-3.0.tar.gz", hash = "sha256:9a9992345353618ae98339c2b63d8201c381c2944f38a2ab49cb45a4c667e412"}, +] + +[package.extras] +default = ["matplotlib (>=3.4)", "numpy (>=1.20)", "pandas (>=1.3)", "scipy (>=1.8)"] +developer = ["mypy (>=0.991)", "pre-commit (>=2.20)"] +doc = ["nb2plots (>=0.6)", "numpydoc (>=1.5)", "pillow (>=9.2)", "pydata-sphinx-theme (>=0.11)", "sphinx (==5.2.3)", "sphinx-gallery (>=0.11)", "texext (>=0.6.7)"] +extra = ["lxml (>=4.6)", "pydot (>=1.4.2)", "pygraphviz (>=1.10)", "sympy (>=1.10)"] +test = ["codecov (>=2.1)", "pytest (>=7.2)", "pytest-cov (>=4.0)"] + +[[package]] +name = "notebook" +version = "6.5.3" +description = "A web-based notebook environment for interactive computing" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "notebook-6.5.3-py3-none-any.whl", hash = "sha256:50a334ad9d60b30cb759405168ef6fc3d60350ab5439fb1631544bb09dcb2cce"}, + {file = "notebook-6.5.3.tar.gz", hash = "sha256:b12bee3292211d85dd7e588a790ddce30cb3e8fbcfa1e803522a207f60819e05"}, +] + +[package.dependencies] +argon2-cffi = "*" +ipykernel = "*" +ipython-genutils = "*" +jinja2 = "*" +jupyter-client = ">=5.3.4" +jupyter-core = ">=4.6.1" +nbclassic = ">=0.4.7" +nbconvert = ">=5" +nbformat = "*" +nest-asyncio = ">=1.5" +prometheus-client = "*" +pyzmq = ">=17" +Send2Trash = ">=1.8.0" +terminado = ">=0.8.3" +tornado = ">=6.1" +traitlets = ">=4.2.1" + +[package.extras] +docs = ["myst-parser", "nbsphinx", "sphinx", "sphinx-rtd-theme", "sphinxcontrib-github-alt"] +json-logging = ["json-logging"] +test = ["coverage", "nbval", "pytest", "pytest-cov", "requests", "requests-unixsocket", "selenium (==4.1.5)", "testpath"] + +[[package]] +name = "notebook-shim" +version = "0.2.2" +description = "A shim layer for notebook traits and config" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "notebook_shim-0.2.2-py3-none-any.whl", hash = "sha256:9c6c30f74c4fbea6fce55c1be58e7fd0409b1c681b075dcedceb005db5026949"}, + {file = "notebook_shim-0.2.2.tar.gz", hash = "sha256:090e0baf9a5582ff59b607af523ca2db68ff216da0c69956b62cab2ef4fc9c3f"}, +] + +[package.dependencies] +jupyter-server = ">=1.8,<3" + +[package.extras] +test = ["pytest", "pytest-console-scripts", "pytest-tornasync"] + +[[package]] +name = "numba" +version = "0.56.4" +description = "compiling Python code using LLVM" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "numba-0.56.4-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:9f62672145f8669ec08762895fe85f4cf0ead08ce3164667f2b94b2f62ab23c3"}, + {file = "numba-0.56.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c602d015478b7958408d788ba00a50272649c5186ea8baa6cf71d4a1c761bba1"}, + {file = "numba-0.56.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:85dbaed7a05ff96492b69a8900c5ba605551afb9b27774f7f10511095451137c"}, + {file = "numba-0.56.4-cp310-cp310-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:f4cfc3a19d1e26448032049c79fc60331b104f694cf570a9e94f4e2c9d0932bb"}, + {file = "numba-0.56.4-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4e08e203b163ace08bad500b0c16f6092b1eb34fd1fce4feaf31a67a3a5ecf3b"}, + {file = "numba-0.56.4-cp310-cp310-win32.whl", hash = "sha256:0611e6d3eebe4cb903f1a836ffdb2bda8d18482bcd0a0dcc56e79e2aa3fefef5"}, + {file = "numba-0.56.4-cp310-cp310-win_amd64.whl", hash = "sha256:fbfb45e7b297749029cb28694abf437a78695a100e7c2033983d69f0ba2698d4"}, + {file = "numba-0.56.4-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:3cb1a07a082a61df80a468f232e452d818f5ae254b40c26390054e4e868556e0"}, + {file = "numba-0.56.4-cp37-cp37m-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d69ad934e13c15684e7887100a8f5f0f61d7a8e57e0fd29d9993210089a5b531"}, + {file = "numba-0.56.4-cp37-cp37m-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:dbcc847bac2d225265d054993a7f910fda66e73d6662fe7156452cac0325b073"}, + {file = "numba-0.56.4-cp37-cp37m-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8a95ca9cc77ea4571081f6594e08bd272b66060634b8324e99cd1843020364f9"}, + {file = "numba-0.56.4-cp37-cp37m-win32.whl", hash = "sha256:fcdf84ba3ed8124eb7234adfbb8792f311991cbf8aed1cad4b1b1a7ee08380c1"}, + {file = "numba-0.56.4-cp37-cp37m-win_amd64.whl", hash = "sha256:42f9e1be942b215df7e6cc9948cf9c15bb8170acc8286c063a9e57994ef82fd1"}, + {file = "numba-0.56.4-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:553da2ce74e8862e18a72a209ed3b6d2924403bdd0fb341fa891c6455545ba7c"}, + {file = "numba-0.56.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4373da9757049db7c90591e9ec55a2e97b2b36ba7ae3bf9c956a513374077470"}, + {file = "numba-0.56.4-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3a993349b90569518739009d8f4b523dfedd7e0049e6838c0e17435c3e70dcc4"}, + {file = "numba-0.56.4-cp38-cp38-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:720886b852a2d62619ae3900fe71f1852c62db4f287d0c275a60219e1643fc04"}, + {file = "numba-0.56.4-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e64d338b504c9394a4a34942df4627e1e6cb07396ee3b49fe7b8d6420aa5104f"}, + {file = "numba-0.56.4-cp38-cp38-win32.whl", hash = "sha256:03fe94cd31e96185cce2fae005334a8cc712fc2ba7756e52dff8c9400718173f"}, + {file = "numba-0.56.4-cp38-cp38-win_amd64.whl", hash = "sha256:91f021145a8081f881996818474ef737800bcc613ffb1e618a655725a0f9e246"}, + {file = "numba-0.56.4-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:d0ae9270a7a5cc0ede63cd234b4ff1ce166c7a749b91dbbf45e0000c56d3eade"}, + {file = "numba-0.56.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c75e8a5f810ce80a0cfad6e74ee94f9fde9b40c81312949bf356b7304ef20740"}, + {file = "numba-0.56.4-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a12ef323c0f2101529d455cfde7f4135eaa147bad17afe10b48634f796d96abd"}, + {file = "numba-0.56.4-cp39-cp39-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:03634579d10a6129181129de293dd6b5eaabee86881369d24d63f8fe352dd6cb"}, + {file = "numba-0.56.4-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0240f9026b015e336069329839208ebd70ec34ae5bfbf402e4fcc8e06197528e"}, + {file = "numba-0.56.4-cp39-cp39-win32.whl", hash = "sha256:14dbbabf6ffcd96ee2ac827389afa59a70ffa9f089576500434c34abf9b054a4"}, + {file = "numba-0.56.4-cp39-cp39-win_amd64.whl", hash = "sha256:0da583c532cd72feefd8e551435747e0e0fbb3c0530357e6845fcc11e38d6aea"}, + {file = "numba-0.56.4.tar.gz", hash = "sha256:32d9fef412c81483d7efe0ceb6cf4d3310fde8b624a9cecca00f790573ac96ee"}, +] + +[package.dependencies] +llvmlite = ">=0.39.0dev0,<0.40" +numpy = ">=1.18,<1.24" +setuptools = "*" + +[[package]] +name = "numpy" +version = "1.23.5" +description = "NumPy is the fundamental package for array computing with Python." +category = "main" +optional = false +python-versions = ">=3.8" +files = [ + {file = "numpy-1.23.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9c88793f78fca17da0145455f0d7826bcb9f37da4764af27ac945488116efe63"}, + {file = "numpy-1.23.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e9f4c4e51567b616be64e05d517c79a8a22f3606499941d97bb76f2ca59f982d"}, + {file = "numpy-1.23.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7903ba8ab592b82014713c491f6c5d3a1cde5b4a3bf116404e08f5b52f6daf43"}, + {file = "numpy-1.23.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e05b1c973a9f858c74367553e236f287e749465f773328c8ef31abe18f691e1"}, + {file = "numpy-1.23.5-cp310-cp310-win32.whl", hash = "sha256:522e26bbf6377e4d76403826ed689c295b0b238f46c28a7251ab94716da0b280"}, + {file = "numpy-1.23.5-cp310-cp310-win_amd64.whl", hash = "sha256:dbee87b469018961d1ad79b1a5d50c0ae850000b639bcb1b694e9981083243b6"}, + {file = "numpy-1.23.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ce571367b6dfe60af04e04a1834ca2dc5f46004ac1cc756fb95319f64c095a96"}, + {file = "numpy-1.23.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:56e454c7833e94ec9769fa0f86e6ff8e42ee38ce0ce1fa4cbb747ea7e06d56aa"}, + {file = "numpy-1.23.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5039f55555e1eab31124a5768898c9e22c25a65c1e0037f4d7c495a45778c9f2"}, + {file = "numpy-1.23.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58f545efd1108e647604a1b5aa809591ccd2540f468a880bedb97247e72db387"}, + {file = "numpy-1.23.5-cp311-cp311-win32.whl", hash = "sha256:b2a9ab7c279c91974f756c84c365a669a887efa287365a8e2c418f8b3ba73fb0"}, + {file = "numpy-1.23.5-cp311-cp311-win_amd64.whl", hash = "sha256:0cbe9848fad08baf71de1a39e12d1b6310f1d5b2d0ea4de051058e6e1076852d"}, + {file = "numpy-1.23.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f063b69b090c9d918f9df0a12116029e274daf0181df392839661c4c7ec9018a"}, + {file = "numpy-1.23.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:0aaee12d8883552fadfc41e96b4c82ee7d794949e2a7c3b3a7201e968c7ecab9"}, + {file = "numpy-1.23.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:92c8c1e89a1f5028a4c6d9e3ccbe311b6ba53694811269b992c0b224269e2398"}, + {file = "numpy-1.23.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d208a0f8729f3fb790ed18a003f3a57895b989b40ea4dce4717e9cf4af62c6bb"}, + {file = "numpy-1.23.5-cp38-cp38-win32.whl", hash = "sha256:06005a2ef6014e9956c09ba07654f9837d9e26696a0470e42beedadb78c11b07"}, + {file = "numpy-1.23.5-cp38-cp38-win_amd64.whl", hash = "sha256:ca51fcfcc5f9354c45f400059e88bc09215fb71a48d3768fb80e357f3b457e1e"}, + {file = "numpy-1.23.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8969bfd28e85c81f3f94eb4a66bc2cf1dbdc5c18efc320af34bffc54d6b1e38f"}, + {file = "numpy-1.23.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a7ac231a08bb37f852849bbb387a20a57574a97cfc7b6cabb488a4fc8be176de"}, + {file = "numpy-1.23.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf837dc63ba5c06dc8797c398db1e223a466c7ece27a1f7b5232ba3466aafe3d"}, + {file = "numpy-1.23.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:33161613d2269025873025b33e879825ec7b1d831317e68f4f2f0f84ed14c719"}, + {file = "numpy-1.23.5-cp39-cp39-win32.whl", hash = "sha256:af1da88f6bc3d2338ebbf0e22fe487821ea4d8e89053e25fa59d1d79786e7481"}, + {file = "numpy-1.23.5-cp39-cp39-win_amd64.whl", hash = "sha256:09b7847f7e83ca37c6e627682f145856de331049013853f344f37b0c9690e3df"}, + {file = "numpy-1.23.5-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:abdde9f795cf292fb9651ed48185503a2ff29be87770c3b8e2a14b0cd7aa16f8"}, + {file = "numpy-1.23.5-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f9a909a8bae284d46bbfdefbdd4a262ba19d3bc9921b1e76126b1d21c3c34135"}, + {file = "numpy-1.23.5-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:01dd17cbb340bf0fc23981e52e1d18a9d4050792e8fb8363cecbf066a84b827d"}, + {file = "numpy-1.23.5.tar.gz", hash = "sha256:1b1766d6f397c18153d40015ddfc79ddb715cabadc04d2d228d4e5a8bc4ded1a"}, +] + +[[package]] +name = "nvidia-cublas-cu11" +version = "11.10.3.66" +description = "CUBLAS native runtime libraries" +category = "main" +optional = false +python-versions = ">=3" +files = [ + {file = "nvidia_cublas_cu11-11.10.3.66-py3-none-manylinux1_x86_64.whl", hash = "sha256:d32e4d75f94ddfb93ea0a5dda08389bcc65d8916a25cb9f37ac89edaeed3bded"}, + {file = "nvidia_cublas_cu11-11.10.3.66-py3-none-win_amd64.whl", hash = "sha256:8ac17ba6ade3ed56ab898a036f9ae0756f1e81052a317bf98f8c6d18dc3ae49e"}, +] + +[package.dependencies] +setuptools = "*" +wheel = "*" + +[[package]] +name = "nvidia-cuda-nvrtc-cu11" +version = "11.7.99" +description = "NVRTC native runtime libraries" +category = "main" +optional = false +python-versions = ">=3" +files = [ + {file = "nvidia_cuda_nvrtc_cu11-11.7.99-2-py3-none-manylinux1_x86_64.whl", hash = "sha256:9f1562822ea264b7e34ed5930567e89242d266448e936b85bc97a3370feabb03"}, + {file = "nvidia_cuda_nvrtc_cu11-11.7.99-py3-none-manylinux1_x86_64.whl", hash = "sha256:f7d9610d9b7c331fa0da2d1b2858a4a8315e6d49765091d28711c8946e7425e7"}, + {file = "nvidia_cuda_nvrtc_cu11-11.7.99-py3-none-win_amd64.whl", hash = "sha256:f2effeb1309bdd1b3854fc9b17eaf997808f8b25968ce0c7070945c4265d64a3"}, +] + +[package.dependencies] +setuptools = "*" +wheel = "*" + +[[package]] +name = "nvidia-cuda-runtime-cu11" +version = "11.7.99" +description = "CUDA Runtime native Libraries" +category = "main" +optional = false +python-versions = ">=3" +files = [ + {file = "nvidia_cuda_runtime_cu11-11.7.99-py3-none-manylinux1_x86_64.whl", hash = "sha256:cc768314ae58d2641f07eac350f40f99dcb35719c4faff4bc458a7cd2b119e31"}, + {file = "nvidia_cuda_runtime_cu11-11.7.99-py3-none-win_amd64.whl", hash = "sha256:bc77fa59a7679310df9d5c70ab13c4e34c64ae2124dd1efd7e5474b71be125c7"}, +] + +[package.dependencies] +setuptools = "*" +wheel = "*" + +[[package]] +name = "nvidia-cudnn-cu11" +version = "8.5.0.96" +description = "cuDNN runtime libraries" +category = "main" +optional = false +python-versions = ">=3" +files = [ + {file = "nvidia_cudnn_cu11-8.5.0.96-2-py3-none-manylinux1_x86_64.whl", hash = "sha256:402f40adfc6f418f9dae9ab402e773cfed9beae52333f6d86ae3107a1b9527e7"}, + {file = "nvidia_cudnn_cu11-8.5.0.96-py3-none-manylinux1_x86_64.whl", hash = "sha256:71f8111eb830879ff2836db3cccf03bbd735df9b0d17cd93761732ac50a8a108"}, +] + +[package.dependencies] +setuptools = "*" +wheel = "*" + +[[package]] +name = "oauthlib" +version = "3.2.2" +description = "A generic, spec-compliant, thorough implementation of the OAuth request-signing logic" +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ + {file = "oauthlib-3.2.2-py3-none-any.whl", hash = "sha256:8139f29aac13e25d502680e9e19963e83f16838d48a0d71c287fe40e7067fbca"}, + {file = "oauthlib-3.2.2.tar.gz", hash = "sha256:9859c40929662bec5d64f34d01c99e093149682a3f38915dc0655d5a633dd918"}, +] + +[package.extras] +rsa = ["cryptography (>=3.0.0)"] +signals = ["blinker (>=1.4.0)"] +signedtoken = ["cryptography (>=3.0.0)", "pyjwt (>=2.0.0,<3)"] + +[[package]] +name = "ordered-set" +version = "4.1.0" +description = "An OrderedSet is a custom MutableSet that remembers its order, so that every" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "ordered-set-4.1.0.tar.gz", hash = "sha256:694a8e44c87657c59292ede72891eb91d34131f6531463aab3009191c77364a8"}, + {file = "ordered_set-4.1.0-py3-none-any.whl", hash = "sha256:046e1132c71fcf3330438a539928932caf51ddbc582496833e23de611de14562"}, +] + +[package.extras] +dev = ["black", "mypy", "pytest"] + +[[package]] +name = "packaging" +version = "23.0" +description = "Core utilities for Python packages" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "packaging-23.0-py3-none-any.whl", hash = "sha256:714ac14496c3e68c99c29b00845f7a2b85f3bb6f1078fd9f72fd20f0570002b2"}, + {file = "packaging-23.0.tar.gz", hash = "sha256:b6ad297f8907de0fa2fe1ccbd26fdaf387f5f47c7275fedf8cce89f99446cf97"}, +] + +[[package]] +name = "pandas" +version = "1.5.3" +description = "Powerful data structures for data analysis, time series, and statistics" +category = "main" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pandas-1.5.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3749077d86e3a2f0ed51367f30bf5b82e131cc0f14260c4d3e499186fccc4406"}, + {file = "pandas-1.5.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:972d8a45395f2a2d26733eb8d0f629b2f90bebe8e8eddbb8829b180c09639572"}, + {file = "pandas-1.5.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:50869a35cbb0f2e0cd5ec04b191e7b12ed688874bd05dd777c19b28cbea90996"}, + {file = "pandas-1.5.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c3ac844a0fe00bfaeb2c9b51ab1424e5c8744f89860b138434a363b1f620f354"}, + {file = "pandas-1.5.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a0a56cef15fd1586726dace5616db75ebcfec9179a3a55e78f72c5639fa2a23"}, + {file = "pandas-1.5.3-cp310-cp310-win_amd64.whl", hash = "sha256:478ff646ca42b20376e4ed3fa2e8d7341e8a63105586efe54fa2508ee087f328"}, + {file = "pandas-1.5.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6973549c01ca91ec96199e940495219c887ea815b2083722821f1d7abfa2b4dc"}, + {file = "pandas-1.5.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c39a8da13cede5adcd3be1182883aea1c925476f4e84b2807a46e2775306305d"}, + {file = "pandas-1.5.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f76d097d12c82a535fda9dfe5e8dd4127952b45fea9b0276cb30cca5ea313fbc"}, + {file = "pandas-1.5.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e474390e60ed609cec869b0da796ad94f420bb057d86784191eefc62b65819ae"}, + {file = "pandas-1.5.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5f2b952406a1588ad4cad5b3f55f520e82e902388a6d5a4a91baa8d38d23c7f6"}, + {file = "pandas-1.5.3-cp311-cp311-win_amd64.whl", hash = "sha256:bc4c368f42b551bf72fac35c5128963a171b40dce866fb066540eeaf46faa003"}, + {file = "pandas-1.5.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:14e45300521902689a81f3f41386dc86f19b8ba8dd5ac5a3c7010ef8d2932813"}, + {file = "pandas-1.5.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9842b6f4b8479e41968eced654487258ed81df7d1c9b7b870ceea24ed9459b31"}, + {file = "pandas-1.5.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:26d9c71772c7afb9d5046e6e9cf42d83dd147b5cf5bcb9d97252077118543792"}, + {file = "pandas-1.5.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5fbcb19d6fceb9e946b3e23258757c7b225ba450990d9ed63ccceeb8cae609f7"}, + {file = "pandas-1.5.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:565fa34a5434d38e9d250af3c12ff931abaf88050551d9fbcdfafca50d62babf"}, + {file = "pandas-1.5.3-cp38-cp38-win32.whl", hash = "sha256:87bd9c03da1ac870a6d2c8902a0e1fd4267ca00f13bc494c9e5a9020920e1d51"}, + {file = "pandas-1.5.3-cp38-cp38-win_amd64.whl", hash = "sha256:41179ce559943d83a9b4bbacb736b04c928b095b5f25dd2b7389eda08f46f373"}, + {file = "pandas-1.5.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c74a62747864ed568f5a82a49a23a8d7fe171d0c69038b38cedf0976831296fa"}, + {file = "pandas-1.5.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c4c00e0b0597c8e4f59e8d461f797e5d70b4d025880516a8261b2817c47759ee"}, + {file = "pandas-1.5.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a50d9a4336a9621cab7b8eb3fb11adb82de58f9b91d84c2cd526576b881a0c5a"}, + {file = "pandas-1.5.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd05f7783b3274aa206a1af06f0ceed3f9b412cf665b7247eacd83be41cf7bf0"}, + {file = "pandas-1.5.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f69c4029613de47816b1bb30ff5ac778686688751a5e9c99ad8c7031f6508e5"}, + {file = "pandas-1.5.3-cp39-cp39-win32.whl", hash = "sha256:7cec0bee9f294e5de5bbfc14d0573f65526071029d036b753ee6507d2a21480a"}, + {file = "pandas-1.5.3-cp39-cp39-win_amd64.whl", hash = "sha256:dfd681c5dc216037e0b0a2c821f5ed99ba9f03ebcf119c7dac0e9a7b960b9ec9"}, + {file = "pandas-1.5.3.tar.gz", hash = "sha256:74a3fd7e5a7ec052f183273dc7b0acd3a863edf7520f5d3a1765c04ffdb3b0b1"}, +] + +[package.dependencies] +numpy = {version = ">=1.21.0", markers = "python_version >= \"3.10\""} +python-dateutil = ">=2.8.1" +pytz = ">=2020.1" + +[package.extras] +test = ["hypothesis (>=5.5.3)", "pytest (>=6.0)", "pytest-xdist (>=1.31)"] + +[[package]] +name = "pandocfilters" +version = "1.5.0" +description = "Utilities for writing pandoc filters in python" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "pandocfilters-1.5.0-py2.py3-none-any.whl", hash = "sha256:33aae3f25fd1a026079f5d27bdd52496f0e0803b3469282162bafdcbdf6ef14f"}, + {file = "pandocfilters-1.5.0.tar.gz", hash = "sha256:0b679503337d233b4339a817bfc8c50064e2eff681314376a47cb582305a7a38"}, +] + +[[package]] +name = "papermill" +version = "2.4.0" +description = "Parametrize and run Jupyter and nteract Notebooks" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "papermill-2.4.0-py3-none-any.whl", hash = "sha256:baa76f0441257d9a25b3ad7c895e761341b94f9a70ca98cf419247fc728932d9"}, + {file = "papermill-2.4.0.tar.gz", hash = "sha256:6f8f8a9b06b39677f207c09100c8d386bcf592f0cbbdda9f0f50e81445697627"}, +] + +[package.dependencies] +ansiwrap = "*" +click = "*" +entrypoints = "*" +nbclient = ">=0.2.0" +nbformat = ">=5.1.2" +pyyaml = "*" +requests = "*" +tenacity = "*" +tqdm = ">=4.32.2" + +[package.extras] +all = ["azure-datalake-store (>=0.0.30)", "azure-storage-blob (>=12.1.0)", "black (>=19.3b0)", "boto3", "gcsfs (>=0.2.0)", "pyarrow (>=2.0)", "requests (>=2.21.0)"] +azure = ["azure-datalake-store (>=0.0.30)", "azure-storage-blob (>=12.1.0)", "requests (>=2.21.0)"] +black = ["black (>=19.3b0)"] +dev = ["attrs (>=17.4.0)", "azure-datalake-store (>=0.0.30)", "azure-storage-blob (>=12.1.0)", "black (>=19.3b0)", "boto3", "botocore", "bumpversion", "check-manifest", "codecov", "coverage", "flake8", "gcsfs (>=0.2.0)", "google-compute-engine", "ipython (>=5.0)", "ipywidgets", "moto", "notebook", "pip (>=18.1)", "pre-commit", "pyarrow (>=2.0)", "pytest (>=4.1)", "pytest-cov (>=2.6.1)", "pytest-env (>=0.6.2)", "pytest-mock (>=1.10)", "recommonmark", "requests (>=2.21.0)", "setuptools (>=38.6.0)", "tox", "twine (>=1.11.0)", "wheel (>=0.31.0)"] +gcs = ["gcsfs (>=0.2.0)"] +github = ["PyGithub (>=1.55)"] +hdfs = ["pyarrow (>=2.0)"] +s3 = ["boto3"] +test = ["attrs (>=17.4.0)", "azure-datalake-store (>=0.0.30)", "azure-storage-blob (>=12.1.0)", "black (>=19.3b0)", "boto3", "botocore", "bumpversion", "check-manifest", "codecov", "coverage", "flake8", "gcsfs (>=0.2.0)", "google-compute-engine", "ipython (>=5.0)", "ipywidgets", "moto", "notebook", "pip (>=18.1)", "pre-commit", "pyarrow (>=2.0)", "pytest (>=4.1)", "pytest-cov (>=2.6.1)", "pytest-env (>=0.6.2)", "pytest-mock (>=1.10)", "recommonmark", "requests (>=2.21.0)", "setuptools (>=38.6.0)", "tox", "twine (>=1.11.0)", "wheel (>=0.31.0)"] + +[[package]] +name = "parso" +version = "0.8.3" +description = "A Python Parser" +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ + {file = "parso-0.8.3-py2.py3-none-any.whl", hash = "sha256:c001d4636cd3aecdaf33cbb40aebb59b094be2a74c556778ef5576c175e19e75"}, + {file = "parso-0.8.3.tar.gz", hash = "sha256:8c07be290bb59f03588915921e29e8a50002acaf2cdc5fa0e0114f91709fafa0"}, +] + +[package.extras] +qa = ["flake8 (==3.8.3)", "mypy (==0.782)"] +testing = ["docopt", "pytest (<6.0.0)"] + +[[package]] +name = "pdoc" +version = "12.3.1" +description = "API Documentation for Python Projects" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pdoc-12.3.1-py3-none-any.whl", hash = "sha256:c3f24f31286e634de9c76fa6e67bd5c0c5e74360b41dc91e6b82499831eb52d8"}, + {file = "pdoc-12.3.1.tar.gz", hash = "sha256:453236f225feddb8a9071428f1982a78d74b9b3da4bc4433aedb64dbd0cc87ab"}, +] + +[package.dependencies] +Jinja2 = ">=2.11.0" +MarkupSafe = "*" +pygments = ">=2.12.0" + +[package.extras] +dev = ["black", "hypothesis", "mypy", "pytest", "pytest-cov", "pytest-timeout", "ruff", "tox", "types-pygments"] + +[[package]] +name = "pdoc3" +version = "0.10.0" +description = "Auto-generate API documentation for Python projects." +category = "dev" +optional = false +python-versions = ">= 3.6" +files = [ + {file = "pdoc3-0.10.0-py3-none-any.whl", hash = "sha256:ba45d1ada1bd987427d2bf5cdec30b2631a3ff5fb01f6d0e77648a572ce6028b"}, + {file = "pdoc3-0.10.0.tar.gz", hash = "sha256:5f22e7bcb969006738e1aa4219c75a32f34c2d62d46dc9d2fb2d3e0b0287e4b7"}, +] + +[package.dependencies] +mako = "*" +markdown = ">=3.0" + +[[package]] +name = "pexpect" +version = "4.8.0" +description = "Pexpect allows easy control of interactive console applications." +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "pexpect-4.8.0-py2.py3-none-any.whl", hash = "sha256:0b48a55dcb3c05f3329815901ea4fc1537514d6ba867a152b581d69ae3710937"}, + {file = "pexpect-4.8.0.tar.gz", hash = "sha256:fc65a43959d153d0114afe13997d439c22823a27cefceb5ff35c2178c6784c0c"}, +] + +[package.dependencies] +ptyprocess = ">=0.5" + +[[package]] +name = "pickleshare" +version = "0.7.5" +description = "Tiny 'shelve'-like database with concurrency support" +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "pickleshare-0.7.5-py2.py3-none-any.whl", hash = "sha256:9649af414d74d4df115d5d718f82acb59c9d418196b7b4290ed47a12ce62df56"}, + {file = "pickleshare-0.7.5.tar.gz", hash = "sha256:87683d47965c1da65cdacaf31c8441d12b8044cdec9aca500cd78fc2c683afca"}, +] + +[[package]] +name = "pillow" +version = "9.4.0" +description = "Python Imaging Library (Fork)" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "Pillow-9.4.0-1-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:1b4b4e9dda4f4e4c4e6896f93e84a8f0bcca3b059de9ddf67dac3c334b1195e1"}, + {file = "Pillow-9.4.0-1-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:fb5c1ad6bad98c57482236a21bf985ab0ef42bd51f7ad4e4538e89a997624e12"}, + {file = "Pillow-9.4.0-1-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:f0caf4a5dcf610d96c3bd32932bfac8aee61c96e60481c2a0ea58da435e25acd"}, + {file = "Pillow-9.4.0-1-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:3f4cc516e0b264c8d4ccd6b6cbc69a07c6d582d8337df79be1e15a5056b258c9"}, + {file = "Pillow-9.4.0-1-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:b8c2f6eb0df979ee99433d8b3f6d193d9590f735cf12274c108bd954e30ca858"}, + {file = "Pillow-9.4.0-1-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:b70756ec9417c34e097f987b4d8c510975216ad26ba6e57ccb53bc758f490dab"}, + {file = "Pillow-9.4.0-1-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:43521ce2c4b865d385e78579a082b6ad1166ebed2b1a2293c3be1d68dd7ca3b9"}, + {file = "Pillow-9.4.0-2-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:9d9a62576b68cd90f7075876f4e8444487db5eeea0e4df3ba298ee38a8d067b0"}, + {file = "Pillow-9.4.0-2-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:87708d78a14d56a990fbf4f9cb350b7d89ee8988705e58e39bdf4d82c149210f"}, + {file = "Pillow-9.4.0-2-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:8a2b5874d17e72dfb80d917213abd55d7e1ed2479f38f001f264f7ce7bae757c"}, + {file = "Pillow-9.4.0-2-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:83125753a60cfc8c412de5896d10a0a405e0bd88d0470ad82e0869ddf0cb3848"}, + {file = "Pillow-9.4.0-2-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:9e5f94742033898bfe84c93c831a6f552bb629448d4072dd312306bab3bd96f1"}, + {file = "Pillow-9.4.0-2-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:013016af6b3a12a2f40b704677f8b51f72cb007dac785a9933d5c86a72a7fe33"}, + {file = "Pillow-9.4.0-2-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:99d92d148dd03fd19d16175b6d355cc1b01faf80dae93c6c3eb4163709edc0a9"}, + {file = "Pillow-9.4.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:2968c58feca624bb6c8502f9564dd187d0e1389964898f5e9e1fbc8533169157"}, + {file = "Pillow-9.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c5c1362c14aee73f50143d74389b2c158707b4abce2cb055b7ad37ce60738d47"}, + {file = "Pillow-9.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd752c5ff1b4a870b7661234694f24b1d2b9076b8bf337321a814c612665f343"}, + {file = "Pillow-9.4.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9a3049a10261d7f2b6514d35bbb7a4dfc3ece4c4de14ef5876c4b7a23a0e566d"}, + {file = "Pillow-9.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:16a8df99701f9095bea8a6c4b3197da105df6f74e6176c5b410bc2df2fd29a57"}, + {file = "Pillow-9.4.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:94cdff45173b1919350601f82d61365e792895e3c3a3443cf99819e6fbf717a5"}, + {file = "Pillow-9.4.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:ed3e4b4e1e6de75fdc16d3259098de7c6571b1a6cc863b1a49e7d3d53e036070"}, + {file = "Pillow-9.4.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d5b2f8a31bd43e0f18172d8ac82347c8f37ef3e0b414431157718aa234991b28"}, + {file = "Pillow-9.4.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:09b89ddc95c248ee788328528e6a2996e09eaccddeeb82a5356e92645733be35"}, + {file = "Pillow-9.4.0-cp310-cp310-win32.whl", hash = "sha256:f09598b416ba39a8f489c124447b007fe865f786a89dbfa48bb5cf395693132a"}, + {file = "Pillow-9.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:f6e78171be3fb7941f9910ea15b4b14ec27725865a73c15277bc39f5ca4f8391"}, + {file = "Pillow-9.4.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:3fa1284762aacca6dc97474ee9c16f83990b8eeb6697f2ba17140d54b453e133"}, + {file = "Pillow-9.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:eaef5d2de3c7e9b21f1e762f289d17b726c2239a42b11e25446abf82b26ac132"}, + {file = "Pillow-9.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a4dfdae195335abb4e89cc9762b2edc524f3c6e80d647a9a81bf81e17e3fb6f0"}, + {file = "Pillow-9.4.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6abfb51a82e919e3933eb137e17c4ae9c0475a25508ea88993bb59faf82f3b35"}, + {file = "Pillow-9.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:451f10ef963918e65b8869e17d67db5e2f4ab40e716ee6ce7129b0cde2876eab"}, + {file = "Pillow-9.4.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:6663977496d616b618b6cfa43ec86e479ee62b942e1da76a2c3daa1c75933ef4"}, + {file = "Pillow-9.4.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:60e7da3a3ad1812c128750fc1bc14a7ceeb8d29f77e0a2356a8fb2aa8925287d"}, + {file = "Pillow-9.4.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:19005a8e58b7c1796bc0167862b1f54a64d3b44ee5d48152b06bb861458bc0f8"}, + {file = "Pillow-9.4.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f715c32e774a60a337b2bb8ad9839b4abf75b267a0f18806f6f4f5f1688c4b5a"}, + {file = "Pillow-9.4.0-cp311-cp311-win32.whl", hash = "sha256:b222090c455d6d1a64e6b7bb5f4035c4dff479e22455c9eaa1bdd4c75b52c80c"}, + {file = "Pillow-9.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:ba6612b6548220ff5e9df85261bddc811a057b0b465a1226b39bfb8550616aee"}, + {file = "Pillow-9.4.0-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:5f532a2ad4d174eb73494e7397988e22bf427f91acc8e6ebf5bb10597b49c493"}, + {file = "Pillow-9.4.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5dd5a9c3091a0f414a963d427f920368e2b6a4c2f7527fdd82cde8ef0bc7a327"}, + {file = "Pillow-9.4.0-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ef21af928e807f10bf4141cad4746eee692a0dd3ff56cfb25fce076ec3cc8abe"}, + {file = "Pillow-9.4.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:847b114580c5cc9ebaf216dd8c8dbc6b00a3b7ab0131e173d7120e6deade1f57"}, + {file = "Pillow-9.4.0-cp37-cp37m-manylinux_2_28_aarch64.whl", hash = "sha256:653d7fb2df65efefbcbf81ef5fe5e5be931f1ee4332c2893ca638c9b11a409c4"}, + {file = "Pillow-9.4.0-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:46f39cab8bbf4a384ba7cb0bc8bae7b7062b6a11cfac1ca4bc144dea90d4a9f5"}, + {file = "Pillow-9.4.0-cp37-cp37m-win32.whl", hash = "sha256:7ac7594397698f77bce84382929747130765f66406dc2cd8b4ab4da68ade4c6e"}, + {file = "Pillow-9.4.0-cp37-cp37m-win_amd64.whl", hash = "sha256:46c259e87199041583658457372a183636ae8cd56dbf3f0755e0f376a7f9d0e6"}, + {file = "Pillow-9.4.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:0e51f608da093e5d9038c592b5b575cadc12fd748af1479b5e858045fff955a9"}, + {file = "Pillow-9.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:765cb54c0b8724a7c12c55146ae4647e0274a839fb6de7bcba841e04298e1011"}, + {file = "Pillow-9.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:519e14e2c49fcf7616d6d2cfc5c70adae95682ae20f0395e9280db85e8d6c4df"}, + {file = "Pillow-9.4.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d197df5489004db87d90b918033edbeee0bd6df3848a204bca3ff0a903bef837"}, + {file = "Pillow-9.4.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0845adc64fe9886db00f5ab68c4a8cd933ab749a87747555cec1c95acea64b0b"}, + {file = "Pillow-9.4.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:e1339790c083c5a4de48f688b4841f18df839eb3c9584a770cbd818b33e26d5d"}, + {file = "Pillow-9.4.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:a96e6e23f2b79433390273eaf8cc94fec9c6370842e577ab10dabdcc7ea0a66b"}, + {file = "Pillow-9.4.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:7cfc287da09f9d2a7ec146ee4d72d6ea1342e770d975e49a8621bf54eaa8f30f"}, + {file = "Pillow-9.4.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d7081c084ceb58278dd3cf81f836bc818978c0ccc770cbbb202125ddabec6628"}, + {file = "Pillow-9.4.0-cp38-cp38-win32.whl", hash = "sha256:df41112ccce5d47770a0c13651479fbcd8793f34232a2dd9faeccb75eb5d0d0d"}, + {file = "Pillow-9.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:7a21222644ab69ddd9967cfe6f2bb420b460dae4289c9d40ff9a4896e7c35c9a"}, + {file = "Pillow-9.4.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:0f3269304c1a7ce82f1759c12ce731ef9b6e95b6df829dccd9fe42912cc48569"}, + {file = "Pillow-9.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:cb362e3b0976dc994857391b776ddaa8c13c28a16f80ac6522c23d5257156bed"}, + {file = "Pillow-9.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a2e0f87144fcbbe54297cae708c5e7f9da21a4646523456b00cc956bd4c65815"}, + {file = "Pillow-9.4.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:28676836c7796805914b76b1837a40f76827ee0d5398f72f7dcc634bae7c6264"}, + {file = "Pillow-9.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0884ba7b515163a1a05440a138adeb722b8a6ae2c2b33aea93ea3118dd3a899e"}, + {file = "Pillow-9.4.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:53dcb50fbdc3fb2c55431a9b30caeb2f7027fcd2aeb501459464f0214200a503"}, + {file = "Pillow-9.4.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:e8c5cf126889a4de385c02a2c3d3aba4b00f70234bfddae82a5eaa3ee6d5e3e6"}, + {file = "Pillow-9.4.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:6c6b1389ed66cdd174d040105123a5a1bc91d0aa7059c7261d20e583b6d8cbd2"}, + {file = "Pillow-9.4.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0dd4c681b82214b36273c18ca7ee87065a50e013112eea7d78c7a1b89a739153"}, + {file = "Pillow-9.4.0-cp39-cp39-win32.whl", hash = "sha256:6d9dfb9959a3b0039ee06c1a1a90dc23bac3b430842dcb97908ddde05870601c"}, + {file = "Pillow-9.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:54614444887e0d3043557d9dbc697dbb16cfb5a35d672b7a0fcc1ed0cf1c600b"}, + {file = "Pillow-9.4.0-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:b9b752ab91e78234941e44abdecc07f1f0d8f51fb62941d32995b8161f68cfe5"}, + {file = "Pillow-9.4.0-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d3b56206244dc8711f7e8b7d6cad4663917cd5b2d950799425076681e8766286"}, + {file = "Pillow-9.4.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aabdab8ec1e7ca7f1434d042bf8b1e92056245fb179790dc97ed040361f16bfd"}, + {file = "Pillow-9.4.0-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:db74f5562c09953b2c5f8ec4b7dfd3f5421f31811e97d1dbc0a7c93d6e3a24df"}, + {file = "Pillow-9.4.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:e9d7747847c53a16a729b6ee5e737cf170f7a16611c143d95aa60a109a59c336"}, + {file = "Pillow-9.4.0-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:b52ff4f4e002f828ea6483faf4c4e8deea8d743cf801b74910243c58acc6eda3"}, + {file = "Pillow-9.4.0-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:575d8912dca808edd9acd6f7795199332696d3469665ef26163cd090fa1f8bfa"}, + {file = "Pillow-9.4.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c3c4ed2ff6760e98d262e0cc9c9a7f7b8a9f61aa4d47c58835cdaf7b0b8811bb"}, + {file = "Pillow-9.4.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:e621b0246192d3b9cb1dc62c78cfa4c6f6d2ddc0ec207d43c0dedecb914f152a"}, + {file = "Pillow-9.4.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:8f127e7b028900421cad64f51f75c051b628db17fb00e099eb148761eed598c9"}, + {file = "Pillow-9.4.0.tar.gz", hash = "sha256:a1c2d7780448eb93fbcc3789bf3916aa5720d942e37945f4056680317f1cd23e"}, +] + +[package.extras] +docs = ["furo", "olefile", "sphinx (>=2.4)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinx-issues (>=3.0.1)", "sphinx-removed-in", "sphinxext-opengraph"] +tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"] + +[[package]] +name = "platformdirs" +version = "3.1.1" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "platformdirs-3.1.1-py3-none-any.whl", hash = "sha256:e5986afb596e4bb5bde29a79ac9061aa955b94fca2399b7aaac4090860920dd8"}, + {file = "platformdirs-3.1.1.tar.gz", hash = "sha256:024996549ee88ec1a9aa99ff7f8fc819bb59e2c3477b410d90a16d32d6e707aa"}, +] + +[package.extras] +docs = ["furo (>=2022.12.7)", "proselint (>=0.13)", "sphinx (>=6.1.3)", "sphinx-autodoc-typehints (>=1.22,!=1.23.4)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.2.2)", "pytest (>=7.2.1)", "pytest-cov (>=4)", "pytest-mock (>=3.10)"] + +[[package]] +name = "pluggy" +version = "1.0.0" +description = "plugin and hook calling mechanisms for python" +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ + {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, + {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, +] + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] + +[[package]] +name = "ply" +version = "3.11" +description = "Python Lex & Yacc" +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "ply-3.11-py2.py3-none-any.whl", hash = "sha256:096f9b8350b65ebd2fd1346b12452efe5b9607f7482813ffca50c22722a807ce"}, + {file = "ply-3.11.tar.gz", hash = "sha256:00c7c1aaa88358b9c765b6d3000c6eec0ba42abca5351b095321aef446081da3"}, +] + +[[package]] +name = "portalocker" +version = "2.7.0" +description = "Wraps the portalocker recipe for easy usage" +category = "main" +optional = false +python-versions = ">=3.5" +files = [ + {file = "portalocker-2.7.0-py2.py3-none-any.whl", hash = "sha256:a07c5b4f3985c3cf4798369631fb7011adb498e2a46d8440efc75a8f29a0f983"}, + {file = "portalocker-2.7.0.tar.gz", hash = "sha256:032e81d534a88ec1736d03f780ba073f047a06c478b06e2937486f334e955c51"}, +] + +[package.dependencies] +pywin32 = {version = ">=226", markers = "platform_system == \"Windows\""} + +[package.extras] +docs = ["sphinx (>=1.7.1)"] +redis = ["redis"] +tests = ["pytest (>=5.4.1)", "pytest-cov (>=2.8.1)", "pytest-mypy (>=0.8.0)", "pytest-timeout (>=2.1.0)", "redis", "sphinx (>=6.0.0)"] + +[[package]] +name = "prometheus-client" +version = "0.16.0" +description = "Python client for the Prometheus monitoring system." +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ + {file = "prometheus_client-0.16.0-py3-none-any.whl", hash = "sha256:0836af6eb2c8f4fed712b2f279f6c0a8bbab29f9f4aa15276b91c7cb0d1616ab"}, + {file = "prometheus_client-0.16.0.tar.gz", hash = "sha256:a03e35b359f14dd1630898543e2120addfdeacd1a6069c1367ae90fd93ad3f48"}, +] + +[package.extras] +twisted = ["twisted"] + +[[package]] +name = "prompt-toolkit" +version = "3.0.38" +description = "Library for building powerful interactive command lines in Python" +category = "dev" +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "prompt_toolkit-3.0.38-py3-none-any.whl", hash = "sha256:45ea77a2f7c60418850331366c81cf6b5b9cf4c7fd34616f733c5427e6abbb1f"}, + {file = "prompt_toolkit-3.0.38.tar.gz", hash = "sha256:23ac5d50538a9a38c8bde05fecb47d0b403ecd0662857a86f886f798563d5b9b"}, +] + +[package.dependencies] +wcwidth = "*" + +[[package]] +name = "protobuf" +version = "4.22.1" +description = "" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "protobuf-4.22.1-cp310-abi3-win32.whl", hash = "sha256:85aa9acc5a777adc0c21b449dafbc40d9a0b6413ff3a4f77ef9df194be7f975b"}, + {file = "protobuf-4.22.1-cp310-abi3-win_amd64.whl", hash = "sha256:8bc971d76c03f1dd49f18115b002254f2ddb2d4b143c583bb860b796bb0d399e"}, + {file = "protobuf-4.22.1-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:5917412347e1da08ce2939eb5cd60650dfb1a9ab4606a415b9278a1041fb4d19"}, + {file = "protobuf-4.22.1-cp37-abi3-manylinux2014_aarch64.whl", hash = "sha256:9e12e2810e7d297dbce3c129ae5e912ffd94240b050d33f9ecf023f35563b14f"}, + {file = "protobuf-4.22.1-cp37-abi3-manylinux2014_x86_64.whl", hash = "sha256:953fc7904ef46900262a26374b28c2864610b60cdc8b272f864e22143f8373c4"}, + {file = "protobuf-4.22.1-cp37-cp37m-win32.whl", hash = "sha256:6e100f7bc787cd0a0ae58dbf0ab8bbf1ee7953f862b89148b6cf5436d5e9eaa1"}, + {file = "protobuf-4.22.1-cp37-cp37m-win_amd64.whl", hash = "sha256:87a6393fa634f294bf24d1cfe9fdd6bb605cbc247af81b9b10c4c0f12dfce4b3"}, + {file = "protobuf-4.22.1-cp38-cp38-win32.whl", hash = "sha256:e3fb58076bdb550e75db06ace2a8b3879d4c4f7ec9dd86e4254656118f4a78d7"}, + {file = "protobuf-4.22.1-cp38-cp38-win_amd64.whl", hash = "sha256:651113695bc2e5678b799ee5d906b5d3613f4ccfa61b12252cfceb6404558af0"}, + {file = "protobuf-4.22.1-cp39-cp39-win32.whl", hash = "sha256:67b7d19da0fda2733702c2299fd1ef6cb4b3d99f09263eacaf1aa151d9d05f02"}, + {file = "protobuf-4.22.1-cp39-cp39-win_amd64.whl", hash = "sha256:b8700792f88e59ccecfa246fa48f689d6eee6900eddd486cdae908ff706c482b"}, + {file = "protobuf-4.22.1-py3-none-any.whl", hash = "sha256:3e19dcf4adbf608924d3486ece469dd4f4f2cf7d2649900f0efcd1a84e8fd3ba"}, + {file = "protobuf-4.22.1.tar.gz", hash = "sha256:dce7a55d501c31ecf688adb2f6c3f763cf11bc0be815d1946a84d74772ab07a7"}, +] + +[[package]] +name = "psutil" +version = "5.9.4" +description = "Cross-platform lib for process and system monitoring in Python." +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "psutil-5.9.4-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:c1ca331af862803a42677c120aff8a814a804e09832f166f226bfd22b56feee8"}, + {file = "psutil-5.9.4-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:68908971daf802203f3d37e78d3f8831b6d1014864d7a85937941bb35f09aefe"}, + {file = "psutil-5.9.4-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:3ff89f9b835100a825b14c2808a106b6fdcc4b15483141482a12c725e7f78549"}, + {file = "psutil-5.9.4-cp27-cp27m-win32.whl", hash = "sha256:852dd5d9f8a47169fe62fd4a971aa07859476c2ba22c2254d4a1baa4e10b95ad"}, + {file = "psutil-5.9.4-cp27-cp27m-win_amd64.whl", hash = "sha256:9120cd39dca5c5e1c54b59a41d205023d436799b1c8c4d3ff71af18535728e94"}, + {file = "psutil-5.9.4-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:6b92c532979bafc2df23ddc785ed116fced1f492ad90a6830cf24f4d1ea27d24"}, + {file = "psutil-5.9.4-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:efeae04f9516907be44904cc7ce08defb6b665128992a56957abc9b61dca94b7"}, + {file = "psutil-5.9.4-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:54d5b184728298f2ca8567bf83c422b706200bcbbfafdc06718264f9393cfeb7"}, + {file = "psutil-5.9.4-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:16653106f3b59386ffe10e0bad3bb6299e169d5327d3f187614b1cb8f24cf2e1"}, + {file = "psutil-5.9.4-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:54c0d3d8e0078b7666984e11b12b88af2db11d11249a8ac8920dd5ef68a66e08"}, + {file = "psutil-5.9.4-cp36-abi3-win32.whl", hash = "sha256:149555f59a69b33f056ba1c4eb22bb7bf24332ce631c44a319cec09f876aaeff"}, + {file = "psutil-5.9.4-cp36-abi3-win_amd64.whl", hash = "sha256:fd8522436a6ada7b4aad6638662966de0d61d241cb821239b2ae7013d41a43d4"}, + {file = "psutil-5.9.4-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:6001c809253a29599bc0dfd5179d9f8a5779f9dffea1da0f13c53ee568115e1e"}, + {file = "psutil-5.9.4.tar.gz", hash = "sha256:3d7f9739eb435d4b1338944abe23f49584bde5395f27487d2ee25ad9a8774a62"}, +] + +[package.extras] +test = ["enum34", "ipaddress", "mock", "pywin32", "wmi"] + +[[package]] +name = "ptpython" +version = "3.0.23" +description = "Python REPL build on top of prompt_toolkit" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "ptpython-3.0.23-py2.py3-none-any.whl", hash = "sha256:51069503684169b21e1980734a9ba2e104643b7e6a50d3ca0e5669ea70d9e21c"}, + {file = "ptpython-3.0.23.tar.gz", hash = "sha256:9fc9bec2cc51bc4000c1224d8c56241ce8a406b3d49ec8dc266f78cd3cd04ba4"}, +] + +[package.dependencies] +appdirs = "*" +jedi = ">=0.16.0" +prompt-toolkit = ">=3.0.28,<3.1.0" +pygments = "*" + +[package.extras] +all = ["black"] +ptipython = ["ipython"] + +[[package]] +name = "ptyprocess" +version = "0.7.0" +description = "Run a subprocess in a pseudo terminal" +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35"}, + {file = "ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"}, +] + +[[package]] +name = "pudb" +version = "2022.1.3" +description = "A full-screen, console-based Python debugger" +category = "dev" +optional = false +python-versions = "~=3.6" +files = [ + {file = "pudb-2022.1.3.tar.gz", hash = "sha256:58e83ada9e19ffe92c1fdc78ae5458ef91aeb892a5b8f0e7379e6fa61e0e664a"}, +] + +[package.dependencies] +jedi = ">=0.18,<1" +packaging = ">=20.0" +pygments = ">=2.7.4" +urwid = ">=1.1.1" +urwid_readline = "*" + +[package.extras] +completion = ["shtab"] + +[[package]] +name = "pure-eval" +version = "0.2.2" +description = "Safely evaluate AST nodes without side effects" +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "pure_eval-0.2.2-py3-none-any.whl", hash = "sha256:01eaab343580944bc56080ebe0a674b39ec44a945e6d09ba7db3cb8cec289350"}, + {file = "pure_eval-0.2.2.tar.gz", hash = "sha256:2b45320af6dfaa1750f543d714b6d1c520a1688dec6fd24d339063ce0aaa9ac3"}, +] + +[package.extras] +tests = ["pytest"] + +[[package]] +name = "pyasn1" +version = "0.4.8" +description = "ASN.1 types and codecs" +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "pyasn1-0.4.8-py2.py3-none-any.whl", hash = "sha256:39c7e2ec30515947ff4e87fb6f456dfc6e84857d34be479c9d4a4ba4bf46aa5d"}, + {file = "pyasn1-0.4.8.tar.gz", hash = "sha256:aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba"}, +] + +[[package]] +name = "pyasn1-modules" +version = "0.2.8" +description = "A collection of ASN.1-based protocols modules." +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "pyasn1-modules-0.2.8.tar.gz", hash = "sha256:905f84c712230b2c592c19470d3ca8d552de726050d1d1716282a1f6146be65e"}, + {file = "pyasn1_modules-0.2.8-py2.py3-none-any.whl", hash = "sha256:a50b808ffeb97cb3601dd25981f6b016cbb3d31fbf57a8b8a87428e6158d0c74"}, +] + +[package.dependencies] +pyasn1 = ">=0.4.6,<0.5.0" + +[[package]] +name = "pybind11" +version = "2.10.4" +description = "Seamless operability between C++11 and Python" +category = "main" +optional = false +python-versions = ">=3.6" +files = [ + {file = "pybind11-2.10.4-py3-none-any.whl", hash = "sha256:ec9be0c45061c829648d7e8c98a7d041768b768c934acd15196e0f1943d9a818"}, + {file = "pybind11-2.10.4.tar.gz", hash = "sha256:0bb621d3c45a049aa5923debb87c5c0e2668227905c55ebe8af722608d8ed927"}, +] + +[package.extras] +global = ["pybind11-global (==2.10.4)"] + +[[package]] +name = "pycodestyle" +version = "2.10.0" +description = "Python style guide checker" +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ + {file = "pycodestyle-2.10.0-py2.py3-none-any.whl", hash = "sha256:8a4eaf0d0495c7395bdab3589ac2db602797d76207242c17d470186815706610"}, + {file = "pycodestyle-2.10.0.tar.gz", hash = "sha256:347187bdb476329d98f695c213d7295a846d1152ff4fe9bacb8a9590b8ee7053"}, +] + +[[package]] +name = "pycparser" +version = "2.21" +description = "C parser in Python" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"}, + {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"}, +] + +[[package]] +name = "pydantic" +version = "1.10.7" +description = "Data validation and settings management using python type hints" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pydantic-1.10.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e79e999e539872e903767c417c897e729e015872040e56b96e67968c3b918b2d"}, + {file = "pydantic-1.10.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:01aea3a42c13f2602b7ecbbea484a98169fb568ebd9e247593ea05f01b884b2e"}, + {file = "pydantic-1.10.7-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:516f1ed9bc2406a0467dd777afc636c7091d71f214d5e413d64fef45174cfc7a"}, + {file = "pydantic-1.10.7-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae150a63564929c675d7f2303008d88426a0add46efd76c3fc797cd71cb1b46f"}, + {file = "pydantic-1.10.7-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:ecbbc51391248116c0a055899e6c3e7ffbb11fb5e2a4cd6f2d0b93272118a209"}, + {file = "pydantic-1.10.7-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f4a2b50e2b03d5776e7f21af73e2070e1b5c0d0df255a827e7c632962f8315af"}, + {file = "pydantic-1.10.7-cp310-cp310-win_amd64.whl", hash = "sha256:a7cd2251439988b413cb0a985c4ed82b6c6aac382dbaff53ae03c4b23a70e80a"}, + {file = "pydantic-1.10.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:68792151e174a4aa9e9fc1b4e653e65a354a2fa0fed169f7b3d09902ad2cb6f1"}, + {file = "pydantic-1.10.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dfe2507b8ef209da71b6fb5f4e597b50c5a34b78d7e857c4f8f3115effaef5fe"}, + {file = "pydantic-1.10.7-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10a86d8c8db68086f1e30a530f7d5f83eb0685e632e411dbbcf2d5c0150e8dcd"}, + {file = "pydantic-1.10.7-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d75ae19d2a3dbb146b6f324031c24f8a3f52ff5d6a9f22f0683694b3afcb16fb"}, + {file = "pydantic-1.10.7-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:464855a7ff7f2cc2cf537ecc421291b9132aa9c79aef44e917ad711b4a93163b"}, + {file = "pydantic-1.10.7-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:193924c563fae6ddcb71d3f06fa153866423ac1b793a47936656e806b64e24ca"}, + {file = "pydantic-1.10.7-cp311-cp311-win_amd64.whl", hash = "sha256:b4a849d10f211389502059c33332e91327bc154acc1845f375a99eca3afa802d"}, + {file = "pydantic-1.10.7-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:cc1dde4e50a5fc1336ee0581c1612215bc64ed6d28d2c7c6f25d2fe3e7c3e918"}, + {file = "pydantic-1.10.7-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e0cfe895a504c060e5d36b287ee696e2fdad02d89e0d895f83037245218a87fe"}, + {file = "pydantic-1.10.7-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:670bb4683ad1e48b0ecb06f0cfe2178dcf74ff27921cdf1606e527d2617a81ee"}, + {file = "pydantic-1.10.7-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:950ce33857841f9a337ce07ddf46bc84e1c4946d2a3bba18f8280297157a3fd1"}, + {file = "pydantic-1.10.7-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:c15582f9055fbc1bfe50266a19771bbbef33dd28c45e78afbe1996fd70966c2a"}, + {file = "pydantic-1.10.7-cp37-cp37m-win_amd64.whl", hash = "sha256:82dffb306dd20bd5268fd6379bc4bfe75242a9c2b79fec58e1041fbbdb1f7914"}, + {file = "pydantic-1.10.7-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8c7f51861d73e8b9ddcb9916ae7ac39fb52761d9ea0df41128e81e2ba42886cd"}, + {file = "pydantic-1.10.7-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:6434b49c0b03a51021ade5c4daa7d70c98f7a79e95b551201fff682fc1661245"}, + {file = "pydantic-1.10.7-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64d34ab766fa056df49013bb6e79921a0265204c071984e75a09cbceacbbdd5d"}, + {file = "pydantic-1.10.7-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:701daea9ffe9d26f97b52f1d157e0d4121644f0fcf80b443248434958fd03dc3"}, + {file = "pydantic-1.10.7-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:cf135c46099ff3f919d2150a948ce94b9ce545598ef2c6c7bf55dca98a304b52"}, + {file = "pydantic-1.10.7-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b0f85904f73161817b80781cc150f8b906d521fa11e3cdabae19a581c3606209"}, + {file = "pydantic-1.10.7-cp38-cp38-win_amd64.whl", hash = "sha256:9f6f0fd68d73257ad6685419478c5aece46432f4bdd8d32c7345f1986496171e"}, + {file = "pydantic-1.10.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c230c0d8a322276d6e7b88c3f7ce885f9ed16e0910354510e0bae84d54991143"}, + {file = "pydantic-1.10.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:976cae77ba6a49d80f461fd8bba183ff7ba79f44aa5cfa82f1346b5626542f8e"}, + {file = "pydantic-1.10.7-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7d45fc99d64af9aaf7e308054a0067fdcd87ffe974f2442312372dfa66e1001d"}, + {file = "pydantic-1.10.7-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d2a5ebb48958754d386195fe9e9c5106f11275867051bf017a8059410e9abf1f"}, + {file = "pydantic-1.10.7-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:abfb7d4a7cd5cc4e1d1887c43503a7c5dd608eadf8bc615413fc498d3e4645cd"}, + {file = "pydantic-1.10.7-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:80b1fab4deb08a8292d15e43a6edccdffa5377a36a4597bb545b93e79c5ff0a5"}, + {file = "pydantic-1.10.7-cp39-cp39-win_amd64.whl", hash = "sha256:d71e69699498b020ea198468e2480a2f1e7433e32a3a99760058c6520e2bea7e"}, + {file = "pydantic-1.10.7-py3-none-any.whl", hash = "sha256:0cd181f1d0b1d00e2b705f1bf1ac7799a2d938cce3376b8007df62b29be3c2c6"}, + {file = "pydantic-1.10.7.tar.gz", hash = "sha256:cfc83c0678b6ba51b0532bea66860617c4cd4251ecf76e9846fa5a9f3454e97e"}, +] + +[package.dependencies] +typing-extensions = ">=4.2.0" + +[package.extras] +dotenv = ["python-dotenv (>=0.10.4)"] +email = ["email-validator (>=1.0.3)"] + +[[package]] +name = "pydocstyle" +version = "6.2.3" +description = "Python docstring style checker" +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ + {file = "pydocstyle-6.2.3-py3-none-any.whl", hash = "sha256:a04ed1e6fe0be0970eddbb1681a7ab59b11eb92729fdb4b9b24f0eb11a25629e"}, + {file = "pydocstyle-6.2.3.tar.gz", hash = "sha256:d867acad25e48471f2ad8a40ef9813125e954ad675202245ca836cb6e28b2297"}, +] + +[package.dependencies] +snowballstemmer = ">=2.2.0" + +[package.extras] +toml = ["tomli (>=1.2.3)"] + +[[package]] +name = "pyembree" +version = "0.2.11" +description = "Python wrapper for Intel Embree 2.17.7" +category = "main" +optional = false +python-versions = ">=3.8,<3.11" +files = [ + {file = "pyembree-0.2.11-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2fea0e9004fff0ca26854bd2bc5baa06fe7cc7816e6713f9505f652c25d8d8c9"}, +] + +[package.dependencies] +Cython = ">=0.29.28,<0.30.0" +numpy = ">=1.22.2,<2.0.0" +Rtree = ">=1.0.0,<2.0.0" +setuptools = ">=60.9.3,<61.0.0" +trimesh = ">=3.10.7,<4.0.0" +wheel = ">=0.37.1,<0.38.0" + +[package.source] +type = "url" +url = "https://folk.ntnu.no/pederbs/pypy/pep503/pyembree/pyembree-0.2.11-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl" + +[[package]] +name = "pyflakes" +version = "3.0.1" +description = "passive checker of Python programs" +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ + {file = "pyflakes-3.0.1-py2.py3-none-any.whl", hash = "sha256:ec55bf7fe21fff7f1ad2f7da62363d749e2a470500eab1b555334b67aa1ef8cf"}, + {file = "pyflakes-3.0.1.tar.gz", hash = "sha256:ec8b276a6b60bd80defed25add7e439881c19e64850afd9b346283d4165fd0fd"}, +] + +[[package]] +name = "pygame" +version = "2.3.0" +description = "Python Game Development" +category = "main" +optional = false +python-versions = ">=3.6" +files = [ + {file = "pygame-2.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3e9535cf1af0c6ca38d94e0b492fc41057d7bf05e9bd64d3ed3e216d336d6d11"}, + {file = "pygame-2.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:23bd3c3a6d4e8acddee2297d609dbc5953d6ba99b0f0cc5ccc2f567889db3785"}, + {file = "pygame-2.3.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:619eed2d97f28af9d4cdb217a5517fd6f59b873f2f1d31b4489ed852b9a175c3"}, + {file = "pygame-2.3.0-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:9ccac73a8c913809ba2c1408d750abf14e45666b3c83493370441c52e99222b4"}, + {file = "pygame-2.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ec8e691407b6c91525b2d7c8386fd6232b97d8f8c33d134ec0c0165b1d52c24"}, + {file = "pygame-2.3.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8308b21804d137a3b7cafbd020d2159eb5bcc18ffc9c3993b20311069c326a2c"}, + {file = "pygame-2.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a1d737db18f4c94b620613c6a047a3a1eecc0f36df7d5da4070de575930cc5f0"}, + {file = "pygame-2.3.0-cp310-cp310-win32.whl", hash = "sha256:788717d0b9a0d0828a763381e1eb6a127ceef815f9a91ff52217ed4b78df62fc"}, + {file = "pygame-2.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:e3948be800b5f251a0741ec3aab3ca508dfc391095726a69af7064fa4d3e0547"}, + {file = "pygame-2.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:82e5806fd797bd1b27fae705683f6822ae5276ec9cda42e6e21bba61985b763a"}, + {file = "pygame-2.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fab0457ab07e8abb99de2b83c0a71f98bdf79afb01ff611873e4333fd8649f02"}, + {file = "pygame-2.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad8fa7a91fa8f2a4fa46366142763675a0a11b7c34b06dfc20b1095d116da820"}, + {file = "pygame-2.3.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cfff49dbb7fcc2a9a88e3f25fda7f181ee4957fd89df78c47fa64c689d19b8a9"}, + {file = "pygame-2.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a5afd712bd7307d034e6940f3025c4b769656fd4cbb38fbdbd6af0f93d6c8386"}, + {file = "pygame-2.3.0-cp311-cp311-win32.whl", hash = "sha256:fa18acc2d6f0d09575802e1db11845fc0f83f9777cc385c51380125df92f3dc9"}, + {file = "pygame-2.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:101c57141d705ca1930377c324d2c7acd3099f1b4ac676981bdf5d5b329842c8"}, + {file = "pygame-2.3.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:17730a2ed1001e5876745702c92906ad31ecedc13825efba56a0cba92e273b7a"}, + {file = "pygame-2.3.0-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:4b334f6dd6c1412dd4b161a8562b7a422db957f67b7eb93e927606e2dd435882"}, + {file = "pygame-2.3.0-cp36-cp36m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4db1b103025fd4b451dfa409c0da16d2ff31714ae82bdf45b1434863cd69370b"}, + {file = "pygame-2.3.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d339f90cc30de4b013670de84abd46de4be602d5c52bbe4e569fa15d17b204ca"}, + {file = "pygame-2.3.0-cp36-cp36m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a7390815dad55a2db9f8daac6f2c2e593801daea2d674433a72b91ea1caee0d3"}, + {file = "pygame-2.3.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:59a1e473c627acf369b30bb52fb5f39d1f68f8c204aa857578b72f07a23c952b"}, + {file = "pygame-2.3.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:228514c0d034c840b8ee6bf99185df34ac15e6a6a99684b8a3900124417c8d8f"}, + {file = "pygame-2.3.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:a8b315203925724f89a81a741682589ba1c36ec858d98e6accb7501ece9e99a3"}, + {file = "pygame-2.3.0-cp36-cp36m-win32.whl", hash = "sha256:38642c6cc6477db6ebddd52be39bad0a9e19cf097f83feaaf8e7573b9a9d2405"}, + {file = "pygame-2.3.0-cp36-cp36m-win_amd64.whl", hash = "sha256:525e11a2b9182ec84d690634016009e382ab8b488593c3f150a0b8aae28aa165"}, + {file = "pygame-2.3.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:32bdf1d5d9e0763779d0b915d4617253949a6c118c4c6b5ae1a77cf1df964e4c"}, + {file = "pygame-2.3.0-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:f57b1ee40387e43ab5c3cf20437283477b5ef52ead4bb1d9bff254ef9ee70623"}, + {file = "pygame-2.3.0-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6ccde93b51d2393216f98e8f81cf5cc628513d837c89dcf5b588f52031659c09"}, + {file = "pygame-2.3.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c60be419d7cca1222895dfe9d520628b7346015208382a19fa678356a22664b3"}, + {file = "pygame-2.3.0-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:43f238229b3a9e5692ba5a31638f1c148257b37a49ef21f03b23b34d7f00b2d9"}, + {file = "pygame-2.3.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d628637d4f0c55613f258b84eef932faf89e683aa842f4fd483a676f44a38606"}, + {file = "pygame-2.3.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:35f5a9cc7a9a2ea3d048e418e79f30e1506cb47015939330903026c636761aab"}, + {file = "pygame-2.3.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:703d5def9d4dbe9c358f63151bee4a55e328dd7737e692f52522bc44be7c7c8c"}, + {file = "pygame-2.3.0-cp37-cp37m-win32.whl", hash = "sha256:53e9418c457fa549294feee7947bc0b24b048b4eba133f0e757dd2348d15af3b"}, + {file = "pygame-2.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:0a664cd6c50870f6749c389a8844318afc8a2d02f8cb7b05d67930fdf99252bd"}, + {file = "pygame-2.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bf236758429d9b9cdadd1fcf40901588818ee440178b932409c40157ab41e902"}, + {file = "pygame-2.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3d035ba196c258876a87451fa7de65b62c087d7016e51000e8d95bc67c8584f7"}, + {file = "pygame-2.3.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:57180b3aabbe17d8017aa724887019943d96ea69810f4315f5c1b7d4f64861f9"}, + {file = "pygame-2.3.0-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:246f75f67d2ad4c2dad21b1f35c6092d67c4c0db13b2fa0a42d794e6e2794f47"}, + {file = "pygame-2.3.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:033352321cc49d60fdc3c3ae4b3e10ecb6614846fb2eb3453c729aba48a2874d"}, + {file = "pygame-2.3.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3ee86606c6c7f61176ed24b427fa230fe4fc9f552aa555b8db21ddb608b4ce88"}, + {file = "pygame-2.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d949e93fbdaf5b43f69a484639104c07028f93686c8305afb0d8e382fde8ff5d"}, + {file = "pygame-2.3.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f2acf958513bd1612960ec68aa5e388262218f7365db59e54e1ee68a55bc544b"}, + {file = "pygame-2.3.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6c5d33355dfb66382bcac1fcf3db64ba71bc9e97082db3ae45a7a0d335e73268"}, + {file = "pygame-2.3.0-cp38-cp38-win32.whl", hash = "sha256:1eda9f30d376d4205e8204e542ab1348dcbb31755c8ba38772e48a3b2f91b2fc"}, + {file = "pygame-2.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:b507df9ea606a87c29e5028b8de9f35066a15f6a5d7f3e5b47b3719e9403f924"}, + {file = "pygame-2.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:25c1b1819211aaa0f98264e6b670a496a9975079d5ae2dffd304b0aca6b1aa3c"}, + {file = "pygame-2.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e516bc6bba5455817bbb0038f4c44d1914aac13c7f7954dee9213c9ae28bd9ac"}, + {file = "pygame-2.3.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:740b9f311c693b00d86a89cc6846afc1d1e013b006975eb8be0b18d5481c5b32"}, + {file = "pygame-2.3.0-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:932034e1738873a55c4e2eb83b6e8c03f9a55feaa6a04a7da7b1e0e5a5050b4a"}, + {file = "pygame-2.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:774233845099d632de676ad4d4dd08ba27ebce5bfa550b1dc9f6cce145e21c35"}, + {file = "pygame-2.3.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4f79a3c5e7f24474d6e722d597ee03d2b0d17958c77d4307787147cf339b4ad9"}, + {file = "pygame-2.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:84fad9538012f1d6b298dcf690c4336e0317fe97ac10993b4d847ff547e919dd"}, + {file = "pygame-2.3.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:910678441d02c3b55ac59fcbc4220a824b094407de084734b5d84e0900d6448b"}, + {file = "pygame-2.3.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:653ec5102b9cb13a24e26663a81d7810790e56b88113b90aa5fdca681c01a5b9"}, + {file = "pygame-2.3.0-cp39-cp39-win32.whl", hash = "sha256:e62607c86e02d29ba5cb00837f73b1dce7b325a1f1f6d93150a0f96fa68da1a1"}, + {file = "pygame-2.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:90931a210325274184860d898df4e87a0972654edbb2a6185afcdce32244dfb6"}, + {file = "pygame-2.3.0-pp36-pypy36_pp73-win32.whl", hash = "sha256:1dc89d825e0ccba5ba3605abbd83be1401e0a32de7ab64b9647a6bb1ecb0a4f7"}, + {file = "pygame-2.3.0-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e323b75abda43345aff5ab2f6b1c017135f937f8a114d7aac8d95a07d200e19f"}, + {file = "pygame-2.3.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e13de2947c496fcb600fa4b5cd00a5fa33d4b3af9d13c169a5f79268268de0a8"}, + {file = "pygame-2.3.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:555234ed6b08242af95406fd3eb43255c3ce8e915e8c751f2d411bd40d574df4"}, + {file = "pygame-2.3.0-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:858d3968aebaca5015ef0ec82c513114a3c3fe64ce910222cfa852a39f03b135"}, + {file = "pygame-2.3.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:250b3ec3f90b05ad50cb0070d994a0a1f39fffe8181fc9508b8749884c313431"}, + {file = "pygame-2.3.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a5e83bd89da26f8360e02d5de2d2575981b0ebad81ea6d48aba610dabf167b88"}, + {file = "pygame-2.3.0-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c2961d44593aaa99580971e4123db00d4ca72fb4b30fa56350b3f6792331a41e"}, + {file = "pygame-2.3.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:385163fd1ed8809a72be68fddc9c76876c304e8712695aff2ea49adf3831caf9"}, + {file = "pygame-2.3.0.tar.gz", hash = "sha256:884b92c9cbf0bfaf8b8dd0f75a746613c55447d307ddd1addf903709b3b9f89f"}, +] + +[[package]] +name = "pyglet" +version = "2.0.5" +description = "Cross-platform windowing and multimedia library" +category = "main" +optional = false +python-versions = "*" +files = [ + {file = "pyglet-2.0.5-py3-none-any.whl", hash = "sha256:b0b94d0a3f9d0016bee506566fd13d7c62b5b9d10b6e16d32765d654959ba4dc"}, + {file = "pyglet-2.0.5.zip", hash = "sha256:c47ff4eded95104d030e0697eedd6082b61dc987460bbca83ec47b6e7cbfd38a"}, +] + +[[package]] +name = "pygments" +version = "2.14.0" +description = "Pygments is a syntax highlighting package written in Python." +category = "main" +optional = false +python-versions = ">=3.6" +files = [ + {file = "Pygments-2.14.0-py3-none-any.whl", hash = "sha256:fa7bd7bd2771287c0de303af8bfdfc731f51bd2c6a47ab69d117138893b82717"}, + {file = "Pygments-2.14.0.tar.gz", hash = "sha256:b3ed06a9e8ac9a9aae5a6f5dbe78a8a58655d17b43b93c078f094ddc476ae297"}, +] + +[package.extras] +plugins = ["importlib-metadata"] + +[[package]] +name = "pykeops" +version = "2.1.1" +description = "Python bindings of KeOps: KErnel OPerationS, on CPUs and GPUs, with autodiff and without memory overflows" +category = "main" +optional = false +python-versions = ">=3" +files = [ + {file = "pykeops-2.1.1.tar.gz", hash = "sha256:1931823c746345ce5a5805adad6baa1add772c6fe1800375f7f9a3ddb38b6f71"}, +] + +[package.dependencies] +keopscore = "2.1.1" +numpy = "*" +pybind11 = "*" + +[package.extras] +full = ["breathe", "faiss", "gpytorch", "h5py", "imageio", "jax", "jaxlib", "matplotlib", "multiprocess", "recommonmark", "scikit-learn", "sphinx", "sphinx-gallery", "sphinx-prompt", "sphinx_rtd_theme", "sphinxcontrib-httpdomain", "torch"] +test = ["numpy", "pytest", "torch"] + +[[package]] +name = "pylint" +version = "2.17.1" +description = "python code static checker" +category = "dev" +optional = false +python-versions = ">=3.7.2" +files = [ + {file = "pylint-2.17.1-py3-none-any.whl", hash = "sha256:8660a54e3f696243d644fca98f79013a959c03f979992c1ab59c24d3f4ec2700"}, + {file = "pylint-2.17.1.tar.gz", hash = "sha256:d4d009b0116e16845533bc2163493d6681846ac725eab8ca8014afb520178ddd"}, +] + +[package.dependencies] +astroid = ">=2.15.0,<=2.17.0-dev0" +colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} +dill = {version = ">=0.2", markers = "python_version < \"3.11\""} +isort = ">=4.2.5,<6" +mccabe = ">=0.6,<0.8" +platformdirs = ">=2.2.0" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +tomlkit = ">=0.10.1" + +[package.extras] +spelling = ["pyenchant (>=3.2,<4.0)"] +testutils = ["gitpython (>3)"] + +[[package]] +name = "pyopengl" +version = "3.1.0" +description = "Standard OpenGL bindings for Python" +category = "main" +optional = false +python-versions = "*" +files = [ + {file = "PyOpenGL-3.1.0.tar.gz", hash = "sha256:9b47c5c3a094fa518ca88aeed35ae75834d53e4285512c61879f67a48c94ddaf"}, + {file = "PyOpenGL-3.1.0.zip", hash = "sha256:efa4e39a49b906ccbe66758812ca81ced13a6f26931ab2ba2dba2750c016c0d0"}, +] + +[[package]] +name = "pyparsing" +version = "3.0.9" +description = "pyparsing module - Classes and methods to define and execute parsing grammars" +category = "main" +optional = false +python-versions = ">=3.6.8" +files = [ + {file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"}, + {file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"}, +] + +[package.extras] +diagrams = ["jinja2", "railroad-diagrams"] + +[[package]] +name = "pyqt5" +version = "5.15.9" +description = "Python bindings for the Qt cross platform application toolkit" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "PyQt5-5.15.9-cp37-abi3-macosx_10_13_x86_64.whl", hash = "sha256:883ba5c8a348be78c8be6a3d3ba014c798e679503bce00d76c666c2dc6afe828"}, + {file = "PyQt5-5.15.9-cp37-abi3-manylinux_2_17_x86_64.whl", hash = "sha256:dd5ce10e79fbf1df29507d2daf99270f2057cdd25e4de6fbf2052b46c652e3a5"}, + {file = "PyQt5-5.15.9-cp37-abi3-win32.whl", hash = "sha256:e45c5cc15d4fd26ab5cb0e5cdba60691a3e9086411f8e3662db07a5a4222a696"}, + {file = "PyQt5-5.15.9-cp37-abi3-win_amd64.whl", hash = "sha256:e030d795df4cbbfcf4f38b18e2e119bcc9e177ef658a5094b87bb16cac0ce4c5"}, + {file = "PyQt5-5.15.9.tar.gz", hash = "sha256:dc41e8401a90dc3e2b692b411bd5492ab559ae27a27424eed4bd3915564ec4c0"}, +] + +[package.dependencies] +PyQt5-Qt5 = ">=5.15.2" +PyQt5-sip = ">=12.11,<13" + +[[package]] +name = "pyqt5-qt5" +version = "5.15.2" +description = "The subset of a Qt installation needed by PyQt5." +category = "main" +optional = false +python-versions = "*" +files = [ + {file = "PyQt5_Qt5-5.15.2-py3-none-macosx_10_13_intel.whl", hash = "sha256:76980cd3d7ae87e3c7a33bfebfaee84448fd650bad6840471d6cae199b56e154"}, + {file = "PyQt5_Qt5-5.15.2-py3-none-manylinux2014_x86_64.whl", hash = "sha256:1988f364ec8caf87a6ee5d5a3a5210d57539988bf8e84714c7d60972692e2f4a"}, + {file = "PyQt5_Qt5-5.15.2-py3-none-win32.whl", hash = "sha256:9cc7a768b1921f4b982ebc00a318ccb38578e44e45316c7a4a850e953e1dd327"}, + {file = "PyQt5_Qt5-5.15.2-py3-none-win_amd64.whl", hash = "sha256:750b78e4dba6bdf1607febedc08738e318ea09e9b10aea9ff0d73073f11f6962"}, +] + +[[package]] +name = "pyqt5-sip" +version = "12.11.1" +description = "The sip module support for PyQt5" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "PyQt5_sip-12.11.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a40a39a6136a90e10c31510295c2be924564fc6260691501cdde669bdc5edea5"}, + {file = "PyQt5_sip-12.11.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:19b06164793177146c7f7604fe8389f44221a7bde196f2182457eb3e4229fa88"}, + {file = "PyQt5_sip-12.11.1-cp310-cp310-win32.whl", hash = "sha256:3afb1d1c07adcfef5c8bb12356a2ec2ec094f324af4417735d43b1ecaf1bb1a4"}, + {file = "PyQt5_sip-12.11.1-cp310-cp310-win_amd64.whl", hash = "sha256:54dad6c2e5dab14e46f6822a889bbb1515bbd2061762273af10d26566d649bd9"}, + {file = "PyQt5_sip-12.11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7218f6a1cefeb0b2fc26b89f15011f841aa4cd77786ccd863bf9792347fa38a8"}, + {file = "PyQt5_sip-12.11.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6b1113538082a7dd63b908587f61ce28ba4c7b8341e801fdf305d53a50a878ab"}, + {file = "PyQt5_sip-12.11.1-cp311-cp311-win32.whl", hash = "sha256:ac5f7ed06213d3bb203e33037f7c1a0716584c21f4f0922dcc044750e3659b80"}, + {file = "PyQt5_sip-12.11.1-cp311-cp311-win_amd64.whl", hash = "sha256:4f0497e2f5eeaea9f5a67b0e55c501168efa86df4e53aace2a46498b87bc55c1"}, + {file = "PyQt5_sip-12.11.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b355d56483edc79dcba30be947a6b700856bb74beb90539e14cc4d92b9bad152"}, + {file = "PyQt5_sip-12.11.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:dd163d9cffc4a56ebb9dd6908c0f0cb0caff8080294d41f4fb60fc3be63ca434"}, + {file = "PyQt5_sip-12.11.1-cp37-cp37m-win32.whl", hash = "sha256:b714f550ea6ddae94fd7acae531971e535f4a4e7277b62eb44e7c649cf3f03d0"}, + {file = "PyQt5_sip-12.11.1-cp37-cp37m-win_amd64.whl", hash = "sha256:d09b2586235deab7a5f2e28e4bde9a70c0b3730fa84f2590804a9932414136a3"}, + {file = "PyQt5_sip-12.11.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9a6f9c058564d0ac573561036299f54c452ae78b7d2a65d7c2d01685e6dca50d"}, + {file = "PyQt5_sip-12.11.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:fc920c0e0d5050474d2d6282b478e4957548bf1dce58e1b0678914514dc70064"}, + {file = "PyQt5_sip-12.11.1-cp38-cp38-win32.whl", hash = "sha256:3358c584832f0ac9fd1ad3623d8a346c705f43414df1fcd0cb285a6ef51fec08"}, + {file = "PyQt5_sip-12.11.1-cp38-cp38-win_amd64.whl", hash = "sha256:f9691c6f4d899ca762dd54442a1be158c3e52017f583183da6ef37d5bae86595"}, + {file = "PyQt5_sip-12.11.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:0bc81cb9e171d29302d393775f95cfa01b7a15f61b199ab1812976e5c4cb2cb9"}, + {file = "PyQt5_sip-12.11.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:b077fb4383536f51382f5516f0347328a4f338c6ccc4c268cc358643bef1b838"}, + {file = "PyQt5_sip-12.11.1-cp39-cp39-win32.whl", hash = "sha256:5c152878443c3e951d5db7df53509d444708dc06a121c267b548146be06b87f8"}, + {file = "PyQt5_sip-12.11.1-cp39-cp39-win_amd64.whl", hash = "sha256:bd935cc46dfdbb89c21042c1db2e46a71f25693af57272f146d6d9418e2934f1"}, + {file = "PyQt5_sip-12.11.1.tar.gz", hash = "sha256:97d3fbda0f61edb1be6529ec2d5c7202ae83aee4353e4b264a159f8c9ada4369"}, +] + +[[package]] +name = "pyrender" +version = "0.1.45" +description = "Easy-to-use Python renderer for 3D visualization" +category = "main" +optional = false +python-versions = "*" +files = [ + {file = "pyrender-0.1.45-py3-none-any.whl", hash = "sha256:5cf751d1f21fba4640e830cef3a0b5a95ed0f05677bf92c6b8330056b4023aeb"}, + {file = "pyrender-0.1.45.tar.gz", hash = "sha256:284b2432bf6832f05c5216c4b979ceb514ea78163bf53b8ce2bdf0069cb3b92e"}, +] + +[package.dependencies] +freetype-py = "*" +imageio = "*" +networkx = "*" +numpy = "*" +Pillow = "*" +pyglet = ">=1.4.10" +PyOpenGL = "3.1.0" +scipy = "*" +six = "*" +trimesh = "*" + +[package.extras] +dev = ["flake8", "pre-commit", "pytest", "pytest-cov", "tox"] +docs = ["sphinx", "sphinx-automodapi", "sphinx-rtd-theme"] + +[[package]] +name = "pyrsistent" +version = "0.19.3" +description = "Persistent/Functional/Immutable data structures" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pyrsistent-0.19.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:20460ac0ea439a3e79caa1dbd560344b64ed75e85d8703943e0b66c2a6150e4a"}, + {file = "pyrsistent-0.19.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4c18264cb84b5e68e7085a43723f9e4c1fd1d935ab240ce02c0324a8e01ccb64"}, + {file = "pyrsistent-0.19.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4b774f9288dda8d425adb6544e5903f1fb6c273ab3128a355c6b972b7df39dcf"}, + {file = "pyrsistent-0.19.3-cp310-cp310-win32.whl", hash = "sha256:5a474fb80f5e0d6c9394d8db0fc19e90fa540b82ee52dba7d246a7791712f74a"}, + {file = "pyrsistent-0.19.3-cp310-cp310-win_amd64.whl", hash = "sha256:49c32f216c17148695ca0e02a5c521e28a4ee6c5089f97e34fe24163113722da"}, + {file = "pyrsistent-0.19.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f0774bf48631f3a20471dd7c5989657b639fd2d285b861237ea9e82c36a415a9"}, + {file = "pyrsistent-0.19.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ab2204234c0ecd8b9368dbd6a53e83c3d4f3cab10ecaf6d0e772f456c442393"}, + {file = "pyrsistent-0.19.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e42296a09e83028b3476f7073fcb69ffebac0e66dbbfd1bd847d61f74db30f19"}, + {file = "pyrsistent-0.19.3-cp311-cp311-win32.whl", hash = "sha256:64220c429e42a7150f4bfd280f6f4bb2850f95956bde93c6fda1b70507af6ef3"}, + {file = "pyrsistent-0.19.3-cp311-cp311-win_amd64.whl", hash = "sha256:016ad1afadf318eb7911baa24b049909f7f3bb2c5b1ed7b6a8f21db21ea3faa8"}, + {file = "pyrsistent-0.19.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c4db1bd596fefd66b296a3d5d943c94f4fac5bcd13e99bffe2ba6a759d959a28"}, + {file = "pyrsistent-0.19.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aeda827381f5e5d65cced3024126529ddc4289d944f75e090572c77ceb19adbf"}, + {file = "pyrsistent-0.19.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:42ac0b2f44607eb92ae88609eda931a4f0dfa03038c44c772e07f43e738bcac9"}, + {file = "pyrsistent-0.19.3-cp37-cp37m-win32.whl", hash = "sha256:e8f2b814a3dc6225964fa03d8582c6e0b6650d68a232df41e3cc1b66a5d2f8d1"}, + {file = "pyrsistent-0.19.3-cp37-cp37m-win_amd64.whl", hash = "sha256:c9bb60a40a0ab9aba40a59f68214eed5a29c6274c83b2cc206a359c4a89fa41b"}, + {file = "pyrsistent-0.19.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:a2471f3f8693101975b1ff85ffd19bb7ca7dd7c38f8a81701f67d6b4f97b87d8"}, + {file = "pyrsistent-0.19.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc5d149f31706762c1f8bda2e8c4f8fead6e80312e3692619a75301d3dbb819a"}, + {file = "pyrsistent-0.19.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3311cb4237a341aa52ab8448c27e3a9931e2ee09561ad150ba94e4cfd3fc888c"}, + {file = "pyrsistent-0.19.3-cp38-cp38-win32.whl", hash = "sha256:f0e7c4b2f77593871e918be000b96c8107da48444d57005b6a6bc61fb4331b2c"}, + {file = "pyrsistent-0.19.3-cp38-cp38-win_amd64.whl", hash = "sha256:c147257a92374fde8498491f53ffa8f4822cd70c0d85037e09028e478cababb7"}, + {file = "pyrsistent-0.19.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b735e538f74ec31378f5a1e3886a26d2ca6351106b4dfde376a26fc32a044edc"}, + {file = "pyrsistent-0.19.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99abb85579e2165bd8522f0c0138864da97847875ecbd45f3e7e2af569bfc6f2"}, + {file = "pyrsistent-0.19.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3a8cb235fa6d3fd7aae6a4f1429bbb1fec1577d978098da1252f0489937786f3"}, + {file = "pyrsistent-0.19.3-cp39-cp39-win32.whl", hash = "sha256:c74bed51f9b41c48366a286395c67f4e894374306b197e62810e0fdaf2364da2"}, + {file = "pyrsistent-0.19.3-cp39-cp39-win_amd64.whl", hash = "sha256:878433581fc23e906d947a6814336eee031a00e6defba224234169ae3d3d6a98"}, + {file = "pyrsistent-0.19.3-py3-none-any.whl", hash = "sha256:ccf0d6bd208f8111179f0c26fdf84ed7c3891982f2edaeae7422575f47e66b64"}, + {file = "pyrsistent-0.19.3.tar.gz", hash = "sha256:1a2994773706bbb4995c31a97bc94f1418314923bd1048c6d964837040376440"}, +] + +[[package]] +name = "python-dateutil" +version = "2.8.2" +description = "Extensions to the standard Python datetime module" +category = "main" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +files = [ + {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, + {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, +] + +[package.dependencies] +six = ">=1.5" + +[[package]] +name = "python-json-logger" +version = "2.0.7" +description = "A python library adding a json log formatter" +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ + {file = "python-json-logger-2.0.7.tar.gz", hash = "sha256:23e7ec02d34237c5aa1e29a070193a4ea87583bb4e7f8fd06d3de8264c4b2e1c"}, + {file = "python_json_logger-2.0.7-py3-none-any.whl", hash = "sha256:f380b826a991ebbe3de4d897aeec42760035ac760345e57b812938dc8b35e2bd"}, +] + +[[package]] +name = "python-lsp-jsonrpc" +version = "1.0.0" +description = "JSON RPC 2.0 server library" +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "python-lsp-jsonrpc-1.0.0.tar.gz", hash = "sha256:7bec170733db628d3506ea3a5288ff76aa33c70215ed223abdb0d95e957660bd"}, + {file = "python_lsp_jsonrpc-1.0.0-py3-none-any.whl", hash = "sha256:079b143be64b0a378bdb21dff5e28a8c1393fe7e8a654ef068322d754e545fc7"}, +] + +[package.dependencies] +ujson = ">=3.0.0" + +[package.extras] +test = ["coverage", "pycodestyle", "pyflakes", "pylint", "pytest", "pytest-cov"] + +[[package]] +name = "python-lsp-server" +version = "1.7.1" +description = "Python Language Server for the Language Server Protocol" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "python-lsp-server-1.7.1.tar.gz", hash = "sha256:67473bb301f35434b5fa8b21fc5ed5fac27dc8a8446ccec8bae456af52a0aef6"}, + {file = "python_lsp_server-1.7.1-py3-none-any.whl", hash = "sha256:8f8b382868b161199aa385659b28427890be628d86f54810463a4d0ee0d6d091"}, +] + +[package.dependencies] +autopep8 = {version = ">=1.6.0,<1.7.0", optional = true, markers = "extra == \"all\""} +docstring-to-markdown = "*" +flake8 = {version = ">=5.0.0,<7", optional = true, markers = "extra == \"all\""} +jedi = ">=0.17.2,<0.19.0" +mccabe = {version = ">=0.7.0,<0.8.0", optional = true, markers = "extra == \"all\""} +pluggy = ">=1.0.0" +pycodestyle = {version = ">=2.9.0,<2.11.0", optional = true, markers = "extra == \"all\""} +pydocstyle = {version = ">=6.2.0,<6.3.0", optional = true, markers = "extra == \"all\""} +pyflakes = {version = ">=2.5.0,<3.1.0", optional = true, markers = "extra == \"all\""} +pylint = {version = ">=2.5.0,<3", optional = true, markers = "extra == \"all\""} +python-lsp-jsonrpc = ">=1.0.0" +rope = {version = ">1.2.0", optional = true, markers = "extra == \"all\""} +setuptools = ">=39.0.0" +ujson = ">=3.0.0" +whatthepatch = {version = ">=1.0.2,<2.0.0", optional = true, markers = "extra == \"all\""} +yapf = {version = "*", optional = true, markers = "extra == \"all\""} + +[package.extras] +all = ["autopep8 (>=1.6.0,<1.7.0)", "flake8 (>=5.0.0,<7)", "mccabe (>=0.7.0,<0.8.0)", "pycodestyle (>=2.9.0,<2.11.0)", "pydocstyle (>=6.2.0,<6.3.0)", "pyflakes (>=2.5.0,<3.1.0)", "pylint (>=2.5.0,<3)", "rope (>1.2.0)", "whatthepatch (>=1.0.2,<2.0.0)", "yapf"] +autopep8 = ["autopep8 (>=1.6.0,<1.7.0)"] +flake8 = ["flake8 (>=5.0.0,<7)"] +mccabe = ["mccabe (>=0.7.0,<0.8.0)"] +pycodestyle = ["pycodestyle (>=2.9.0,<2.11.0)"] +pydocstyle = ["pydocstyle (>=6.2.0,<6.3.0)"] +pyflakes = ["pyflakes (>=2.5.0,<3.1.0)"] +pylint = ["pylint (>=2.5.0,<3)"] +rope = ["rope (>1.2.0)"] +test = ["coverage", "flaky", "matplotlib", "numpy", "pandas", "pylint (>=2.5.0,<3)", "pyqt5", "pytest", "pytest-cov"] +websockets = ["websockets (>=10.3)"] +yapf = ["whatthepatch (>=1.0.2,<2.0.0)", "yapf"] + +[[package]] +name = "pytoolconfig" +version = "1.2.5" +description = "Python tool configuration" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pytoolconfig-1.2.5-py3-none-any.whl", hash = "sha256:239ba9d3e537b91d0243275a497700ea39a5e259ddb80421c366e3b288bf30fe"}, + {file = "pytoolconfig-1.2.5.tar.gz", hash = "sha256:a50f9dfe23b03a9d40414c1fdf902fefbeae12f2ac75a3c8f915944d6ffac279"}, +] + +[package.dependencies] +packaging = ">=22.0" +platformdirs = {version = ">=1.4.4", optional = true, markers = "extra == \"global\""} +tomli = {version = ">=2.0.1", markers = "python_version < \"3.11\""} + +[package.extras] +doc = ["sphinx (>=4.5.0)", "tabulate (>=0.8.9)"] +gendocs = ["pytoolconfig[doc]", "sphinx (>=4.5.0)", "sphinx-autodoc-typehints (>=1.18.1)", "sphinx-rtd-theme (>=1.0.0)"] +global = ["platformdirs (>=1.4.4)"] +validation = ["pydantic (>=1.7.4)"] + +[[package]] +name = "pytorch-lightning" +version = "1.9.4" +description = "PyTorch Lightning is the lightweight PyTorch wrapper for ML researchers. Scale your models. Write less boilerplate." +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pytorch-lightning-1.9.4.tar.gz", hash = "sha256:188a7f4468acf23512e7f4903253d86fc7929a49f0c09d699872e364162001e8"}, + {file = "pytorch_lightning-1.9.4-py3-none-any.whl", hash = "sha256:a2d2bd7657716087c294b076fe385ed17879764d6daaad0a541394a8f7164f93"}, +] + +[package.dependencies] +fsspec = {version = ">2021.06.0", extras = ["http"]} +lightning-utilities = ">=0.6.0.post0" +numpy = ">=1.17.2" +packaging = ">=17.1" +PyYAML = ">=5.4" +torch = ">=1.10.0" +torchmetrics = ">=0.7.0" +tqdm = ">=4.57.0" +typing-extensions = ">=4.0.0" + +[package.extras] +all = ["colossalai (>=0.2.0)", "deepspeed (>=0.6.0)", "fairscale (>=0.4.5)", "gym[classic-control] (>=0.17.0)", "hivemind (==1.1.5)", "horovod (>=0.21.2,!=0.24.0)", "hydra-core (>=1.0.5)", "ipython[all] (<8.7.1)", "jsonargparse[signatures] (>=4.18.0)", "matplotlib (>3.1)", "omegaconf (>=2.0.5)", "rich (>=10.14.0,!=10.15.0.a)", "tensorboardX (>=2.2)", "torchvision (>=0.11.1)"] +colossalai = ["colossalai (>=0.2.0)"] +deepspeed = ["deepspeed (>=0.6.0)"] +dev = ["cloudpickle (>=1.3)", "codecov (==2.1.12)", "colossalai (>=0.2.0)", "coverage (==6.5.0)", "deepspeed (>=0.6.0)", "fairscale (>=0.4.5)", "fastapi (<0.87.0)", "gym[classic-control] (>=0.17.0)", "hivemind (==1.1.5)", "horovod (>=0.21.2,!=0.24.0)", "hydra-core (>=1.0.5)", "ipython[all] (<8.7.1)", "jsonargparse[signatures] (>=4.18.0)", "matplotlib (>3.1)", "omegaconf (>=2.0.5)", "onnxruntime (<1.14.0)", "pandas (>1.0)", "pre-commit (==2.20.0)", "protobuf (<=3.20.1)", "psutil (<5.9.5)", "pytest (==7.2.0)", "pytest-cov (==4.0.0)", "pytest-forked (==1.4.0)", "pytest-rerunfailures (==10.3)", "rich (>=10.14.0,!=10.15.0.a)", "scikit-learn (>0.22.1)", "tensorboard (>=2.9.1)", "tensorboardX (>=2.2)", "torchvision (>=0.11.1)", "uvicorn (<0.19.1)"] +examples = ["gym[classic-control] (>=0.17.0)", "ipython[all] (<8.7.1)", "torchvision (>=0.11.1)"] +extra = ["hydra-core (>=1.0.5)", "jsonargparse[signatures] (>=4.18.0)", "matplotlib (>3.1)", "omegaconf (>=2.0.5)", "rich (>=10.14.0,!=10.15.0.a)", "tensorboardX (>=2.2)"] +fairscale = ["fairscale (>=0.4.5)"] +hivemind = ["hivemind (==1.1.5)"] +horovod = ["horovod (>=0.21.2,!=0.24.0)"] +strategies = ["colossalai (>=0.2.0)", "deepspeed (>=0.6.0)", "fairscale (>=0.4.5)", "hivemind (==1.1.5)", "horovod (>=0.21.2,!=0.24.0)"] +test = ["cloudpickle (>=1.3)", "codecov (==2.1.12)", "coverage (==6.5.0)", "fastapi (<0.87.0)", "onnxruntime (<1.14.0)", "pandas (>1.0)", "pre-commit (==2.20.0)", "protobuf (<=3.20.1)", "psutil (<5.9.5)", "pytest (==7.2.0)", "pytest-cov (==4.0.0)", "pytest-forked (==1.4.0)", "pytest-rerunfailures (==10.3)", "scikit-learn (>0.22.1)", "tensorboard (>=2.9.1)", "uvicorn (<0.19.1)"] + +[[package]] +name = "pytorch3d" +version = "0.7.2" +description = "PyTorch3D is FAIR's library of reusable components for deep Learning with 3D data." +category = "main" +optional = false +python-versions = "*" +files = [ + {file = "pytorch3d-0.7.2-cp310-cp310-linux_x86_64.whl", hash = "sha256:52f9af847687efa608825452d6c54d398af56bccf72cfcb0a44bd06a67b8ee70"}, +] + +[package.dependencies] +fvcore = "*" +iopath = "*" + +[package.extras] +all = ["imageio", "ipywidgets", "matplotlib", "tqdm (>4.29.0)"] +dev = ["flake8", "usort"] +implicitron = ["accelerate", "hydra-core (>=1.1)", "lpips", "matplotlib", "tqdm (>4.29.0)", "visdom"] + +[package.source] +type = "url" +url = "https://dl.fbaipublicfiles.com/pytorch3d/packaging/wheels/py310_cu116_pyt1130/pytorch3d-0.7.2-cp310-cp310-linux_x86_64.whl" + +[[package]] +name = "pytz" +version = "2022.7.1" +description = "World timezone definitions, modern and historical" +category = "main" +optional = false +python-versions = "*" +files = [ + {file = "pytz-2022.7.1-py2.py3-none-any.whl", hash = "sha256:78f4f37d8198e0627c5f1143240bb0206b8691d8d7ac6d78fee88b78733f8c4a"}, + {file = "pytz-2022.7.1.tar.gz", hash = "sha256:01a0681c4b9684a28304615eba55d1ab31ae00bf68ec157ec3708a8182dbbcd0"}, +] + +[[package]] +name = "pywavelets" +version = "1.4.1" +description = "PyWavelets, wavelet transform module" +category = "main" +optional = false +python-versions = ">=3.8" +files = [ + {file = "PyWavelets-1.4.1-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:d854411eb5ee9cb4bc5d0e66e3634aeb8f594210f6a1bed96dbed57ec70f181c"}, + {file = "PyWavelets-1.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:231b0e0b1cdc1112f4af3c24eea7bf181c418d37922a67670e9bf6cfa2d544d4"}, + {file = "PyWavelets-1.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:754fa5085768227c4f4a26c1e0c78bc509a266d9ebd0eb69a278be7e3ece943c"}, + {file = "PyWavelets-1.4.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da7b9c006171be1f9ddb12cc6e0d3d703b95f7f43cb5e2c6f5f15d3233fcf202"}, + {file = "PyWavelets-1.4.1-cp310-cp310-win32.whl", hash = "sha256:67a0d28a08909f21400cb09ff62ba94c064882ffd9e3a6b27880a111211d59bd"}, + {file = "PyWavelets-1.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:91d3d393cffa634f0e550d88c0e3f217c96cfb9e32781f2960876f1808d9b45b"}, + {file = "PyWavelets-1.4.1-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:64c6bac6204327321db30b775060fbe8e8642316e6bff17f06b9f34936f88875"}, + {file = "PyWavelets-1.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3f19327f2129fb7977bc59b966b4974dfd72879c093e44a7287500a7032695de"}, + {file = "PyWavelets-1.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad987748f60418d5f4138db89d82ba0cb49b086e0cbb8fd5c3ed4a814cfb705e"}, + {file = "PyWavelets-1.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:875d4d620eee655346e3589a16a73790cf9f8917abba062234439b594e706784"}, + {file = "PyWavelets-1.4.1-cp311-cp311-win32.whl", hash = "sha256:7231461d7a8eb3bdc7aa2d97d9f67ea5a9f8902522818e7e2ead9c2b3408eeb1"}, + {file = "PyWavelets-1.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:daf0aa79842b571308d7c31a9c43bc99a30b6328e6aea3f50388cd8f69ba7dbc"}, + {file = "PyWavelets-1.4.1-cp38-cp38-macosx_10_13_x86_64.whl", hash = "sha256:ab7da0a17822cd2f6545626946d3b82d1a8e106afc4b50e3387719ba01c7b966"}, + {file = "PyWavelets-1.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:578af438a02a86b70f1975b546f68aaaf38f28fb082a61ceb799816049ed18aa"}, + {file = "PyWavelets-1.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cb5ca8d11d3f98e89e65796a2125be98424d22e5ada360a0dbabff659fca0fc"}, + {file = "PyWavelets-1.4.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:058b46434eac4c04dd89aeef6fa39e4b6496a951d78c500b6641fd5b2cc2f9f4"}, + {file = "PyWavelets-1.4.1-cp38-cp38-win32.whl", hash = "sha256:de7cd61a88a982edfec01ea755b0740e94766e00a1ceceeafef3ed4c85c605cd"}, + {file = "PyWavelets-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:7ab8d9db0fe549ab2ee0bea61f614e658dd2df419d5b75fba47baa761e95f8f2"}, + {file = "PyWavelets-1.4.1-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:23bafd60350b2b868076d976bdd92f950b3944f119b4754b1d7ff22b7acbf6c6"}, + {file = "PyWavelets-1.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d0e56cd7a53aed3cceca91a04d62feb3a0aca6725b1912d29546c26f6ea90426"}, + {file = "PyWavelets-1.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:030670a213ee8fefa56f6387b0c8e7d970c7f7ad6850dc048bd7c89364771b9b"}, + {file = "PyWavelets-1.4.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:71ab30f51ee4470741bb55fc6b197b4a2b612232e30f6ac069106f0156342356"}, + {file = "PyWavelets-1.4.1-cp39-cp39-win32.whl", hash = "sha256:47cac4fa25bed76a45bc781a293c26ac63e8eaae9eb8f9be961758d22b58649c"}, + {file = "PyWavelets-1.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:88aa5449e109d8f5e7f0adef85f7f73b1ab086102865be64421a3a3d02d277f4"}, + {file = "PyWavelets-1.4.1.tar.gz", hash = "sha256:6437af3ddf083118c26d8f97ab43b0724b956c9f958e9ea788659f6a2834ba93"}, +] + +[package.dependencies] +numpy = ">=1.17.3" + +[[package]] +name = "pywin32" +version = "305" +description = "Python for Window Extensions" +category = "main" +optional = false +python-versions = "*" +files = [ + {file = "pywin32-305-cp310-cp310-win32.whl", hash = "sha256:421f6cd86e84bbb696d54563c48014b12a23ef95a14e0bdba526be756d89f116"}, + {file = "pywin32-305-cp310-cp310-win_amd64.whl", hash = "sha256:73e819c6bed89f44ff1d690498c0a811948f73777e5f97c494c152b850fad478"}, + {file = "pywin32-305-cp310-cp310-win_arm64.whl", hash = "sha256:742eb905ce2187133a29365b428e6c3b9001d79accdc30aa8969afba1d8470f4"}, + {file = "pywin32-305-cp311-cp311-win32.whl", hash = "sha256:19ca459cd2e66c0e2cc9a09d589f71d827f26d47fe4a9d09175f6aa0256b51c2"}, + {file = "pywin32-305-cp311-cp311-win_amd64.whl", hash = "sha256:326f42ab4cfff56e77e3e595aeaf6c216712bbdd91e464d167c6434b28d65990"}, + {file = "pywin32-305-cp311-cp311-win_arm64.whl", hash = "sha256:4ecd404b2c6eceaca52f8b2e3e91b2187850a1ad3f8b746d0796a98b4cea04db"}, + {file = "pywin32-305-cp36-cp36m-win32.whl", hash = "sha256:48d8b1659284f3c17b68587af047d110d8c44837736b8932c034091683e05863"}, + {file = "pywin32-305-cp36-cp36m-win_amd64.whl", hash = "sha256:13362cc5aa93c2beaf489c9c9017c793722aeb56d3e5166dadd5ef82da021fe1"}, + {file = "pywin32-305-cp37-cp37m-win32.whl", hash = "sha256:a55db448124d1c1484df22fa8bbcbc45c64da5e6eae74ab095b9ea62e6d00496"}, + {file = "pywin32-305-cp37-cp37m-win_amd64.whl", hash = "sha256:109f98980bfb27e78f4df8a51a8198e10b0f347257d1e265bb1a32993d0c973d"}, + {file = "pywin32-305-cp38-cp38-win32.whl", hash = "sha256:9dd98384da775afa009bc04863426cb30596fd78c6f8e4e2e5bbf4edf8029504"}, + {file = "pywin32-305-cp38-cp38-win_amd64.whl", hash = "sha256:56d7a9c6e1a6835f521788f53b5af7912090674bb84ef5611663ee1595860fc7"}, + {file = "pywin32-305-cp39-cp39-win32.whl", hash = "sha256:9d968c677ac4d5cbdaa62fd3014ab241718e619d8e36ef8e11fb930515a1e918"}, + {file = "pywin32-305-cp39-cp39-win_amd64.whl", hash = "sha256:50768c6b7c3f0b38b7fb14dd4104da93ebced5f1a50dc0e834594bff6fbe1271"}, +] + +[[package]] +name = "pywinpty" +version = "2.0.10" +description = "Pseudo terminal support for Windows from Python." +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pywinpty-2.0.10-cp310-none-win_amd64.whl", hash = "sha256:4c7d06ad10f6e92bc850a467f26d98f4f30e73d2fe5926536308c6ae0566bc16"}, + {file = "pywinpty-2.0.10-cp311-none-win_amd64.whl", hash = "sha256:7ffbd66310b83e42028fc9df7746118978d94fba8c1ebf15a7c1275fdd80b28a"}, + {file = "pywinpty-2.0.10-cp37-none-win_amd64.whl", hash = "sha256:38cb924f2778b5751ef91a75febd114776b3af0ae411bc667be45dd84fc881d3"}, + {file = "pywinpty-2.0.10-cp38-none-win_amd64.whl", hash = "sha256:902d79444b29ad1833b8d5c3c9aabdfd428f4f068504430df18074007c8c0de8"}, + {file = "pywinpty-2.0.10-cp39-none-win_amd64.whl", hash = "sha256:3c46aef80dd50979aff93de199e4a00a8ee033ba7a03cadf0a91fed45f0c39d7"}, + {file = "pywinpty-2.0.10.tar.gz", hash = "sha256:cdbb5694cf8c7242c2ecfaca35c545d31fa5d5814c3d67a4e628f803f680ebea"}, +] + +[[package]] +name = "pyyaml" +version = "6.0" +description = "YAML parser and emitter for Python" +category = "main" +optional = false +python-versions = ">=3.6" +files = [ + {file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"}, + {file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"}, + {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc"}, + {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b"}, + {file = "PyYAML-6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"}, + {file = "PyYAML-6.0-cp310-cp310-win32.whl", hash = "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513"}, + {file = "PyYAML-6.0-cp310-cp310-win_amd64.whl", hash = "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a"}, + {file = "PyYAML-6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d4b0ba9512519522b118090257be113b9468d804b19d63c71dbcf4a48fa32358"}, + {file = "PyYAML-6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:81957921f441d50af23654aa6c5e5eaf9b06aba7f0a19c18a538dc7ef291c5a1"}, + {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afa17f5bc4d1b10afd4466fd3a44dc0e245382deca5b3c353d8b757f9e3ecb8d"}, + {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dbad0e9d368bb989f4515da330b88a057617d16b6a8245084f1b05400f24609f"}, + {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:432557aa2c09802be39460360ddffd48156e30721f5e8d917f01d31694216782"}, + {file = "PyYAML-6.0-cp311-cp311-win32.whl", hash = "sha256:bfaef573a63ba8923503d27530362590ff4f576c626d86a9fed95822a8255fd7"}, + {file = "PyYAML-6.0-cp311-cp311-win_amd64.whl", hash = "sha256:01b45c0191e6d66c470b6cf1b9531a771a83c1c4208272ead47a3ae4f2f603bf"}, + {file = "PyYAML-6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86"}, + {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f"}, + {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92"}, + {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4"}, + {file = "PyYAML-6.0-cp36-cp36m-win32.whl", hash = "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293"}, + {file = "PyYAML-6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57"}, + {file = "PyYAML-6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c"}, + {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0"}, + {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4"}, + {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9"}, + {file = "PyYAML-6.0-cp37-cp37m-win32.whl", hash = "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737"}, + {file = "PyYAML-6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d"}, + {file = "PyYAML-6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b"}, + {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba"}, + {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34"}, + {file = "PyYAML-6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287"}, + {file = "PyYAML-6.0-cp38-cp38-win32.whl", hash = "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78"}, + {file = "PyYAML-6.0-cp38-cp38-win_amd64.whl", hash = "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07"}, + {file = "PyYAML-6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b"}, + {file = "PyYAML-6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174"}, + {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803"}, + {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3"}, + {file = "PyYAML-6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0"}, + {file = "PyYAML-6.0-cp39-cp39-win32.whl", hash = "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb"}, + {file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"}, + {file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"}, +] + +[[package]] +name = "pyzmq" +version = "25.0.2" +description = "Python bindings for 0MQ" +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ + {file = "pyzmq-25.0.2-cp310-cp310-macosx_10_15_universal2.whl", hash = "sha256:ac178e666c097c8d3deb5097b58cd1316092fc43e8ef5b5fdb259b51da7e7315"}, + {file = "pyzmq-25.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:659e62e1cbb063151c52f5b01a38e1df6b54feccfa3e2509d44c35ca6d7962ee"}, + {file = "pyzmq-25.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8280ada89010735a12b968ec3ea9a468ac2e04fddcc1cede59cb7f5178783b9c"}, + {file = "pyzmq-25.0.2-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9b5eeb5278a8a636bb0abdd9ff5076bcbb836cd2302565df53ff1fa7d106d54"}, + {file = "pyzmq-25.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a2e5fe42dfe6b73ca120b97ac9f34bfa8414feb15e00e37415dbd51cf227ef6"}, + {file = "pyzmq-25.0.2-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:827bf60e749e78acb408a6c5af6688efbc9993e44ecc792b036ec2f4b4acf485"}, + {file = "pyzmq-25.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:7b504ae43d37e282301da586529e2ded8b36d4ee2cd5e6db4386724ddeaa6bbc"}, + {file = "pyzmq-25.0.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:cb1f69a0a2a2b1aae8412979dd6293cc6bcddd4439bf07e4758d864ddb112354"}, + {file = "pyzmq-25.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2b9c9cc965cdf28381e36da525dcb89fc1571d9c54800fdcd73e3f73a2fc29bd"}, + {file = "pyzmq-25.0.2-cp310-cp310-win32.whl", hash = "sha256:24abbfdbb75ac5039205e72d6c75f10fc39d925f2df8ff21ebc74179488ebfca"}, + {file = "pyzmq-25.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:6a821a506822fac55d2df2085a52530f68ab15ceed12d63539adc32bd4410f6e"}, + {file = "pyzmq-25.0.2-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:9af0bb0277e92f41af35e991c242c9c71920169d6aa53ade7e444f338f4c8128"}, + {file = "pyzmq-25.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:54a96cf77684a3a537b76acfa7237b1e79a8f8d14e7f00e0171a94b346c5293e"}, + {file = "pyzmq-25.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88649b19ede1cab03b96b66c364cbbf17c953615cdbc844f7f6e5f14c5e5261c"}, + {file = "pyzmq-25.0.2-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:715cff7644a80a7795953c11b067a75f16eb9fc695a5a53316891ebee7f3c9d5"}, + {file = "pyzmq-25.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:312b3f0f066b4f1d17383aae509bacf833ccaf591184a1f3c7a1661c085063ae"}, + {file = "pyzmq-25.0.2-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:d488c5c8630f7e782e800869f82744c3aca4aca62c63232e5d8c490d3d66956a"}, + {file = "pyzmq-25.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:38d9f78d69bcdeec0c11e0feb3bc70f36f9b8c44fc06e5d06d91dc0a21b453c7"}, + {file = "pyzmq-25.0.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:3059a6a534c910e1d5d068df42f60d434f79e6cc6285aa469b384fa921f78cf8"}, + {file = "pyzmq-25.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6526d097b75192f228c09d48420854d53dfbc7abbb41b0e26f363ccb26fbc177"}, + {file = "pyzmq-25.0.2-cp311-cp311-win32.whl", hash = "sha256:5c5fbb229e40a89a2fe73d0c1181916f31e30f253cb2d6d91bea7927c2e18413"}, + {file = "pyzmq-25.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:ed15e3a2c3c2398e6ae5ce86d6a31b452dfd6ad4cd5d312596b30929c4b6e182"}, + {file = "pyzmq-25.0.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:032f5c8483c85bf9c9ca0593a11c7c749d734ce68d435e38c3f72e759b98b3c9"}, + {file = "pyzmq-25.0.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:374b55516393bfd4d7a7daa6c3b36d6dd6a31ff9d2adad0838cd6a203125e714"}, + {file = "pyzmq-25.0.2-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:08bfcc21b5997a9be4fefa405341320d8e7f19b4d684fb9c0580255c5bd6d695"}, + {file = "pyzmq-25.0.2-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:1a843d26a8da1b752c74bc019c7b20e6791ee813cd6877449e6a1415589d22ff"}, + {file = "pyzmq-25.0.2-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:b48616a09d7df9dbae2f45a0256eee7b794b903ddc6d8657a9948669b345f220"}, + {file = "pyzmq-25.0.2-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:d4427b4a136e3b7f85516c76dd2e0756c22eec4026afb76ca1397152b0ca8145"}, + {file = "pyzmq-25.0.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:26b0358e8933990502f4513c991c9935b6c06af01787a36d133b7c39b1df37fa"}, + {file = "pyzmq-25.0.2-cp36-cp36m-win32.whl", hash = "sha256:c8fedc3ccd62c6b77dfe6f43802057a803a411ee96f14e946f4a76ec4ed0e117"}, + {file = "pyzmq-25.0.2-cp36-cp36m-win_amd64.whl", hash = "sha256:2da6813b7995b6b1d1307329c73d3e3be2fd2d78e19acfc4eff2e27262732388"}, + {file = "pyzmq-25.0.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a35960c8b2f63e4ef67fd6731851030df68e4b617a6715dd11b4b10312d19fef"}, + {file = "pyzmq-25.0.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eef2a0b880ab40aca5a878933376cb6c1ec483fba72f7f34e015c0f675c90b20"}, + {file = "pyzmq-25.0.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:85762712b74c7bd18e340c3639d1bf2f23735a998d63f46bb6584d904b5e401d"}, + {file = "pyzmq-25.0.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:64812f29d6eee565e129ca14b0c785744bfff679a4727137484101b34602d1a7"}, + {file = "pyzmq-25.0.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:510d8e55b3a7cd13f8d3e9121edf0a8730b87d925d25298bace29a7e7bc82810"}, + {file = "pyzmq-25.0.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:b164cc3c8acb3d102e311f2eb6f3c305865ecb377e56adc015cb51f721f1dda6"}, + {file = "pyzmq-25.0.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:28fdb9224a258134784a9cf009b59265a9dde79582fb750d4e88a6bcbc6fa3dc"}, + {file = "pyzmq-25.0.2-cp37-cp37m-win32.whl", hash = "sha256:dd771a440effa1c36d3523bc6ba4e54ff5d2e54b4adcc1e060d8f3ca3721d228"}, + {file = "pyzmq-25.0.2-cp37-cp37m-win_amd64.whl", hash = "sha256:9bdc40efb679b9dcc39c06d25629e55581e4c4f7870a5e88db4f1c51ce25e20d"}, + {file = "pyzmq-25.0.2-cp38-cp38-macosx_10_15_universal2.whl", hash = "sha256:1f82906a2d8e4ee310f30487b165e7cc8ed09c009e4502da67178b03083c4ce0"}, + {file = "pyzmq-25.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:21ec0bf4831988af43c8d66ba3ccd81af2c5e793e1bf6790eb2d50e27b3c570a"}, + {file = "pyzmq-25.0.2-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:abbce982a17c88d2312ec2cf7673985d444f1beaac6e8189424e0a0e0448dbb3"}, + {file = "pyzmq-25.0.2-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:9e1d2f2d86fc75ed7f8845a992c5f6f1ab5db99747fb0d78b5e4046d041164d2"}, + {file = "pyzmq-25.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a2e92ff20ad5d13266bc999a29ed29a3b5b101c21fdf4b2cf420c09db9fb690e"}, + {file = "pyzmq-25.0.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:edbbf06cc2719889470a8d2bf5072bb00f423e12de0eb9ffec946c2c9748e149"}, + {file = "pyzmq-25.0.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:77942243ff4d14d90c11b2afd8ee6c039b45a0be4e53fb6fa7f5e4fd0b59da39"}, + {file = "pyzmq-25.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ab046e9cb902d1f62c9cc0eca055b1d11108bdc271caf7c2171487298f229b56"}, + {file = "pyzmq-25.0.2-cp38-cp38-win32.whl", hash = "sha256:ad761cfbe477236802a7ab2c080d268c95e784fe30cafa7e055aacd1ca877eb0"}, + {file = "pyzmq-25.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:8560756318ec7c4c49d2c341012167e704b5a46d9034905853c3d1ade4f55bee"}, + {file = "pyzmq-25.0.2-cp39-cp39-macosx_10_15_universal2.whl", hash = "sha256:ab2c056ac503f25a63f6c8c6771373e2a711b98b304614151dfb552d3d6c81f6"}, + {file = "pyzmq-25.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:cca8524b61c0eaaa3505382dc9b9a3bc8165f1d6c010fdd1452c224225a26689"}, + {file = "pyzmq-25.0.2-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:cfb9f7eae02d3ac42fbedad30006b7407c984a0eb4189a1322241a20944d61e5"}, + {file = "pyzmq-25.0.2-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:5eaeae038c68748082137d6896d5c4db7927e9349237ded08ee1bbd94f7361c9"}, + {file = "pyzmq-25.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a31992a8f8d51663ebf79df0df6a04ffb905063083d682d4380ab8d2c67257c"}, + {file = "pyzmq-25.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:6a979e59d2184a0c8f2ede4b0810cbdd86b64d99d9cc8a023929e40dce7c86cc"}, + {file = "pyzmq-25.0.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:1f124cb73f1aa6654d31b183810febc8505fd0c597afa127c4f40076be4574e0"}, + {file = "pyzmq-25.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:65c19a63b4a83ae45d62178b70223adeee5f12f3032726b897431b6553aa25af"}, + {file = "pyzmq-25.0.2-cp39-cp39-win32.whl", hash = "sha256:83d822e8687621bed87404afc1c03d83fa2ce39733d54c2fd52d8829edb8a7ff"}, + {file = "pyzmq-25.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:24683285cc6b7bf18ad37d75b9db0e0fefe58404e7001f1d82bf9e721806daa7"}, + {file = "pyzmq-25.0.2-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:4a4b4261eb8f9ed71f63b9eb0198dd7c934aa3b3972dac586d0ef502ba9ab08b"}, + {file = "pyzmq-25.0.2-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:62ec8d979f56c0053a92b2b6a10ff54b9ec8a4f187db2b6ec31ee3dd6d3ca6e2"}, + {file = "pyzmq-25.0.2-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:affec1470351178e892121b3414c8ef7803269f207bf9bef85f9a6dd11cde264"}, + {file = "pyzmq-25.0.2-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffc71111433bd6ec8607a37b9211f4ef42e3d3b271c6d76c813669834764b248"}, + {file = "pyzmq-25.0.2-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:6fadc60970714d86eff27821f8fb01f8328dd36bebd496b0564a500fe4a9e354"}, + {file = "pyzmq-25.0.2-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:269968f2a76c0513490aeb3ba0dc3c77b7c7a11daa894f9d1da88d4a0db09835"}, + {file = "pyzmq-25.0.2-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:f7c8b8368e84381ae7c57f1f5283b029c888504aaf4949c32e6e6fb256ec9bf0"}, + {file = "pyzmq-25.0.2-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:25e6873a70ad5aa31e4a7c41e5e8c709296edef4a92313e1cd5fc87bbd1874e2"}, + {file = "pyzmq-25.0.2-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b733076ff46e7db5504c5e7284f04a9852c63214c74688bdb6135808531755a3"}, + {file = "pyzmq-25.0.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:a6f6ae12478fdc26a6d5fdb21f806b08fa5403cd02fd312e4cb5f72df078f96f"}, + {file = "pyzmq-25.0.2-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:67da1c213fbd208906ab3470cfff1ee0048838365135a9bddc7b40b11e6d6c89"}, + {file = "pyzmq-25.0.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:531e36d9fcd66f18de27434a25b51d137eb546931033f392e85674c7a7cea853"}, + {file = "pyzmq-25.0.2-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:34a6fddd159ff38aa9497b2e342a559f142ab365576284bc8f77cb3ead1f79c5"}, + {file = "pyzmq-25.0.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b491998ef886662c1f3d49ea2198055a9a536ddf7430b051b21054f2a5831800"}, + {file = "pyzmq-25.0.2-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:5d496815074e3e3d183fe2c7fcea2109ad67b74084c254481f87b64e04e9a471"}, + {file = "pyzmq-25.0.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:56a94ab1d12af982b55ca96c6853db6ac85505e820d9458ac76364c1998972f4"}, + {file = "pyzmq-25.0.2.tar.gz", hash = "sha256:6b8c1bbb70e868dc88801aa532cae6bd4e3b5233784692b786f17ad2962e5149"}, +] + +[package.dependencies] +cffi = {version = "*", markers = "implementation_name == \"pypy\""} + +[[package]] +name = "qtconsole" +version = "5.4.1" +description = "Jupyter Qt console" +category = "dev" +optional = false +python-versions = ">= 3.7" +files = [ + {file = "qtconsole-5.4.1-py3-none-any.whl", hash = "sha256:bae8c7e10170cdcdcaf7e6d53ad7d6a7412249b9b8310a0eaa6b6f3b260f32db"}, + {file = "qtconsole-5.4.1.tar.gz", hash = "sha256:f67a03f40f722e13261791280f73068dbaf9dafcc335cbba644ccc8f892640e5"}, +] + +[package.dependencies] +ipykernel = ">=4.1" +ipython-genutils = "*" +jupyter-client = ">=4.1" +jupyter-core = "*" +packaging = "*" +pygments = "*" +pyzmq = ">=17.1" +qtpy = ">=2.0.1" +traitlets = "<5.2.1 || >5.2.1,<5.2.2 || >5.2.2" + +[package.extras] +doc = ["Sphinx (>=1.3)"] +test = ["flaky", "pytest", "pytest-qt"] + +[[package]] +name = "qtpy" +version = "2.3.0" +description = "Provides an abstraction layer on top of the various Qt bindings (PyQt5/6 and PySide2/6)." +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "QtPy-2.3.0-py3-none-any.whl", hash = "sha256:8d6d544fc20facd27360ea189592e6135c614785f0dec0b4f083289de6beb408"}, + {file = "QtPy-2.3.0.tar.gz", hash = "sha256:0603c9c83ccc035a4717a12908bf6bc6cb22509827ea2ec0e94c2da7c9ed57c5"}, +] + +[package.dependencies] +packaging = "*" + +[package.extras] +test = ["pytest (>=6,!=7.0.0,!=7.0.1)", "pytest-cov (>=3.0.0)", "pytest-qt"] + +[[package]] +name = "redbaron" +version = "0.9.2" +description = "Abstraction on top of baron, a FST for python to make writing refactoring code a realistic task" +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "redbaron-0.9.2-py2.py3-none-any.whl", hash = "sha256:d01032b6a848b5521a8d6ef72486315c2880f420956870cdd742e2b5a09b9bab"}, + {file = "redbaron-0.9.2.tar.gz", hash = "sha256:472d0739ca6b2240bb2278ae428604a75472c9c12e86c6321e8c016139c0132f"}, +] + +[package.dependencies] +baron = ">=0.7" + +[package.extras] +notebook = ["pygments"] + +[[package]] +name = "remote-exec" +version = "1.11.0" +description = "A CLI to sync codebases and execute commands remotely" +category = "dev" +optional = false +python-versions = ">=3.6" +files = [] +develop = false + +[package.dependencies] +click = ">=7.1.1" +pydantic = ">=1.5.1" +toml = ">=0.10.0" +watchdog = ">=0.10.3" + +[package.source] +type = "git" +url = "https://github.com/pbsds/remote" +reference = "whitespace-push" +resolved_reference = "84c9d9917f233e2acbded75692b7f7a235a169aa" + +[[package]] +name = "requests" +version = "2.28.2" +description = "Python HTTP for Humans." +category = "main" +optional = false +python-versions = ">=3.7, <4" +files = [ + {file = "requests-2.28.2-py3-none-any.whl", hash = "sha256:64299f4909223da747622c030b781c0d7811e359c37124b4bd368fb8c6518baa"}, + {file = "requests-2.28.2.tar.gz", hash = "sha256:98b1b2782e3c6c4904938b84c0eb932721069dfdb9134313beff7c83c2df24bf"}, +] + +[package.dependencies] +certifi = ">=2017.4.17" +charset-normalizer = ">=2,<4" +idna = ">=2.5,<4" +urllib3 = ">=1.21.1,<1.27" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] + +[[package]] +name = "requests-oauthlib" +version = "1.3.1" +description = "OAuthlib authentication support for Requests." +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "requests-oauthlib-1.3.1.tar.gz", hash = "sha256:75beac4a47881eeb94d5ea5d6ad31ef88856affe2332b9aafb52c6452ccf0d7a"}, + {file = "requests_oauthlib-1.3.1-py2.py3-none-any.whl", hash = "sha256:2577c501a2fb8d05a304c09d090d6e47c306fef15809d102b327cf8364bddab5"}, +] + +[package.dependencies] +oauthlib = ">=3.0.0" +requests = ">=2.0.0" + +[package.extras] +rsa = ["oauthlib[signedtoken] (>=3.0.0)"] + +[[package]] +name = "rfc3339-validator" +version = "0.1.4" +description = "A pure python RFC3339 validator" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +files = [ + {file = "rfc3339_validator-0.1.4-py2.py3-none-any.whl", hash = "sha256:24f6ec1eda14ef823da9e36ec7113124b39c04d50a4d3d3a3c2859577e7791fa"}, + {file = "rfc3339_validator-0.1.4.tar.gz", hash = "sha256:138a2abdf93304ad60530167e51d2dfb9549521a836871b88d7f4695d0022f6b"}, +] + +[package.dependencies] +six = "*" + +[[package]] +name = "rfc3986-validator" +version = "0.1.1" +description = "Pure python rfc3986 validator" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +files = [ + {file = "rfc3986_validator-0.1.1-py2.py3-none-any.whl", hash = "sha256:2f235c432ef459970b4306369336b9d5dbdda31b510ca1e327636e01f528bfa9"}, + {file = "rfc3986_validator-0.1.1.tar.gz", hash = "sha256:3d44bde7921b3b9ec3ae4e3adca370438eccebc676456449b145d533b240d055"}, +] + +[[package]] +name = "rich" +version = "13.3.2" +description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" +category = "main" +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "rich-13.3.2-py3-none-any.whl", hash = "sha256:a104f37270bf677148d8acb07d33be1569eeee87e2d1beb286a4e9113caf6f2f"}, + {file = "rich-13.3.2.tar.gz", hash = "sha256:91954fe80cfb7985727a467ca98a7618e5dd15178cc2da10f553b36a93859001"}, +] + +[package.dependencies] +markdown-it-py = ">=2.2.0,<3.0.0" +pygments = ">=2.13.0,<3.0.0" + +[package.extras] +jupyter = ["ipywidgets (>=7.5.1,<9)"] + +[[package]] +name = "rope" +version = "1.7.0" +description = "a python refactoring library..." +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "rope-1.7.0-py3-none-any.whl", hash = "sha256:893dd80ba7077fc9f6f42b0a849372076b70f1d6e405b9f0cc52781ffa0e6890"}, + {file = "rope-1.7.0.tar.gz", hash = "sha256:ba39581d0f8dee4ae8b5b5e82e35d03cebad965ccb127b7eaab9755cdc85e85a"}, +] + +[package.dependencies] +pytoolconfig = {version = ">=1.2.2", extras = ["global"]} + +[package.extras] +dev = ["build (>=0.7.0)", "pre-commit (>=2.20.0)", "pytest (>=7.0.1)", "pytest-timeout (>=2.1.0)"] +doc = ["pytoolconfig[doc]", "sphinx (>=4.5.0)", "sphinx-autodoc-typehints (>=1.18.1)", "sphinx-rtd-theme (>=1.0.0)"] + +[[package]] +name = "rply" +version = "0.7.8" +description = "A pure Python Lex/Yacc that works with RPython" +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "rply-0.7.8-py2.py3-none-any.whl", hash = "sha256:28ffd11d656c48aeb8c508eb382acd6a0bd906662624b34388751732a27807e7"}, + {file = "rply-0.7.8.tar.gz", hash = "sha256:2a808ac25a4580a9991fc304d64434e299a8fc75760574492f242cbb5bb301c9"}, +] + +[package.dependencies] +appdirs = "*" + +[[package]] +name = "rsa" +version = "4.9" +description = "Pure-Python RSA implementation" +category = "dev" +optional = false +python-versions = ">=3.6,<4" +files = [ + {file = "rsa-4.9-py3-none-any.whl", hash = "sha256:90260d9058e514786967344d0ef75fa8727eed8a7d2e43ce9f4bcf1b536174f7"}, + {file = "rsa-4.9.tar.gz", hash = "sha256:e38464a49c6c85d7f1351b0126661487a7e0a14a50f1675ec50eb34d4f20ef21"}, +] + +[package.dependencies] +pyasn1 = ">=0.1.3" + +[[package]] +name = "rtree" +version = "1.0.1" +description = "R-Tree spatial index for Python GIS" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "Rtree-1.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9855b8f11cdad99c56eb361b7b632a4fbd3d8cbe3f2081426b445f0cfb7fdca9"}, + {file = "Rtree-1.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:18ce7e4d04b85c48f2d364835620b3b20e38e199639746e7b12f07a2303e18ff"}, + {file = "Rtree-1.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:784efa6b7be9e99b33613ae8495931032689441eabb6120c9b3eb91188c33794"}, + {file = "Rtree-1.0.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:157207191aebdacbbdbb369e698cfbfebce53bc97114e96c8af5bed3126475f1"}, + {file = "Rtree-1.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c5fb3671a8d440c24b1dd29ec621d4345ced7185e26f02abe98e85a6629fcb50"}, + {file = "Rtree-1.0.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:11d16f51cf9205cd6995af36e24efe8f184270f667fb49bb69b09fc46b97e7d4"}, + {file = "Rtree-1.0.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6db6a0a93e41594ffc14b053f386dd414ab5a82535bbd9aedafa6ac8dc0650d8"}, + {file = "Rtree-1.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c6e29e5eb3083ad12ac5c1ce6e37465ea3428d894d3466cc9c9e2ee4bf768e53"}, + {file = "Rtree-1.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:656b148589c0b5bab4a7db4d033634329f42a5feaac10ca40aceeca109d83c1f"}, + {file = "Rtree-1.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7b2c15f9373ba314c83a8df5cb6d99b4e3af23c376c6b1317add995432dd0970"}, + {file = "Rtree-1.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93c5e0bf31e76b4f92a6eec3d2891e938408774c75a8ed6ac3d2c8db04a2be33"}, + {file = "Rtree-1.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6792de0e3c2fd3ad7e069445027603bec7a47000432f49c80246886311f4f152"}, + {file = "Rtree-1.0.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:004e131b570dc360a49e7f3b60e7bc6517943a54df056587964d1cb903889e7e"}, + {file = "Rtree-1.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:becd711fe97c2e09b1b7969e83080a3c8012bce2d30f6db879aade255fcba5c1"}, + {file = "Rtree-1.0.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:015df09e1bc55ddf7c88799bf1515d058cd0ee78eacf4cd443a32876d3b3a863"}, + {file = "Rtree-1.0.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c2973b76f61669a85e160b4ad09879c4089fc0e3f20fd99adf161ca298fe8374"}, + {file = "Rtree-1.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e4335e131a58952635560a003458011d97f9ea6f3c010dc24906050b42ee2c03"}, + {file = "Rtree-1.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:e7ca5d743f6a1dc62653dfac8ee7ce2e1ba91be7cf97916a7f60b7cbe48fb48d"}, + {file = "Rtree-1.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2ee7165e9872a026ccb868c021711eba39cedf7d1820763c9de52d5324691a92"}, + {file = "Rtree-1.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8de99f28af0f1783eefb80918959903b4b18112f6a12b48f296ecb162804e69d"}, + {file = "Rtree-1.0.1-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1a94e2f4bf74bd202ea8b67ea3d7c71e763ad41f79be1d6b72aa2c8d5a8e92c4"}, + {file = "Rtree-1.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5120da3a1b96f3a7a17dd6af0afdd4e6f3cc9baa87e9ee0a272882f01f980bb"}, + {file = "Rtree-1.0.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7e3d5f0e7b28250afbb290ab88b49aa0f121c9714d0da2080581783690347507"}, + {file = "Rtree-1.0.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:296203e933b6ec0dd07f6a7456c4f1492def95b6993f20cc61c92b0fee0aecc5"}, + {file = "Rtree-1.0.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:77908cd7acdd519a731979ebf5baff8afd102109c2f52864c1e6ee75d3ea2d87"}, + {file = "Rtree-1.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:1a213e5d385278ca7668bc5b27083f8d6e39996a9bd59b6528f3a30009dae4ed"}, + {file = "Rtree-1.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cfa8cffec5cb9fed494c4bb335ebdb69b3c26178b0b685f67f79296c6b3d800c"}, + {file = "Rtree-1.0.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b31fd22d214160859d038da7cb2aaa27acb71efc24a7bcc75c84b5e502721549"}, + {file = "Rtree-1.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d68a81ad419d5c2ea5fecc677e6c178666c057e2c7b24100a6c48392196f1e9"}, + {file = "Rtree-1.0.1-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:62f38020af47b765adc6b0bc7c4e810c6c3d1eab44ba339b592ff25a4c0dc0a7"}, + {file = "Rtree-1.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50b658a6707f215a0056d52e9f83a97148c0af62dea07cf29b3789a2c429e78a"}, + {file = "Rtree-1.0.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:3573cbb0de872f54d0a0c29596a84e8ac3939c47ca3bece4a82e92775730a0d0"}, + {file = "Rtree-1.0.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:d5abe5a19d943a88bea14901970e4c53e4579fc2662404cdea6163bf4c04d49a"}, + {file = "Rtree-1.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:1e894112cef4de6c518bdea0b43eada65f12888c3645cc437c3a677aa023039f"}, + {file = "Rtree-1.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:582854252b8fd5c8472478af060635434931fb55edd269bac128cbf2eef43620"}, + {file = "Rtree-1.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b54057e8a8ad92c1d8e9eaa5cf32aad70dde454abbf9b638e9d6024520a52c02"}, + {file = "Rtree-1.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:698de8ce6c62e159d93b35bacf64bcf3619077b5367bc88cd2cff5e0bc36169b"}, + {file = "Rtree-1.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:273ee61783de3a1664e5f868feebf5eea4629447137751bfa4087b0f82093082"}, + {file = "Rtree-1.0.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:16900ee02cf5c198a42b03635268a80f606aa102f3f7618b89f75023d406da1c"}, + {file = "Rtree-1.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6ce4a6fdb63254a4c1efebe7a4f7a59b1c333c703bde4ae715d9ad88c833e10b"}, + {file = "Rtree-1.0.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:5b20f69e040a05503b22297af223f336fe7047909b57e4b207b98292f33a229f"}, + {file = "Rtree-1.0.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:57128293dd625cb1f07726f32208097953e8854d70ab1fc55d6858733618b9ed"}, + {file = "Rtree-1.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e898d7409ab645c25e06d4e058f99271182601d70b2887aba3351bf08e09a0c6"}, + {file = "Rtree-1.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:ad9912faeddb1ddcec5e26b33089166d58a107af6862d8b7f1bb2b7c0002ab39"}, + {file = "Rtree-1.0.1.tar.gz", hash = "sha256:222121699c303a64065d849bf7038b1ecabc37b65c7fa340bedb38ef0e805429"}, +] + +[[package]] +name = "scikit-image" +version = "0.19.3" +description = "Image processing in Python" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "scikit-image-0.19.3.tar.gz", hash = "sha256:24b5367de1762da6ee126dd8f30cc4e7efda474e0d7d70685433f0e3aa2ec450"}, + {file = "scikit_image-0.19.3-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:3a01372ae4bca223873304b0bff79b9d92446ac6d6177f73d89b45561e2d09d8"}, + {file = "scikit_image-0.19.3-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:fdf48d9b1f13af69e4e2c78e05067e322e9c8c97463c315cd0ecb47a94e259fc"}, + {file = "scikit_image-0.19.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b6a8f98f2ac9bb73706461fd1dec875f6a5141759ed526850a5a49e90003d19"}, + {file = "scikit_image-0.19.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cfbb073f23deb48e0e60c47f8741d8089121d89cc78629ea8c5b51096efc5be7"}, + {file = "scikit_image-0.19.3-cp310-cp310-win_amd64.whl", hash = "sha256:cc24177de3fdceca5d04807ad9c87d665f0bf01032ed94a9055cd1ed2b3f33e9"}, + {file = "scikit_image-0.19.3-cp37-cp37m-macosx_10_13_x86_64.whl", hash = "sha256:fd9dd3994bb6f9f7a35f228323f3c4dc44b3cf2ff15fd72d895216e9333550c6"}, + {file = "scikit_image-0.19.3-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:ad5d8000207a264d1a55681a9276e6a739d3f05cf4429004ad00d61d1892235f"}, + {file = "scikit_image-0.19.3-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:84baa3179f3ae983c3a5d81c1e404bc92dcf7daeb41bfe9369badcda3fb22b92"}, + {file = "scikit_image-0.19.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7f9f8a1387afc6c70f2bed007c3854a2d7489f9f7713c242f16f32ee05934bc2"}, + {file = "scikit_image-0.19.3-cp37-cp37m-win32.whl", hash = "sha256:9fb0923a3bfa99457c5e17888f27b3b8a83a3600b4fef317992e7b7234764732"}, + {file = "scikit_image-0.19.3-cp37-cp37m-win_amd64.whl", hash = "sha256:ce3d2207f253b8eb2c824e30d145a9f07a34a14212d57f3beca9f7e03c383cbe"}, + {file = "scikit_image-0.19.3-cp38-cp38-macosx_10_13_x86_64.whl", hash = "sha256:2a02d1bd0e2b53e36b952bd5fd6118d9ccc3ee51de35705d63d8eb1f2e86adef"}, + {file = "scikit_image-0.19.3-cp38-cp38-macosx_12_0_arm64.whl", hash = "sha256:03779a7e1736fdf89d83c0ba67d44110496edd736a3bfce61a2b5177a1c8a099"}, + {file = "scikit_image-0.19.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:19a21a101a20c587a3b611a2cf6f86c35aae9f8d9563279b987e83ee1c9a9790"}, + {file = "scikit_image-0.19.3-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2f50b923f8099c1045fcde7418d86b206c87e333e43da980f41d8577b9605245"}, + {file = "scikit_image-0.19.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e207c6ce5ce121d7d9b9d2b61b9adca57d1abed112c902d8ffbfdc20fb42c12b"}, + {file = "scikit_image-0.19.3-cp38-cp38-win32.whl", hash = "sha256:a7c3985c68bfe05f7571167ee021d14f5b8d1a4a250c91f0b13be7fb07e6af34"}, + {file = "scikit_image-0.19.3-cp38-cp38-win_amd64.whl", hash = "sha256:651de1c2ce1fbee834753b46b8e7d81cb12a5594898babba63ac82b30ddad49d"}, + {file = "scikit_image-0.19.3-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:8d8917fcf85b987b1f287f823f3a1a7dac38b70aaca759bc0200f3bc292d5ced"}, + {file = "scikit_image-0.19.3-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:0b0a199157ce8487c77de4fde0edc0b42d6d42818881c11f459262351d678b2d"}, + {file = "scikit_image-0.19.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:33dfd463ee6cc509defa279b963829f2230c9e0639ccd3931045be055878eea6"}, + {file = "scikit_image-0.19.3-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a8714348ddd671f819457a797c97d4c672166f093def66d66c3254cbd1d43f83"}, + {file = "scikit_image-0.19.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ff3b1025356508d41f4fe48528e509d95f9e4015e90cf158cd58c56dc63e0ac5"}, + {file = "scikit_image-0.19.3-cp39-cp39-win32.whl", hash = "sha256:9439e5294de3f18d6e82ec8eee2c46590231cf9c690da80545e83a0733b7a69e"}, + {file = "scikit_image-0.19.3-cp39-cp39-win_amd64.whl", hash = "sha256:32fb88cc36203b99c9672fb972c9ef98635deaa5fc889fe969f3e11c44f22919"}, +] + +[package.dependencies] +imageio = ">=2.4.1" +networkx = ">=2.2" +numpy = ">=1.17.0" +packaging = ">=20.0" +pillow = ">=6.1.0,<7.1.0 || >7.1.0,<7.1.1 || >7.1.1,<8.3.0 || >8.3.0" +PyWavelets = ">=1.1.1" +scipy = ">=1.4.1" +tifffile = ">=2019.7.26" + +[package.extras] +data = ["pooch (>=1.3.0)"] +docs = ["cloudpickle (>=0.2.1)", "dask[array] (>=0.15.0,!=2.17.0)", "ipywidgets", "kaleido", "matplotlib (>=3.3)", "myst-parser", "numpydoc (>=1.0)", "pandas (>=0.23.0)", "plotly (>=4.14.0)", "pooch (>=1.3.0)", "pytest-runner", "scikit-learn", "seaborn (>=0.7.1)", "sphinx (>=1.8)", "sphinx-copybutton", "sphinx-gallery (>=0.10.1)", "tifffile (>=2020.5.30)"] +optional = ["SimpleITK", "astropy (>=3.1.2)", "cloudpickle (>=0.2.1)", "dask[array] (>=1.0.0,!=2.17.0)", "matplotlib (>=3.0.3)", "pooch (>=1.3.0)", "pyamg", "qtpy"] +test = ["asv", "codecov", "flake8", "matplotlib (>=3.0.3)", "pooch (>=1.3.0)", "pytest (>=5.2.0)", "pytest-cov (>=2.7.0)", "pytest-faulthandler", "pytest-localserver"] + +[[package]] +name = "scikit-learn" +version = "1.2.2" +description = "A set of python modules for machine learning and data mining" +category = "main" +optional = false +python-versions = ">=3.8" +files = [ + {file = "scikit-learn-1.2.2.tar.gz", hash = "sha256:8429aea30ec24e7a8c7ed8a3fa6213adf3814a6efbea09e16e0a0c71e1a1a3d7"}, + {file = "scikit_learn-1.2.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:99cc01184e347de485bf253d19fcb3b1a3fb0ee4cea5ee3c43ec0cc429b6d29f"}, + {file = "scikit_learn-1.2.2-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:e6e574db9914afcb4e11ade84fab084536a895ca60aadea3041e85b8ac963edb"}, + {file = "scikit_learn-1.2.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6fe83b676f407f00afa388dd1fdd49e5c6612e551ed84f3b1b182858f09e987d"}, + {file = "scikit_learn-1.2.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2e2642baa0ad1e8f8188917423dd73994bf25429f8893ddbe115be3ca3183584"}, + {file = "scikit_learn-1.2.2-cp310-cp310-win_amd64.whl", hash = "sha256:ad66c3848c0a1ec13464b2a95d0a484fd5b02ce74268eaa7e0c697b904f31d6c"}, + {file = "scikit_learn-1.2.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:dfeaf8be72117eb61a164ea6fc8afb6dfe08c6f90365bde2dc16456e4bc8e45f"}, + {file = "scikit_learn-1.2.2-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:fe0aa1a7029ed3e1dcbf4a5bc675aa3b1bc468d9012ecf6c6f081251ca47f590"}, + {file = "scikit_learn-1.2.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:065e9673e24e0dc5113e2dd2b4ca30c9d8aa2fa90f4c0597241c93b63130d233"}, + {file = "scikit_learn-1.2.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf036ea7ef66115e0d49655f16febfa547886deba20149555a41d28f56fd6d3c"}, + {file = "scikit_learn-1.2.2-cp311-cp311-win_amd64.whl", hash = "sha256:8b0670d4224a3c2d596fd572fb4fa673b2a0ccfb07152688ebd2ea0b8c61025c"}, + {file = "scikit_learn-1.2.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9c710ff9f9936ba8a3b74a455ccf0dcf59b230caa1e9ba0223773c490cab1e51"}, + {file = "scikit_learn-1.2.2-cp38-cp38-macosx_12_0_arm64.whl", hash = "sha256:2dd3ffd3950e3d6c0c0ef9033a9b9b32d910c61bd06cb8206303fb4514b88a49"}, + {file = "scikit_learn-1.2.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:44b47a305190c28dd8dd73fc9445f802b6ea716669cfc22ab1eb97b335d238b1"}, + {file = "scikit_learn-1.2.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:953236889928d104c2ef14027539f5f2609a47ebf716b8cbe4437e85dce42744"}, + {file = "scikit_learn-1.2.2-cp38-cp38-win_amd64.whl", hash = "sha256:7f69313884e8eb311460cc2f28676d5e400bd929841a2c8eb8742ae78ebf7c20"}, + {file = "scikit_learn-1.2.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8156db41e1c39c69aa2d8599ab7577af53e9e5e7a57b0504e116cc73c39138dd"}, + {file = "scikit_learn-1.2.2-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:fe175ee1dab589d2e1033657c5b6bec92a8a3b69103e3dd361b58014729975c3"}, + {file = "scikit_learn-1.2.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7d5312d9674bed14f73773d2acf15a3272639b981e60b72c9b190a0cffed5bad"}, + {file = "scikit_learn-1.2.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ea061bf0283bf9a9f36ea3c5d3231ba2176221bbd430abd2603b1c3b2ed85c89"}, + {file = "scikit_learn-1.2.2-cp39-cp39-win_amd64.whl", hash = "sha256:6477eed40dbce190f9f9e9d0d37e020815825b300121307942ec2110302b66a3"}, +] + +[package.dependencies] +joblib = ">=1.1.1" +numpy = ">=1.17.3" +scipy = ">=1.3.2" +threadpoolctl = ">=2.0.0" + +[package.extras] +benchmark = ["matplotlib (>=3.1.3)", "memory-profiler (>=0.57.0)", "pandas (>=1.0.5)"] +docs = ["Pillow (>=7.1.2)", "matplotlib (>=3.1.3)", "memory-profiler (>=0.57.0)", "numpydoc (>=1.2.0)", "pandas (>=1.0.5)", "plotly (>=5.10.0)", "pooch (>=1.6.0)", "scikit-image (>=0.16.2)", "seaborn (>=0.9.0)", "sphinx (>=4.0.1)", "sphinx-gallery (>=0.7.0)", "sphinx-prompt (>=1.3.0)", "sphinxext-opengraph (>=0.4.2)"] +examples = ["matplotlib (>=3.1.3)", "pandas (>=1.0.5)", "plotly (>=5.10.0)", "pooch (>=1.6.0)", "scikit-image (>=0.16.2)", "seaborn (>=0.9.0)"] +tests = ["black (>=22.3.0)", "flake8 (>=3.8.2)", "matplotlib (>=3.1.3)", "mypy (>=0.961)", "numpydoc (>=1.2.0)", "pandas (>=1.0.5)", "pooch (>=1.6.0)", "pyamg (>=4.0.0)", "pytest (>=5.3.1)", "pytest-cov (>=2.9.0)", "scikit-image (>=0.16.2)"] + +[[package]] +name = "scipy" +version = "1.10.1" +description = "Fundamental algorithms for scientific computing in Python" +category = "main" +optional = false +python-versions = "<3.12,>=3.8" +files = [ + {file = "scipy-1.10.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e7354fd7527a4b0377ce55f286805b34e8c54b91be865bac273f527e1b839019"}, + {file = "scipy-1.10.1-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:4b3f429188c66603a1a5c549fb414e4d3bdc2a24792e061ffbd607d3d75fd84e"}, + {file = "scipy-1.10.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1553b5dcddd64ba9a0d95355e63fe6c3fc303a8fd77c7bc91e77d61363f7433f"}, + {file = "scipy-1.10.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c0ff64b06b10e35215abce517252b375e580a6125fd5fdf6421b98efbefb2d2"}, + {file = "scipy-1.10.1-cp310-cp310-win_amd64.whl", hash = "sha256:fae8a7b898c42dffe3f7361c40d5952b6bf32d10c4569098d276b4c547905ee1"}, + {file = "scipy-1.10.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0f1564ea217e82c1bbe75ddf7285ba0709ecd503f048cb1236ae9995f64217bd"}, + {file = "scipy-1.10.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:d925fa1c81b772882aa55bcc10bf88324dadb66ff85d548c71515f6689c6dac5"}, + {file = "scipy-1.10.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aaea0a6be54462ec027de54fca511540980d1e9eea68b2d5c1dbfe084797be35"}, + {file = "scipy-1.10.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15a35c4242ec5f292c3dd364a7c71a61be87a3d4ddcc693372813c0b73c9af1d"}, + {file = "scipy-1.10.1-cp311-cp311-win_amd64.whl", hash = "sha256:43b8e0bcb877faf0abfb613d51026cd5cc78918e9530e375727bf0625c82788f"}, + {file = "scipy-1.10.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:5678f88c68ea866ed9ebe3a989091088553ba12c6090244fdae3e467b1139c35"}, + {file = "scipy-1.10.1-cp38-cp38-macosx_12_0_arm64.whl", hash = "sha256:39becb03541f9e58243f4197584286e339029e8908c46f7221abeea4b749fa88"}, + {file = "scipy-1.10.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bce5869c8d68cf383ce240e44c1d9ae7c06078a9396df68ce88a1230f93a30c1"}, + {file = "scipy-1.10.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:07c3457ce0b3ad5124f98a86533106b643dd811dd61b548e78cf4c8786652f6f"}, + {file = "scipy-1.10.1-cp38-cp38-win_amd64.whl", hash = "sha256:049a8bbf0ad95277ffba9b3b7d23e5369cc39e66406d60422c8cfef40ccc8415"}, + {file = "scipy-1.10.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:cd9f1027ff30d90618914a64ca9b1a77a431159df0e2a195d8a9e8a04c78abf9"}, + {file = "scipy-1.10.1-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:79c8e5a6c6ffaf3a2262ef1be1e108a035cf4f05c14df56057b64acc5bebffb6"}, + {file = "scipy-1.10.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:51af417a000d2dbe1ec6c372dfe688e041a7084da4fdd350aeb139bd3fb55353"}, + {file = "scipy-1.10.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1b4735d6c28aad3cdcf52117e0e91d6b39acd4272f3f5cd9907c24ee931ad601"}, + {file = "scipy-1.10.1-cp39-cp39-win_amd64.whl", hash = "sha256:7ff7f37b1bf4417baca958d254e8e2875d0cc23aaadbe65b3d5b3077b0eb23ea"}, + {file = "scipy-1.10.1.tar.gz", hash = "sha256:2cf9dfb80a7b4589ba4c40ce7588986d6d5cebc5457cad2c2880f6bc2d42f3a5"}, +] + +[package.dependencies] +numpy = ">=1.19.5,<1.27.0" + +[package.extras] +dev = ["click", "doit (>=0.36.0)", "flake8", "mypy", "pycodestyle", "pydevtool", "rich-click", "typing_extensions"] +doc = ["matplotlib (>2)", "numpydoc", "pydata-sphinx-theme (==0.9.0)", "sphinx (!=4.1.0)", "sphinx-design (>=0.2.0)"] +test = ["asv", "gmpy2", "mpmath", "pooch", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "scikit-umfpack", "threadpoolctl"] + +[[package]] +name = "seaborn" +version = "0.12.2" +description = "Statistical data visualization" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "seaborn-0.12.2-py3-none-any.whl", hash = "sha256:ebf15355a4dba46037dfd65b7350f014ceb1f13c05e814eda2c9f5fd731afc08"}, + {file = "seaborn-0.12.2.tar.gz", hash = "sha256:374645f36509d0dcab895cba5b47daf0586f77bfe3b36c97c607db7da5be0139"}, +] + +[package.dependencies] +matplotlib = ">=3.1,<3.6.1 || >3.6.1" +numpy = ">=1.17,<1.24.0 || >1.24.0" +pandas = ">=0.25" + +[package.extras] +dev = ["flake8", "flit", "mypy", "pandas-stubs", "pre-commit", "pytest", "pytest-cov", "pytest-xdist"] +docs = ["ipykernel", "nbconvert", "numpydoc", "pydata_sphinx_theme (==0.10.0rc2)", "pyyaml", "sphinx-copybutton", "sphinx-design", "sphinx-issues"] +stats = ["scipy (>=1.3)", "statsmodels (>=0.10)"] + +[[package]] +name = "send2trash" +version = "1.8.0" +description = "Send file to trash natively under Mac OS X, Windows and Linux." +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "Send2Trash-1.8.0-py3-none-any.whl", hash = "sha256:f20eaadfdb517eaca5ce077640cb261c7d2698385a6a0f072a4a5447fd49fa08"}, + {file = "Send2Trash-1.8.0.tar.gz", hash = "sha256:d2c24762fd3759860a0aff155e45871447ea58d2be6bdd39b5c8f966a0c99c2d"}, +] + +[package.extras] +nativelib = ["pyobjc-framework-Cocoa", "pywin32"] +objc = ["pyobjc-framework-Cocoa"] +win32 = ["pywin32"] + +[[package]] +name = "serve-me-once" +version = "0.1.2" +description = "" +category = "main" +optional = false +python-versions = ">=3.6" +files = [ + {file = "serve-me-once-0.1.2.tar.gz", hash = "sha256:d2dcf4b218ccd6e47374dd7ceffdd349236fcbec8a0d4c9116311e4a0018ed21"}, + {file = "serve_me_once-0.1.2-py3-none-any.whl", hash = "sha256:de5f0eb96a1eedd9f1e3f36edd79f6f3416ce11febd39b423d59e2efe24b97cd"}, +] + +[[package]] +name = "setuptools" +version = "60.10.0" +description = "Easily download, build, install, upgrade, and uninstall Python packages" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "setuptools-60.10.0-py3-none-any.whl", hash = "sha256:782ef48d58982ddb49920c11a0c5c9c0b02e7d7d1c2ad0aa44e1a1e133051c96"}, + {file = "setuptools-60.10.0.tar.gz", hash = "sha256:6599055eeb23bfef457d5605d33a4d68804266e6cb430b0fb12417c5efeae36c"}, +] + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx", "sphinx-favicon", "sphinx-inline-tabs", "sphinxcontrib-towncrier"] +testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "mock", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.0.1)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-xdist", "virtualenv (>=13.0.0)", "wheel"] +testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] + +[[package]] +name = "shapely" +version = "2.0.1" +description = "Manipulation and analysis of geometric objects" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "shapely-2.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b06d031bc64149e340448fea25eee01360a58936c89985cf584134171e05863f"}, + {file = "shapely-2.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9a6ac34c16f4d5d3c174c76c9d7614ec8fe735f8f82b6cc97a46b54f386a86bf"}, + {file = "shapely-2.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:865bc3d7cc0ea63189d11a0b1120d1307ed7a64720a8bfa5be2fde5fc6d0d33f"}, + {file = "shapely-2.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45b4833235b90bc87ee26c6537438fa77559d994d2d3be5190dd2e54d31b2820"}, + {file = "shapely-2.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce88ec79df55430e37178a191ad8df45cae90b0f6972d46d867bf6ebbb58cc4d"}, + {file = "shapely-2.0.1-cp310-cp310-win32.whl", hash = "sha256:01224899ff692a62929ef1a3f5fe389043e262698a708ab7569f43a99a48ae82"}, + {file = "shapely-2.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:da71de5bf552d83dcc21b78cc0020e86f8d0feea43e202110973987ffa781c21"}, + {file = "shapely-2.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:502e0a607f1dcc6dee0125aeee886379be5242c854500ea5fd2e7ac076b9ce6d"}, + {file = "shapely-2.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7d3bbeefd8a6a1a1017265d2d36f8ff2d79d0162d8c141aa0d37a87063525656"}, + {file = "shapely-2.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f470a130d6ddb05b810fc1776d918659407f8d025b7f56d2742a596b6dffa6c7"}, + {file = "shapely-2.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4641325e065fd3e07d55677849c9ddfd0cf3ee98f96475126942e746d55b17c8"}, + {file = "shapely-2.0.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:90cfa4144ff189a3c3de62e2f3669283c98fb760cfa2e82ff70df40f11cadb39"}, + {file = "shapely-2.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70a18fc7d6418e5aea76ac55dce33f98e75bd413c6eb39cfed6a1ba36469d7d4"}, + {file = "shapely-2.0.1-cp311-cp311-win32.whl", hash = "sha256:09d6c7763b1bee0d0a2b84bb32a4c25c6359ad1ac582a62d8b211e89de986154"}, + {file = "shapely-2.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:d8f55f355be7821dade839df785a49dc9f16d1af363134d07eb11e9207e0b189"}, + {file = "shapely-2.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:83a8ec0ee0192b6e3feee9f6a499d1377e9c295af74d7f81ecba5a42a6b195b7"}, + {file = "shapely-2.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a529218e72a3dbdc83676198e610485fdfa31178f4be5b519a8ae12ea688db14"}, + {file = "shapely-2.0.1-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:91575d97fd67391b85686573d758896ed2fc7476321c9d2e2b0c398b628b961c"}, + {file = "shapely-2.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c8b0d834b11be97d5ab2b4dceada20ae8e07bcccbc0f55d71df6729965f406ad"}, + {file = "shapely-2.0.1-cp37-cp37m-win32.whl", hash = "sha256:b4f0711cc83734c6fad94fc8d4ec30f3d52c1787b17d9dca261dc841d4731c64"}, + {file = "shapely-2.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:05c51a29336e604c084fb43ae5dbbfa2c0ef9bd6fedeae0a0d02c7b57a56ba46"}, + {file = "shapely-2.0.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:b519cf3726ddb6c67f6a951d1bb1d29691111eaa67ea19ddca4d454fbe35949c"}, + {file = "shapely-2.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:193a398d81c97a62fc3634a1a33798a58fd1dcf4aead254d080b273efbb7e3ff"}, + {file = "shapely-2.0.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e55698e0ed95a70fe9ff9a23c763acfe0bf335b02df12142f74e4543095e9a9b"}, + {file = "shapely-2.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f32a748703e7bf6e92dfa3d2936b2fbfe76f8ce5f756e24f49ef72d17d26ad02"}, + {file = "shapely-2.0.1-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1a34a23d6266ca162499e4a22b79159dc0052f4973d16f16f990baa4d29e58b6"}, + {file = "shapely-2.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d173d24e85e51510e658fb108513d5bc11e3fd2820db6b1bd0522266ddd11f51"}, + {file = "shapely-2.0.1-cp38-cp38-win32.whl", hash = "sha256:3cb256ae0c01b17f7bc68ee2ffdd45aebf42af8992484ea55c29a6151abe4386"}, + {file = "shapely-2.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:c7eed1fb3008a8a4a56425334b7eb82651a51f9e9a9c2f72844a2fb394f38a6c"}, + {file = "shapely-2.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:ac1dfc397475d1de485e76de0c3c91cc9d79bd39012a84bb0f5e8a199fc17bef"}, + {file = "shapely-2.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:33403b8896e1d98aaa3a52110d828b18985d740cc9f34f198922018b1e0f8afe"}, + {file = "shapely-2.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2569a4b91caeef54dd5ae9091ae6f63526d8ca0b376b5bb9fd1a3195d047d7d4"}, + {file = "shapely-2.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a70a614791ff65f5e283feed747e1cc3d9e6c6ba91556e640636bbb0a1e32a71"}, + {file = "shapely-2.0.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c43755d2c46b75a7b74ac6226d2cc9fa2a76c3263c5ae70c195c6fb4e7b08e79"}, + {file = "shapely-2.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad81f292fffbd568ae71828e6c387da7eb5384a79db9b4fde14dd9fdeffca9a"}, + {file = "shapely-2.0.1-cp39-cp39-win32.whl", hash = "sha256:b50c401b64883e61556a90b89948297f1714dbac29243d17ed9284a47e6dd731"}, + {file = "shapely-2.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:bca57b683e3d94d0919e2f31e4d70fdfbb7059650ef1b431d9f4e045690edcd5"}, + {file = "shapely-2.0.1.tar.gz", hash = "sha256:66a6b1a3e72ece97fc85536a281476f9b7794de2e646ca8a4517e2e3c1446893"}, +] + +[package.dependencies] +numpy = ">=1.14" + +[package.extras] +docs = ["matplotlib", "numpydoc (>=1.1.0,<1.2.0)", "sphinx", "sphinx-book-theme", "sphinx-remove-toctrees"] +test = ["pytest", "pytest-cov"] + +[[package]] +name = "six" +version = "1.16.0" +description = "Python 2 and 3 compatibility utilities" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, +] + +[[package]] +name = "sniffio" +version = "1.3.0" +description = "Sniff out which async library your code is running under" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "sniffio-1.3.0-py3-none-any.whl", hash = "sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384"}, + {file = "sniffio-1.3.0.tar.gz", hash = "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101"}, +] + +[[package]] +name = "snowballstemmer" +version = "2.2.0" +description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms." +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a"}, + {file = "snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1"}, +] + +[[package]] +name = "soupsieve" +version = "2.4" +description = "A modern CSS selector implementation for Beautiful Soup." +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "soupsieve-2.4-py3-none-any.whl", hash = "sha256:49e5368c2cda80ee7e84da9dbe3e110b70a4575f196efb74e51b94549d921955"}, + {file = "soupsieve-2.4.tar.gz", hash = "sha256:e28dba9ca6c7c00173e34e4ba57448f0688bb681b7c5e8bf4971daafc093d69a"}, +] + +[[package]] +name = "stack-data" +version = "0.6.2" +description = "Extract data from python stack frames and tracebacks for informative displays" +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "stack_data-0.6.2-py3-none-any.whl", hash = "sha256:cbb2a53eb64e5785878201a97ed7c7b94883f48b87bfb0bbe8b623c74679e4a8"}, + {file = "stack_data-0.6.2.tar.gz", hash = "sha256:32d2dd0376772d01b6cb9fc996f3c8b57a357089dec328ed4b6553d037eaf815"}, +] + +[package.dependencies] +asttokens = ">=2.1.0" +executing = ">=1.2.0" +pure-eval = "*" + +[package.extras] +tests = ["cython", "littleutils", "pygments", "pytest", "typeguard"] + +[[package]] +name = "sympy" +version = "1.11.1" +description = "Computer algebra system (CAS) in Python" +category = "dev" +optional = false +python-versions = ">=3.8" +files = [ + {file = "sympy-1.11.1-py3-none-any.whl", hash = "sha256:938f984ee2b1e8eae8a07b884c8b7a1146010040fccddc6539c54f401c8f6fcf"}, + {file = "sympy-1.11.1.tar.gz", hash = "sha256:e32380dce63cb7c0108ed525570092fd45168bdae2faa17e528221ef72e88658"}, +] + +[package.dependencies] +mpmath = ">=0.19" + +[[package]] +name = "tabulate" +version = "0.9.0" +description = "Pretty-print tabular data" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tabulate-0.9.0-py3-none-any.whl", hash = "sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f"}, + {file = "tabulate-0.9.0.tar.gz", hash = "sha256:0095b12bf5966de529c0feb1fa08671671b3368eec77d7ef7ab114be2c068b3c"}, +] + +[package.extras] +widechars = ["wcwidth"] + +[[package]] +name = "tenacity" +version = "8.2.2" +description = "Retry code until it succeeds" +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ + {file = "tenacity-8.2.2-py3-none-any.whl", hash = "sha256:2f277afb21b851637e8f52e6a613ff08734c347dc19ade928e519d7d2d8569b0"}, + {file = "tenacity-8.2.2.tar.gz", hash = "sha256:43af037822bd0029025877f3b2d97cc4d7bb0c2991000a3d59d71517c5c969e0"}, +] + +[package.extras] +doc = ["reno", "sphinx", "tornado (>=4.5)"] + +[[package]] +name = "tensorboard" +version = "2.12.0" +description = "TensorBoard lets you watch Tensors Flow" +category = "dev" +optional = false +python-versions = ">=3.8" +files = [ + {file = "tensorboard-2.12.0-py3-none-any.whl", hash = "sha256:3cbdc32448d7a28dc1bf0b1754760c08b8e0e2e37c451027ebd5ff4896613012"}, +] + +[package.dependencies] +absl-py = ">=0.4" +google-auth = ">=1.6.3,<3" +google-auth-oauthlib = ">=0.4.1,<0.5" +grpcio = ">=1.48.2" +markdown = ">=2.6.8" +numpy = ">=1.12.0" +protobuf = ">=3.19.6" +requests = ">=2.21.0,<3" +setuptools = ">=41.0.0" +tensorboard-data-server = ">=0.7.0,<0.8.0" +tensorboard-plugin-wit = ">=1.6.0" +werkzeug = ">=1.0.1" +wheel = ">=0.26" + +[[package]] +name = "tensorboard-data-server" +version = "0.7.0" +description = "Fast data loading for TensorBoard" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tensorboard_data_server-0.7.0-py3-none-any.whl", hash = "sha256:753d4214799b31da7b6d93837959abebbc6afa86e69eacf1e9a317a48daa31eb"}, + {file = "tensorboard_data_server-0.7.0-py3-none-macosx_10_9_x86_64.whl", hash = "sha256:eb7fa518737944dbf4f0cf83c2e40a7ac346bf91be2e6a0215de98be74e85454"}, + {file = "tensorboard_data_server-0.7.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:64aa1be7c23e80b1a42c13b686eb0875bb70f5e755f4d2b8de5c1d880cf2267f"}, +] + +[[package]] +name = "tensorboard-plugin-wit" +version = "1.8.1" +description = "What-If Tool TensorBoard plugin." +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "tensorboard_plugin_wit-1.8.1-py3-none-any.whl", hash = "sha256:ff26bdd583d155aa951ee3b152b3d0cffae8005dc697f72b44a8e8c2a77a8cbe"}, +] + +[[package]] +name = "termcolor" +version = "2.2.0" +description = "ANSI color formatting for output in terminal" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "termcolor-2.2.0-py3-none-any.whl", hash = "sha256:91ddd848e7251200eac969846cbae2dacd7d71c2871e92733289e7e3666f48e7"}, + {file = "termcolor-2.2.0.tar.gz", hash = "sha256:dfc8ac3f350788f23b2947b3e6cfa5a53b630b612e6cd8965a015a776020b99a"}, +] + +[package.extras] +tests = ["pytest", "pytest-cov"] + +[[package]] +name = "terminado" +version = "0.17.1" +description = "Tornado websocket backend for the Xterm.js Javascript terminal emulator library." +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "terminado-0.17.1-py3-none-any.whl", hash = "sha256:8650d44334eba354dd591129ca3124a6ba42c3d5b70df5051b6921d506fdaeae"}, + {file = "terminado-0.17.1.tar.gz", hash = "sha256:6ccbbcd3a4f8a25a5ec04991f39a0b8db52dfcd487ea0e578d977e6752380333"}, +] + +[package.dependencies] +ptyprocess = {version = "*", markers = "os_name != \"nt\""} +pywinpty = {version = ">=1.1.0", markers = "os_name == \"nt\""} +tornado = ">=6.1.0" + +[package.extras] +docs = ["myst-parser", "pydata-sphinx-theme", "sphinx"] +test = ["pre-commit", "pytest (>=7.0)", "pytest-timeout"] + +[[package]] +name = "textwrap3" +version = "0.9.2" +description = "textwrap from Python 3.6 backport (plus a few tweaks)" +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "textwrap3-0.9.2-py2.py3-none-any.whl", hash = "sha256:bf5f4c40faf2a9ff00a9e0791fed5da7415481054cef45bb4a3cfb1f69044ae0"}, + {file = "textwrap3-0.9.2.zip", hash = "sha256:5008eeebdb236f6303dcd68f18b856d355f6197511d952ba74bc75e40e0c3414"}, +] + +[[package]] +name = "threadpoolctl" +version = "3.1.0" +description = "threadpoolctl" +category = "main" +optional = false +python-versions = ">=3.6" +files = [ + {file = "threadpoolctl-3.1.0-py3-none-any.whl", hash = "sha256:8b99adda265feb6773280df41eece7b2e6561b772d21ffd52e372f999024907b"}, + {file = "threadpoolctl-3.1.0.tar.gz", hash = "sha256:a335baacfaa4400ae1f0d8e3a58d6674d2f8828e3716bb2802c44955ad391380"}, +] + +[[package]] +name = "tifffile" +version = "2023.3.21" +description = "Read and write TIFF files" +category = "main" +optional = false +python-versions = ">=3.8" +files = [ + {file = "tifffile-2023.3.21-py3-none-any.whl", hash = "sha256:b83af3dbe914663aeaf250e9751ae503154f2599dd27ed7a4df1b2dc7c148efa"}, + {file = "tifffile-2023.3.21.tar.gz", hash = "sha256:16027be65e9d5a1b26bf106a98a639345fecf83ebac9004dc7e617647e4dbfeb"}, +] + +[package.dependencies] +numpy = "*" + +[package.extras] +all = ["defusedxml", "fsspec", "imagecodecs (>=2023.1.23)", "lxml", "matplotlib", "zarr"] + +[[package]] +name = "tinycss2" +version = "1.2.1" +description = "A tiny CSS parser" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tinycss2-1.2.1-py3-none-any.whl", hash = "sha256:2b80a96d41e7c3914b8cda8bc7f705a4d9c49275616e886103dd839dfc847847"}, + {file = "tinycss2-1.2.1.tar.gz", hash = "sha256:8cff3a8f066c2ec677c06dbc7b45619804a6938478d9d73c284b29d14ecb0627"}, +] + +[package.dependencies] +webencodings = ">=0.4" + +[package.extras] +doc = ["sphinx", "sphinx_rtd_theme"] +test = ["flake8", "isort", "pytest"] + +[[package]] +name = "toml" +version = "0.10.2" +description = "Python Library for Tom's Obvious, Minimal Language" +category = "dev" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ + {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, + {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, +] + +[[package]] +name = "tomli" +version = "2.0.1" +description = "A lil' TOML parser" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, + {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, +] + +[[package]] +name = "tomlkit" +version = "0.11.6" +description = "Style preserving TOML library" +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ + {file = "tomlkit-0.11.6-py3-none-any.whl", hash = "sha256:07de26b0d8cfc18f871aec595fda24d95b08fef89d147caa861939f37230bf4b"}, + {file = "tomlkit-0.11.6.tar.gz", hash = "sha256:71b952e5721688937fb02cf9d354dbcf0785066149d2855e44531ebdd2b65d73"}, +] + +[[package]] +name = "torch" +version = "1.13.1" +description = "Tensors and Dynamic neural networks in Python with strong GPU acceleration" +category = "main" +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "torch-1.13.1-cp310-cp310-manylinux1_x86_64.whl", hash = "sha256:fd12043868a34a8da7d490bf6db66991108b00ffbeecb034228bfcbbd4197143"}, + {file = "torch-1.13.1-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:d9fe785d375f2e26a5d5eba5de91f89e6a3be5d11efb497e76705fdf93fa3c2e"}, + {file = "torch-1.13.1-cp310-cp310-win_amd64.whl", hash = "sha256:98124598cdff4c287dbf50f53fb455f0c1e3a88022b39648102957f3445e9b76"}, + {file = "torch-1.13.1-cp310-none-macosx_10_9_x86_64.whl", hash = "sha256:393a6273c832e047581063fb74335ff50b4c566217019cc6ace318cd79eb0566"}, + {file = "torch-1.13.1-cp310-none-macosx_11_0_arm64.whl", hash = "sha256:0122806b111b949d21fa1a5f9764d1fd2fcc4a47cb7f8ff914204fd4fc752ed5"}, + {file = "torch-1.13.1-cp311-cp311-manylinux1_x86_64.whl", hash = "sha256:22128502fd8f5b25ac1cd849ecb64a418382ae81dd4ce2b5cebaa09ab15b0d9b"}, + {file = "torch-1.13.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:76024be052b659ac1304ab8475ab03ea0a12124c3e7626282c9c86798ac7bc11"}, + {file = "torch-1.13.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:ea8dda84d796094eb8709df0fcd6b56dc20b58fdd6bc4e8d7109930dafc8e419"}, + {file = "torch-1.13.1-cp37-cp37m-win_amd64.whl", hash = "sha256:2ee7b81e9c457252bddd7d3da66fb1f619a5d12c24d7074de91c4ddafb832c93"}, + {file = "torch-1.13.1-cp37-none-macosx_10_9_x86_64.whl", hash = "sha256:0d9b8061048cfb78e675b9d2ea8503bfe30db43d583599ae8626b1263a0c1380"}, + {file = "torch-1.13.1-cp37-none-macosx_11_0_arm64.whl", hash = "sha256:f402ca80b66e9fbd661ed4287d7553f7f3899d9ab54bf5c67faada1555abde28"}, + {file = "torch-1.13.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:727dbf00e2cf858052364c0e2a496684b9cb5aa01dc8a8bc8bbb7c54502bdcdd"}, + {file = "torch-1.13.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:df8434b0695e9ceb8cc70650afc1310d8ba949e6db2a0525ddd9c3b2b181e5fe"}, + {file = "torch-1.13.1-cp38-cp38-win_amd64.whl", hash = "sha256:5e1e722a41f52a3f26f0c4fcec227e02c6c42f7c094f32e49d4beef7d1e213ea"}, + {file = "torch-1.13.1-cp38-none-macosx_10_9_x86_64.whl", hash = "sha256:33e67eea526e0bbb9151263e65417a9ef2d8fa53cbe628e87310060c9dcfa312"}, + {file = "torch-1.13.1-cp38-none-macosx_11_0_arm64.whl", hash = "sha256:eeeb204d30fd40af6a2d80879b46a7efbe3cf43cdbeb8838dd4f3d126cc90b2b"}, + {file = "torch-1.13.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:50ff5e76d70074f6653d191fe4f6a42fdbe0cf942fbe2a3af0b75eaa414ac038"}, + {file = "torch-1.13.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:2c3581a3fd81eb1f0f22997cddffea569fea53bafa372b2c0471db373b26aafc"}, + {file = "torch-1.13.1-cp39-cp39-win_amd64.whl", hash = "sha256:0aa46f0ac95050c604bcf9ef71da9f1172e5037fdf2ebe051962d47b123848e7"}, + {file = "torch-1.13.1-cp39-none-macosx_10_9_x86_64.whl", hash = "sha256:6930791efa8757cb6974af73d4996b6b50c592882a324b8fb0589c6a9ba2ddaf"}, + {file = "torch-1.13.1-cp39-none-macosx_11_0_arm64.whl", hash = "sha256:e0df902a7c7dd6c795698532ee5970ce898672625635d885eade9976e5a04949"}, +] + +[package.dependencies] +nvidia-cublas-cu11 = {version = "11.10.3.66", markers = "platform_system == \"Linux\""} +nvidia-cuda-nvrtc-cu11 = {version = "11.7.99", markers = "platform_system == \"Linux\""} +nvidia-cuda-runtime-cu11 = {version = "11.7.99", markers = "platform_system == \"Linux\""} +nvidia-cudnn-cu11 = {version = "8.5.0.96", markers = "platform_system == \"Linux\""} +typing-extensions = "*" + +[package.extras] +opt-einsum = ["opt-einsum (>=3.3)"] + +[[package]] +name = "torchmeta" +version = "1.8.0" +description = "Dataloaders for meta-learning in Pytorch" +category = "main" +optional = false +python-versions = "*" +files = [] +develop = false + +[package.dependencies] +h5py = "*" +numpy = ">=1.14.0" +ordered-set = "*" +Pillow = ">=7.0.0" +requests = "*" +torch = ">=1.4.0,<1.15.0" +torchvision = ">=0.5.0,<0.16.0" +tqdm = ">=4.0.0" + +[package.extras] +tcga = ["academictorrents (>=2.1.0,<2.2.0)", "pandas (>=0.24.0,<0.25.0)", "six (>=1.11.0,<1.12.0)"] +test = ["flaky"] + +[package.source] +type = "git" +url = "https://github.com/pbsds/pytorch-meta" +reference = "upgrade" +resolved_reference = "ef53cc17c9f645902c96ffc611ce3d2b5cd08e99" + +[[package]] +name = "torchmetrics" +version = "0.11.4" +description = "PyTorch native Metrics" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "torchmetrics-0.11.4-py3-none-any.whl", hash = "sha256:45f892f3534e91f3ad9e2488d1b05a93b7cb76b7d037969435a41a1f24750d9a"}, + {file = "torchmetrics-0.11.4.tar.gz", hash = "sha256:1fe45a14b44dd65d90199017dd5a4b5a128d56a8a311da7916c402c18c671494"}, +] + +[package.dependencies] +numpy = ">=1.17.2" +packaging = "*" +torch = ">=1.8.1" + +[package.extras] +all = ["lpips (<=0.1.4)", "nltk (>=3.6)", "pycocotools (>2.0.0)", "pystoi (<=0.3.3)", "regex (>=2021.9.24)", "scipy (>1.0.0)", "torch-fidelity (<=0.3.0)", "torchvision (>=0.8)", "tqdm (>=4.41.0)", "transformers (>=4.10.0)"] +audio = ["pystoi (<=0.3.3)"] +detection = ["pycocotools (>2.0.0)", "torchvision (>=0.8)"] +image = ["lpips (<=0.1.4)", "scipy (>1.0.0)", "torch-fidelity (<=0.3.0)", "torchvision (>=0.8)"] +multimodal = ["transformers (>=4.10.0)"] +test = ["bert-score (==0.3.13)", "cloudpickle (>1.3)", "coverage (>5.2)", "dython (<=0.7.3)", "fast-bss-eval (>=0.1.0)", "fire (<=0.5.0)", "huggingface-hub (<0.7)", "jiwer (>=2.3.0)", "kornia (>=0.6.7)", "mir-eval (>=0.6)", "mypy (==0.982)", "netcal (>1.0.0)", "pandas (>1.0.0)", "phmdoctest (>=1.1.1)", "psutil (<=5.9.4)", "pypesq (>1.2)", "pytest (>=6.0.0)", "pytest-cov (>2.10)", "pytest-doctestplus (>=0.9.0)", "pytest-rerunfailures (>=10.0)", "pytest-timeout (<=2.1.0)", "pytorch-msssim (==0.2.1)", "requests (<=2.28.2)", "rouge-score (>0.1.0)", "sacrebleu (>=2.0.0)", "scikit-image (>0.17.1)", "scikit-learn (>1.0)", "scipy (>1.0.0)", "torch-complex (<=0.4.3)", "transformers (>4.4.0)", "types-PyYAML", "types-emoji", "types-protobuf", "types-requests", "types-setuptools", "types-six", "types-tabulate"] +text = ["nltk (>=3.6)", "regex (>=2021.9.24)", "tqdm (>=4.41.0)"] + +[[package]] +name = "torchvision" +version = "0.14.1" +description = "image and video datasets and models for torch deep learning" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "torchvision-0.14.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:eeb05dd9dd3af5428fee525400759daf8da8e4caec45ddd6908cfb36571f6433"}, + {file = "torchvision-0.14.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8d0766ea92affa7af248e327dd85f7c9cfdf51a57530b43212d4e1858548e9d7"}, + {file = "torchvision-0.14.1-cp310-cp310-manylinux1_x86_64.whl", hash = "sha256:6d7b35653113664ea3fdcb71f515cfbf29d2fe393000fd8aaff27a1284de6908"}, + {file = "torchvision-0.14.1-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:8a9eb773a2fa8f516e404ac09c059fb14e6882c48fdbb9c946327d2ce5dba6cd"}, + {file = "torchvision-0.14.1-cp310-cp310-win_amd64.whl", hash = "sha256:13986f0c15377ff23039e1401012ccb6ecf71024ce53def27139e4eac5a57592"}, + {file = "torchvision-0.14.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:fb7a793fd33ce1abec24b42778419a3fb1e3159d7dfcb274a3ca8fb8cbc408dc"}, + {file = "torchvision-0.14.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:89fb0419780ec9a9eb9f7856a0149f6ac9f956b28f44b0c0080c6b5b48044db7"}, + {file = "torchvision-0.14.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:a2d4237d3c9705d7729eb4534e4eb06f1d6be7ff1df391204dfb51586d9b0ecb"}, + {file = "torchvision-0.14.1-cp37-cp37m-win_amd64.whl", hash = "sha256:92a324712a87957443cc34223274298ae9496853f115c252f8fc02b931f2340e"}, + {file = "torchvision-0.14.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:68ed03359dcd3da9cd21b8ab94da21158df8a6a0c5bad0bf4a42f0e448d28cb3"}, + {file = "torchvision-0.14.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:30fcf0e9fe57d4ac4ce6426659a57dce199637ccb6c70be1128670f177692624"}, + {file = "torchvision-0.14.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:0ed02aefd09bf1114d35f1aa7dce55aa61c2c7e57f9aa02dce362860be654e85"}, + {file = "torchvision-0.14.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:a541e49fc3c4e90e49e6988428ab047415ed52ea97d0c0bfd147d8bacb8f4df8"}, + {file = "torchvision-0.14.1-cp38-cp38-win_amd64.whl", hash = "sha256:6099b3191dc2516099a32ae38a5fb349b42e863872a13545ab1a524b6567be60"}, + {file = "torchvision-0.14.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c5e744f56e5f5b452deb5fc0f3f2ba4d2f00612d14d8da0dbefea8f09ac7690b"}, + {file = "torchvision-0.14.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:758b20d079e810b4740bd60d1eb16e49da830e3360f9be379eb177ee221fa5d4"}, + {file = "torchvision-0.14.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:83045507ef8d3c015d4df6be79491375b2f901352cfca6e72b4723e9c4f9a55d"}, + {file = "torchvision-0.14.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:eaed58cf454323ed9222d4e0dd5fb897064f454b400696e03a5200e65d3a1e76"}, + {file = "torchvision-0.14.1-cp39-cp39-win_amd64.whl", hash = "sha256:b337e1245ca4353623dd563c03cd8f020c2496a7c5d12bba4d2e381999c766e0"}, +] + +[package.dependencies] +numpy = "*" +pillow = ">=5.3.0,<8.3.0 || >=8.4.0" +requests = "*" +torch = "1.13.1" +typing-extensions = "*" + +[package.extras] +scipy = ["scipy"] + +[[package]] +name = "torchviz" +version = "0.0.2" +description = "A small package to create visualizations of PyTorch execution graphs" +category = "main" +optional = false +python-versions = "*" +files = [ + {file = "torchviz-0.0.2.tar.gz", hash = "sha256:c790b4c993f783433604bf610cfa58bb0a031260be4ee5196a00c0884e768051"}, +] + +[package.dependencies] +graphviz = "*" +torch = "*" + +[[package]] +name = "tornado" +version = "6.2" +description = "Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed." +category = "dev" +optional = false +python-versions = ">= 3.7" +files = [ + {file = "tornado-6.2-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:20f638fd8cc85f3cbae3c732326e96addff0a15e22d80f049e00121651e82e72"}, + {file = "tornado-6.2-cp37-abi3-macosx_10_9_x86_64.whl", hash = "sha256:87dcafae3e884462f90c90ecc200defe5e580a7fbbb4365eda7c7c1eb809ebc9"}, + {file = "tornado-6.2-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba09ef14ca9893954244fd872798b4ccb2367c165946ce2dd7376aebdde8e3ac"}, + {file = "tornado-6.2-cp37-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b8150f721c101abdef99073bf66d3903e292d851bee51910839831caba341a75"}, + {file = "tornado-6.2-cp37-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3a2f5999215a3a06a4fc218026cd84c61b8b2b40ac5296a6db1f1451ef04c1e"}, + {file = "tornado-6.2-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:5f8c52d219d4995388119af7ccaa0bcec289535747620116a58d830e7c25d8a8"}, + {file = "tornado-6.2-cp37-abi3-musllinux_1_1_i686.whl", hash = "sha256:6fdfabffd8dfcb6cf887428849d30cf19a3ea34c2c248461e1f7d718ad30b66b"}, + {file = "tornado-6.2-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:1d54d13ab8414ed44de07efecb97d4ef7c39f7438cf5e976ccd356bebb1b5fca"}, + {file = "tornado-6.2-cp37-abi3-win32.whl", hash = "sha256:5c87076709343557ef8032934ce5f637dbb552efa7b21d08e89ae7619ed0eb23"}, + {file = "tornado-6.2-cp37-abi3-win_amd64.whl", hash = "sha256:e5f923aa6a47e133d1cf87d60700889d7eae68988704e20c75fb2d65677a8e4b"}, + {file = "tornado-6.2.tar.gz", hash = "sha256:9b630419bde84ec666bfd7ea0a4cb2a8a651c2d5cccdbdd1972a0c859dfc3c13"}, +] + +[[package]] +name = "tqdm" +version = "4.65.0" +description = "Fast, Extensible Progress Meter" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tqdm-4.65.0-py3-none-any.whl", hash = "sha256:c4f53a17fe37e132815abceec022631be8ffe1b9381c2e6e30aa70edc99e9671"}, + {file = "tqdm-4.65.0.tar.gz", hash = "sha256:1871fb68a86b8fb3b59ca4cdd3dcccbc7e6d613eeed31f4c332531977b89beb5"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[package.extras] +dev = ["py-make (>=0.1.0)", "twine", "wheel"] +notebook = ["ipywidgets (>=6)"] +slack = ["slack-sdk"] +telegram = ["requests"] + +[[package]] +name = "traitlets" +version = "5.9.0" +description = "Traitlets Python configuration system" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "traitlets-5.9.0-py3-none-any.whl", hash = "sha256:9e6ec080259b9a5940c797d58b613b5e31441c2257b87c2e795c5228ae80d2d8"}, + {file = "traitlets-5.9.0.tar.gz", hash = "sha256:f6cde21a9c68cf756af02035f72d5a723bf607e862e7be33ece505abf4a3bad9"}, +] + +[package.extras] +docs = ["myst-parser", "pydata-sphinx-theme", "sphinx"] +test = ["argcomplete (>=2.0)", "pre-commit", "pytest", "pytest-mock"] + +[[package]] +name = "trimesh" +version = "3.21.0" +description = "Import, export, process, analyze and view triangular meshes." +category = "main" +optional = false +python-versions = "*" +files = [ + {file = "trimesh-3.21.0-py3-none-any.whl", hash = "sha256:459c811448facf410247bd9f284cd3026bee71804d0b0704b90352bbb069944c"}, + {file = "trimesh-3.21.0.tar.gz", hash = "sha256:2aa8c312900e24a7487744a7e9d47e794bd6e79ad629aaa48cde0d37ee63a094"}, +] + +[package.dependencies] +numpy = "*" + +[package.extras] +all = ["chardet", "colorlog", "glooey", "jsonschema", "lxml", "mapbox-earcut", "meshio", "networkx", "pillow", "psutil", "pycollada", "pyglet (<2)", "python-fcl", "requests", "rtree", "scikit-image", "scipy", "setuptools", "shapely", "svg.path", "sympy", "xatlas", "xxhash"] +easy = ["chardet", "colorlog", "jsonschema", "lxml", "mapbox-earcut", "networkx", "pillow", "pycollada", "requests", "rtree", "scipy", "setuptools", "shapely", "svg.path", "sympy", "xxhash"] +test = ["autopep8", "coveralls", "ezdxf", "pyinstrument", "pytest", "pytest-cov", "ruff"] + +[[package]] +name = "typer" +version = "0.7.0" +description = "Typer, build great CLIs. Easy to code. Based on Python type hints." +category = "main" +optional = false +python-versions = ">=3.6" +files = [ + {file = "typer-0.7.0-py3-none-any.whl", hash = "sha256:b5e704f4e48ec263de1c0b3a2387cd405a13767d2f907f44c1a08cbad96f606d"}, + {file = "typer-0.7.0.tar.gz", hash = "sha256:ff797846578a9f2a201b53442aedeb543319466870fbe1c701eab66dd7681165"}, +] + +[package.dependencies] +click = ">=7.1.1,<9.0.0" + +[package.extras] +all = ["colorama (>=0.4.3,<0.5.0)", "rich (>=10.11.0,<13.0.0)", "shellingham (>=1.3.0,<2.0.0)"] +dev = ["autoflake (>=1.3.1,<2.0.0)", "flake8 (>=3.8.3,<4.0.0)", "pre-commit (>=2.17.0,<3.0.0)"] +doc = ["cairosvg (>=2.5.2,<3.0.0)", "mdx-include (>=1.4.1,<2.0.0)", "mkdocs (>=1.1.2,<2.0.0)", "mkdocs-material (>=8.1.4,<9.0.0)", "pillow (>=9.3.0,<10.0.0)"] +test = ["black (>=22.3.0,<23.0.0)", "coverage (>=6.2,<7.0)", "isort (>=5.0.6,<6.0.0)", "mypy (==0.910)", "pytest (>=4.4.0,<8.0.0)", "pytest-cov (>=2.10.0,<5.0.0)", "pytest-sugar (>=0.9.4,<0.10.0)", "pytest-xdist (>=1.32.0,<4.0.0)", "rich (>=10.11.0,<13.0.0)", "shellingham (>=1.3.0,<2.0.0)"] + +[[package]] +name = "typing-extensions" +version = "4.5.0" +description = "Backported and Experimental Type Hints for Python 3.7+" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "typing_extensions-4.5.0-py3-none-any.whl", hash = "sha256:fb33085c39dd998ac16d1431ebc293a8b3eedd00fd4a32de0ff79002c19511b4"}, + {file = "typing_extensions-4.5.0.tar.gz", hash = "sha256:5cb5f4a79139d699607b3ef622a1dedafa84e115ab0024e0d9c044a9479ca7cb"}, +] + +[[package]] +name = "ujson" +version = "5.7.0" +description = "Ultra fast JSON encoder and decoder for Python" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "ujson-5.7.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5eba5e69e4361ac3a311cf44fa71bc619361b6e0626768a494771aacd1c2f09b"}, + {file = "ujson-5.7.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:aae4d9e1b4c7b61780f0a006c897a4a1904f862fdab1abb3ea8f45bd11aa58f3"}, + {file = "ujson-5.7.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d2e43ccdba1cb5c6d3448eadf6fc0dae7be6c77e357a3abc968d1b44e265866d"}, + {file = "ujson-5.7.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:54384ce4920a6d35fa9ea8e580bc6d359e3eb961fa7e43f46c78e3ed162d56ff"}, + {file = "ujson-5.7.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:24ad1aa7fc4e4caa41d3d343512ce68e41411fb92adf7f434a4d4b3749dc8f58"}, + {file = "ujson-5.7.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:afff311e9f065a8f03c3753db7011bae7beb73a66189c7ea5fcb0456b7041ea4"}, + {file = "ujson-5.7.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6e80f0d03e7e8646fc3d79ed2d875cebd4c83846e129737fdc4c2532dbd43d9e"}, + {file = "ujson-5.7.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:137831d8a0db302fb6828ee21c67ad63ac537bddc4376e1aab1c8573756ee21c"}, + {file = "ujson-5.7.0-cp310-cp310-win32.whl", hash = "sha256:7df3fd35ebc14dafeea031038a99232b32f53fa4c3ecddb8bed132a43eefb8ad"}, + {file = "ujson-5.7.0-cp310-cp310-win_amd64.whl", hash = "sha256:af4639f684f425177d09ae409c07602c4096a6287027469157bfb6f83e01448b"}, + {file = "ujson-5.7.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9b0f2680ce8a70f77f5d70aaf3f013d53e6af6d7058727a35d8ceb4a71cdd4e9"}, + {file = "ujson-5.7.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:67a19fd8e7d8cc58a169bea99fed5666023adf707a536d8f7b0a3c51dd498abf"}, + {file = "ujson-5.7.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6abb8e6d8f1ae72f0ed18287245f5b6d40094e2656d1eab6d99d666361514074"}, + {file = "ujson-5.7.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d8cd622c069368d5074bd93817b31bdb02f8d818e57c29e206f10a1f9c6337dd"}, + {file = "ujson-5.7.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:14f9082669f90e18e64792b3fd0bf19f2b15e7fe467534a35ea4b53f3bf4b755"}, + {file = "ujson-5.7.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d7ff6ebb43bc81b057724e89550b13c9a30eda0f29c2f506f8b009895438f5a6"}, + {file = "ujson-5.7.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:f7f241488879d91a136b299e0c4ce091996c684a53775e63bb442d1a8e9ae22a"}, + {file = "ujson-5.7.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5593263a7fcfb934107444bcfba9dde8145b282de0ee9f61e285e59a916dda0f"}, + {file = "ujson-5.7.0-cp311-cp311-win32.whl", hash = "sha256:26c2b32b489c393106e9cb68d0a02e1a7b9d05a07429d875c46b94ee8405bdb7"}, + {file = "ujson-5.7.0-cp311-cp311-win_amd64.whl", hash = "sha256:ed24406454bb5a31df18f0a423ae14beb27b28cdfa34f6268e7ebddf23da807e"}, + {file = "ujson-5.7.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:18679484e3bf9926342b1c43a3bd640f93a9eeeba19ef3d21993af7b0c44785d"}, + {file = "ujson-5.7.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ee295761e1c6c30400641f0a20d381633d7622633cdf83a194f3c876a0e4b7e"}, + {file = "ujson-5.7.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b738282e12a05f400b291966630a98d622da0938caa4bc93cf65adb5f4281c60"}, + {file = "ujson-5.7.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:00343501dbaa5172e78ef0e37f9ebd08040110e11c12420ff7c1f9f0332d939e"}, + {file = "ujson-5.7.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c0d1f7c3908357ee100aa64c4d1cf91edf99c40ac0069422a4fd5fd23b263263"}, + {file = "ujson-5.7.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:a5d2f44331cf04689eafac7a6596c71d6657967c07ac700b0ae1c921178645da"}, + {file = "ujson-5.7.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:16b2254a77b310f118717715259a196662baa6b1f63b1a642d12ab1ff998c3d7"}, + {file = "ujson-5.7.0-cp37-cp37m-win32.whl", hash = "sha256:6faf46fa100b2b89e4db47206cf8a1ffb41542cdd34dde615b2fc2288954f194"}, + {file = "ujson-5.7.0-cp37-cp37m-win_amd64.whl", hash = "sha256:ff0004c3f5a9a6574689a553d1b7819d1a496b4f005a7451f339dc2d9f4cf98c"}, + {file = "ujson-5.7.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:75204a1dd7ec6158c8db85a2f14a68d2143503f4bafb9a00b63fe09d35762a5e"}, + {file = "ujson-5.7.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7312731c7826e6c99cdd3ac503cd9acd300598e7a80bcf41f604fee5f49f566c"}, + {file = "ujson-5.7.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b9dc5a90e2149643df7f23634fe202fed5ebc787a2a1be95cf23632b4d90651"}, + {file = "ujson-5.7.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b6a6961fc48821d84b1198a09516e396d56551e910d489692126e90bf4887d29"}, + {file = "ujson-5.7.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b01a9af52a0d5c46b2c68e3f258fdef2eacaa0ce6ae3e9eb97983f5b1166edb6"}, + {file = "ujson-5.7.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b7316d3edeba8a403686cdcad4af737b8415493101e7462a70ff73dd0609eafc"}, + {file = "ujson-5.7.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:4ee997799a23227e2319a3f8817ce0b058923dbd31904761b788dc8f53bd3e30"}, + {file = "ujson-5.7.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:dda9aa4c33435147262cd2ea87c6b7a1ca83ba9b3933ff7df34e69fee9fced0c"}, + {file = "ujson-5.7.0-cp38-cp38-win32.whl", hash = "sha256:bea8d30e362180aafecabbdcbe0e1f0b32c9fa9e39c38e4af037b9d3ca36f50c"}, + {file = "ujson-5.7.0-cp38-cp38-win_amd64.whl", hash = "sha256:c96e3b872bf883090ddf32cc41957edf819c5336ab0007d0cf3854e61841726d"}, + {file = "ujson-5.7.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6411aea4c94a8e93c2baac096fbf697af35ba2b2ed410b8b360b3c0957a952d3"}, + {file = "ujson-5.7.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3d3b3499c55911f70d4e074c626acdb79a56f54262c3c83325ffb210fb03e44d"}, + {file = "ujson-5.7.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:341f891d45dd3814d31764626c55d7ab3fd21af61fbc99d070e9c10c1190680b"}, + {file = "ujson-5.7.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2f242eec917bafdc3f73a1021617db85f9958df80f267db69c76d766058f7b19"}, + {file = "ujson-5.7.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c3af9f9f22a67a8c9466a32115d9073c72a33ae627b11de6f592df0ee09b98b6"}, + {file = "ujson-5.7.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4a3d794afbf134df3056a813e5c8a935208cddeae975bd4bc0ef7e89c52f0ce0"}, + {file = "ujson-5.7.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:800bf998e78dae655008dd10b22ca8dc93bdcfcc82f620d754a411592da4bbf2"}, + {file = "ujson-5.7.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:b5ac3d5c5825e30b438ea92845380e812a476d6c2a1872b76026f2e9d8060fc2"}, + {file = "ujson-5.7.0-cp39-cp39-win32.whl", hash = "sha256:cd90027e6d93e8982f7d0d23acf88c896d18deff1903dd96140613389b25c0dd"}, + {file = "ujson-5.7.0-cp39-cp39-win_amd64.whl", hash = "sha256:523ee146cdb2122bbd827f4dcc2a8e66607b3f665186bce9e4f78c9710b6d8ab"}, + {file = "ujson-5.7.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:e87cec407ec004cf1b04c0ed7219a68c12860123dfb8902ef880d3d87a71c172"}, + {file = "ujson-5.7.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bab10165db6a7994e67001733f7f2caf3400b3e11538409d8756bc9b1c64f7e8"}, + {file = "ujson-5.7.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b522be14a28e6ac1cf818599aeff1004a28b42df4ed4d7bc819887b9dac915fc"}, + {file = "ujson-5.7.0-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7592f40175c723c032cdbe9fe5165b3b5903604f774ab0849363386e99e1f253"}, + {file = "ujson-5.7.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:ed22f9665327a981f288a4f758a432824dc0314e4195a0eaeb0da56a477da94d"}, + {file = "ujson-5.7.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:adf445a49d9a97a5a4c9bb1d652a1528de09dd1c48b29f79f3d66cea9f826bf6"}, + {file = "ujson-5.7.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:64772a53f3c4b6122ed930ae145184ebaed38534c60f3d859d8c3f00911eb122"}, + {file = "ujson-5.7.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:35209cb2c13fcb9d76d249286105b4897b75a5e7f0efb0c0f4b90f222ce48910"}, + {file = "ujson-5.7.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:90712dfc775b2c7a07d4d8e059dd58636bd6ff1776d79857776152e693bddea6"}, + {file = "ujson-5.7.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:0e4e8981c6e7e9e637e637ad8ffe948a09e5434bc5f52ecbb82b4b4cfc092bfb"}, + {file = "ujson-5.7.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:581c945b811a3d67c27566539bfcb9705ea09cb27c4be0002f7a553c8886b817"}, + {file = "ujson-5.7.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d36a807a24c7d44f71686685ae6fbc8793d784bca1adf4c89f5f780b835b6243"}, + {file = "ujson-5.7.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8b4257307e3662aa65e2644a277ca68783c5d51190ed9c49efebdd3cbfd5fa44"}, + {file = "ujson-5.7.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea7423d8a2f9e160c5e011119741682414c5b8dce4ae56590a966316a07a4618"}, + {file = "ujson-5.7.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:4c592eb91a5968058a561d358d0fef59099ed152cfb3e1cd14eee51a7a93879e"}, + {file = "ujson-5.7.0.tar.gz", hash = "sha256:e788e5d5dcae8f6118ac9b45d0b891a0d55f7ac480eddcb7f07263f2bcf37b23"}, +] + +[[package]] +name = "uri-template" +version = "1.2.0" +description = "RFC 6570 URI Template Processor" +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ + {file = "uri_template-1.2.0-py3-none-any.whl", hash = "sha256:f1699c77b73b925cf4937eae31ab282a86dc885c333f2e942513f08f691fc7db"}, + {file = "uri_template-1.2.0.tar.gz", hash = "sha256:934e4d09d108b70eb8a24410af8615294d09d279ce0e7cbcdaef1bd21f932b06"}, +] + +[package.extras] +dev = ["flake8 (<4.0.0)", "flake8-annotations", "flake8-bugbear", "flake8-commas", "flake8-comprehensions", "flake8-continuation", "flake8-datetimez", "flake8-docstrings", "flake8-import-order", "flake8-literal", "flake8-noqa", "flake8-requirements", "flake8-type-annotations", "flake8-use-fstring", "mypy", "pep8-naming"] + +[[package]] +name = "urllib3" +version = "1.26.15" +description = "HTTP library with thread-safe connection pooling, file post, and more." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" +files = [ + {file = "urllib3-1.26.15-py2.py3-none-any.whl", hash = "sha256:aa751d169e23c7479ce47a0cb0da579e3ede798f994f5816a74e4f4500dcea42"}, + {file = "urllib3-1.26.15.tar.gz", hash = "sha256:8a388717b9476f934a21484e8c8e61875ab60644d29b9b39e11e4b9dc1c6b305"}, +] + +[package.extras] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] +secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"] +socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] + +[[package]] +name = "urwid" +version = "2.1.2" +description = "A full-featured console (xterm et al.) user interface library" +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "urwid-2.1.2.tar.gz", hash = "sha256:588bee9c1cb208d0906a9f73c613d2bd32c3ed3702012f51efe318a3f2127eae"}, +] + +[[package]] +name = "urwid-readline" +version = "0.13" +description = "A textbox edit widget for urwid that supports readline shortcuts" +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "urwid_readline-0.13.tar.gz", hash = "sha256:018020cbc864bb5ed87be17dc26b069eae2755cb29f3a9c569aac3bded1efaf4"}, +] + +[package.dependencies] +urwid = "*" + +[package.extras] +dev = ["black", "pytest"] + +[[package]] +name = "visidata" +version = "2.11" +description = "terminal interface for exploring and arranging tabular data" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "visidata-2.11-py3-none-any.whl", hash = "sha256:84fd9c31ff3bce07055bfe735d0455fca69db462db284c61b53e88e55debba77"}, + {file = "visidata-2.11.tar.gz", hash = "sha256:c09ecb65025dc9d6513bfd5e5aff9c430ffc325cc183b81abf5f2df5a96b66b3"}, +] + +[package.dependencies] +importlib-metadata = ">=3.6" +python-dateutil = "*" +windows-curses = {version = "*", markers = "platform_system == \"Windows\""} + +[[package]] +name = "watchdog" +version = "3.0.0" +description = "Filesystem events monitoring" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "watchdog-3.0.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:336adfc6f5cc4e037d52db31194f7581ff744b67382eb6021c868322e32eef41"}, + {file = "watchdog-3.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a70a8dcde91be523c35b2bf96196edc5730edb347e374c7de7cd20c43ed95397"}, + {file = "watchdog-3.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:adfdeab2da79ea2f76f87eb42a3ab1966a5313e5a69a0213a3cc06ef692b0e96"}, + {file = "watchdog-3.0.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2b57a1e730af3156d13b7fdddfc23dea6487fceca29fc75c5a868beed29177ae"}, + {file = "watchdog-3.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7ade88d0d778b1b222adebcc0927428f883db07017618a5e684fd03b83342bd9"}, + {file = "watchdog-3.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7e447d172af52ad204d19982739aa2346245cc5ba6f579d16dac4bfec226d2e7"}, + {file = "watchdog-3.0.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:9fac43a7466eb73e64a9940ac9ed6369baa39b3bf221ae23493a9ec4d0022674"}, + {file = "watchdog-3.0.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:8ae9cda41fa114e28faf86cb137d751a17ffd0316d1c34ccf2235e8a84365c7f"}, + {file = "watchdog-3.0.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:25f70b4aa53bd743729c7475d7ec41093a580528b100e9a8c5b5efe8899592fc"}, + {file = "watchdog-3.0.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4f94069eb16657d2c6faada4624c39464f65c05606af50bb7902e036e3219be3"}, + {file = "watchdog-3.0.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7c5f84b5194c24dd573fa6472685b2a27cc5a17fe5f7b6fd40345378ca6812e3"}, + {file = "watchdog-3.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3aa7f6a12e831ddfe78cdd4f8996af9cf334fd6346531b16cec61c3b3c0d8da0"}, + {file = "watchdog-3.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:233b5817932685d39a7896b1090353fc8efc1ef99c9c054e46c8002561252fb8"}, + {file = "watchdog-3.0.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:13bbbb462ee42ec3c5723e1205be8ced776f05b100e4737518c67c8325cf6100"}, + {file = "watchdog-3.0.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:8f3ceecd20d71067c7fd4c9e832d4e22584318983cabc013dbf3f70ea95de346"}, + {file = "watchdog-3.0.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:c9d8c8ec7efb887333cf71e328e39cffbf771d8f8f95d308ea4125bf5f90ba64"}, + {file = "watchdog-3.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:0e06ab8858a76e1219e68c7573dfeba9dd1c0219476c5a44d5333b01d7e1743a"}, + {file = "watchdog-3.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:d00e6be486affb5781468457b21a6cbe848c33ef43f9ea4a73b4882e5f188a44"}, + {file = "watchdog-3.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:c07253088265c363d1ddf4b3cdb808d59a0468ecd017770ed716991620b8f77a"}, + {file = "watchdog-3.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:5113334cf8cf0ac8cd45e1f8309a603291b614191c9add34d33075727a967709"}, + {file = "watchdog-3.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:51f90f73b4697bac9c9a78394c3acbbd331ccd3655c11be1a15ae6fe289a8c83"}, + {file = "watchdog-3.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:ba07e92756c97e3aca0912b5cbc4e5ad802f4557212788e72a72a47ff376950d"}, + {file = "watchdog-3.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:d429c2430c93b7903914e4db9a966c7f2b068dd2ebdd2fa9b9ce094c7d459f33"}, + {file = "watchdog-3.0.0-py3-none-win32.whl", hash = "sha256:3ed7c71a9dccfe838c2f0b6314ed0d9b22e77d268c67e015450a29036a81f60f"}, + {file = "watchdog-3.0.0-py3-none-win_amd64.whl", hash = "sha256:4c9956d27be0bb08fc5f30d9d0179a855436e655f046d288e2bcc11adfae893c"}, + {file = "watchdog-3.0.0-py3-none-win_ia64.whl", hash = "sha256:5d9f3a10e02d7371cd929b5d8f11e87d4bad890212ed3901f9b4d68767bee759"}, + {file = "watchdog-3.0.0.tar.gz", hash = "sha256:4d98a320595da7a7c5a18fc48cb633c2e73cda78f93cac2ef42d42bf609a33f9"}, +] + +[package.extras] +watchmedo = ["PyYAML (>=3.10)"] + +[[package]] +name = "wcwidth" +version = "0.2.6" +description = "Measures the displayed width of unicode strings in a terminal" +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "wcwidth-0.2.6-py2.py3-none-any.whl", hash = "sha256:795b138f6875577cd91bba52baf9e445cd5118fd32723b460e30a0af30ea230e"}, + {file = "wcwidth-0.2.6.tar.gz", hash = "sha256:a5220780a404dbe3353789870978e472cfe477761f06ee55077256e509b156d0"}, +] + +[[package]] +name = "webcolors" +version = "1.12" +description = "A library for working with color names and color values formats defined by HTML and CSS." +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "webcolors-1.12-py3-none-any.whl", hash = "sha256:d98743d81d498a2d3eaf165196e65481f0d2ea85281463d856b1e51b09f62dce"}, + {file = "webcolors-1.12.tar.gz", hash = "sha256:16d043d3a08fd6a1b1b7e3e9e62640d09790dce80d2bdd4792a175b35fe794a9"}, +] + +[[package]] +name = "webencodings" +version = "0.5.1" +description = "Character encoding aliases for legacy web content" +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78"}, + {file = "webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923"}, +] + +[[package]] +name = "websocket-client" +version = "1.5.1" +description = "WebSocket client for Python with low level API options" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "websocket-client-1.5.1.tar.gz", hash = "sha256:3f09e6d8230892547132177f575a4e3e73cfdf06526e20cc02aa1c3b47184d40"}, + {file = "websocket_client-1.5.1-py3-none-any.whl", hash = "sha256:cdf5877568b7e83aa7cf2244ab56a3213de587bbe0ce9d8b9600fc77b455d89e"}, +] + +[package.extras] +docs = ["Sphinx (>=3.4)", "sphinx-rtd-theme (>=0.5)"] +optional = ["python-socks", "wsaccel"] +test = ["websockets"] + +[[package]] +name = "werkzeug" +version = "2.2.3" +description = "The comprehensive WSGI web application library." +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "Werkzeug-2.2.3-py3-none-any.whl", hash = "sha256:56433961bc1f12533306c624f3be5e744389ac61d722175d543e1751285da612"}, + {file = "Werkzeug-2.2.3.tar.gz", hash = "sha256:2e1ccc9417d4da358b9de6f174e3ac094391ea1d4fbef2d667865d819dfd0afe"}, +] + +[package.dependencies] +MarkupSafe = ">=2.1.1" + +[package.extras] +watchdog = ["watchdog"] + +[[package]] +name = "whatthepatch" +version = "1.0.4" +description = "A patch parsing and application library." +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "whatthepatch-1.0.4-py3-none-any.whl", hash = "sha256:e1261068ba4df71f72ac1b0dfd2271362691c25e8843c1fcbd170166c219f3aa"}, + {file = "whatthepatch-1.0.4.tar.gz", hash = "sha256:e95c108087845b09258ddfaf82aa13cf83ba8319475117c0909754ca8b54d742"}, +] + +[[package]] +name = "wheel" +version = "0.37.1" +description = "A built-package format for Python" +category = "main" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" +files = [ + {file = "wheel-0.37.1-py2.py3-none-any.whl", hash = "sha256:4bdcd7d840138086126cd09254dc6195fb4fc6f01c050a1d7236f2630db1d22a"}, + {file = "wheel-0.37.1.tar.gz", hash = "sha256:e9a504e793efbca1b8e0e9cb979a249cf4a0a7b5b8c9e8b65a5e39d49529c1c4"}, +] + +[package.extras] +test = ["pytest (>=3.0.0)", "pytest-cov"] + +[[package]] +name = "widgetsnbextension" +version = "4.0.6" +description = "Jupyter interactive widgets for Jupyter Notebook" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "widgetsnbextension-4.0.6-py3-none-any.whl", hash = "sha256:7df2bffa274b0b416c1fa0789e321451858a9e276e1220b40a16cc994192e2b7"}, + {file = "widgetsnbextension-4.0.6.tar.gz", hash = "sha256:1a07d06c881a7c16ca7ab4541b476edbe2e404f5c5f0cf524ffa2406a8bd7c80"}, +] + +[[package]] +name = "windows-curses" +version = "2.3.1" +description = "Support for the standard curses module on Windows" +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "windows_curses-2.3.1-cp310-cp310-win32.whl", hash = "sha256:2644f4547ae5124ce5129b66faa59ee0995b7b7205ed5e3920f6ecfef2e46275"}, + {file = "windows_curses-2.3.1-cp310-cp310-win_amd64.whl", hash = "sha256:8b541520895649c0835771775034a2b4edf36da3c3d9381c5022b5b4f9a5014e"}, + {file = "windows_curses-2.3.1-cp311-cp311-win32.whl", hash = "sha256:25e7ff3d77aed6c747456b06fbc1528d67fc59d1ef3be9ca244774e65e6bdbb2"}, + {file = "windows_curses-2.3.1-cp311-cp311-win_amd64.whl", hash = "sha256:395656bfe88d6f60cb18604605d423e0f2d1c3a8f550507dca5877a9d0b3a0f3"}, + {file = "windows_curses-2.3.1-cp36-cp36m-win32.whl", hash = "sha256:6ea8e1c4536fee248ee3f88e5010871df749932b7e829e2f012e5d23bd2fe31d"}, + {file = "windows_curses-2.3.1-cp36-cp36m-win_amd64.whl", hash = "sha256:59856b41676c4b3eb527eb6b1478803d4dc92413b2e63aea762407807ffcd3ac"}, + {file = "windows_curses-2.3.1-cp37-cp37m-win32.whl", hash = "sha256:9cd0ba6efde23930736eff45a0aa0af6fd82e60b4787a46157ef4956d2c52b06"}, + {file = "windows_curses-2.3.1-cp37-cp37m-win_amd64.whl", hash = "sha256:f9a7fcd03934e40238f9bbeddae51e3fdc442f28bca50afccdc521245ed39439"}, + {file = "windows_curses-2.3.1-cp38-cp38-win32.whl", hash = "sha256:5c55ebafdb402cfa927174a03d651cd1b1e76d6e6cf71818f9d3378636c00e74"}, + {file = "windows_curses-2.3.1-cp38-cp38-win_amd64.whl", hash = "sha256:a551aaa09d6ec28f64ade8e85fd0c52880c8e9114729a79c34803104e49bed71"}, + {file = "windows_curses-2.3.1-cp39-cp39-win32.whl", hash = "sha256:aab7e28133bf81769cddf8b3c3c8ab89e76cd43effd371c6370e918b6dfccf1b"}, + {file = "windows_curses-2.3.1-cp39-cp39-win_amd64.whl", hash = "sha256:85675de4ae7058348140daae83a8a7b81147a84ef9ab699307b3168f9490292f"}, +] + +[[package]] +name = "wirerope" +version = "0.4.7" +description = "'Turn functions and methods into fully controllable objects'" +category = "main" +optional = false +python-versions = "*" +files = [ + {file = "wirerope-0.4.7.tar.gz", hash = "sha256:f3961039218276283c5037da0fa164619def0327595f10892d562a61a8603990"}, +] + +[package.dependencies] +six = ">=1.11.0" + +[package.extras] +doc = ["sphinx"] +test = ["pytest (>=4.6.7)", "pytest-cov (>=2.6.1)"] + +[[package]] +name = "wrapt" +version = "1.15.0" +description = "Module for decorators, wrappers and monkey patching." +category = "dev" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" +files = [ + {file = "wrapt-1.15.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:ca1cccf838cd28d5a0883b342474c630ac48cac5df0ee6eacc9c7290f76b11c1"}, + {file = "wrapt-1.15.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:e826aadda3cae59295b95343db8f3d965fb31059da7de01ee8d1c40a60398b29"}, + {file = "wrapt-1.15.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:5fc8e02f5984a55d2c653f5fea93531e9836abbd84342c1d1e17abc4a15084c2"}, + {file = "wrapt-1.15.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:96e25c8603a155559231c19c0349245eeb4ac0096fe3c1d0be5c47e075bd4f46"}, + {file = "wrapt-1.15.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:40737a081d7497efea35ab9304b829b857f21558acfc7b3272f908d33b0d9d4c"}, + {file = "wrapt-1.15.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:f87ec75864c37c4c6cb908d282e1969e79763e0d9becdfe9fe5473b7bb1e5f09"}, + {file = "wrapt-1.15.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:1286eb30261894e4c70d124d44b7fd07825340869945c79d05bda53a40caa079"}, + {file = "wrapt-1.15.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:493d389a2b63c88ad56cdc35d0fa5752daac56ca755805b1b0c530f785767d5e"}, + {file = "wrapt-1.15.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:58d7a75d731e8c63614222bcb21dd992b4ab01a399f1f09dd82af17bbfc2368a"}, + {file = "wrapt-1.15.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:21f6d9a0d5b3a207cdf7acf8e58d7d13d463e639f0c7e01d82cdb671e6cb7923"}, + {file = "wrapt-1.15.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ce42618f67741d4697684e501ef02f29e758a123aa2d669e2d964ff734ee00ee"}, + {file = "wrapt-1.15.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41d07d029dd4157ae27beab04d22b8e261eddfc6ecd64ff7000b10dc8b3a5727"}, + {file = "wrapt-1.15.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:54accd4b8bc202966bafafd16e69da9d5640ff92389d33d28555c5fd4f25ccb7"}, + {file = "wrapt-1.15.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2fbfbca668dd15b744418265a9607baa970c347eefd0db6a518aaf0cfbd153c0"}, + {file = "wrapt-1.15.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:76e9c727a874b4856d11a32fb0b389afc61ce8aaf281ada613713ddeadd1cfec"}, + {file = "wrapt-1.15.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e20076a211cd6f9b44a6be58f7eeafa7ab5720eb796975d0c03f05b47d89eb90"}, + {file = "wrapt-1.15.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a74d56552ddbde46c246b5b89199cb3fd182f9c346c784e1a93e4dc3f5ec9975"}, + {file = "wrapt-1.15.0-cp310-cp310-win32.whl", hash = "sha256:26458da5653aa5b3d8dc8b24192f574a58984c749401f98fff994d41d3f08da1"}, + {file = "wrapt-1.15.0-cp310-cp310-win_amd64.whl", hash = "sha256:75760a47c06b5974aa5e01949bf7e66d2af4d08cb8c1d6516af5e39595397f5e"}, + {file = "wrapt-1.15.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ba1711cda2d30634a7e452fc79eabcadaffedf241ff206db2ee93dd2c89a60e7"}, + {file = "wrapt-1.15.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:56374914b132c702aa9aa9959c550004b8847148f95e1b824772d453ac204a72"}, + {file = "wrapt-1.15.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a89ce3fd220ff144bd9d54da333ec0de0399b52c9ac3d2ce34b569cf1a5748fb"}, + {file = "wrapt-1.15.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3bbe623731d03b186b3d6b0d6f51865bf598587c38d6f7b0be2e27414f7f214e"}, + {file = "wrapt-1.15.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3abbe948c3cbde2689370a262a8d04e32ec2dd4f27103669a45c6929bcdbfe7c"}, + {file = "wrapt-1.15.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b67b819628e3b748fd3c2192c15fb951f549d0f47c0449af0764d7647302fda3"}, + {file = "wrapt-1.15.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:7eebcdbe3677e58dd4c0e03b4f2cfa346ed4049687d839adad68cc38bb559c92"}, + {file = "wrapt-1.15.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:74934ebd71950e3db69960a7da29204f89624dde411afbfb3b4858c1409b1e98"}, + {file = "wrapt-1.15.0-cp311-cp311-win32.whl", hash = "sha256:bd84395aab8e4d36263cd1b9308cd504f6cf713b7d6d3ce25ea55670baec5416"}, + {file = "wrapt-1.15.0-cp311-cp311-win_amd64.whl", hash = "sha256:a487f72a25904e2b4bbc0817ce7a8de94363bd7e79890510174da9d901c38705"}, + {file = "wrapt-1.15.0-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:4ff0d20f2e670800d3ed2b220d40984162089a6e2c9646fdb09b85e6f9a8fc29"}, + {file = "wrapt-1.15.0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:9ed6aa0726b9b60911f4aed8ec5b8dd7bf3491476015819f56473ffaef8959bd"}, + {file = "wrapt-1.15.0-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:896689fddba4f23ef7c718279e42f8834041a21342d95e56922e1c10c0cc7afb"}, + {file = "wrapt-1.15.0-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:75669d77bb2c071333417617a235324a1618dba66f82a750362eccbe5b61d248"}, + {file = "wrapt-1.15.0-cp35-cp35m-win32.whl", hash = "sha256:fbec11614dba0424ca72f4e8ba3c420dba07b4a7c206c8c8e4e73f2e98f4c559"}, + {file = "wrapt-1.15.0-cp35-cp35m-win_amd64.whl", hash = "sha256:fd69666217b62fa5d7c6aa88e507493a34dec4fa20c5bd925e4bc12fce586639"}, + {file = "wrapt-1.15.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:b0724f05c396b0a4c36a3226c31648385deb6a65d8992644c12a4963c70326ba"}, + {file = "wrapt-1.15.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bbeccb1aa40ab88cd29e6c7d8585582c99548f55f9b2581dfc5ba68c59a85752"}, + {file = "wrapt-1.15.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:38adf7198f8f154502883242f9fe7333ab05a5b02de7d83aa2d88ea621f13364"}, + {file = "wrapt-1.15.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:578383d740457fa790fdf85e6d346fda1416a40549fe8db08e5e9bd281c6a475"}, + {file = "wrapt-1.15.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:a4cbb9ff5795cd66f0066bdf5947f170f5d63a9274f99bdbca02fd973adcf2a8"}, + {file = "wrapt-1.15.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:af5bd9ccb188f6a5fdda9f1f09d9f4c86cc8a539bd48a0bfdc97723970348418"}, + {file = "wrapt-1.15.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:b56d5519e470d3f2fe4aa7585f0632b060d532d0696c5bdfb5e8319e1d0f69a2"}, + {file = "wrapt-1.15.0-cp36-cp36m-win32.whl", hash = "sha256:77d4c1b881076c3ba173484dfa53d3582c1c8ff1f914c6461ab70c8428b796c1"}, + {file = "wrapt-1.15.0-cp36-cp36m-win_amd64.whl", hash = "sha256:077ff0d1f9d9e4ce6476c1a924a3332452c1406e59d90a2cf24aeb29eeac9420"}, + {file = "wrapt-1.15.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:5c5aa28df055697d7c37d2099a7bc09f559d5053c3349b1ad0c39000e611d317"}, + {file = "wrapt-1.15.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3a8564f283394634a7a7054b7983e47dbf39c07712d7b177b37e03f2467a024e"}, + {file = "wrapt-1.15.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:780c82a41dc493b62fc5884fb1d3a3b81106642c5c5c78d6a0d4cbe96d62ba7e"}, + {file = "wrapt-1.15.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e169e957c33576f47e21864cf3fc9ff47c223a4ebca8960079b8bd36cb014fd0"}, + {file = "wrapt-1.15.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b02f21c1e2074943312d03d243ac4388319f2456576b2c6023041c4d57cd7019"}, + {file = "wrapt-1.15.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f2e69b3ed24544b0d3dbe2c5c0ba5153ce50dcebb576fdc4696d52aa22db6034"}, + {file = "wrapt-1.15.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d787272ed958a05b2c86311d3a4135d3c2aeea4fc655705f074130aa57d71653"}, + {file = "wrapt-1.15.0-cp37-cp37m-win32.whl", hash = "sha256:02fce1852f755f44f95af51f69d22e45080102e9d00258053b79367d07af39c0"}, + {file = "wrapt-1.15.0-cp37-cp37m-win_amd64.whl", hash = "sha256:abd52a09d03adf9c763d706df707c343293d5d106aea53483e0ec8d9e310ad5e"}, + {file = "wrapt-1.15.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cdb4f085756c96a3af04e6eca7f08b1345e94b53af8921b25c72f096e704e145"}, + {file = "wrapt-1.15.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:230ae493696a371f1dbffaad3dafbb742a4d27a0afd2b1aecebe52b740167e7f"}, + {file = "wrapt-1.15.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63424c681923b9f3bfbc5e3205aafe790904053d42ddcc08542181a30a7a51bd"}, + {file = "wrapt-1.15.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d6bcbfc99f55655c3d93feb7ef3800bd5bbe963a755687cbf1f490a71fb7794b"}, + {file = "wrapt-1.15.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c99f4309f5145b93eca6e35ac1a988f0dc0a7ccf9ccdcd78d3c0adf57224e62f"}, + {file = "wrapt-1.15.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b130fe77361d6771ecf5a219d8e0817d61b236b7d8b37cc045172e574ed219e6"}, + {file = "wrapt-1.15.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:96177eb5645b1c6985f5c11d03fc2dbda9ad24ec0f3a46dcce91445747e15094"}, + {file = "wrapt-1.15.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d5fe3e099cf07d0fb5a1e23d399e5d4d1ca3e6dfcbe5c8570ccff3e9208274f7"}, + {file = "wrapt-1.15.0-cp38-cp38-win32.whl", hash = "sha256:abd8f36c99512755b8456047b7be10372fca271bf1467a1caa88db991e7c421b"}, + {file = "wrapt-1.15.0-cp38-cp38-win_amd64.whl", hash = "sha256:b06fa97478a5f478fb05e1980980a7cdf2712015493b44d0c87606c1513ed5b1"}, + {file = "wrapt-1.15.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2e51de54d4fb8fb50d6ee8327f9828306a959ae394d3e01a1ba8b2f937747d86"}, + {file = "wrapt-1.15.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0970ddb69bba00670e58955f8019bec4a42d1785db3faa043c33d81de2bf843c"}, + {file = "wrapt-1.15.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76407ab327158c510f44ded207e2f76b657303e17cb7a572ffe2f5a8a48aa04d"}, + {file = "wrapt-1.15.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cd525e0e52a5ff16653a3fc9e3dd827981917d34996600bbc34c05d048ca35cc"}, + {file = "wrapt-1.15.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d37ac69edc5614b90516807de32d08cb8e7b12260a285ee330955604ed9dd29"}, + {file = "wrapt-1.15.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:078e2a1a86544e644a68422f881c48b84fef6d18f8c7a957ffd3f2e0a74a0d4a"}, + {file = "wrapt-1.15.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:2cf56d0e237280baed46f0b5316661da892565ff58309d4d2ed7dba763d984b8"}, + {file = "wrapt-1.15.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7dc0713bf81287a00516ef43137273b23ee414fe41a3c14be10dd95ed98a2df9"}, + {file = "wrapt-1.15.0-cp39-cp39-win32.whl", hash = "sha256:46ed616d5fb42f98630ed70c3529541408166c22cdfd4540b88d5f21006b0eff"}, + {file = "wrapt-1.15.0-cp39-cp39-win_amd64.whl", hash = "sha256:eef4d64c650f33347c1f9266fa5ae001440b232ad9b98f1f43dfe7a79435c0a6"}, + {file = "wrapt-1.15.0-py3-none-any.whl", hash = "sha256:64b1df0f83706b4ef4cfb4fb0e4c2669100fd7ecacfb59e091fad300d4e04640"}, + {file = "wrapt-1.15.0.tar.gz", hash = "sha256:d06730c6aed78cee4126234cf2d071e01b44b915e725a6cb439a879ec9754a3a"}, +] + +[[package]] +name = "y-py" +version = "0.5.9" +description = "Python bindings for the Y-CRDT built from yrs (Rust)" +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "y_py-0.5.9-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:afa9a11aa2880dd8689894f3269b653e6d3bd1956963d5329be9a5bf021dab62"}, + {file = "y_py-0.5.9-cp310-cp310-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:e370ce076781adea161b04d2f666e8b4f89bc7e8927ef842fbb0283d3bfa73e0"}, + {file = "y_py-0.5.9-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b67dad339f9b6701f74ff7a6e901c7909eca4eea02cf955b28d87a42650bd1be"}, + {file = "y_py-0.5.9-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ae82a6d9cbaff8cb7505e81b5b7f9cd7756bb7e7110aef7914375fe56b012a90"}, + {file = "y_py-0.5.9-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c7ca64a2a97f708569dcabd55865915943e30267bf6d26c4d212d005951efe62"}, + {file = "y_py-0.5.9-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:55098440e32339c2dc3d652fb36bb77a4927dee5fd4ab0cb1fe12fdd163fd4f5"}, + {file = "y_py-0.5.9-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc9052a814e8b7ec756371a191f38de68b956437e0bb429c2dd503e658f298f9"}, + {file = "y_py-0.5.9-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:95d13b38c9055d607565b77cbae12e2bf0c1671c5cb8f2ee2e1230d41d2d6d34"}, + {file = "y_py-0.5.9-cp310-none-win32.whl", hash = "sha256:5dbd8d177ec7b9fef4a7b6d22eb2f8d5606fd5aac31cf2eab0dc18f0b3504c7c"}, + {file = "y_py-0.5.9-cp310-none-win_amd64.whl", hash = "sha256:d373c6bb8e21d5f7ec0833b76fa1ab480086ada602ef5bbf4724a25a21a00b6a"}, + {file = "y_py-0.5.9-cp311-cp311-macosx_10_7_x86_64.whl", hash = "sha256:f8f238144a302f17eb26b122cad9382fcff5ec6653b8a562130b9a5e44010098"}, + {file = "y_py-0.5.9-cp311-cp311-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:25637e3d011ca6f877a24f3083ff2549d1d619406d7e8a1455c445527205046c"}, + {file = "y_py-0.5.9-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2ffebe5e62cbfee6e24593927dedba77dc13ac4cfb9c822074ab566b1fb63d59"}, + {file = "y_py-0.5.9-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b0ed760e6aa5316227a0ba2d5d29634a4ef2d72c8bc55169ac01664e17e4b536"}, + {file = "y_py-0.5.9-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91be189fae8ba242528333e266e38d65cae3d9a09fe45867fab8578a3ddf2ea2"}, + {file = "y_py-0.5.9-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c3ae6d22b7cc599220a26b06da6ead9fd582eea5fdb6273b06fa3f060d0a26a7"}, + {file = "y_py-0.5.9-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:065f90501cf008375d70be6ce72dd41745e09d088f0b545f5f914d2c3f04f7ae"}, + {file = "y_py-0.5.9-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:742c486d5b792c4ad76e09426161302edddca85efe826fa01dcee50907326cd7"}, + {file = "y_py-0.5.9-cp311-none-win32.whl", hash = "sha256:2692c808bf28f797f8d693f45dc86563ac3b1626579f67ce9546dca69644d687"}, + {file = "y_py-0.5.9-cp311-none-win_amd64.whl", hash = "sha256:c1f5f287cc7ae127ed6a2fb1546e631b316a41d087d7d2db9caa3e5f59906dcf"}, + {file = "y_py-0.5.9-cp37-cp37m-macosx_10_7_x86_64.whl", hash = "sha256:9a59603cf42c20d02ee5add2e3d0ce48e89c480a2a02f642fb77f142c4f37958"}, + {file = "y_py-0.5.9-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b44473bb32217c78e18db66f497f6c8be33e339bab5f52398bb2468c904d5140"}, + {file = "y_py-0.5.9-cp37-cp37m-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1906f13e8d5ebfbd9c7948f57bc6f6f53b451b19c99350f42a0f648147a8acfe"}, + {file = "y_py-0.5.9-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:202b2a3e42e0a1eaedee26f8a3bc73cd9f994c4c2b15511ea56b9838178eb380"}, + {file = "y_py-0.5.9-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:13b9d2959d9a26536b6ad118fb026ff19bd79da52e4addf6f3a562e7c01d516e"}, + {file = "y_py-0.5.9-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ff3ddedaa95284f4f22a92b362f658f3d92f272d8c0fa009051bd5490c4d5a04"}, + {file = "y_py-0.5.9-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:85585e669d7679126e4a04e4bc0a063a641175a74eecfe47539e8da3e5b1da6e"}, + {file = "y_py-0.5.9-cp37-none-win32.whl", hash = "sha256:caf9b1feb69379d424a1d3d7c899b8e0389a3fb3131d39c3c03dcc3d4a93dbdc"}, + {file = "y_py-0.5.9-cp37-none-win_amd64.whl", hash = "sha256:7353af0e9c1f42fbf0ab340e253eeb333d58c890fa91d3eadb1b9adaf9336732"}, + {file = "y_py-0.5.9-cp38-cp38-macosx_10_7_x86_64.whl", hash = "sha256:ed0fd5265905cc7e23709479bc152d69f4972dec32fa322d20cb77f749707e78"}, + {file = "y_py-0.5.9-cp38-cp38-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:db1ac7f2d1862eb4c448cf76183399d555a63dbe2452bafecb1c2f691e36d687"}, + {file = "y_py-0.5.9-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fa685f7e43ce490dfb1e392ac48f584b75cd21f05dc526c160d15308236ce8a0"}, + {file = "y_py-0.5.9-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c42f3a6cd20153925b00c49af855a3277989d411bb8ea849095be943ee160821"}, + {file = "y_py-0.5.9-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:753aaae817d658a1e9d271663439d8e83d9d8effa45590ecdcadc600c7cf77e3"}, + {file = "y_py-0.5.9-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cc8e5f38842a4b043c9592bfa9a740147ddb8fac2d7a5b7bf6d52466c090ec23"}, + {file = "y_py-0.5.9-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ecd3cb0d13ac92e7b9235d1024dba9af0788161246f12dcf1f635d634ccb206a"}, + {file = "y_py-0.5.9-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9983e99e3a61452b39ffce98206c7e4c6d260f4e917c8fe53fb54aaf25df89a3"}, + {file = "y_py-0.5.9-cp38-none-win32.whl", hash = "sha256:63ef8e5b76cd54578a7fd5f72d8c698d9ccd7c555c7900ebfd38a24d397c3b15"}, + {file = "y_py-0.5.9-cp38-none-win_amd64.whl", hash = "sha256:fe70d0134fe2115c08866f0cac0eb5c0788093872b5026eb438a74e1ebafd659"}, + {file = "y_py-0.5.9-cp39-cp39-macosx_10_7_x86_64.whl", hash = "sha256:05f805b58422d5d7c8e7e8e2141d1c3cac4daaa4557ae6a9b84b141fe8d6289e"}, + {file = "y_py-0.5.9-cp39-cp39-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:a7977eeaceaeb0dfffcc5643c985c337ebc33a0b1d792ae0a9b1331cdd97366f"}, + {file = "y_py-0.5.9-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:800e73d2110b97a74c52db2c8ce03a78e96f0d66a7e0c87d8254170a67c2db0e"}, + {file = "y_py-0.5.9-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:add793f5f5c7c7a3eb1b09ffc771bdaae10a0bd482a370bf696b83f8dee8d1b4"}, + {file = "y_py-0.5.9-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f8b67ae37af8aac6160fda66c0f73bcdf65c06da9022eb76192c3fc45cfab994"}, + {file = "y_py-0.5.9-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2532ea5aefb223fd688c93860199d348a7601d814aac9e8784d816314588ddeb"}, + {file = "y_py-0.5.9-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:df78a0409dca11554a4b6442d7a8e61f762c3cfc78d55d98352392869a6b9ae0"}, + {file = "y_py-0.5.9-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d2da2a9e28dceab4832945a745cad507579f52b4d0c9e2f54ae156eb56875861"}, + {file = "y_py-0.5.9-cp39-none-win32.whl", hash = "sha256:fdafb93bfd5532b13a53c4090675bcd31724160017ecc73e492dc1211bc0377a"}, + {file = "y_py-0.5.9-cp39-none-win_amd64.whl", hash = "sha256:73200c59bb253b880825466717941ac57267f2f685b053e183183cb6fe82874d"}, + {file = "y_py-0.5.9-pp38-pypy38_pp73-macosx_10_7_x86_64.whl", hash = "sha256:af6df5ec1d66ee2d962026635d60e84ad35fc01b2a1e36b993360c0ce60ae349"}, + {file = "y_py-0.5.9-pp38-pypy38_pp73-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:0c0e333c20b0a6ce4a5851203d45898ab93f16426c342420b931e190c5b71d3d"}, + {file = "y_py-0.5.9-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f7434c77cd23592973ed63341b8d337e6aebaba5ed40d7f22e2d43dfd0c3a56e"}, + {file = "y_py-0.5.9-pp38-pypy38_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e30fe2491d095c6d695a2c96257967fd3e2497f0f777030c8492d03c18d46e2a"}, + {file = "y_py-0.5.9-pp38-pypy38_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a57d81260e048caacf43a2f851766687f53e8a8356df6947fb0eee7336a7e2de"}, + {file = "y_py-0.5.9-pp38-pypy38_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d4dfc276f988175baaa4ab321c3321a16ce33db3356c9bc5f4dea0db3de55aa"}, + {file = "y_py-0.5.9-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cb68445414940efe547291340e91604c7b8379b60822678ef29f4fc2a0e11c62"}, + {file = "y_py-0.5.9-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cd6f373dbf592ad83aaf95c16abebc8678928e49bd509ebd593259e1908345ae"}, + {file = "y_py-0.5.9-pp39-pypy39_pp73-macosx_10_7_x86_64.whl", hash = "sha256:76b3480e7037ac9390c450e2aff9e46e2c9e61520c0d88afe228110ec728adc5"}, + {file = "y_py-0.5.9-pp39-pypy39_pp73-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:9484a3fc33f812234e58a5ee834b42bb0a628054d61b5c06c323aa56c12e557d"}, + {file = "y_py-0.5.9-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f6d87d0c2e87990bc00c049742d36a5dbbb1510949459af17198728890ee748a"}, + {file = "y_py-0.5.9-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fce5feb57f6231376eb10d1fb68c60da106ffa0b520b3129471c466eff0304cc"}, + {file = "y_py-0.5.9-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:27c1e9a866146d250e9e16d99fe22a40c82f5b592ab85da97e5679fc3841c7ce"}, + {file = "y_py-0.5.9-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d722d6a27230c1f395535da5cee6a9a16497c6343afd262c846090075c083009"}, + {file = "y_py-0.5.9-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f54625b9ed4e787872c45d3044dcfd04c0da4258d9914f3d32308830b35246c"}, + {file = "y_py-0.5.9-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9513ae81fcc805671ae134c4c7421ca322acf92ce8b33817e1775ea8c0176973"}, + {file = "y_py-0.5.9.tar.gz", hash = "sha256:50cfa0532bcee27edb8c64743b49570e28bb76a00cd384ead1d84b6f052d9368"}, +] + +[[package]] +name = "yacs" +version = "0.1.8" +description = "Yet Another Configuration System" +category = "main" +optional = false +python-versions = "*" +files = [ + {file = "yacs-0.1.8-py2-none-any.whl", hash = "sha256:d43d1854c1ffc4634c5b349d1c1120f86f05c3a294c9d141134f282961ab5d94"}, + {file = "yacs-0.1.8-py3-none-any.whl", hash = "sha256:99f893e30497a4b66842821bac316386f7bd5c4f47ad35c9073ef089aa33af32"}, + {file = "yacs-0.1.8.tar.gz", hash = "sha256:efc4c732942b3103bea904ee89af98bcd27d01f0ac12d8d4d369f1e7a2914384"}, +] + +[package.dependencies] +PyYAML = "*" + +[[package]] +name = "yapf" +version = "0.32.0" +description = "A formatter for Python code." +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "yapf-0.32.0-py2.py3-none-any.whl", hash = "sha256:8fea849025584e486fd06d6ba2bed717f396080fd3cc236ba10cb97c4c51cf32"}, + {file = "yapf-0.32.0.tar.gz", hash = "sha256:a3f5085d37ef7e3e004c4ba9f9b3e40c54ff1901cd111f05145ae313a7c67d1b"}, +] + +[[package]] +name = "yarl" +version = "1.8.2" +description = "Yet another URL library" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "yarl-1.8.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:bb81f753c815f6b8e2ddd2eef3c855cf7da193b82396ac013c661aaa6cc6b0a5"}, + {file = "yarl-1.8.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:47d49ac96156f0928f002e2424299b2c91d9db73e08c4cd6742923a086f1c863"}, + {file = "yarl-1.8.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3fc056e35fa6fba63248d93ff6e672c096f95f7836938241ebc8260e062832fe"}, + {file = "yarl-1.8.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:58a3c13d1c3005dbbac5c9f0d3210b60220a65a999b1833aa46bd6677c69b08e"}, + {file = "yarl-1.8.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:10b08293cda921157f1e7c2790999d903b3fd28cd5c208cf8826b3b508026996"}, + {file = "yarl-1.8.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:de986979bbd87272fe557e0a8fcb66fd40ae2ddfe28a8b1ce4eae22681728fef"}, + {file = "yarl-1.8.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c4fcfa71e2c6a3cb568cf81aadc12768b9995323186a10827beccf5fa23d4f8"}, + {file = "yarl-1.8.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae4d7ff1049f36accde9e1ef7301912a751e5bae0a9d142459646114c70ecba6"}, + {file = "yarl-1.8.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:bf071f797aec5b96abfc735ab97da9fd8f8768b43ce2abd85356a3127909d146"}, + {file = "yarl-1.8.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:74dece2bfc60f0f70907c34b857ee98f2c6dd0f75185db133770cd67300d505f"}, + {file = "yarl-1.8.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:df60a94d332158b444301c7f569659c926168e4d4aad2cfbf4bce0e8fb8be826"}, + {file = "yarl-1.8.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:63243b21c6e28ec2375f932a10ce7eda65139b5b854c0f6b82ed945ba526bff3"}, + {file = "yarl-1.8.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:cfa2bbca929aa742b5084fd4663dd4b87c191c844326fcb21c3afd2d11497f80"}, + {file = "yarl-1.8.2-cp310-cp310-win32.whl", hash = "sha256:b05df9ea7496df11b710081bd90ecc3a3db6adb4fee36f6a411e7bc91a18aa42"}, + {file = "yarl-1.8.2-cp310-cp310-win_amd64.whl", hash = "sha256:24ad1d10c9db1953291f56b5fe76203977f1ed05f82d09ec97acb623a7976574"}, + {file = "yarl-1.8.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2a1fca9588f360036242f379bfea2b8b44cae2721859b1c56d033adfd5893634"}, + {file = "yarl-1.8.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f37db05c6051eff17bc832914fe46869f8849de5b92dc4a3466cd63095d23dfd"}, + {file = "yarl-1.8.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:77e913b846a6b9c5f767b14dc1e759e5aff05502fe73079f6f4176359d832581"}, + {file = "yarl-1.8.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0978f29222e649c351b173da2b9b4665ad1feb8d1daa9d971eb90df08702668a"}, + {file = "yarl-1.8.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:388a45dc77198b2460eac0aca1efd6a7c09e976ee768b0d5109173e521a19daf"}, + {file = "yarl-1.8.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2305517e332a862ef75be8fad3606ea10108662bc6fe08509d5ca99503ac2aee"}, + {file = "yarl-1.8.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42430ff511571940d51e75cf42f1e4dbdded477e71c1b7a17f4da76c1da8ea76"}, + {file = "yarl-1.8.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3150078118f62371375e1e69b13b48288e44f6691c1069340081c3fd12c94d5b"}, + {file = "yarl-1.8.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c15163b6125db87c8f53c98baa5e785782078fbd2dbeaa04c6141935eb6dab7a"}, + {file = "yarl-1.8.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:4d04acba75c72e6eb90745447d69f84e6c9056390f7a9724605ca9c56b4afcc6"}, + {file = "yarl-1.8.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:e7fd20d6576c10306dea2d6a5765f46f0ac5d6f53436217913e952d19237efc4"}, + {file = "yarl-1.8.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:75c16b2a900b3536dfc7014905a128a2bea8fb01f9ee26d2d7d8db0a08e7cb2c"}, + {file = "yarl-1.8.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6d88056a04860a98341a0cf53e950e3ac9f4e51d1b6f61a53b0609df342cc8b2"}, + {file = "yarl-1.8.2-cp311-cp311-win32.whl", hash = "sha256:fb742dcdd5eec9f26b61224c23baea46c9055cf16f62475e11b9b15dfd5c117b"}, + {file = "yarl-1.8.2-cp311-cp311-win_amd64.whl", hash = "sha256:8c46d3d89902c393a1d1e243ac847e0442d0196bbd81aecc94fcebbc2fd5857c"}, + {file = "yarl-1.8.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:ceff9722e0df2e0a9e8a79c610842004fa54e5b309fe6d218e47cd52f791d7ef"}, + {file = "yarl-1.8.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3f6b4aca43b602ba0f1459de647af954769919c4714706be36af670a5f44c9c1"}, + {file = "yarl-1.8.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1684a9bd9077e922300ecd48003ddae7a7474e0412bea38d4631443a91d61077"}, + {file = "yarl-1.8.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ebb78745273e51b9832ef90c0898501006670d6e059f2cdb0e999494eb1450c2"}, + {file = "yarl-1.8.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3adeef150d528ded2a8e734ebf9ae2e658f4c49bf413f5f157a470e17a4a2e89"}, + {file = "yarl-1.8.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57a7c87927a468e5a1dc60c17caf9597161d66457a34273ab1760219953f7f4c"}, + {file = "yarl-1.8.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:efff27bd8cbe1f9bd127e7894942ccc20c857aa8b5a0327874f30201e5ce83d0"}, + {file = "yarl-1.8.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:a783cd344113cb88c5ff7ca32f1f16532a6f2142185147822187913eb989f739"}, + {file = "yarl-1.8.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:705227dccbe96ab02c7cb2c43e1228e2826e7ead880bb19ec94ef279e9555b5b"}, + {file = "yarl-1.8.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:34c09b43bd538bf6c4b891ecce94b6fa4f1f10663a8d4ca589a079a5018f6ed7"}, + {file = "yarl-1.8.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:a48f4f7fea9a51098b02209d90297ac324241bf37ff6be6d2b0149ab2bd51b37"}, + {file = "yarl-1.8.2-cp37-cp37m-win32.whl", hash = "sha256:0414fd91ce0b763d4eadb4456795b307a71524dbacd015c657bb2a39db2eab89"}, + {file = "yarl-1.8.2-cp37-cp37m-win_amd64.whl", hash = "sha256:d881d152ae0007809c2c02e22aa534e702f12071e6b285e90945aa3c376463c5"}, + {file = "yarl-1.8.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5df5e3d04101c1e5c3b1d69710b0574171cc02fddc4b23d1b2813e75f35a30b1"}, + {file = "yarl-1.8.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7a66c506ec67eb3159eea5096acd05f5e788ceec7b96087d30c7d2865a243918"}, + {file = "yarl-1.8.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2b4fa2606adf392051d990c3b3877d768771adc3faf2e117b9de7eb977741229"}, + {file = "yarl-1.8.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e21fb44e1eff06dd6ef971d4bdc611807d6bd3691223d9c01a18cec3677939e"}, + {file = "yarl-1.8.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:93202666046d9edadfe9f2e7bf5e0782ea0d497b6d63da322e541665d65a044e"}, + {file = "yarl-1.8.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fc77086ce244453e074e445104f0ecb27530d6fd3a46698e33f6c38951d5a0f1"}, + {file = "yarl-1.8.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64dd68a92cab699a233641f5929a40f02a4ede8c009068ca8aa1fe87b8c20ae3"}, + {file = "yarl-1.8.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1b372aad2b5f81db66ee7ec085cbad72c4da660d994e8e590c997e9b01e44901"}, + {file = "yarl-1.8.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:e6f3515aafe0209dd17fb9bdd3b4e892963370b3de781f53e1746a521fb39fc0"}, + {file = "yarl-1.8.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:dfef7350ee369197106805e193d420b75467b6cceac646ea5ed3049fcc950a05"}, + {file = "yarl-1.8.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:728be34f70a190566d20aa13dc1f01dc44b6aa74580e10a3fb159691bc76909d"}, + {file = "yarl-1.8.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:ff205b58dc2929191f68162633d5e10e8044398d7a45265f90a0f1d51f85f72c"}, + {file = "yarl-1.8.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:baf211dcad448a87a0d9047dc8282d7de59473ade7d7fdf22150b1d23859f946"}, + {file = "yarl-1.8.2-cp38-cp38-win32.whl", hash = "sha256:272b4f1599f1b621bf2aabe4e5b54f39a933971f4e7c9aa311d6d7dc06965165"}, + {file = "yarl-1.8.2-cp38-cp38-win_amd64.whl", hash = "sha256:326dd1d3caf910cd26a26ccbfb84c03b608ba32499b5d6eeb09252c920bcbe4f"}, + {file = "yarl-1.8.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:f8ca8ad414c85bbc50f49c0a106f951613dfa5f948ab69c10ce9b128d368baf8"}, + {file = "yarl-1.8.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:418857f837347e8aaef682679f41e36c24250097f9e2f315d39bae3a99a34cbf"}, + {file = "yarl-1.8.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ae0eec05ab49e91a78700761777f284c2df119376e391db42c38ab46fd662b77"}, + {file = "yarl-1.8.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:009a028127e0a1755c38b03244c0bea9d5565630db9c4cf9572496e947137a87"}, + {file = "yarl-1.8.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3edac5d74bb3209c418805bda77f973117836e1de7c000e9755e572c1f7850d0"}, + {file = "yarl-1.8.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:da65c3f263729e47351261351b8679c6429151ef9649bba08ef2528ff2c423b2"}, + {file = "yarl-1.8.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0ef8fb25e52663a1c85d608f6dd72e19bd390e2ecaf29c17fb08f730226e3a08"}, + {file = "yarl-1.8.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bcd7bb1e5c45274af9a1dd7494d3c52b2be5e6bd8d7e49c612705fd45420b12d"}, + {file = "yarl-1.8.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:44ceac0450e648de86da8e42674f9b7077d763ea80c8ceb9d1c3e41f0f0a9951"}, + {file = "yarl-1.8.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:97209cc91189b48e7cfe777237c04af8e7cc51eb369004e061809bcdf4e55220"}, + {file = "yarl-1.8.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:48dd18adcf98ea9cd721a25313aef49d70d413a999d7d89df44f469edfb38a06"}, + {file = "yarl-1.8.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:e59399dda559688461762800d7fb34d9e8a6a7444fd76ec33220a926c8be1516"}, + {file = "yarl-1.8.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d617c241c8c3ad5c4e78a08429fa49e4b04bedfc507b34b4d8dceb83b4af3588"}, + {file = "yarl-1.8.2-cp39-cp39-win32.whl", hash = "sha256:cb6d48d80a41f68de41212f3dfd1a9d9898d7841c8f7ce6696cf2fd9cb57ef83"}, + {file = "yarl-1.8.2-cp39-cp39-win_amd64.whl", hash = "sha256:6604711362f2dbf7160df21c416f81fac0de6dbcf0b5445a2ef25478ecc4c778"}, + {file = "yarl-1.8.2.tar.gz", hash = "sha256:49d43402c6e3013ad0978602bf6bf5328535c48d192304b91b97a3c6790b1562"}, +] + +[package.dependencies] +idna = ">=2.0" +multidict = ">=4.0" + +[[package]] +name = "ypy-websocket" +version = "0.8.2" +description = "WebSocket connector for Ypy" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "ypy_websocket-0.8.2-py3-none-any.whl", hash = "sha256:9049d5a7d61c26c2b5a39757c9ffcbe2274bf3553adeea8de7fe1c04671d4145"}, + {file = "ypy_websocket-0.8.2.tar.gz", hash = "sha256:491b2cc4271df4dde9be83017c15f4532b597dc43148472eb20c5aeb838a5b46"}, +] + +[package.dependencies] +aiofiles = ">=22.1.0,<23" +aiosqlite = ">=0.17.0,<1" +y-py = ">=0.5.3,<0.6.0" + +[package.extras] +test = ["mypy", "pre-commit", "pytest", "pytest-asyncio", "websockets (>=10.0)"] + +[[package]] +name = "zipp" +version = "3.15.0" +description = "Backport of pathlib-compatible object wrapper for zip files" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "zipp-3.15.0-py3-none-any.whl", hash = "sha256:48904fc76a60e542af151aded95726c1a5c34ed43ab4134b597665c86d7ad556"}, + {file = "zipp-3.15.0.tar.gz", hash = "sha256:112929ad649da941c23de50f356a2b5570c954b65150642bccdd66bf194d224b"}, +] + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +testing = ["big-O", "flake8 (<5)", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] + +[metadata] +lock-version = "2.0" +python-versions = ">=3.10,<3.11" +content-hash = "ac011d99b8b2769be7c7b851ba1af8f48658b7676aa4241978c66761abcf71eb" diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..2776600 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,80 @@ +[tool.poetry] +name = "ifield" +version = "0.2.0" +description = "" +authors = ["Peder Bergebakken Sundt "] + +[build-system] +requires = ["poetry-core>=1.0.0", "setuptools>=60"] +build-backend = "poetry.core.masonry.api" + +[tool.poetry.dependencies] +python = ">=3.10,<3.11" +faiss-cpu = "^1.7.3" +geomloss = "0.2.4" # 0.2.5 has no bdist on pypi +h5py = "^3.7.0" +hdf5plugin = "^4.0.1" +imageio = "^2.23.0" +jinja2 = "^3.1.2" +matplotlib = "^3.6.2" +mesh-to-sdf = {git = "https://github.com/pbsds/mesh_to_sdf", rev = "no_flip_normals"} +methodtools = "^0.4.5" +more-itertools = "^9.1.0" +munch = "^2.5.0" +numpy = "^1.23.0" +pyembree = {url = "https://folk.ntnu.no/pederbs/pypy/pep503/pyembree/pyembree-0.2.11-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl"} +pygame = "^2.1.2" +pykeops = "^2.1.1" +pytorch3d = [ + {url = "https://dl.fbaipublicfiles.com/pytorch3d/packaging/wheels/py310_cu116_pyt1130/pytorch3d-0.7.2-cp310-cp310-linux_x86_64.whl"}, +] +pyqt5 = "^5.15.7" +pyrender = "^0.1.45" +pytorch-lightning = "^1.8.6" +pyyaml = "^6.0" +rich = "^13.3.2" +rtree = "^1.0.1" +scikit-image = "^0.19.3" +scikit-learn = "^1.2.0" +seaborn = "^0.12.1" +serve-me-once = "^0.1.2" +torch = "^1.13.0" +torchmeta = {git = "https://github.com/pbsds/pytorch-meta", rev = "upgrade"} +torchviz = "^0.0.2" +tqdm = "^4.64.1" +trimesh = "^3.17.1" +typer = "^0.7.0" + + +[tool.poetry.dev-dependencies] +python-lsp-server = {extras = ["all"], version = "^1.6.0"} +fix-my-functions = "^0.1.3" +imageio-ffmpeg = "^0.4.7" +jupyter = "^1.0.0" +jupyter-contrib-nbextensions = "^0.7.0" +jupyterlab = "^3.5.2" +jupyterthemes = "^0.20.0" +llvmlite = "^0.39.1" # only to make poetry install the python3.10 wheels instead of building them +nbconvert = "<=6.5.0" # https://github.com/jupyter/nbconvert/issues/1894 +numba = "^0.56.4" # only to make poetry install the python3.10 wheels instead of building them +papermill = "^2.4.0" +pdoc = "^12.3.0" +pdoc3 = "^0.10.0" +ptpython = "^3.0.22" +pudb = "^2022.1.3" +remote-exec = {git = "https://github.com/pbsds/remote", rev = "whitespace-push"} # https://github.com/remote-cli/remote/pull/52 +shapely = "^2.0.0" +sympy = "^1.11.1" +tensorboard = "^2.11.0" +visidata = "^2.11" + +[tool.poetry.scripts] +show-schedule = 'ifield.utils.loss:main' +show-h5-items = 'ifield.cli_utils:show_h5_items' +show-h5-img = 'ifield.cli_utils:show_h5_img' +show-h5-scan-cloud = 'ifield.cli_utils:show_h5_scan_cloud' +show-model = 'ifield.cli_utils:show_model' +download-stanford = 'ifield.data.stanford.download:cli' +download-coseg = 'ifield.data.coseg.download:cli' +preprocess-stanford = 'ifield.data.stanford.preprocess:cli' +preprocess-coseg = 'ifield.data.coseg.preprocess:cli'