diff --git a/.gitignore b/.gitignore index 2a8cbeda..b8d15a19 100644 --- a/.gitignore +++ b/.gitignore @@ -21,7 +21,7 @@ results/* # ignore downloaded csv files in notebook tutorials *.csv !data/unit_test/**/*.csv -!notebooks/tutorials/myreceiver.csv +!notebooks/tutorials/data/myreceiver.csv # ignore txt files in notebook tutorials *.txt diff --git a/README.md b/README.md index 79c7816b..321a5780 100644 --- a/README.md +++ b/README.md @@ -47,16 +47,20 @@ Code Organization ├── docs/ # Documentation files ├── gnss_lib_py/ # gnss_lib_py source files ├── algorithms/ # Navigation algorithms + ├── navdata/ # NavData data structure ├── parsers/ # Data parsers ├── utils/ # GNSS and common utilities + ├── visualizations/ # plotting functions └── __init__.py ├── notebooks/ # Interactive Jupyter notebooks ├── tutorials/ # Notebooks with tutorial code ├── results/ # Location for result images/files ├── tests/ # Tests for source files ├── algorithms/ # Tests for files in algorithms + ├── navdata/ # Tests for files in navdata ├── parsers/ # Tests for files in parsers ├── utils/ # Tests for files in utils + ├── visualizations/ # Tests for files in visualizations └── test_gnss_lib_py.py # High level checks for repository ├── CONTRIBUTORS.md # List of contributors ├── build_docs.sh # Bash script to build docs diff --git a/docs/source/contributing/documentation.rst b/docs/source/contributing/documentation.rst index 851756b5..3cdbd262 100644 --- a/docs/source/contributing/documentation.rst +++ b/docs/source/contributing/documentation.rst @@ -59,7 +59,7 @@ following: single axis arrays should be rows and time should be across the columns * :code:`pd.DataFrame` - * :code:`gnss_lib_py.parsers.NavData` + * :code:`gnss_lib_py.navdata.navdata.NavData` PEP 8 Style Guide ----------------- diff --git a/docs/source/index.rst b/docs/source/index.rst index 3cfd5933..dd2d6ce7 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -57,16 +57,20 @@ Code Organization ├── docs/ # Documentation files ├── gnss_lib_py/ # gnss_lib_py source files ├── algorithms/ # Navigation algorithms + ├── navdata/ # NavData data structure ├── parsers/ # Data parsers ├── utils/ # GNSS and common utilities + ├── visualizations/ # plotting functions └── __init__.py ├── notebooks/ # Interactive Jupyter notebooks ├── tutorials/ # Notebooks with tutorial code ├── results/ # Location for result images/files ├── tests/ # Tests for source files ├── algorithms/ # Tests for files in algorithms + ├── navdata/ # Tests for files in navdata ├── parsers/ # Tests for files in parsers ├── utils/ # Tests for files in utils + ├── visualizations/ # Tests for files in visualizations └── test_gnss_lib_py.py # High level checks for repository ├── CONTRIBUTORS.md # List of contributors ├── build_docs.sh # Bash script to build docs diff --git a/docs/source/reference/parsers/modules.rst b/docs/source/reference/parsers/modules.rst index bcbe66dd..306b0070 100644 --- a/docs/source/reference/parsers/modules.rst +++ b/docs/source/reference/parsers/modules.rst @@ -7,7 +7,6 @@ parsers android clk google_decimeter - navdata nmea rinex_nav rinex_obs diff --git a/docs/source/reference/parsers/navdata.rst b/docs/source/reference/parsers/navdata.rst deleted file mode 100644 index 925b0123..00000000 --- a/docs/source/reference/parsers/navdata.rst +++ /dev/null @@ -1,7 +0,0 @@ -navdata module -============== - -.. automodule:: navdata - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/source/reference/reference.rst b/docs/source/reference/reference.rst index 560dce32..328e9cd5 100644 --- a/docs/source/reference/reference.rst +++ b/docs/source/reference/reference.rst @@ -49,22 +49,6 @@ their use in ``gnss_lib_py``. retrieved on 27th June, 2023): for determining GLOASS SV states from broadcast satellite positions, velocities, and accelerations. -Package Architecture --------------------- - -The gnss_lib_py package is broadly divided into the following sections. -Please choose the most appropriate location based on the descriptions -below when adding new features or functionality. - -* :code:`algorithms` : This directory contains localization algorithms. -* :code:`parsers` : This directory contains functions to read and process various - GNSS data/file types. -* :code:`utils` : This directory contains utilities used to handle - GNSS measurements, time conversions, visualizations, satellite - simulation, file operations, etc. - -More information about currently available methods and the folder -organization can be found in the :ref:`organization subsection `. Details about NavData Class --------------------------- diff --git a/docs/source/reference/test_parsers/modules.rst b/docs/source/reference/test_parsers/modules.rst index 7d6baf94..c0c49b79 100644 --- a/docs/source/reference/test_parsers/modules.rst +++ b/docs/source/reference/test_parsers/modules.rst @@ -7,7 +7,6 @@ parsers test_android test_clk test_google_decimeter - test_navdata test_nmea test_rinex_nav test_rinex_obs diff --git a/docs/source/reference/test_parsers/test_navdata.rst b/docs/source/reference/test_parsers/test_navdata.rst deleted file mode 100644 index 4247e4b7..00000000 --- a/docs/source/reference/test_parsers/test_navdata.rst +++ /dev/null @@ -1,7 +0,0 @@ -test\_navdata module -==================== - -.. automodule:: test_navdata - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/source/reference/test_utils/modules.rst b/docs/source/reference/test_utils/modules.rst index 4f314037..20300e61 100644 --- a/docs/source/reference/test_utils/modules.rst +++ b/docs/source/reference/test_utils/modules.rst @@ -12,4 +12,3 @@ utils test_gnss_models test_sv_models test_time_conversions - test_visualizations diff --git a/docs/source/reference/test_utils/test_visualizations.rst b/docs/source/reference/test_utils/test_visualizations.rst deleted file mode 100644 index cbe2ce21..00000000 --- a/docs/source/reference/test_utils/test_visualizations.rst +++ /dev/null @@ -1,7 +0,0 @@ -test\_visualizations module -=========================== - -.. automodule:: test_visualizations - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/source/reference/utils/modules.rst b/docs/source/reference/utils/modules.rst index c789510f..62c59583 100644 --- a/docs/source/reference/utils/modules.rst +++ b/docs/source/reference/utils/modules.rst @@ -12,4 +12,3 @@ utils gnss_models sv_models time_conversions - visualizations diff --git a/docs/source/reference/utils/visualizations.rst b/docs/source/reference/utils/visualizations.rst deleted file mode 100644 index 5b944a4f..00000000 --- a/docs/source/reference/utils/visualizations.rst +++ /dev/null @@ -1,7 +0,0 @@ -visualizations module -===================== - -.. automodule:: visualizations - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/source/requirements.txt b/docs/source/requirements.txt index a196aba0..6b6dee9e 100644 --- a/docs/source/requirements.txt +++ b/docs/source/requirements.txt @@ -1,5 +1,5 @@ alabaster==0.7.13 ; python_version >= "3.8" and python_version < "3.12" -anyio==4.0.0 ; python_version >= "3.8" and python_version < "3.12" +anyio==4.2.0 ; python_version >= "3.8" and python_version < "3.12" appnope==0.1.3 ; python_version >= "3.8" and python_version < "3.12" and (platform_system == "Darwin" or sys_platform == "darwin") argon2-cffi-bindings==21.2.0 ; python_version >= "3.8" and python_version < "3.12" argon2-cffi==23.1.0 ; python_version >= "3.8" and python_version < "3.12" @@ -7,123 +7,121 @@ arrow==1.3.0 ; python_version >= "3.8" and python_version < "3.12" astroid==2.15.8 ; python_version >= "3.8" and python_version < "3.12" asttokens==2.4.1 ; python_version >= "3.8" and python_version < "3.12" async-lru==2.0.4 ; python_version >= "3.8" and python_version < "3.12" -attrs==23.1.0 ; python_version >= "3.8" and python_version < "3.12" -babel==2.13.1 ; python_version >= "3.8" and python_version < "3.12" +attrs==23.2.0 ; python_version >= "3.8" and python_version < "3.12" +babel==2.14.0 ; python_version >= "3.8" and python_version < "3.12" backcall==0.2.0 ; python_version >= "3.8" and python_version < "3.12" -beautifulsoup4==4.12.2 ; python_version >= "3.8" and python_version < "3.12" +beautifulsoup4==4.12.3 ; python_version >= "3.8" and python_version < "3.12" bleach==6.1.0 ; python_version >= "3.8" and python_version < "3.12" -certifi==2023.7.22 ; python_version >= "3.8" and python_version < "3.12" +certifi==2023.11.17 ; python_version >= "3.8" and python_version < "3.12" cffi==1.16.0 ; python_version >= "3.8" and python_version < "3.12" charset-normalizer==3.3.2 ; python_version >= "3.8" and python_version < "3.12" colorama==0.4.6 ; python_version >= "3.8" and python_version < "3.12" and (sys_platform == "win32" or platform_system == "Windows") -comm==0.2.0 ; python_version >= "3.8" and python_version < "3.12" +comm==0.2.1 ; python_version >= "3.8" and python_version < "3.12" contourpy==1.1.1 ; python_version >= "3.8" and python_version < "3.12" -coverage[toml]==7.3.2 ; python_version >= "3.8" and python_version < "3.12" +coverage[toml]==7.4.0 ; python_version >= "3.8" and python_version < "3.12" cycler==0.12.1 ; python_version >= "3.8" and python_version < "3.12" debugpy==1.8.0 ; python_version >= "3.8" and python_version < "3.12" decorator==5.1.1 ; python_version >= "3.8" and python_version < "3.12" defusedxml==0.7.1 ; python_version >= "3.8" and python_version < "3.12" dill==0.3.7 ; python_version >= "3.8" and python_version < "3.12" docutils==0.18.1 ; python_version >= "3.8" and python_version < "3.12" -exceptiongroup==1.1.3 ; python_version >= "3.8" and python_version < "3.11" +exceptiongroup==1.2.0 ; python_version >= "3.8" and python_version < "3.11" executing==2.0.1 ; python_version >= "3.8" and python_version < "3.12" -fastjsonschema==2.19.0 ; python_version >= "3.8" and python_version < "3.12" -fonttools==4.44.3 ; python_version >= "3.8" and python_version < "3.12" +fastjsonschema==2.19.1 ; python_version >= "3.8" and python_version < "3.12" +fonttools==4.47.2 ; python_version >= "3.8" and python_version < "3.12" fqdn==1.5.1 ; python_version >= "3.8" and python_version < "3.12" georinex==1.16.1 ; python_version >= "3.8" and python_version < "3.12" hatanaka==2.8.1 ; python_version >= "3.8" and python_version < "3.12" -idna==3.4 ; python_version >= "3.8" and python_version < "3.12" +idna==3.6 ; python_version >= "3.8" and python_version < "3.12" imagesize==1.4.1 ; python_version >= "3.8" and python_version < "3.12" -importlib-metadata==6.8.0 ; python_version >= "3.8" and python_version < "3.10" +importlib-metadata==7.0.1 ; python_version >= "3.8" and python_version < "3.10" importlib-resources==6.1.1 ; python_version >= "3.8" and python_version < "3.12" iniconfig==2.0.0 ; python_version >= "3.8" and python_version < "3.12" -ipykernel==6.26.0 ; python_version >= "3.8" and python_version < "3.12" +ipykernel==6.29.0 ; python_version >= "3.8" and python_version < "3.12" ipython==8.12.3 ; python_version >= "3.8" and python_version < "3.12" ipywidgets==8.1.1 ; python_version >= "3.8" and python_version < "3.12" isoduration==20.11.0 ; python_version >= "3.8" and python_version < "3.12" -isort==5.12.0 ; python_version >= "3.8" and python_version < "3.12" +isort==5.13.2 ; python_version >= "3.8" and python_version < "3.12" jedi==0.19.1 ; python_version >= "3.8" and python_version < "3.12" -jinja2==3.1.2 ; python_version >= "3.8" and python_version < "3.12" +jinja2==3.1.3 ; python_version >= "3.8" and python_version < "3.12" json5==0.9.14 ; python_version >= "3.8" and python_version < "3.12" jsonpointer==2.4 ; python_version >= "3.8" and python_version < "3.12" -jsonschema-specifications==2023.11.1 ; python_version >= "3.8" and python_version < "3.12" -jsonschema==4.20.0 ; python_version >= "3.8" and python_version < "3.12" -jsonschema[format-nongpl]==4.20.0 ; python_version >= "3.8" and python_version < "3.12" +jsonschema-specifications==2023.12.1 ; python_version >= "3.8" and python_version < "3.12" +jsonschema==4.21.1 ; python_version >= "3.8" and python_version < "3.12" +jsonschema[format-nongpl]==4.21.1 ; python_version >= "3.8" and python_version < "3.12" jupyter-client==8.6.0 ; python_version >= "3.8" and python_version < "3.12" jupyter-console==6.6.3 ; python_version >= "3.8" and python_version < "3.12" -jupyter-core==5.5.0 ; python_version >= "3.8" and python_version < "3.12" +jupyter-core==5.7.1 ; python_version >= "3.8" and python_version < "3.12" jupyter-events==0.9.0 ; python_version >= "3.8" and python_version < "3.12" -jupyter-lsp==2.2.0 ; python_version >= "3.8" and python_version < "3.12" -jupyter-server-terminals==0.4.4 ; python_version >= "3.8" and python_version < "3.12" -jupyter-server==2.10.1 ; python_version >= "3.8" and python_version < "3.12" +jupyter-lsp==2.2.2 ; python_version >= "3.8" and python_version < "3.12" +jupyter-server-terminals==0.5.1 ; python_version >= "3.8" and python_version < "3.12" +jupyter-server==2.12.5 ; python_version >= "3.8" and python_version < "3.12" jupyter==1.0.0 ; python_version >= "3.8" and python_version < "3.12" -jupyterlab-pygments==0.2.2 ; python_version >= "3.8" and python_version < "3.12" -jupyterlab-server==2.25.1 ; python_version >= "3.8" and python_version < "3.12" +jupyterlab-pygments==0.3.0 ; python_version >= "3.8" and python_version < "3.12" +jupyterlab-server==2.25.2 ; python_version >= "3.8" and python_version < "3.12" jupyterlab-widgets==3.0.9 ; python_version >= "3.8" and python_version < "3.12" -jupyterlab==4.0.8 ; python_version >= "3.8" and python_version < "3.12" +jupyterlab==4.0.11 ; python_version >= "3.8" and python_version < "3.12" kaleido==0.2.1 ; python_version >= "3.8" and python_version < "3.12" kiwisolver==1.4.5 ; python_version >= "3.8" and python_version < "3.12" -lazy-object-proxy==1.9.0 ; python_version >= "3.8" and python_version < "3.12" +lazy-object-proxy==1.10.0 ; python_version >= "3.8" and python_version < "3.12" markupsafe==2.1.3 ; python_version >= "3.8" and python_version < "3.12" matplotlib-inline==0.1.6 ; python_version >= "3.8" and python_version < "3.12" -matplotlib==3.7.3 ; python_version >= "3.8" and python_version < "3.12" +matplotlib==3.7.4 ; python_version >= "3.8" and python_version < "3.12" mccabe==0.7.0 ; python_version >= "3.8" and python_version < "3.12" mistune==3.0.2 ; python_version >= "3.8" and python_version < "3.12" nbclient==0.9.0 ; python_version >= "3.8" and python_version < "3.12" -nbconvert==7.11.0 ; python_version >= "3.8" and python_version < "3.12" +nbconvert==7.14.2 ; python_version >= "3.8" and python_version < "3.12" nbformat==5.9.2 ; python_version >= "3.8" and python_version < "3.12" nbsphinx-link==1.3.0 ; python_version >= "3.8" and python_version < "3.12" nbsphinx==0.8.12 ; python_version >= "3.8" and python_version < "3.12" ncompress==1.0.1 ; python_version >= "3.8" and python_version < "3.12" -nest-asyncio==1.5.8 ; python_version >= "3.8" and python_version < "3.12" +nest-asyncio==1.5.9 ; python_version >= "3.8" and python_version < "3.12" notebook-shim==0.2.3 ; python_version >= "3.8" and python_version < "3.12" -notebook==7.0.6 ; python_version >= "3.8" and python_version < "3.12" +notebook==7.0.7 ; python_version >= "3.8" and python_version < "3.12" numpy==1.24.4 ; python_version >= "3.8" and python_version < "3.12" overrides==7.4.0 ; python_version >= "3.8" and python_version < "3.12" packaging==23.2 ; python_version >= "3.8" and python_version < "3.12" pandas==2.0.3 ; python_version >= "3.8" and python_version < "3.12" -pandocfilters==1.5.0 ; python_version >= "3.8" and python_version < "3.12" +pandocfilters==1.5.1 ; python_version >= "3.8" and python_version < "3.12" parso==0.8.3 ; python_version >= "3.8" and python_version < "3.12" -pexpect==4.8.0 ; python_version >= "3.8" and python_version < "3.12" and sys_platform != "win32" +pexpect==4.9.0 ; python_version >= "3.8" and python_version < "3.12" and sys_platform != "win32" pickleshare==0.7.5 ; python_version >= "3.8" and python_version < "3.12" -pillow==10.1.0 ; python_version >= "3.8" and python_version < "3.12" +pillow==10.2.0 ; python_version >= "3.8" and python_version < "3.12" pkgutil-resolve-name==1.3.10 ; python_version >= "3.8" and python_version < "3.9" -platformdirs==4.0.0 ; python_version >= "3.8" and python_version < "3.12" +platformdirs==4.1.0 ; python_version >= "3.8" and python_version < "3.12" plotly==5.18.0 ; python_version >= "3.8" and python_version < "3.12" pluggy==1.3.0 ; python_version >= "3.8" and python_version < "3.12" pockets==0.9.1 ; python_version >= "3.8" and python_version < "3.12" -prometheus-client==0.18.0 ; python_version >= "3.8" and python_version < "3.12" -prompt-toolkit==3.0.41 ; python_version >= "3.8" and python_version < "3.12" -psutil==5.9.6 ; python_version >= "3.8" and python_version < "3.12" +prometheus-client==0.19.0 ; python_version >= "3.8" and python_version < "3.12" +prompt-toolkit==3.0.43 ; python_version >= "3.8" and python_version < "3.12" +psutil==5.9.8 ; python_version >= "3.8" and python_version < "3.12" ptyprocess==0.7.0 ; python_version >= "3.8" and python_version < "3.12" and (os_name != "nt" or sys_platform != "win32") pure-eval==0.2.2 ; python_version >= "3.8" and python_version < "3.12" pycparser==2.21 ; python_version >= "3.8" and python_version < "3.12" -pygments==2.16.1 ; python_version >= "3.8" and python_version < "3.12" +pygments==2.17.2 ; python_version >= "3.8" and python_version < "3.12" pylint-exit==1.2.0 ; python_version >= "3.8" and python_version < "3.12" pylint==2.17.7 ; python_version >= "3.8" and python_version < "3.12" pynmea2==1.19.0 ; python_version >= "3.8" and python_version < "3.12" pyparsing==3.1.1 ; python_version >= "3.8" and python_version < "3.12" pytest-cov==4.1.0 ; python_version >= "3.8" and python_version < "3.12" pytest-lazy-fixture==0.6.3 ; python_version >= "3.8" and python_version < "3.12" -pytest==7.4.3 ; python_version >= "3.8" and python_version < "3.12" +pytest==7.4.4 ; python_version >= "3.8" and python_version < "3.12" python-dateutil==2.8.2 ; python_version >= "3.8" and python_version < "3.12" python-json-logger==2.0.7 ; python_version >= "3.8" and python_version < "3.12" pytz==2023.3.post1 ; python_version >= "3.8" and python_version < "3.12" pywin32==306 ; sys_platform == "win32" and platform_python_implementation != "PyPy" and python_version >= "3.8" and python_version < "3.12" pywinpty==2.0.12 ; python_version >= "3.8" and python_version < "3.12" and os_name == "nt" pyyaml==6.0.1 ; python_version >= "3.8" and python_version < "3.12" -pyzmq==25.1.1 ; python_version >= "3.8" and python_version < "3.12" +pyzmq==25.1.2 ; python_version >= "3.8" and python_version < "3.12" qtconsole==5.5.1 ; python_version >= "3.8" and python_version < "3.12" qtpy==2.4.1 ; python_version >= "3.8" and python_version < "3.12" -referencing==0.31.0 ; python_version >= "3.8" and python_version < "3.12" +referencing==0.32.1 ; python_version >= "3.8" and python_version < "3.12" requests==2.31.0 ; python_version >= "3.8" and python_version < "3.12" rfc3339-validator==0.1.4 ; python_version >= "3.8" and python_version < "3.12" rfc3986-validator==0.1.1 ; python_version >= "3.8" and python_version < "3.12" -rpds-py==0.13.0 ; python_version >= "3.8" and python_version < "3.12" +rpds-py==0.17.1 ; python_version >= "3.8" and python_version < "3.12" scipy==1.10.1 ; python_version >= "3.8" and python_version < "3.12" send2trash==1.8.2 ; python_version >= "3.8" and python_version < "3.12" -setuptools-scm==8.0.4 ; python_version >= "3.8" and python_version < "3.12" -setuptools==68.2.2 ; python_version >= "3.8" and python_version < "3.12" six==1.16.0 ; python_version >= "3.8" and python_version < "3.12" sniffio==1.3.0 ; python_version >= "3.8" and python_version < "3.12" snowballstemmer==2.2.0 ; python_version >= "3.8" and python_version < "3.12" @@ -145,19 +143,19 @@ terminado==0.18.0 ; python_version >= "3.8" and python_version < "3.12" tinycss2==1.2.1 ; python_version >= "3.8" and python_version < "3.12" tomli==2.0.1 ; python_version >= "3.8" and python_full_version <= "3.11.0a6" tomlkit==0.12.3 ; python_version >= "3.8" and python_version < "3.12" -tornado==6.3.3 ; python_version >= "3.8" and python_version < "3.12" +tornado==6.4 ; python_version >= "3.8" and python_version < "3.12" tqdm==4.66.1 ; python_version >= "3.8" and python_version < "3.12" -traitlets==5.13.0 ; python_version >= "3.8" and python_version < "3.12" -types-python-dateutil==2.8.19.14 ; python_version >= "3.8" and python_version < "3.12" -typing-extensions==4.8.0 ; python_version >= "3.8" and python_version < "3.12" -tzdata==2023.3 ; python_version >= "3.8" and python_version < "3.12" +traitlets==5.14.1 ; python_version >= "3.8" and python_version < "3.12" +types-python-dateutil==2.8.19.20240106 ; python_version >= "3.8" and python_version < "3.12" +typing-extensions==4.9.0 ; python_version >= "3.8" and python_version < "3.11" +tzdata==2023.4 ; python_version >= "3.8" and python_version < "3.12" unlzw3==0.2.2 ; python_version >= "3.8" and python_version < "3.12" uri-template==1.3.0 ; python_version >= "3.8" and python_version < "3.12" urllib3==2.1.0 ; python_version >= "3.8" and python_version < "3.12" -wcwidth==0.2.10 ; python_version >= "3.8" and python_version < "3.12" +wcwidth==0.2.13 ; python_version >= "3.8" and python_version < "3.12" webcolors==1.13 ; python_version >= "3.8" and python_version < "3.12" webencodings==0.5.1 ; python_version >= "3.8" and python_version < "3.12" -websocket-client==1.6.4 ; python_version >= "3.8" and python_version < "3.12" +websocket-client==1.7.0 ; python_version >= "3.8" and python_version < "3.12" widgetsnbextension==4.0.9 ; python_version >= "3.8" and python_version < "3.12" wrapt==1.16.0 ; python_version >= "3.8" and python_version < "3.12" xarray==2023.1.0 ; python_version >= "3.8" and python_version < "3.12" diff --git a/docs/source/tutorials/algorithms/tutorials_fde_notebook.nblink b/docs/source/tutorials/algorithms/tutorials_fde_notebook.nblink new file mode 100644 index 00000000..672fe1ae --- /dev/null +++ b/docs/source/tutorials/algorithms/tutorials_fde_notebook.nblink @@ -0,0 +1,3 @@ +{ + "path": "../../../../notebooks/tutorials/algorithms/fde.ipynb" +} diff --git a/docs/source/tutorials/algorithms/tutorials_gnss_filters_notebook.nblink b/docs/source/tutorials/algorithms/tutorials_gnss_filters_notebook.nblink new file mode 100644 index 00000000..ef7a4824 --- /dev/null +++ b/docs/source/tutorials/algorithms/tutorials_gnss_filters_notebook.nblink @@ -0,0 +1,3 @@ +{ + "path": "../../../../notebooks/tutorials/algorithms/gnss_filters.ipynb" +} diff --git a/docs/source/tutorials/algorithms/tutorials_residuals_notebook.nblink b/docs/source/tutorials/algorithms/tutorials_residuals_notebook.nblink new file mode 100644 index 00000000..557bb17b --- /dev/null +++ b/docs/source/tutorials/algorithms/tutorials_residuals_notebook.nblink @@ -0,0 +1,3 @@ +{ + "path": "../../../../notebooks/tutorials/algorithms/residuals.ipynb" +} diff --git a/docs/source/tutorials/algorithms/tutorials_snapshot_notebook.nblink b/docs/source/tutorials/algorithms/tutorials_snapshot_notebook.nblink new file mode 100644 index 00000000..110112b2 --- /dev/null +++ b/docs/source/tutorials/algorithms/tutorials_snapshot_notebook.nblink @@ -0,0 +1,3 @@ +{ + "path": "../../../../notebooks/tutorials/algorithms/snapshot.ipynb" +} diff --git a/docs/source/tutorials/navdata/tutorials_navdata_notebook.nblink b/docs/source/tutorials/navdata/tutorials_navdata_notebook.nblink new file mode 100644 index 00000000..bbfe31da --- /dev/null +++ b/docs/source/tutorials/navdata/tutorials_navdata_notebook.nblink @@ -0,0 +1,3 @@ +{ + "path": "../../../../notebooks/tutorials/navdata/navdata.ipynb" +} diff --git a/docs/source/tutorials/navdata/tutorials_operations_notebook.nblink b/docs/source/tutorials/navdata/tutorials_operations_notebook.nblink new file mode 100644 index 00000000..691523b3 --- /dev/null +++ b/docs/source/tutorials/navdata/tutorials_operations_notebook.nblink @@ -0,0 +1,3 @@ +{ + "path": "../../../../notebooks/tutorials/navdata/operations.ipynb" +} diff --git a/docs/source/tutorials/parsers/tutorials_android_notebook.nblink b/docs/source/tutorials/parsers/tutorials_android_notebook.nblink new file mode 100644 index 00000000..a77c79de --- /dev/null +++ b/docs/source/tutorials/parsers/tutorials_android_notebook.nblink @@ -0,0 +1,3 @@ +{ + "path": "../../../../notebooks/tutorials/parsers/android.ipynb" +} diff --git a/docs/source/tutorials/parsers/tutorials_clk_notebook.nblink b/docs/source/tutorials/parsers/tutorials_clk_notebook.nblink new file mode 100644 index 00000000..33713031 --- /dev/null +++ b/docs/source/tutorials/parsers/tutorials_clk_notebook.nblink @@ -0,0 +1,3 @@ +{ + "path": "../../../../notebooks/tutorials/parsers/clk.ipynb" +} diff --git a/docs/source/tutorials/parsers/tutorials_google_decimeter_notebook.nblink b/docs/source/tutorials/parsers/tutorials_google_decimeter_notebook.nblink new file mode 100644 index 00000000..3b6f8170 --- /dev/null +++ b/docs/source/tutorials/parsers/tutorials_google_decimeter_notebook.nblink @@ -0,0 +1,3 @@ +{ + "path": "../../../../notebooks/tutorials/parsers/google_decimeter.ipynb" +} diff --git a/docs/source/tutorials/parsers/tutorials_new_parsers_notebook.nblink b/docs/source/tutorials/parsers/tutorials_new_parsers_notebook.nblink new file mode 100644 index 00000000..1e55e285 --- /dev/null +++ b/docs/source/tutorials/parsers/tutorials_new_parsers_notebook.nblink @@ -0,0 +1,3 @@ +{ + "path": "../../../../notebooks/tutorials/parsers/new_parsers.ipynb" +} diff --git a/docs/source/tutorials/parsers/tutorials_nmea_notebook.nblink b/docs/source/tutorials/parsers/tutorials_nmea_notebook.nblink new file mode 100644 index 00000000..881245db --- /dev/null +++ b/docs/source/tutorials/parsers/tutorials_nmea_notebook.nblink @@ -0,0 +1,3 @@ +{ + "path": "../../../../notebooks/tutorials/parsers/nmea.ipynb" +} diff --git a/docs/source/tutorials/parsers/tutorials_rinex_nav_notebook.nblink b/docs/source/tutorials/parsers/tutorials_rinex_nav_notebook.nblink new file mode 100644 index 00000000..b33ef6b6 --- /dev/null +++ b/docs/source/tutorials/parsers/tutorials_rinex_nav_notebook.nblink @@ -0,0 +1,3 @@ +{ + "path": "../../../../notebooks/tutorials/parsers/rinex_nav.ipynb" +} diff --git a/docs/source/tutorials/parsers/tutorials_rinex_obs_notebook.nblink b/docs/source/tutorials/parsers/tutorials_rinex_obs_notebook.nblink new file mode 100644 index 00000000..cdc7c5c9 --- /dev/null +++ b/docs/source/tutorials/parsers/tutorials_rinex_obs_notebook.nblink @@ -0,0 +1,3 @@ +{ + "path": "../../../../notebooks/tutorials/parsers/rinex_obs.ipynb" +} diff --git a/docs/source/tutorials/parsers/tutorials_smartloc_notebook.nblink b/docs/source/tutorials/parsers/tutorials_smartloc_notebook.nblink new file mode 100644 index 00000000..b2cebf4e --- /dev/null +++ b/docs/source/tutorials/parsers/tutorials_smartloc_notebook.nblink @@ -0,0 +1,3 @@ +{ + "path": "../../../../notebooks/tutorials/parsers/smartloc.ipynb" +} diff --git a/docs/source/tutorials/parsers/tutorials_sp3_notebook.nblink b/docs/source/tutorials/parsers/tutorials_sp3_notebook.nblink new file mode 100644 index 00000000..8d4c9a70 --- /dev/null +++ b/docs/source/tutorials/parsers/tutorials_sp3_notebook.nblink @@ -0,0 +1,3 @@ +{ + "path": "../../../../notebooks/tutorials/parsers/sp3.ipynb" +} diff --git a/docs/source/tutorials/tutorials.rst b/docs/source/tutorials/tutorials.rst index 851b9619..33401a32 100644 --- a/docs/source/tutorials/tutorials.rst +++ b/docs/source/tutorials/tutorials.rst @@ -11,16 +11,18 @@ The tutorials below show you how to interact with our standard :code:`NavData` class and how to run standard baselines all with only a few lines of code. -NavData Tutorial ---------------------- +NavData Tutorials +----------------- Sections of this tutorial show how to interact with our standard :code:`NavData` -class. +class and its corresponding operations. .. toctree:: :maxdepth: 2 - tutorials_navdata_notebook + navdata/tutorials_navdata_notebook + navdata/tutorials_operations_notebook + Parser Tutorials ---------------- @@ -29,10 +31,17 @@ This tutorial explains details about existing parsers and how to create a new parser if necessary. .. toctree:: - :maxdepth: 1 + :maxdepth: 2 - tutorials_android_notebook - tutorials_parsers_notebook + parsers/tutorials_android_notebook + parsers/tutorials_clk_notebook + parsers/tutorials_google_decimeter_notebook + parsers/tutorials_nmea_notebook + parsers/tutorials_rinex_nav_notebook + parsers/tutorials_rinex_obs_notebook + parsers/tutorials_smartloc_notebook + parsers/tutorials_sp3_notebook + parsers/tutorials_new_parsers_notebook Algorithm Tutorials ------------------- @@ -41,10 +50,12 @@ This tutorial walks through the existing algorithms that you can use for baseline position solutions. .. toctree:: - :maxdepth: 1 + :maxdepth: 2 - tutorials_algorithms_notebook - tutorials_fde_notebook + algorithms/tutorials_fde_notebook + algorithms/tutorials_residuals_notebook + algorithms/tutorials_gnss_filters_notebook + algorithms/tutorials_snapshot_notebook Utility Tutorials @@ -56,8 +67,25 @@ available in the :code:`utils` directory. .. toctree:: :maxdepth: 2 - tutorials_visualizations_notebook - tutorials_coordinates_notebook - tutorials_utilities_notebook - tutorials_ephemeris_downloader_notebook - tutorials_sv_models_notebook + utils/tutorials_constants_notebook + utils/tutorials_coordinates_notebook + utils/tutorials_ephemeris_downloader_notebook + utils/tutorials_file_operations_notebook + utils/tutorials_filters_notebook + utils/tutorials_gnss_models_notebook + utils/tutorials_sv_models_notebook + utils/tutorials_time_conversions_notebook + +Visualization Tutorials +----------------------- + +This tutorial illustrates a few of the most common plotting functions +available in the :code:`visualizations` directory. + +.. toctree:: + :maxdepth: 2 + + visualizations/tutorials_plot_metric_notebook + visualizations/tutorials_plot_map_notebook + visualizations/tutorials_plot_skyplot_notebook + visualizations/tutorials_style_notebook diff --git a/docs/source/tutorials/tutorials_algorithms_notebook.nblink b/docs/source/tutorials/tutorials_algorithms_notebook.nblink deleted file mode 100644 index 996a09e5..00000000 --- a/docs/source/tutorials/tutorials_algorithms_notebook.nblink +++ /dev/null @@ -1,3 +0,0 @@ -{ - "path": "../../../notebooks/tutorials/algorithms.ipynb" -} diff --git a/docs/source/tutorials/tutorials_android_notebook.nblink b/docs/source/tutorials/tutorials_android_notebook.nblink deleted file mode 100644 index 5e327d45..00000000 --- a/docs/source/tutorials/tutorials_android_notebook.nblink +++ /dev/null @@ -1,3 +0,0 @@ -{ - "path": "../../../notebooks/tutorials/android.ipynb" -} diff --git a/docs/source/tutorials/tutorials_coordinates_notebook.nblink b/docs/source/tutorials/tutorials_coordinates_notebook.nblink deleted file mode 100644 index 69a615a6..00000000 --- a/docs/source/tutorials/tutorials_coordinates_notebook.nblink +++ /dev/null @@ -1,3 +0,0 @@ -{ - "path": "../../../notebooks/tutorials/coordinates.ipynb" -} diff --git a/docs/source/tutorials/tutorials_ephemeris_downloader_notebook.nblink b/docs/source/tutorials/tutorials_ephemeris_downloader_notebook.nblink deleted file mode 100644 index 33c39370..00000000 --- a/docs/source/tutorials/tutorials_ephemeris_downloader_notebook.nblink +++ /dev/null @@ -1,3 +0,0 @@ -{ - "path": "../../../notebooks/tutorials/ephemeris_downloader.ipynb" -} diff --git a/docs/source/tutorials/tutorials_fde_notebook.nblink b/docs/source/tutorials/tutorials_fde_notebook.nblink deleted file mode 100644 index e36b8395..00000000 --- a/docs/source/tutorials/tutorials_fde_notebook.nblink +++ /dev/null @@ -1,3 +0,0 @@ -{ - "path": "../../../notebooks/tutorials/fde.ipynb" -} diff --git a/docs/source/tutorials/tutorials_navdata_notebook.nblink b/docs/source/tutorials/tutorials_navdata_notebook.nblink deleted file mode 100644 index 31df35f5..00000000 --- a/docs/source/tutorials/tutorials_navdata_notebook.nblink +++ /dev/null @@ -1,3 +0,0 @@ -{ - "path": "../../../notebooks/tutorials/navdata.ipynb" -} diff --git a/docs/source/tutorials/tutorials_parsers_notebook.nblink b/docs/source/tutorials/tutorials_parsers_notebook.nblink deleted file mode 100644 index 2a61ec9e..00000000 --- a/docs/source/tutorials/tutorials_parsers_notebook.nblink +++ /dev/null @@ -1,3 +0,0 @@ -{ - "path": "../../../notebooks/tutorials/parsers.ipynb" -} diff --git a/docs/source/tutorials/tutorials_sv_models_notebook.nblink b/docs/source/tutorials/tutorials_sv_models_notebook.nblink deleted file mode 100644 index 3bd37bf6..00000000 --- a/docs/source/tutorials/tutorials_sv_models_notebook.nblink +++ /dev/null @@ -1,3 +0,0 @@ -{ - "path": "../../../notebooks/tutorials/sv_models.ipynb" -} diff --git a/docs/source/tutorials/tutorials_utilities_notebook.nblink b/docs/source/tutorials/tutorials_utilities_notebook.nblink deleted file mode 100644 index c6092016..00000000 --- a/docs/source/tutorials/tutorials_utilities_notebook.nblink +++ /dev/null @@ -1,3 +0,0 @@ -{ - "path": "../../../notebooks/tutorials/utilities.ipynb" -} diff --git a/docs/source/tutorials/tutorials_visualizations_notebook.nblink b/docs/source/tutorials/tutorials_visualizations_notebook.nblink deleted file mode 100644 index 9c52d5d7..00000000 --- a/docs/source/tutorials/tutorials_visualizations_notebook.nblink +++ /dev/null @@ -1,3 +0,0 @@ -{ - "path": "../../../notebooks/tutorials/visualizations.ipynb" -} diff --git a/docs/source/tutorials/utils/tutorials_constants_notebook.nblink b/docs/source/tutorials/utils/tutorials_constants_notebook.nblink new file mode 100644 index 00000000..d84ff525 --- /dev/null +++ b/docs/source/tutorials/utils/tutorials_constants_notebook.nblink @@ -0,0 +1,3 @@ +{ + "path": "../../../../notebooks/tutorials/utils/constants.ipynb" +} diff --git a/docs/source/tutorials/utils/tutorials_coordinates_notebook.nblink b/docs/source/tutorials/utils/tutorials_coordinates_notebook.nblink new file mode 100644 index 00000000..9005a19f --- /dev/null +++ b/docs/source/tutorials/utils/tutorials_coordinates_notebook.nblink @@ -0,0 +1,3 @@ +{ + "path": "../../../../notebooks/tutorials/utils/coordinates.ipynb" +} diff --git a/docs/source/tutorials/utils/tutorials_ephemeris_downloader_notebook.nblink b/docs/source/tutorials/utils/tutorials_ephemeris_downloader_notebook.nblink new file mode 100644 index 00000000..02b89bd2 --- /dev/null +++ b/docs/source/tutorials/utils/tutorials_ephemeris_downloader_notebook.nblink @@ -0,0 +1,3 @@ +{ + "path": "../../../../notebooks/tutorials/utils/ephemeris_downloader.ipynb" +} diff --git a/docs/source/tutorials/utils/tutorials_file_operations_notebook.nblink b/docs/source/tutorials/utils/tutorials_file_operations_notebook.nblink new file mode 100644 index 00000000..55e8e8e1 --- /dev/null +++ b/docs/source/tutorials/utils/tutorials_file_operations_notebook.nblink @@ -0,0 +1,3 @@ +{ + "path": "../../../../notebooks/tutorials/utils/file_operations.ipynb" +} diff --git a/docs/source/tutorials/utils/tutorials_filters_notebook.nblink b/docs/source/tutorials/utils/tutorials_filters_notebook.nblink new file mode 100644 index 00000000..7cc4109a --- /dev/null +++ b/docs/source/tutorials/utils/tutorials_filters_notebook.nblink @@ -0,0 +1,3 @@ +{ + "path": "../../../../notebooks/tutorials/utils/filters.ipynb" +} diff --git a/docs/source/tutorials/utils/tutorials_gnss_models_notebook.nblink b/docs/source/tutorials/utils/tutorials_gnss_models_notebook.nblink new file mode 100644 index 00000000..847aa432 --- /dev/null +++ b/docs/source/tutorials/utils/tutorials_gnss_models_notebook.nblink @@ -0,0 +1,3 @@ +{ + "path": "../../../../notebooks/tutorials/utils/gnss_models.ipynb" +} diff --git a/docs/source/tutorials/utils/tutorials_sv_models_notebook.nblink b/docs/source/tutorials/utils/tutorials_sv_models_notebook.nblink new file mode 100644 index 00000000..5839015b --- /dev/null +++ b/docs/source/tutorials/utils/tutorials_sv_models_notebook.nblink @@ -0,0 +1,3 @@ +{ + "path": "../../../../notebooks/tutorials/utils/sv_models.ipynb" +} diff --git a/docs/source/tutorials/utils/tutorials_time_conversions_notebook.nblink b/docs/source/tutorials/utils/tutorials_time_conversions_notebook.nblink new file mode 100644 index 00000000..a26184c5 --- /dev/null +++ b/docs/source/tutorials/utils/tutorials_time_conversions_notebook.nblink @@ -0,0 +1,3 @@ +{ + "path": "../../../../notebooks/tutorials/utils/time_conversions.ipynb" +} diff --git a/docs/source/tutorials/visualizations/tutorials_plot_map_notebook.nblink b/docs/source/tutorials/visualizations/tutorials_plot_map_notebook.nblink new file mode 100644 index 00000000..a19c88ba --- /dev/null +++ b/docs/source/tutorials/visualizations/tutorials_plot_map_notebook.nblink @@ -0,0 +1,3 @@ +{ + "path": "../../../../notebooks/tutorials/visualizations/plot_map.ipynb" +} diff --git a/docs/source/tutorials/visualizations/tutorials_plot_metric_notebook.nblink b/docs/source/tutorials/visualizations/tutorials_plot_metric_notebook.nblink new file mode 100644 index 00000000..880401dd --- /dev/null +++ b/docs/source/tutorials/visualizations/tutorials_plot_metric_notebook.nblink @@ -0,0 +1,3 @@ +{ + "path": "../../../../notebooks/tutorials/visualizations/plot_metric.ipynb" +} diff --git a/docs/source/tutorials/visualizations/tutorials_plot_skyplot_notebook.nblink b/docs/source/tutorials/visualizations/tutorials_plot_skyplot_notebook.nblink new file mode 100644 index 00000000..dd1e48d4 --- /dev/null +++ b/docs/source/tutorials/visualizations/tutorials_plot_skyplot_notebook.nblink @@ -0,0 +1,3 @@ +{ + "path": "../../../../notebooks/tutorials/visualizations/plot_skyplot.ipynb" +} diff --git a/docs/source/tutorials/visualizations/tutorials_style_notebook.nblink b/docs/source/tutorials/visualizations/tutorials_style_notebook.nblink new file mode 100644 index 00000000..da2a4336 --- /dev/null +++ b/docs/source/tutorials/visualizations/tutorials_style_notebook.nblink @@ -0,0 +1,3 @@ +{ + "path": "../../../../notebooks/tutorials/visualizations/style.ipynb" +} diff --git a/gnss_lib_py/__init__.py b/gnss_lib_py/__init__.py index 6df8be20..0f3fd2a4 100644 --- a/gnss_lib_py/__init__.py +++ b/gnss_lib_py/__init__.py @@ -8,23 +8,31 @@ from gnss_lib_py.algorithms.residuals import * from gnss_lib_py.algorithms.snapshot import * +from gnss_lib_py.navdata.navdata import * +from gnss_lib_py.navdata.operations import * + from gnss_lib_py.parsers.android import * from gnss_lib_py.parsers.clk import * from gnss_lib_py.parsers.google_decimeter import * -from gnss_lib_py.parsers.navdata import * from gnss_lib_py.parsers.nmea import * from gnss_lib_py.parsers.rinex_nav import * from gnss_lib_py.parsers.rinex_obs import * from gnss_lib_py.parsers.smartloc import * from gnss_lib_py.parsers.sp3 import * +from gnss_lib_py.utils.constants import * from gnss_lib_py.utils.coordinates import * from gnss_lib_py.utils.ephemeris_downloader import * +from gnss_lib_py.utils.file_operations import * from gnss_lib_py.utils.filters import * from gnss_lib_py.utils.gnss_models import * from gnss_lib_py.utils.sv_models import * from gnss_lib_py.utils.time_conversions import * -from gnss_lib_py.utils.visualizations import * + +from gnss_lib_py.visualizations.plot_map import * +from gnss_lib_py.visualizations.plot_metric import * +from gnss_lib_py.visualizations.plot_skyplot import * +from gnss_lib_py.visualizations.style import * # single location of version exists in pyproject.toml __version__ = metadata.version("gnss-lib-py") diff --git a/gnss_lib_py/algorithms/fde.py b/gnss_lib_py/algorithms/fde.py index 831e265a..c2f53a79 100644 --- a/gnss_lib_py/algorithms/fde.py +++ b/gnss_lib_py/algorithms/fde.py @@ -8,8 +8,9 @@ import time import numpy as np -from gnss_lib_py.algorithms.residuals import solve_residuals +from gnss_lib_py.navdata.operations import loop_time from gnss_lib_py.algorithms.snapshot import solve_wls +from gnss_lib_py.algorithms.residuals import solve_residuals def solve_fde(navdata, method="residual", remove_outliers=False, @@ -22,7 +23,7 @@ def solve_fde(navdata, method="residual", remove_outliers=False, Parameters ---------- - navdata : gnss_lib_py.parsers.navdata.NavData + navdata : gnss_lib_py.navdata.navdata.NavData NavData of GNSS measurements which must include the satellite positions: ``x_sv_m``, ``y_sv_m``, ``z_sv_m``, ``b_sv_m`` as well as the time row ``gps_millis`` and the corrected @@ -44,7 +45,7 @@ def solve_fde(navdata, method="residual", remove_outliers=False, Returns ------- - navdata : gnss_lib_py.parsers.navdata.NavData + navdata : gnss_lib_py.navdata.navdata.NavData Result includes a new row of ``fault_`` where a value of 1 indicates a detected fault, 0 indicates that no fault was detected, and 2 indicates an unknown fault status @@ -77,7 +78,7 @@ def fde_edm(navdata, max_faults=None, threshold=1.0, time_fde=False, Parameters ---------- - navdata : gnss_lib_py.parsers.navdata.NavData + navdata : gnss_lib_py.navdata.navdata.NavData NavData of GNSS measurements which must include the satellite positions: ``x_sv_m``, ``y_sv_m``, ``z_sv_m``, ``b_sv_m`` as well as the time row ``gps_millis`` and the corrected @@ -94,7 +95,7 @@ def fde_edm(navdata, max_faults=None, threshold=1.0, time_fde=False, Returns ------- - navdata : gnss_lib_py.parsers.navdata.NavData + navdata : gnss_lib_py.navdata.navdata.NavData Result includes a new row of ``fault_edm`` where a value of 1 indicates a detected fault and 0 indicates that no fault was detected, and 2 indicates an unknown fault status @@ -125,7 +126,7 @@ def fde_edm(navdata, max_faults=None, threshold=1.0, time_fde=False, compute_times = [] navdata_timing = [] - for _, _, navdata_subset in navdata.loop_time('gps_millis'): + for _, _, navdata_subset in loop_time(navdata,'gps_millis'): if time_fde: time_start = time.time() @@ -261,7 +262,7 @@ def fde_greedy_residual(navdata, max_faults, threshold, time_fde=False, Parameters ---------- - navdata : gnss_lib_py.parsers.navdata.NavData + navdata : gnss_lib_py.navdata.navdata.NavData NavData of GNSS measurements which must include the satellite positions: ``x_sv_m``, ``y_sv_m``, ``z_sv_m``, ``b_sv_m`` as well as the time row ``gps_millis`` and the corrected @@ -278,7 +279,7 @@ def fde_greedy_residual(navdata, max_faults, threshold, time_fde=False, Returns ------- - navdata : gnss_lib_py.parsers.navdata.NavData + navdata : gnss_lib_py.navdata.navdata.NavData Result includes a new row of ``fault_residual`` where a value of 1 indicates a detected fault and 0 indicates that no fault was detected, and 2 indicates an unknown fault status @@ -305,7 +306,7 @@ def fde_greedy_residual(navdata, max_faults, threshold, time_fde=False, compute_times = [] navdata_timing = [] - for _, _, navdata_subset in navdata.loop_time('gps_millis'): + for _, _, navdata_subset in loop_time(navdata,'gps_millis'): if time_fde: time_start = time.time() @@ -415,7 +416,7 @@ def evaluate_fde(navdata, method, fault_truth_row="fault_gt", Parameters ---------- - navdata : gnss_lib_py.parsers.navdata.NavData + navdata : gnss_lib_py.navdata.navdata.NavData NavData of GNSS measurements which must include the satellite positions: ``x_sv_m``, ``y_sv_m``, ``z_sv_m``, ``b_sv_m`` as well as the time row ``gps_millis`` and the corrected @@ -438,7 +439,7 @@ def evaluate_fde(navdata, method, fault_truth_row="fault_gt", ------- metrics : dict Combined metrics that were computed. - navdata : gnss_lib_py.parsers.navdata.NavData + navdata : gnss_lib_py.navdata.navdata.NavData Resulting NavData from ``solve_fde()``. """ @@ -446,7 +447,7 @@ def evaluate_fde(navdata, method, fault_truth_row="fault_gt", measurement_counts = [] fault_percentages = [] timesteps = 0 - for _, _, navdata_subset in navdata.loop_time("gps_millis"): + for _, _, navdata_subset in loop_time(navdata,"gps_millis"): measurement_count = len(navdata_subset) truth_fault_count = len(np.argwhere(navdata_subset[fault_truth_row]==1)) measurement_counts.append(measurement_count) @@ -701,11 +702,11 @@ def _residual_chi_square(navdata, receiver_state): Parameters ---------- - navdata : gnss_lib_py.parsers.navdata.NavData + navdata : gnss_lib_py.navdata.navdata.NavData NavData of GNSS measurements which must include the satellite positions: ``x_sv_m``, ``y_sv_m``, ``z_sv_m`` and residuals ``residuals_m``. - receiver_state : gnss_lib_py.parsers.navdata.NavData + receiver_state : gnss_lib_py.navdata.navdata.NavData Reciever state that must include the receiver's state of: ``x_rx_wls_m``, ``y_rx_wls_m``, and ``z_rx_wls_m``. @@ -749,11 +750,11 @@ def _residual_exclude(navdata, receiver_state): Parameters ---------- - navdata : gnss_lib_py.parsers.navdata.NavData + navdata : gnss_lib_py.navdata.navdata.NavData NavData of GNSS measurements which must include the satellite positions: ``x_sv_m``, ``y_sv_m``, ``z_sv_m`` and residuals ``residuals_m``. - receiver_state : gnss_lib_py.parsers.navdata.NavData + receiver_state : gnss_lib_py.navdata.navdata.NavData Reciever state that must include the receiver's state of: ``x_rx_wls_m``, ``y_rx_wls_m``, and ``z_rx_wls_m``. diff --git a/gnss_lib_py/algorithms/gnss_filters.py b/gnss_lib_py/algorithms/gnss_filters.py index f36f46ce..bb3bb688 100644 --- a/gnss_lib_py/algorithms/gnss_filters.py +++ b/gnss_lib_py/algorithms/gnss_filters.py @@ -9,7 +9,8 @@ import numpy as np -from gnss_lib_py.parsers.navdata import NavData +from gnss_lib_py.navdata.navdata import NavData +from gnss_lib_py.navdata.operations import loop_time from gnss_lib_py.algorithms.snapshot import solve_wls from gnss_lib_py.utils.coordinates import ecef_to_geodetic from gnss_lib_py.utils.filters import BaseExtendedKalmanFilter @@ -23,7 +24,7 @@ def solve_gnss_ekf(measurements, init_dict = None, Parameters ---------- - measurements : gnss_lib_py.parsers.navdata.NavData + measurements : gnss_lib_py.navdata.navdata.NavData Instance of the NavData class init_dict : dict Initialization dict with initial states and covariances. @@ -32,7 +33,7 @@ def solve_gnss_ekf(measurements, init_dict = None, Returns ------- - state_estimate : gnss_lib_py.parsers.navdata.NavData + state_estimate : gnss_lib_py.navdata.navdata.NavData Estimated receiver position in ECEF frame in meters and the estimated receiver clock bias also in meters as an instance of the NavData class with shape (4 x # unique timesteps) and @@ -50,7 +51,7 @@ def solve_gnss_ekf(measurements, init_dict = None, if "state_0" not in init_dict: pos_0 = None - for _, _, measurement_subset in measurements.loop_time("gps_millis", + for _, _, measurement_subset in loop_time(measurements,"gps_millis", delta_t_decimals=delta_t_decimals): pos_0 = solve_wls(measurement_subset) if pos_0 is not None: @@ -90,7 +91,7 @@ def solve_gnss_ekf(measurements, init_dict = None, states = [] - for timestamp, delta_t, measurement_subset in measurements.loop_time("gps_millis"): + for timestamp, delta_t, measurement_subset in loop_time(measurements,"gps_millis"): pos_sv_m = measurement_subset[["x_sv_m","y_sv_m","z_sv_m"]].T pos_sv_m = np.atleast_2d(pos_sv_m) diff --git a/gnss_lib_py/algorithms/residuals.py b/gnss_lib_py/algorithms/residuals.py index 4132c6f1..410822a1 100644 --- a/gnss_lib_py/algorithms/residuals.py +++ b/gnss_lib_py/algorithms/residuals.py @@ -7,16 +7,17 @@ import numpy as np -from gnss_lib_py.parsers.navdata import NavData +from gnss_lib_py.navdata.navdata import NavData +from gnss_lib_py.navdata.operations import loop_time, find_wildcard_indexes def solve_residuals(measurements, receiver_state, inplace=True): """Calculates residuals given pseudoranges and receiver position. Parameters ---------- - measurements : gnss_lib_py.parsers.navdata.NavData + measurements : gnss_lib_py.navdata.navdata.NavData Instance of the NavData class - receiver_state : gnss_lib_py.parsers.navdata.NavData + receiver_state : gnss_lib_py.navdata.navdata.NavData Either estimated or ground truth receiver position in ECEF frame in meters and the estimated or ground truth receiver clock bias also in meters as an instance of the NavData class with the @@ -28,7 +29,7 @@ def solve_residuals(measurements, receiver_state, inplace=True): Returns ------- - new_navdata : gnss_lib_py.parsers.navdata.NavData or None + new_navdata : gnss_lib_py.navdata.navdata.NavData or None If inplace is False, returns new NavData instance containing "gps_millis" and residual rows. If inplace is True, returns None. @@ -40,14 +41,14 @@ def solve_residuals(measurements, receiver_state, inplace=True): receiver_state.in_rows(["gps_millis"]) - rx_idxs = receiver_state.find_wildcard_indexes(["x_rx*_m", + rx_idxs = find_wildcard_indexes(receiver_state,["x_rx*_m", "y_rx*_m", "z_rx*_m", "b_rx*_m"], max_allow=1) residuals = [] - for timestamp, _, measurement_subset in measurements.loop_time("gps_millis"): + for timestamp, _, measurement_subset in loop_time(measurements,"gps_millis"): pos_sv_m = measurement_subset[["x_sv_m","y_sv_m","z_sv_m"]].T pos_sv_m = np.atleast_2d(pos_sv_m) diff --git a/gnss_lib_py/algorithms/snapshot.py b/gnss_lib_py/algorithms/snapshot.py index c744e705..2f178ddf 100644 --- a/gnss_lib_py/algorithms/snapshot.py +++ b/gnss_lib_py/algorithms/snapshot.py @@ -13,8 +13,9 @@ import numpy as np -from gnss_lib_py.parsers.navdata import NavData +from gnss_lib_py.navdata.navdata import NavData from gnss_lib_py.utils import constants as consts +from gnss_lib_py.navdata.operations import loop_time, find_wildcard_indexes from gnss_lib_py.utils.coordinates import ecef_to_geodetic def solve_wls(measurements, weight_type = None, only_bias = False, @@ -34,7 +35,7 @@ def solve_wls(measurements, weight_type = None, only_bias = False, Parameters ---------- - measurements : gnss_lib_py.parsers.navdata.NavData + measurements : gnss_lib_py.navdata.navdata.NavData Instance of the NavData class which must include at least ``gps_millis``, ``x_sv_m``, ``y_sv_m``, and ``z_sv_m`` weight_type : string @@ -42,7 +43,7 @@ def solve_wls(measurements, weight_type = None, only_bias = False, only_bias : bool If True, then only the receiver clock bias is estimated. Otherwise, both position and clock bias are estimated. - receiver_state : gnss_lib_py.parsers.navdata.NavData + receiver_state : gnss_lib_py.navdata.navdata.NavData Only used if only_bias is set to True, see description above. Receiver position in ECEF frame in meters as an instance of the NavData class with at least the following rows: ``x_rx*_m``, @@ -65,7 +66,7 @@ def solve_wls(measurements, weight_type = None, only_bias = False, Returns ------- - state_estimate : gnss_lib_py.parsers.navdata.NavData + state_estimate : gnss_lib_py.navdata.navdata.NavData Estimated receiver position in ECEF frame in meters and the estimated receiver clock bias also in meters as an instance of the NavData class with shape (4 x # unique timesteps) and @@ -84,14 +85,15 @@ def solve_wls(measurements, weight_type = None, only_bias = False, + "for only_bias.") rx_rows_to_find = ['x_rx*_m', 'y_rx*_m', 'z_rx*_m'] - rx_idxs = receiver_state.find_wildcard_indexes( + rx_idxs = find_wildcard_indexes(receiver_state, rx_rows_to_find, max_allow=1) states = [] runtime_error_idxs = {} position = np.zeros((4,1)) - for timestamp, _, measurement_subset in measurements.loop_time("gps_millis", delta_t_decimals=delta_t_decimals): + for timestamp, _, measurement_subset in loop_time(measurements,"gps_millis", + delta_t_decimals=delta_t_decimals): pos_sv_m = measurement_subset[["x_sv_m","y_sv_m","z_sv_m"]].T pos_sv_m = np.atleast_2d(pos_sv_m) diff --git a/gnss_lib_py/parsers/navdata.py b/gnss_lib_py/navdata/navdata.py similarity index 74% rename from gnss_lib_py/parsers/navdata.py rename to gnss_lib_py/navdata/navdata.py index 7c62f7d1..a8dfa4e3 100644 --- a/gnss_lib_py/parsers/navdata.py +++ b/gnss_lib_py/navdata/navdata.py @@ -162,110 +162,6 @@ def from_numpy_array(self, numpy_array): for row_num in range(numpy_array.shape[0]): self[str(row_num)] = numpy_array[row_num,:] - def concat(self, navdata=None, axis=1, inplace=False): - """Concatenates second NavData instance by row or column. - - Concatenates a second NavData instance to the existing NavData - instance by either row or column. - - Each type of data is included in a row, so adding new rows with - ``axis=0``, means adding new types of data. Concat requires that - the new NavData matches the length of the existing NavData. Row - concatenation assumes the same ordering across both NavData - instances (e.g. sorted by timestamp) and does not perform any - matching/sorting itself. - - You can also concatenate new columns ``axis=1``. If the row - names of the new NavData instance don't match the row names of - the existing NavData instance, the mismatched values will be - filled with np.nan. - - Parameters - ---------- - navdata : gnss_lib_py.parsers.navdata.NavData - Navdata instance to concatenate. - axis : int - Either add new rows (type) of data ``axis=0`` or new columns - (e.g. timesteps) of data ``axis=1``. - inplace : bool - If False, will return new concatenated NavData instance. - If True, will concatenate data to the current NavData - instance. - - Returns - ------- - new_navdata : gnss_lib_py.parsers.navdata.NavData or None - If inplace is False, returns NavData instance after - concatenating specified data. If inplace is True, returns - None. - - """ - - if not isinstance(navdata,NavData): - raise TypeError("concat input data must be a NavData instance.") - - if axis == 0: # concatenate new rows - if len(self) != len(navdata): - raise RuntimeError("concat input data must be same " \ - + "length to concatenate new rows.") - if not inplace: - new_navdata = self.copy() - for row in navdata.rows: - new_row_name = row - suffix = None - while new_row_name in self.rows: - if suffix is None: - suffix = 0 - else: - suffix += 1 - new_row_name = row + "_" + str(suffix) - new_row = navdata[row].astype(navdata.orig_dtypes[row]) - if inplace: - self[new_row_name] = new_row - else: - new_navdata[new_row_name] = new_row - - elif axis == 1: # concatenate new columns - new_navdata = NavData() - # get unique list of row names - combined_rows = self.rows + [row for row in navdata.rows - if row not in self.rows] - - for row in combined_rows: - combined_row = np.array([]) - # combine data from existing and new instance - for data in [self, navdata]: - if row in data.rows: - new_row = np.atleast_1d(data[row]) - elif len(data) == 0: - continue - else: - # add np.nan for missing values - new_row = np.empty((len(data),)) - new_row.fill(np.nan) - new_row = np.array(new_row, ndmin=1) - combined_row = np.concatenate((combined_row, - new_row)) - new_navdata[row] = combined_row - - if len(self) > 0 and len(navdata) > 0: - new_navdata.orig_dtypes = navdata.orig_dtypes.copy() - new_navdata.orig_dtypes.update(self.orig_dtypes) - elif len(self) > 0: - new_navdata.orig_dtypes = self.orig_dtypes.copy() - elif len(navdata) > 0: - new_navdata.orig_dtypes = navdata.orig_dtypes.copy() - - if inplace: - self.array = new_navdata.array - self.map = new_navdata.map - self.str_map = new_navdata.str_map - self.orig_dtypes = new_navdata.orig_dtypes.copy() - - if inplace: - return None - return new_navdata - def where(self, key_idx, value, condition="eq"): """Return NavData where conditions are met for the given row. @@ -304,7 +200,7 @@ def where(self, key_idx, value, condition="eq"): Returns ------- - new_navdata : gnss_lib_py.parsers.navdata.NavData + new_navdata : gnss_lib_py.navdata.navdata.NavData NavData with columns where given condition is satisfied for specified row """ @@ -426,93 +322,6 @@ def argwhere(self, key_idx, value, condition="eq"): new_cols = np.squeeze(new_cols) return new_cols - def sort(self, order=None, ind=None, ascending=True, - inplace=False): - """Sort values along given row or using given index - - Parameters - ---------- - order : string/int - Key or index of the row on which NavData will be sorted - ind : list/np.ndarray - Ordering of indices to be used for sorting - ascending : bool - If true, sorts "ascending", otherwise sorts "descending" - inplace : bool - If False, will return new NavData instance with rows - sorted. If True, will sorted data rows in the - current NavData instance. - - Returns - ------- - new_navdata : gnss_lib_py.parsers.navdata.NavData or None - If inplace is False, returns NavData instance after renaming - specified rows. If inplace is True, returns - None. - - """ - # check if there is only one column - no sorting needed - if self.shape[1] == 1: - return self - - if ind is None: - assert order is not None, \ - "Provide 'order' arg as row on which NavData is sorted" - if ascending: - ind = np.argsort(self[order]) - else: - ind = np.argsort(-self[order]) - - if not inplace: - new_navdata = self.copy() # create copy to return - for row_idx in range(self.shape[0]): - if inplace: - self.array[row_idx,:] = self.array[row_idx,ind] - else: - new_navdata.array[row_idx,:] = new_navdata.array[row_idx,ind] - - if inplace: - return None - return new_navdata - - def loop_time(self, time_row, delta_t_decimals=2): - """Generator object to loop over columns from same times. - - Parameters - ---------- - time_row : string/int - Key or index of the row in which times are stored. - delta_t_decimals : int - Decimal places after which times are considered equal. - - Yields - ------ - timestamp : float - Current timestamp. - delta_t : float - Difference between current time and previous time. - new_navdata : gnss_lib_py.parsers.navdata.NavData - NavData with same time, up to given decimal tolerance. - - """ - - times = self[time_row] - times_unique = np.sort(np.unique(np.around(times, - decimals=delta_t_decimals))) - for time_idx, time in enumerate(times_unique): - if time_idx==0: - delta_t = 0 - else: - delta_t = time-times_unique[time_idx-1] - new_navdata = self.where(time_row, [time-10**(-delta_t_decimals), - time+10**(-delta_t_decimals)], - condition="between") - if len(np.unique(new_navdata[time_row]))==1: - frame_time = new_navdata[time_row, 0] - else: - frame_time = time - yield frame_time, delta_t, new_navdata - def is_str(self, row_name): """Check whether a row contained string values. @@ -553,7 +362,7 @@ def rename(self, mapper=None, inplace=False): Returns ------- - new_navdata : gnss_lib_py.parsers.navdata.NavData or None + new_navdata : gnss_lib_py.navdata.navdata.NavData or None If inplace is False, returns NavData instance after renaming specified rows. If inplace is True, returns None. @@ -611,7 +420,7 @@ def replace(self, mapper=None, rows=None, inplace=False): Returns ------- - new_navdata : gnss_lib_py.parsers.navdata.NavData or None + new_navdata : gnss_lib_py.navdata.navdata.NavData or None If inplace is False, returns NavData instance after replacing specified data. If inplace is True, returns None. @@ -671,7 +480,7 @@ def copy(self, rows=None, cols=None): Returns ------- - new_navdata : gnss_lib_py.parsers.navdata.NavData + new_navdata : gnss_lib_py.navdata.navdata.NavData Copy of original NavData with desired rows and columns """ new_navdata = NavData() @@ -710,7 +519,7 @@ def remove(self, rows=None, cols=None, inplace=False): Returns ------- - new_navdata : gnss_lib_py.parsers.navdata.NavData or None + new_navdata : gnss_lib_py.navdata.navdata.NavData or None If inplace is False, returns NavData instance after removing specified rows and columns. If inplace is True, returns None. @@ -767,64 +576,6 @@ def remove(self, rows=None, cols=None, inplace=False): new_navdata[key] = new_row return new_navdata - def interpolate(self, x_row, y_rows, inplace=False, *args): - """Interpolate NaN values based on row data. - - Additional ``*args`` arguments are passed into the ``np.interp`` - function. - - Parameters - ---------- - x_row : string - Row name for x-coordinate of all values (e.g. gps_millis). - Row must not contain any nan values. - y_rows : list or string - Row name(s) for y-coordinate which includes nan values that - will be interpolated. - inplace : bool - If False, will return new NavData instance with nan values - interpolated. If True, will interpolate nan values within - the current NavData instance. - - Returns - ------- - new_navdata : gnss_lib_py.parsers.navdata.NavData or None - If inplace is False, returns NavData instance after removing - specified rows and columns. If inplace is True, returns - None. - - """ - - if isinstance(y_rows,str): - y_rows = [y_rows] - if not isinstance(x_row, str): - raise TypeError("'x_row' must be row name as a string.") - if not isinstance(y_rows, list): - raise TypeError("'y_rows' must be single or list of " \ - + "row names as a string.") - self.in_rows([x_row] + y_rows) - - if not inplace: - new_navdata = self.copy() - for y_row in y_rows: - nan_idxs = self.argwhere(y_row,np.nan) - if nan_idxs.size == 0: - continue - not_nan_idxs = self.argwhere(y_row,np.nan,"neq") - x_vals = self[x_row,nan_idxs] - xp_vals = self[x_row,not_nan_idxs] - yp_vals = self[y_row,not_nan_idxs] - - if inplace: - self[y_row,nan_idxs] = np.interp(x_vals, xp_vals, - yp_vals, *args) - else: - new_navdata[y_row,nan_idxs] = np.interp(x_vals, xp_vals, - yp_vals, *args) - if inplace: - return None - return new_navdata - def in_rows(self, rows): """Checks whether the given rows are in NavData. @@ -852,104 +603,6 @@ def in_rows(self, rows): raise KeyError(", ".join(missing_rows) + " row(s) are" \ + " missing from NavData object.") - def find_wildcard_indexes(self, wildcards, max_allow = None, - excludes = None): - """Searches for indexes matching wildcard search input. - - For example, a search for ``x_*_m`` would find ``x_rx_m`` or - ``x_sv_m`` or ``x_alpha_beta_gamma_m`` depending on the rows - existing in the NavData instance. - - The ``excludes`` variable allows you to exclude indexes when - trying to match a wildcard. For example, if there are rows named - ``pr_raw_m``and ``pr_raw_sigma_m`` then the input - ``wildcards="pr_*_m", excludes=None`` would return - ``{"pr_*_m", ["pr_raw_m","pr_raw_sigma_m"]}`` but with the excludes - parameter set, the input ``wildcards="pr_*_m", excludes="pr_*_sigma_m"`` - would only return ``{"pr_*_m", ["pr_raw_m"]}`` - - Will return an error no index is found matching the wildcard or - if more than ``max_allow`` indexes are found. - - Currently only allows for a single wildcard '*' per index. - - Parameters - ---------- - wildcards : array-like or str - List/tuple/np.ndarray/set of indexes for which to search. - max_allow : int or None - Maximum number of valid indexes to allow before throwing an - error. If None, then no limit is placed. - excludes : array-like or str - List or string to exclude for each wildcard in wildcards. - Must be the same length as wildcards. Allowed to include a - wildcard '*' character but not necessary. - - Returns - ------- - wildcard_indexes : dict - Dictionary of the form {"search_term", [indexes,...]}, - - """ - - if isinstance(wildcards,str): - wildcards = [wildcards] - if not isinstance(wildcards, (list,tuple,np.ndarray,set)): - raise TypeError("wildcards input in find_wildcard_indexes" \ - + " must be array-like or single string") - if not (isinstance(max_allow,int) or max_allow is None): - raise TypeError("max_allow input in find_wildcard_indexes" \ - + " must be an integer or None.") - # handle exclude types - if isinstance(excludes,str): - excludes = [excludes] - if excludes is None: - excludes = [None] * len(wildcards) - if not isinstance(excludes, (list,tuple,np.ndarray,set)): - raise TypeError("excludes input in find_wildcard_indexes" \ - + " must be array-like, single string, " \ - + "or None for each wildcard") - if len(excludes) != len(wildcards): - raise TypeError("excludes input must match length of " \ - + "wildcard input.") - for ex_idx, exclude in enumerate(excludes): - if exclude is None or isinstance(exclude,str): - excludes[ex_idx] = [exclude] - if not isinstance(excludes[ex_idx], (list,tuple,np.ndarray,set)): - raise TypeError("excludes input in find_wildcard_indexes" \ - + " must be array-like, single string, " \ - + "or None for each wildcard") - - wildcard_indexes = {} - - for wild_idx, wildcard in enumerate(wildcards): - if not isinstance(wildcard,str): - raise TypeError("wildcards must be strings") - if wildcard.count("*") != 1: - raise RuntimeError("One wildcard '*' and only one "\ - + "wildcard must be present in search string") - indexes = [row for row in self.rows - if row.startswith(wildcard.split("*",maxsplit=1)[0]) - and row.endswith(wildcard.split("*",maxsplit=1)[1])] - if excludes[wild_idx] is not None: - for exclude in excludes[wild_idx]: - if exclude is not None: - if '*' in exclude: - indexes = [row for row in indexes - if not (row.startswith(exclude.split("*",maxsplit=1)[0]) - and row.endswith(exclude.split("*",maxsplit=1)[1]))] - else: - indexes = [row for row in indexes if exclude != row] - if max_allow is not None and len(indexes) > max_allow: - raise KeyError("More than " + str(max_allow) \ - + " possible row indexes for " + wildcard) - if len(indexes) == 0: - raise KeyError("Missing " + wildcard + " row.") - - wildcard_indexes[wildcard] = indexes - - return wildcard_indexes - def pandas_df(self): """Return pandas DataFrame equivalent to class @@ -961,7 +614,19 @@ def pandas_df(self): df_list = [] for row in self.rows: - df_list.append(np.atleast_1d( + dtype = self.orig_dtypes[row] + if np.issubdtype(dtype, np.integer): + row_data = self[row] + row_data = row_data.astype(np.float64) + if np.any(np.isnan(row_data)): + nan_indexes = np.isnan(row_data) + row_data[~nan_indexes] = row_data[~nan_indexes].astype(dtype) + df_list.append(np.atleast_1d(row_data)) + else: + df_list.append(np.atleast_1d( + self[row].astype(self.orig_dtypes[row]))) + else: + df_list.append(np.atleast_1d( self[row].astype(self.orig_dtypes[row]))) # transpose list to conform to Pandas input @@ -1086,7 +751,16 @@ def __getitem__(self, key_idx): if isinstance(rows,list) and len(rows) == 1 \ and self.inv_map[rows[0]] in self.orig_dtypes: - arr_slice = arr_slice.astype(self.orig_dtypes[self.inv_map[rows[0]]]) + dtype = self.orig_dtypes[self.inv_map[rows[0]]] + if np.issubdtype(dtype, np.integer): + arr_slice = arr_slice.astype(np.float64) + if np.any(np.isnan(arr_slice)): + nan_indexes = np.isnan(arr_slice) + arr_slice[~nan_indexes] = arr_slice[~nan_indexes].astype(dtype) + else: + arr_slice = arr_slice.astype(dtype) + else: + arr_slice = arr_slice.astype(dtype) # remove all dimensions of length one arr_slice = np.squeeze(arr_slice) @@ -1201,7 +875,7 @@ def __iter__(self): Returns ------- - self: gnss_lib_py.parsers.NavData + self: gnss_lib_py.navdata.navdata.NavData Instantiation of NavData class with iteration initialized """ self.curr_col = 0 @@ -1213,7 +887,7 @@ def __next__(self): Returns ------- - x_curr : gnss_lib_py.parsers.NavData + x_curr : gnss_lib_py.navdata.navdata.NavData Current column (based on iteration count) """ if self.curr_col >= self.num_cols: diff --git a/gnss_lib_py/navdata/operations.py b/gnss_lib_py/navdata/operations.py new file mode 100644 index 00000000..863d8136 --- /dev/null +++ b/gnss_lib_py/navdata/operations.py @@ -0,0 +1,355 @@ + +"""Base class for moving values between different modules/functions + +""" + +__authors__ = "Ashwin Kanhere, D. Knowles" +__date__ = "03 Nov 2021" + +import numpy as np + +from gnss_lib_py.navdata.navdata import NavData + +def concat(*navdatas, axis=1): + """Concatenates NavData instances by row or column. + + Concatenates given NavData instances together by either row or + column. + + Each type of data is included in a row, so adding new rows with + ``axis=0``, means adding new types of data. Concat requires that + the new NavData matches the length of the existing NavData. Row + concatenation assumes the same ordering across both NavData + instances (e.g. sorted by timestamp) and does not perform any + matching/sorting itself. + + You can also concatenate new columns ``axis=1``. If the row + names of the new NavData instance don't match the row names of + the existing NavData instance, the mismatched values will be + filled with np.nan. + + Parameters + ---------- + navdatas : List-like of gnss_lib_py.navdata.navdata.NavData + Navdata instances to concatenate. + axis : int + Either add new rows (type) of data ``axis=0`` or new columns + (e.g. timesteps) of data ``axis=1``. + + Returns + ------- + new_navdata : gnss_lib_py.navdata.navdata.NavData or None + NavData instance after concatenating specified data. + + """ + + concat_navdata = navdatas[0].copy() + for navdata in navdatas[1:]: + if not isinstance(navdata,NavData): + raise TypeError("concat input data must be a NavData instance.") + + if axis == 0: # concatenate new rows + if len(concat_navdata) != len(navdata): + raise RuntimeError("concat input data must be same " \ + + "length to concatenate new rows.") + + for row in navdata.rows: + new_row_name = row + suffix = None + while new_row_name in concat_navdata.rows: + if suffix is None: + suffix = 0 + else: + suffix += 1 + new_row_name = row + "_" + str(suffix) + new_row = navdata[row].astype(navdata.orig_dtypes[row]) + concat_navdata[new_row_name] = new_row + + elif axis == 1: # concatenate new columns + new_navdata = NavData() + # get unique list of row names + combined_rows = concat_navdata.rows + [row for row in navdata.rows + if row not in concat_navdata.rows] + + for row in combined_rows: + combined_row = np.array([]) + # combine data from existing and new instance + for data in [concat_navdata, navdata]: + if row in data.rows: + new_row = np.atleast_1d(data[row]) + elif len(data) == 0: + continue + else: + # add np.nan for missing values + new_row = np.empty((len(data),)) + new_row.fill(np.nan) + new_row = np.array(new_row, ndmin=1) + combined_row = np.concatenate((combined_row, + new_row)) + new_navdata[row] = combined_row + + if len(concat_navdata) > 0 and len(navdata) > 0: + new_navdata.orig_dtypes = navdata.orig_dtypes.copy() + new_navdata.orig_dtypes.update(concat_navdata.orig_dtypes) + elif len(concat_navdata) > 0: + new_navdata.orig_dtypes = concat_navdata.orig_dtypes.copy() + elif len(navdata) > 0: + new_navdata.orig_dtypes = navdata.orig_dtypes.copy() + + concat_navdata.array = new_navdata.array + concat_navdata.map = new_navdata.map + concat_navdata.str_map = new_navdata.str_map + concat_navdata.orig_dtypes = new_navdata.orig_dtypes.copy() + + return concat_navdata + +def sort(navdata, order=None, ind=None, ascending=True, + inplace=False): + """Sort values along given row or using given index + + Parameters + ---------- + navdata : gnss_lib_py.navdata.navdata.NavData + Navdata instance. + order : string/int + Key or index of the row on which NavData will be sorted + ind : list/np.ndarray + Ordering of indices to be used for sorting + ascending : bool + If true, sorts "ascending", otherwise sorts "descending" + inplace : bool + If False, will return new NavData instance with rows + sorted. If True, will sorted data rows in the + current NavData instance. + + Returns + ------- + new_navdata : gnss_lib_py.navdata.navdata.NavData or None + If inplace is False, returns NavData instance after renaming + specified rows. If inplace is True, returns + None. + + """ + # check if there is only one column - no sorting needed + if navdata.shape[1] == 1: + return navdata + + if ind is None: + assert order is not None, \ + "Provide 'order' arg as row on which NavData is sorted" + if ascending: + ind = np.argsort(navdata[order]) + else: + ind = np.argsort(-navdata[order]) + + if not inplace: + new_navdata = navdata.copy() # create copy to return + for row_idx in range(navdata.shape[0]): + if inplace: + navdata.array[row_idx,:] = navdata.array[row_idx,ind] + else: + new_navdata.array[row_idx,:] = new_navdata.array[row_idx,ind] + + if inplace: + return None + return new_navdata + +def loop_time(navdata, time_row, delta_t_decimals=2): + """Generator object to loop over columns from same times. + + Parameters + ---------- + navdata : gnss_lib_py.navdata.navdata.NavData + Navdata instance. + time_row : string/int + Key or index of the row in which times are stored. + delta_t_decimals : int + Decimal places after which times are considered equal. + + Yields + ------ + timestamp : float + Current timestamp. + delta_t : float + Difference between current time and previous time. + new_navdata : gnss_lib_py.navdata.navdata.NavData + NavData with same time, up to given decimal tolerance. + + """ + + times = navdata[time_row] + times_unique = np.sort(np.unique(np.around(times, + decimals=delta_t_decimals))) + for time_idx, time in enumerate(times_unique): + if time_idx==0: + delta_t = 0 + else: + delta_t = time-times_unique[time_idx-1] + new_navdata = navdata.where(time_row, [time-10**(-delta_t_decimals), + time+10**(-delta_t_decimals)], + condition="between") + if len(np.unique(new_navdata[time_row]))==1: + frame_time = new_navdata[time_row, 0] + else: + frame_time = time + yield frame_time, delta_t, new_navdata + +def interpolate(navdata, x_row, y_rows, inplace=False, *args): + """Interpolate NaN values based on row data. + + Additional ``*args`` arguments are passed into the ``np.interp`` + function. + + Parameters + ---------- + navdata : gnss_lib_py.navdata.navdata.NavData + Navdata instance. + x_row : string + Row name for x-coordinate of all values (e.g. gps_millis). + Row must not contain any nan values. + y_rows : list or string + Row name(s) for y-coordinate which includes nan values that + will be interpolated. + inplace : bool + If False, will return new NavData instance with nan values + interpolated. If True, will interpolate nan values within + the current NavData instance. + + Returns + ------- + new_navdata : gnss_lib_py.navdata.navdata.NavData or None + If inplace is False, returns NavData instance after removing + specified rows and columns. If inplace is True, returns + None. + + """ + + if isinstance(y_rows,str): + y_rows = [y_rows] + if not isinstance(x_row, str): + raise TypeError("'x_row' must be row name as a string.") + if not isinstance(y_rows, list): + raise TypeError("'y_rows' must be single or list of " \ + + "row names as a string.") + navdata.in_rows([x_row] + y_rows) + + if not inplace: + new_navdata = navdata.copy() + for y_row in y_rows: + nan_idxs = navdata.argwhere(y_row,np.nan) + if nan_idxs.size == 0: + continue + not_nan_idxs = navdata.argwhere(y_row,np.nan,"neq") + x_vals = navdata[x_row,nan_idxs] + xp_vals = navdata[x_row,not_nan_idxs] + yp_vals = navdata[y_row,not_nan_idxs] + + if inplace: + navdata[y_row,nan_idxs] = np.interp(x_vals, xp_vals, + yp_vals, *args) + else: + new_navdata[y_row,nan_idxs] = np.interp(x_vals, xp_vals, + yp_vals, *args) + if inplace: + return None + return new_navdata + +def find_wildcard_indexes(navdata, wildcards, max_allow = None, + excludes = None): + """Searches for indexes matching wildcard search input. + + For example, a search for ``x_*_m`` would find ``x_rx_m`` or + ``x_sv_m`` or ``x_alpha_beta_gamma_m`` depending on the rows + existing in the NavData instance. + + The ``excludes`` variable allows you to exclude indexes when + trying to match a wildcard. For example, if there are rows named + ``pr_raw_m``and ``pr_raw_sigma_m`` then the input + ``wildcards="pr_*_m", excludes=None`` would return + ``{"pr_*_m", ["pr_raw_m","pr_raw_sigma_m"]}`` but with the excludes + parameter set, the input ``wildcards="pr_*_m", excludes="pr_*_sigma_m"`` + would only return ``{"pr_*_m", ["pr_raw_m"]}`` + + Will return an error no index is found matching the wildcard or + if more than ``max_allow`` indexes are found. + + Currently only allows for a single wildcard '*' per index. + + Parameters + ---------- + navdata : gnss_lib_py.navdata.navdata.NavData + Navdata instance. + wildcards : array-like or str + List/tuple/np.ndarray/set of indexes for which to search. + max_allow : int or None + Maximum number of valid indexes to allow before throwing an + error. If None, then no limit is placed. + excludes : array-like or str + List or string to exclude for each wildcard in wildcards. + Must be the same length as wildcards. Allowed to include a + wildcard '*' character but not necessary. + + Returns + ------- + wildcard_indexes : dict + Dictionary of the form {"search_term", [indexes,...]}, + + """ + + if isinstance(wildcards,str): + wildcards = [wildcards] + if not isinstance(wildcards, (list,tuple,np.ndarray,set)): + raise TypeError("wildcards input in find_wildcard_indexes" \ + + " must be array-like or single string") + if not (isinstance(max_allow,int) or max_allow is None): + raise TypeError("max_allow input in find_wildcard_indexes" \ + + " must be an integer or None.") + # handle exclude types + if isinstance(excludes,str): + excludes = [excludes] + if excludes is None: + excludes = [None] * len(wildcards) + if not isinstance(excludes, (list,tuple,np.ndarray,set)): + raise TypeError("excludes input in find_wildcard_indexes" \ + + " must be array-like, single string, " \ + + "or None for each wildcard") + if len(excludes) != len(wildcards): + raise TypeError("excludes input must match length of " \ + + "wildcard input.") + for ex_idx, exclude in enumerate(excludes): + if exclude is None or isinstance(exclude,str): + excludes[ex_idx] = [exclude] + if not isinstance(excludes[ex_idx], (list,tuple,np.ndarray,set)): + raise TypeError("excludes input in find_wildcard_indexes" \ + + " must be array-like, single string, " \ + + "or None for each wildcard") + + wildcard_indexes = {} + + for wild_idx, wildcard in enumerate(wildcards): + if not isinstance(wildcard,str): + raise TypeError("wildcards must be strings") + if wildcard.count("*") != 1: + raise RuntimeError("One wildcard '*' and only one "\ + + "wildcard must be present in search string") + indexes = [row for row in navdata.rows + if row.startswith(wildcard.split("*",maxsplit=1)[0]) + and row.endswith(wildcard.split("*",maxsplit=1)[1])] + if excludes[wild_idx] is not None: + for exclude in excludes[wild_idx]: + if exclude is not None: + if '*' in exclude: + indexes = [row for row in indexes + if not (row.startswith(exclude.split("*",maxsplit=1)[0]) + and row.endswith(exclude.split("*",maxsplit=1)[1]))] + else: + indexes = [row for row in indexes if exclude != row] + if max_allow is not None and len(indexes) > max_allow: + raise KeyError("More than " + str(max_allow) \ + + " possible row indexes for " + wildcard) + if len(indexes) == 0: + raise KeyError("Missing " + wildcard + " row.") + + wildcard_indexes[wildcard] = indexes + + return wildcard_indexes diff --git a/gnss_lib_py/parsers/android.py b/gnss_lib_py/parsers/android.py index f0a928e0..a58b59ff 100644 --- a/gnss_lib_py/parsers/android.py +++ b/gnss_lib_py/parsers/android.py @@ -22,8 +22,9 @@ import numpy as np import pandas as pd -from gnss_lib_py.parsers.navdata import NavData +from gnss_lib_py.navdata.navdata import NavData from gnss_lib_py.algorithms.snapshot import solve_wls +from gnss_lib_py.navdata.operations import loop_time import gnss_lib_py.utils.constants as consts from gnss_lib_py.utils.sv_models import add_sv_states from gnss_lib_py.utils.time_conversions import get_leap_seconds @@ -218,7 +219,7 @@ def postprocess(self): if self.remove_rx_b_from_pr: # remove the receiver's clock bias at the first timestamp - for _, _, subset in self.loop_time("gps_millis", delta_t_decimals=-2): + for _, _, subset in loop_time(self,"gps_millis", delta_t_decimals=-2): subset = add_sv_states(subset.where("gnss_id",("gps","galileo")), source="precise", verbose=self.verbose) diff --git a/gnss_lib_py/parsers/clk.py b/gnss_lib_py/parsers/clk.py index 36e9ce96..5d3acbe6 100644 --- a/gnss_lib_py/parsers/clk.py +++ b/gnss_lib_py/parsers/clk.py @@ -11,7 +11,7 @@ import numpy as np from scipy import interpolate -from gnss_lib_py.parsers.navdata import NavData +from gnss_lib_py.navdata.navdata import NavData from gnss_lib_py.utils.constants import CONSTELLATION_CHARS, C from gnss_lib_py.utils.time_conversions import gps_datetime_to_gps_millis @@ -99,7 +99,7 @@ def interpolate_clk(self, navdata, window=6, verbose=False): Parameters ---------- - navdata : gnss_lib_py.parsers.navdata.NavData + navdata : gnss_lib_py.navdata.navdata.NavData Instance of the NavData class that must include rows for ``gps_millis`` and ``gnss_sv_id`` window : int diff --git a/gnss_lib_py/parsers/google_decimeter.py b/gnss_lib_py/parsers/google_decimeter.py index 88877f19..9f708c07 100644 --- a/gnss_lib_py/parsers/google_decimeter.py +++ b/gnss_lib_py/parsers/google_decimeter.py @@ -12,7 +12,8 @@ import numpy as np import pandas as pd -from gnss_lib_py.parsers.navdata import NavData +from gnss_lib_py.navdata.navdata import NavData +from gnss_lib_py.navdata.operations import loop_time, concat, find_wildcard_indexes, interpolate from gnss_lib_py.utils.coordinates import wrap_0_to_2pi from gnss_lib_py.utils.coordinates import geodetic_to_ecef from gnss_lib_py.utils.coordinates import ecef_to_geodetic @@ -198,7 +199,7 @@ def get_state_estimate(self): Returns ------- - state_estimate : gnss_lib_py.parsers.navdata.NavData + state_estimate : gnss_lib_py.navdata.navdata.NavData Instance of `NavData` containing state estimate rows present in the instance of `AndroidDerived2022`. """ @@ -209,7 +210,7 @@ def get_state_estimate(self): rx_rows_in_measure = ['gps_millis'] for row_wildcard in rx_rows_to_find: try: - row_map = self.find_wildcard_indexes(row_wildcard, max_allow=1) + row_map = find_wildcard_indexes(self,row_wildcard, max_allow=1) row = row_map[row_wildcard][0] rx_rows_in_measure.append(row) except KeyError: @@ -217,14 +218,14 @@ def get_state_estimate(self): continue state_estimate = NavData() - for _, _, measure_frame in self.loop_time('gps_millis', delta_t_decimals=-2): + for _, _, measure_frame in loop_time(self,'gps_millis', delta_t_decimals=-2): temp_est = NavData() for row_wildcard in rx_rows_in_measure: temp_est[row_wildcard] = measure_frame[row_wildcard, 0] if len(state_estimate)==0: state_estimate = temp_est else: - state_estimate.concat(temp_est, inplace=True) + state_estimate = concat(state_estimate,temp_est) return state_estimate @staticmethod @@ -414,7 +415,7 @@ def solve_kaggle_baseline(navdata): Returns ------- - state_estimate : gnss_lib_py.parsers.navdata.NavData + state_estimate : gnss_lib_py.navdata.navdata.NavData Baseline state estimate. """ @@ -446,7 +447,7 @@ def prepare_kaggle_submission(state_estimate, trip_id="trace/phone"): Parameters ---------- - state_estimate : gnss_lib_py.parsers.navdata.NavData + state_estimate : gnss_lib_py.navdata.navdata.NavData Estimated receiver position in latitude and longitude as an instance of the NavData class with the following rows: ``gps_millis``, ``lat_rx*_deg``, ``lon_rx*_deg``. @@ -456,13 +457,13 @@ def prepare_kaggle_submission(state_estimate, trip_id="trace/phone"): Returns ------- - output : gnss_lib_py.parsers.navdata.NavData + output : gnss_lib_py.navdata.navdata.NavData NavData structure ready for Kaggle submission. """ state_estimate.in_rows("gps_millis") - wildcards = state_estimate.find_wildcard_indexes(["lat_rx*_deg", + wildcards = find_wildcard_indexes(state_estimate,["lat_rx*_deg", "lon_rx*_deg"],max_allow = 1) output = NavData() @@ -472,7 +473,7 @@ def prepare_kaggle_submission(state_estimate, trip_id="trace/phone"): output["LatitudeDegrees"] = state_estimate[wildcards["lat_rx*_deg"]] output["LongitudeDegrees"] = state_estimate[wildcards["lon_rx*_deg"]] - output.interpolate("UnixTimeMillis",["LatitudeDegrees", + interpolate(output,"UnixTimeMillis",["LatitudeDegrees", "LongitudeDegrees"],inplace=True) return output @@ -498,7 +499,7 @@ def solve_kaggle_dataset(folder_path, solver, verbose=False, *args, **kwargs): Returns ------- - solution : gnss_lib_py.parsers.navdata.NavData + solution : gnss_lib_py.navdata.navdata.NavData Full solution submission across all traces. Can then be saved using submission.to_csv(). @@ -531,7 +532,7 @@ def solve_kaggle_dataset(folder_path, solver, verbose=False, *args, **kwargs): trip_id) # concatenate solution to previous solutions - solution.concat(navdata=output, inplace=True) + solution = concat(solution, output) except FileNotFoundError: continue diff --git a/gnss_lib_py/parsers/nmea.py b/gnss_lib_py/parsers/nmea.py index b6cb5803..8d003ac2 100644 --- a/gnss_lib_py/parsers/nmea.py +++ b/gnss_lib_py/parsers/nmea.py @@ -13,7 +13,7 @@ import pandas as pd from pynmea2.nmea_utils import timestamp, datestamp -from gnss_lib_py.parsers.navdata import NavData +from gnss_lib_py.navdata.navdata import NavData from gnss_lib_py.utils.coordinates import geodetic_to_ecef from gnss_lib_py.utils.time_conversions import datetime_to_gps_millis diff --git a/gnss_lib_py/parsers/rinex_nav.py b/gnss_lib_py/parsers/rinex_nav.py index 571bc9a4..5c6c8393 100644 --- a/gnss_lib_py/parsers/rinex_nav.py +++ b/gnss_lib_py/parsers/rinex_nav.py @@ -22,7 +22,7 @@ import georinex as gr import gnss_lib_py.utils.constants as consts -from gnss_lib_py.parsers.navdata import NavData +from gnss_lib_py.navdata.navdata import NavData from gnss_lib_py.utils.time_conversions import datetime_to_gps_millis, gps_millis_to_tow from gnss_lib_py.utils.ephemeris_downloader import load_ephemeris, DEFAULT_EPHEM_PATH @@ -359,7 +359,7 @@ def _compute_eccentric_anomaly(gps_week, gps_tow, ephem, tol=1e-5, max_iter=10): Week of GPS calendar corresponding to time of clock. gps_tow : np.ndarray GPS time of the week at which positions are required [s]. - ephem : gnss_lib_py.parsers.navdata.NavData + ephem : gnss_lib_py.navdata.navdata.NavData NavData instance containing ephemeris parameters of satellites for which states are required. tol : float @@ -410,7 +410,7 @@ def _estimate_sv_clock_corr(gps_millis, ephem): gps_millis : int Time at which measurements are needed, measured in milliseconds since start of GPS epoch [ms]. - ephem : gnss_lib_py.parsers.navdata.NavData + ephem : gnss_lib_py.navdata.navdata.NavData Satellite ephemeris parameters for measurement SVs. Returns @@ -502,7 +502,7 @@ def get_time_cropped_rinex(gps_millis, satellites=None, Returns ------- - rinex_data : gnss_lib_py.parsers.navdata.NavData + rinex_data : gnss_lib_py.navdata.navdata.NavData ephemeris entries corresponding to timestamp Notes diff --git a/gnss_lib_py/parsers/rinex_obs.py b/gnss_lib_py/parsers/rinex_obs.py index a07cf66c..4081e9b2 100644 --- a/gnss_lib_py/parsers/rinex_obs.py +++ b/gnss_lib_py/parsers/rinex_obs.py @@ -8,10 +8,10 @@ import numpy as np import georinex as gr -from gnss_lib_py.parsers.navdata import NavData +from gnss_lib_py.navdata.navdata import NavData import gnss_lib_py.utils.constants as consts from gnss_lib_py.utils.time_conversions import datetime_to_gps_millis - +from gnss_lib_py.navdata.operations import sort, concat class RinexObs(NavData): """Class handling Rinex observation files [1]_. @@ -109,10 +109,16 @@ def __init__(self, input_path): band_navdata['signal_type'] = signal_types band_navdata['observation_code'] = observation_codes if len(self) == 0: - self.concat(band_navdata, inplace=True) + concat_navdata = concat(self, band_navdata) else: - self.concat(band_navdata, inplace=True) - self.sort('gps_millis', inplace=True) + concat_navdata = concat(self, band_navdata) + + self.array = concat_navdata.array + self.map = concat_navdata.map + self.str_map = concat_navdata.str_map + self.orig_dtypes = concat_navdata.orig_dtypes.copy() + + sort(self,'gps_millis', inplace=True) @staticmethod def _measure_type_dict(): diff --git a/gnss_lib_py/parsers/smartloc.py b/gnss_lib_py/parsers/smartloc.py index deb4fec0..ca35725d 100644 --- a/gnss_lib_py/parsers/smartloc.py +++ b/gnss_lib_py/parsers/smartloc.py @@ -7,7 +7,8 @@ import numpy as np -from gnss_lib_py.parsers.navdata import NavData +from gnss_lib_py.navdata.navdata import NavData +from gnss_lib_py.navdata.operations import loop_time from gnss_lib_py.utils.coordinates import LocalCoord, geodetic_to_ecef, wrap_0_to_2pi from gnss_lib_py.utils.time_conversions import tow_to_gps_millis @@ -133,12 +134,12 @@ def remove_nlos(smartloc_raw): Parameters ---------- - smartloc_raw : gnss_lib_py.parsers.navdata.NavData + smartloc_raw : gnss_lib_py.navdata.navdata.NavData Instance of NavData containing SmartLoc data Returns ------- - smartloc_los : gnss_lib_py.parsers.navdata.NavData + smartloc_los : gnss_lib_py.navdata.navdata.NavData Instance of NavData containing only LOS labeled measurements References @@ -159,12 +160,12 @@ def calculate_gt_ecef(smartloc_raw): Parameters ---------- - smartloc_raw : gnss_lib_py.parsers.navdata.NavData + smartloc_raw : gnss_lib_py.navdata.navdata.NavData Instance of NavData containing SmartLoc data Returns ------- - smartloc_los : gnss_lib_py.parsers.navdata.NavData + smartloc_los : gnss_lib_py.navdata.navdata.NavData Instance of NavData containing GT ECEF positions in meters. """ @@ -181,12 +182,12 @@ def calculate_gt_vel(smartloc_raw): Parameters ---------- - smartloc_raw : gnss_lib_py.parsers.navdata.NavData + smartloc_raw : gnss_lib_py.navdata.navdata.NavData Instance of NavData containing SmartLoc data Returns ------- - smartloc_los : gnss_lib_py.parsers.navdata.NavData + smartloc_los : gnss_lib_py.navdata.navdata.NavData Instance of NavData containing GT ECEF velocity and acceleration in meters per second and meters per second^2. @@ -199,7 +200,7 @@ def calculate_gt_vel(smartloc_raw): 'ax_rx_gt_mps2' : [], 'ay_rx_gt_mps2' : [], 'az_rx_gt_mps2' : []} - for _, _, measure_frame in smartloc_raw.loop_time('gps_millis', \ + for _, _, measure_frame in loop_time(smartloc_raw,'gps_millis', \ delta_t_decimals = -2): yaw = measure_frame['heading_rx_gt_rad', 0] vel_gt = measure_frame['v_rx_gt_mps', 0] diff --git a/gnss_lib_py/parsers/sp3.py b/gnss_lib_py/parsers/sp3.py index 0bec92c2..aa15faff 100644 --- a/gnss_lib_py/parsers/sp3.py +++ b/gnss_lib_py/parsers/sp3.py @@ -11,7 +11,7 @@ import numpy as np from scipy import interpolate -from gnss_lib_py.parsers.navdata import NavData +from gnss_lib_py.navdata.navdata import NavData from gnss_lib_py.utils.constants import CONSTELLATION_CHARS from gnss_lib_py.utils.time_conversions import gps_datetime_to_gps_millis @@ -109,7 +109,7 @@ def interpolate_sp3(self, navdata, window=6, verbose=False): Parameters ---------- - navdata : gnss_lib_py.parsers.navdata.NavData + navdata : gnss_lib_py.navdata.navdata.NavData Instance of the NavData class that must include rows for ``gps_millis`` and ``gnss_sv_id``. window : int diff --git a/gnss_lib_py/utils/coordinates.py b/gnss_lib_py/utils/coordinates.py index 11af1390..ee233cb1 100644 --- a/gnss_lib_py/utils/coordinates.py +++ b/gnss_lib_py/utils/coordinates.py @@ -33,7 +33,8 @@ import numpy as np import gnss_lib_py.utils.constants as consts -from gnss_lib_py.parsers.navdata import NavData +from gnss_lib_py.navdata.navdata import NavData +from gnss_lib_py.navdata.operations import loop_time, find_wildcard_indexes EPSILON = 1e-7 @@ -473,11 +474,11 @@ def add_el_az(navdata, receiver_state, inplace=False): Parameters ---------- - navdata : gnss_lib_py.parsers.navdata.NavData + navdata : gnss_lib_py.navdata.navdata.NavData Instance of the NavData class. Must include ``gps_millis`` as well as satellite ECEF positions as ``x_sv_m``, ``y_sv_m``, ``z_sv_m``, ``gnss_id`` and ``sv_id``. - receiver_state : gnss_lib_py.parsers.navdata.NavData + receiver_state : gnss_lib_py.navdata.navdata.NavData Either estimated or ground truth receiver position in ECEF frame in meters as an instance of the NavData class with the following rows: ``x_rx*_m``, ``y_rx*_m``, ``z_rx*_m``, @@ -489,7 +490,7 @@ def add_el_az(navdata, receiver_state, inplace=False): Returns ------- - data_el_az : gnss_lib_py.parsers.navdata.NavData + data_el_az : gnss_lib_py.navdata.navdata.NavData If inplace is True, adds ``el_sv_deg`` and ``az_sv_deg`` to the input navdata and returns the same object. If inplace is False, returns ``el_sv_deg`` and ``az_sv_deg`` @@ -504,12 +505,12 @@ def add_el_az(navdata, receiver_state, inplace=False): receiver_state.in_rows(["gps_millis"]) # check for receiver_state indexes - rx_idxs = receiver_state.find_wildcard_indexes(["x_rx*_m", + rx_idxs = find_wildcard_indexes(receiver_state,["x_rx*_m", "y_rx*_m", "z_rx*_m"],max_allow=1) sv_el_az = None - for timestamp, _, navdata_subset in navdata.loop_time("gps_millis"): + for timestamp, _, navdata_subset in loop_time(navdata,"gps_millis"): pos_sv_m = navdata_subset[["x_sv_m","y_sv_m","z_sv_m"]] # handle scenario with only a single SV returned as 1D array diff --git a/gnss_lib_py/utils/gnss_models.py b/gnss_lib_py/utils/gnss_models.py index b0654827..55d138f8 100644 --- a/gnss_lib_py/utils/gnss_models.py +++ b/gnss_lib_py/utils/gnss_models.py @@ -15,14 +15,14 @@ import gnss_lib_py.utils.constants as consts from gnss_lib_py.utils.coordinates import ecef_to_geodetic, ecef_to_el_az -from gnss_lib_py.parsers.navdata import NavData +from gnss_lib_py.navdata.navdata import NavData from gnss_lib_py.utils.time_conversions import gps_millis_to_tow from gnss_lib_py.utils.sv_models import find_visible_ephem, _extract_pos_vel_arr, \ find_sv_location, find_sv_states, \ find_visible_sv_posvel, _sort_ephem_measures, \ _filter_ephemeris_measurements from gnss_lib_py.utils.ephemeris_downloader import DEFAULT_EPHEM_PATH - +from gnss_lib_py.navdata.operations import loop_time, sort, concat, find_wildcard_indexes def add_measures(measurements, state_estimate, ephemeris_path = DEFAULT_EPHEM_PATH, iono_params=None, @@ -64,10 +64,10 @@ def add_measures(measurements, state_estimate, Parameters ---------- - measurements : gnss_lib_py.parsers.navdata.NavData + measurements : gnss_lib_py.navdata.navdata.NavData Received measurements for which SV states are required. Must contain `gps_millis`, `gnss_id`, and `sv_id` fields. - state_estimate : gnss_lib_py.parsers.navdata.NavData + state_estimate : gnss_lib_py.navdata.navdata.NavData Estimate for receiver states --- ECEF x, y, and z positions in meters, ECEF x, y, and z velocities in meters, clock bias in meters, and the clock drift in meters per second --- stored in a NavData instance. @@ -108,7 +108,7 @@ def add_measures(measurements, state_estimate, 'vx_sv_mps', 'vy_sv_mps', 'vz_sv_mps', 'b_sv_m'] rx_pos_rows_to_find = ['x_rx*_m', 'y_rx*_m', 'z_rx*_m'] - rx_pos_rows_idxs = state_estimate.find_wildcard_indexes( + rx_pos_rows_idxs = find_wildcard_indexes(state_estimate, rx_pos_rows_to_find, max_allow=1) rx_pos_rows = [rx_pos_rows_idxs['x_rx*_m'][0], @@ -121,7 +121,7 @@ def add_measures(measurements, state_estimate, est_measurements = NavData() # Loop through the measurement file per time step - for gps_millis, _, measure_frame in measurements.loop_time('gps_millis', + for gps_millis, _, measure_frame in loop_time(measurements,'gps_millis', delta_t_decimals=delta_t_dec): # Sort the satellites rx_ephem, sorted_sats_ind, inv_sort_order = _sort_ephem_measures(measure_frame, ephem) @@ -135,8 +135,7 @@ def add_measures(measurements, state_estimate, sv_posvel[row] = measure_frame[row] for row in info_rows: sv_posvel[row] = measure_frame[row] - # sv_posvel = sv_posvel.sort(ind=sorted_sats_ind) - sv_posvel = sv_posvel.sort(ind=sorted_sats_ind) + sort(sv_posvel,ind=sorted_sats_ind,inplace=True) except KeyError: sv_posvel = None use_posvel = True @@ -152,7 +151,7 @@ def add_measures(measurements, state_estimate, vel_clk_rows = rx_vel_rows_to_find + rx_clk_rows_to_find for row in vel_clk_rows: try: - row_idx = state_estimate.find_wildcard_indexes(row,max_allow=1) + row_idx = find_wildcard_indexes(state_estimate,row,max_allow=1) state[row_idx[row][0]] = state_estimate[row_idx[row][0], state_col] except KeyError: warnings.warn("Assuming 0 "+ row + " for Rx", RuntimeWarning) @@ -164,7 +163,7 @@ def add_measures(measurements, state_estimate, ephem=rx_ephem, sv_posvel=sv_posvel) # Reverse the sorting to match the input measurements - est_meas = est_meas.sort(ind=inv_sort_order) + sort(est_meas, ind=inv_sort_order, inplace=True) else: est_meas = None if corrections: @@ -180,7 +179,7 @@ def add_measures(measurements, state_estimate, # Add required values to new rows if sv_posvel is not None: # Reverse the sorting to match the input measurements - sv_posvel = sv_posvel.sort(ind=sorted_sats_ind) + sort(sv_posvel,ind=sorted_sats_ind,inplace=True) est_frame = NavData() if pseudorange: est_frame['est_pr_m'] = est_meas['est_pr_m'] @@ -196,8 +195,8 @@ def add_measures(measurements, state_estimate, if len(est_measurements)==0: est_measurements = est_frame else: - est_measurements.concat(est_frame, inplace=True) - est_measurements = measurements.concat(est_measurements, axis=0, inplace=False) + est_measurements = concat(est_measurements,est_frame) + est_measurements = concat(measurements, est_measurements, axis=0) return est_measurements @@ -215,7 +214,7 @@ def simulate_measures(gps_millis, state, noise_dict=None, ephem=None, gps_millis : int Time at which measurements are needed, measured in milliseconds since start of GPS epoch [ms]. - state : gnss_lib_py.parsers.navdata.NavData + state : gnss_lib_py.navdata.navdata.NavData NavData instance containing state i.e. 3D position, 3D velocity, receiver clock bias and receiver clock drift rate at which measurements have to be simulated. @@ -225,11 +224,11 @@ def simulate_measures(gps_millis, state, noise_dict=None, ephem=None, ('doppler_sigma') standard deviation values in [m] and [m/s]. If None, uses default values `prange_sigma=6` and `doppler_sigma=1`. - ephem : gnss_lib_py.parsers.navdata.NavData + ephem : gnss_lib_py.navdata.navdata.NavData NavData instance containing satellite ephemeris parameters for a particular time of ephemeris. Use None if not available and using SV positions directly instead. - sv_posvel : gnss_lib_py.parsers.navdata.NavData + sv_posvel : gnss_lib_py.navdata.navdata.NavData Precomputed positions of satellites, set to None if not available. rng : np.random.Generator A random number generator for sampling random noise values. @@ -239,11 +238,11 @@ def simulate_measures(gps_millis, state, noise_dict=None, ephem=None, Returns ------- - measurements : gnss_lib_py.parsers.navdata.NavData + measurements : gnss_lib_py.navdata.navdata.NavData Pseudorange (label: `prange`) and doppler (label: `doppler`) measurements with satellite SV. Gaussian noise is added to expected measurements to simulate stochasticity. - sv_posvel : gnss_lib_py.parsers.navdata.NavData + sv_posvel : gnss_lib_py.navdata.navdata.NavData Satellite positions and velocities (same as input if provided). """ @@ -293,25 +292,25 @@ def expected_measures(gps_millis, state, ephem=None, sv_posvel=None): gps_millis : int Time at which measurements are needed, measured in milliseconds since start of GPS epoch [ms]. - state : gnss_lib_py.parsers.navdata.NavData + state : gnss_lib_py.navdata.navdata.NavData NavData instance containing state i.e. 3D position, 3D velocity, receiver clock bias and receiver clock drift rate at which measurements have to be simulated. Must be a single state (single column) - ephem : gnss_lib_py.parsers.navdata.NavData + ephem : gnss_lib_py.navdata.navdata.NavData NavData instance containing satellite ephemeris parameters for a particular time of ephemeris, use None if not available and using position directly. - sv_posvel : gnss_lib_py.parsers.navdata.NavData + sv_posvel : gnss_lib_py.navdata.navdata.NavData Precomputed positions of satellites (if available). Returns ------- - measurements : gnss_lib_py.parsers.navdata.NavData + measurements : gnss_lib_py.navdata.navdata.NavData Pseudorange (label: `prange`) and doppler (label: `doppler`) measurements with satellite SV. Also contains SVs and gps_tow at which the measurements are simulated. - sv_posvel : gnss_lib_py.parsers.navdata.NavData + sv_posvel : gnss_lib_py.navdata.navdata.NavData Satellite positions and velocities (same as input if provided). """ # and satellite positions in sv_posvel @@ -343,7 +342,7 @@ def _extract_state_variables(state): Parameters ---------- - state : gnss_lib_py.parsers.navdata.NavData + state : gnss_lib_py.navdata.navdata.NavData NavData containing state values i.e. 3D position, 3D velocity, receiver clock bias and receiver clock drift rate at which measurements will be simulated. @@ -362,7 +361,7 @@ def _extract_state_variables(state): """ assert len(state)==1, "Only single state accepted for GNSS simulation" - rx_idxs = state.find_wildcard_indexes(['x_rx*_m', + rx_idxs = find_wildcard_indexes(state,['x_rx*_m', 'y_rx*_m', 'z_rx*_m', 'vx_rx*_mps', @@ -396,14 +395,14 @@ def calculate_pseudorange_corr(gps_millis, state=None, ephem=None, sv_posvel=Non gps_millis : int Time at which measurements are needed, measured in milliseconds since start of GPS epoch [ms]. - state : gnss_lib_py.parsers.navdata.NavData + state : gnss_lib_py.navdata.navdata.NavData NavData containing state values i.e. 3D position, 3D velocity, receiver clock bias and receiver clock drift rate at which measurements will be simulated. - ephem : gnss_lib_py.parsers.navdata.NavData + ephem : gnss_lib_py.navdata.navdata.NavData Satellite ephemeris parameters for measurement SVs, use None if using satellite positions instead. - sv_posvel : gnss_lib_py.parsers.navdata.NavData + sv_posvel : gnss_lib_py.navdata.navdata.NavData Precomputed positions of satellites corresponding to the input `gps_millis`, set to None if not available. iono_params : np.ndarray @@ -469,9 +468,9 @@ def _calculate_tropo_delay(gps_millis, rx_ecef, ephem=None, sv_posvel=None): since start of GPS epoch [ms]. rx_ecef : np.ndarray 3x1 array of ECEF rx_pos position [m]. - ephem : gnss_lib_py.parsers.navdata.NavData + ephem : gnss_lib_py.navdata.navdata.NavData Satellite ephemeris parameters for measurement SVs. - sv_posvel : gnss_lib_py.parsers.navdata.NavData + sv_posvel : gnss_lib_py.navdata.navdata.NavData Precomputed positions of satellites, set to None if not available. Returns @@ -535,10 +534,10 @@ def _calculate_iono_delay(gps_millis, iono_params, rx_ecef, ephem=None, rx_ecef : np.ndarray 3x1 receiver position in ECEF frame of reference [m], use None if not available. - ephem : gnss_lib_py.parsers.navdata.NavData + ephem : gnss_lib_py.navdata.navdata.NavData Satellite ephemeris parameters for measurement SVs, use None if using satellite positions instead. - sv_posvel : gnss_lib_py.parsers.navdata.NavData + sv_posvel : gnss_lib_py.navdata.navdata.NavData Precomputed positions of satellites corresponding to the input `gps_millis`, set to None if not available. constellation : string diff --git a/gnss_lib_py/utils/sv_models.py b/gnss_lib_py/utils/sv_models.py index 569967e6..2627c2de 100644 --- a/gnss_lib_py/utils/sv_models.py +++ b/gnss_lib_py/utils/sv_models.py @@ -12,7 +12,7 @@ from gnss_lib_py.parsers.sp3 import Sp3 from gnss_lib_py.parsers.clk import Clk -from gnss_lib_py.parsers.navdata import NavData +from gnss_lib_py.navdata.navdata import NavData from gnss_lib_py.parsers.rinex_nav import get_time_cropped_rinex, RinexNav from gnss_lib_py.parsers.rinex_nav import _compute_eccentric_anomaly from gnss_lib_py.parsers.rinex_nav import _estimate_sv_clock_corr @@ -20,7 +20,7 @@ from gnss_lib_py.utils.coordinates import ecef_to_el_az from gnss_lib_py.utils.time_conversions import gps_millis_to_tow from gnss_lib_py.utils.ephemeris_downloader import DEFAULT_EPHEM_PATH, load_ephemeris - +from gnss_lib_py.navdata.operations import loop_time, sort, concat, find_wildcard_indexes def add_sv_states(navdata, source = 'precise', file_paths = None, download_directory = DEFAULT_EPHEM_PATH, @@ -31,7 +31,7 @@ def add_sv_states(navdata, source = 'precise', file_paths = None, Parameters ---------- - navdata : gnss_lib_py.parsers.navdata.NavData + navdata : gnss_lib_py.navdata.navdata.NavData Instance of the NavData class that must include rows for ``gps_millis``, ``gnss_id``, ``sv_id``, and ``raw_pr_m``. source : string @@ -46,7 +46,7 @@ def add_sv_states(navdata, source = 'precise', file_paths = None, Returns ------- - navdata_w_sv_states : gnss_lib_py.parsers.navdata.NavData + navdata_w_sv_states : gnss_lib_py.navdata.navdata.NavData Updated NavData class with satellite information computed. """ @@ -67,7 +67,7 @@ def add_sv_states_precise(navdata, file_paths = None, Parameters ---------- - navdata : gnss_lib_py.parsers.navdata.NavData + navdata : gnss_lib_py.navdata.navdata.NavData Instance of the NavData class that must include rows for ``gps_millis``, ``gnss_id``, ``sv_id``, and ``raw_pr_m``. file_paths : list, string or path-like @@ -79,7 +79,7 @@ def add_sv_states_precise(navdata, file_paths = None, Returns ------- - navdata_w_sv_states : gnss_lib_py.parsers.navdata.NavData + navdata_w_sv_states : gnss_lib_py.navdata.navdata.NavData Updated NavData class with satellite information computed. """ @@ -130,7 +130,7 @@ def add_sv_states_rinex(measurements, ephemeris_path= DEFAULT_EPHEM_PATH, Parameters ---------- - measurements : gnss_lib_py.parsers.navdata.NavData + measurements : gnss_lib_py.navdata.navdata.NavData Recorded measurements with time of recpetion, GNSS ID and SV ID, corresponding to which SV states are calculated ephemeris_path : string or path-like @@ -146,16 +146,15 @@ def add_sv_states_rinex(measurements, ephemeris_path= DEFAULT_EPHEM_PATH, Returns ------- - sv_states_all_time : gnss_lib_py.parsers.navdata.NavData + sv_states_all_time : gnss_lib_py.navdata.navdata.NavData Input measurements with rows containing SV states appended. """ measurements_subset, ephem, _ = \ _filter_ephemeris_measurements(measurements, constellations, ephemeris_path) sv_states_all_time = NavData() # Loop through the measurement file per time step - for _, _, measure_frame in measurements_subset.loop_time('gps_millis', \ + for _, _, measure_frame in loop_time(measurements_subset,'gps_millis', \ delta_t_decimals=delta_t_dec): - # measure_frame = measure_frame.sort('sv_id', order="descending") # Sort the satellites rx_ephem, _, inv_sort_order = _sort_ephem_measures(measure_frame, ephem) if rx_ephem.shape[1] != measure_frame.shape[1]: #pragma: no cover @@ -163,7 +162,7 @@ def add_sv_states_rinex(measurements, ephemeris_path= DEFAULT_EPHEM_PATH, try: # The following statement raises a KeyError if rows don't exist rx_rows_to_find = ['x_rx*_m', 'y_rx*_m', 'z_rx*_m'] - rx_idxs = measure_frame.find_wildcard_indexes( + rx_idxs = find_wildcard_indexes(measure_frame, rx_rows_to_find, max_allow=1) rx_ecef = measure_frame[[rx_idxs["x_rx*_m"][0], @@ -174,7 +173,7 @@ def add_sv_states_rinex(measurements, ephemeris_path= DEFAULT_EPHEM_PATH, except KeyError: sv_states = find_sv_states(measure_frame['gps_millis'], rx_ephem) # Reverse the sorting - sv_states = sv_states.sort(ind=inv_sort_order) + sort(sv_states,ind=inv_sort_order,inplace=True) # Add them to new rows for row in sv_states.rows: if row not in ('gps_millis','gnss_id','sv_id'): @@ -182,7 +181,7 @@ def add_sv_states_rinex(measurements, ephemeris_path= DEFAULT_EPHEM_PATH, if len(sv_states_all_time)==0: sv_states_all_time = measure_frame else: - sv_states_all_time.concat(measure_frame, inplace=True) + sv_states_all_time = concat(sv_states_all_time, measure_frame) return sv_states_all_time @@ -203,7 +202,7 @@ def add_visible_svs_for_trajectory(rx_states, Parameters ---------- - rx_states : gnss_lib_py.parsers.navdata.NavData + rx_states : gnss_lib_py.navdata.navdata.NavData NavData containing position states of receiver at which SV states are needed. ephemeris_path : string @@ -218,7 +217,7 @@ def add_visible_svs_for_trajectory(rx_states, Returns ------- - sv_posvel_trajectory : gnss_lib_py.parsers.navdata.Navdata + sv_posvel_trajectory : gnss_lib_py.navdata.navdata.Navdata NavData instance containing @@ -242,7 +241,7 @@ def add_visible_svs_for_trajectory(rx_states, # Find rows that correspond to receiver positions rx_rows_to_find = ['x_rx*_m', 'y_rx*_m', 'z_rx*_m'] - rx_idxs = rx_states.find_wildcard_indexes(rx_rows_to_find, + rx_idxs = find_wildcard_indexes(rx_states,rx_rows_to_find, max_allow=1) # Loop through all times and positions, estimated SV states and adding @@ -259,7 +258,7 @@ def add_visible_svs_for_trajectory(rx_states, if len(sv_posvel_trajectory) == 0: sv_posvel_trajectory = sv_posvel else: - sv_posvel_trajectory.concat(sv_posvel, inplace=True) + sv_posvel_trajectory = concat(sv_posvel_trajectory,sv_posvel) return sv_posvel_trajectory @@ -324,13 +323,13 @@ def find_sv_states(gps_millis, ephem): gps_millis : int Time at which measurements are needed, measured in milliseconds since start of GPS epoch [ms]. - ephem : gnss_lib_py.parsers.navdata.NavData + ephem : gnss_lib_py.navdata.navdata.NavData NavData instance containing ephemeris parameters of satellites for which states are required. Returns ------- - sv_posvel : gnss_lib_py.parsers.navdata.NavData + sv_posvel : gnss_lib_py.navdata.navdata.NavData NavData containing satellite positions, velocities, corresponding time with GNSS ID and SV number. @@ -493,7 +492,7 @@ def find_visible_ephem(gps_millis, rx_ecef, ephem, el_mask=5.): since start of GPS epoch [ms]. rx_ecef : np.ndarray 3x1 row rx_pos ECEF position vector [m]. - ephem : gnss_lib_py.parsers.navdata.NavData + ephem : gnss_lib_py.navdata.navdata.NavData NavData instance containing satellite ephemeris parameters including gps_week and gps_tow for the ephemeris el_mask : float @@ -501,7 +500,7 @@ def find_visible_ephem(gps_millis, rx_ecef, ephem, el_mask=5.): Returns ------- - eph : gnss_lib_py.parsers.navdata.NavData + eph : gnss_lib_py.navdata.navdata.NavData Ephemeris parameters of visible satellites """ @@ -523,7 +522,7 @@ def find_visible_sv_posvel(rx_ecef, sv_posvel, el_mask=5.): ---------- rx_ecef : np.ndarray 3x1 row rx_pos ECEF position vector [m]. - sv_posvel : gnss_lib_py.parsers.navdata.NavData + sv_posvel : gnss_lib_py.navdata.navdata.NavData NavData instance containing satellite positions and velocities at the time at which visible satellites are needed. el_mask : float @@ -531,7 +530,7 @@ def find_visible_sv_posvel(rx_ecef, sv_posvel, el_mask=5.): Returns ------- - vis_posvel : gnss_lib_py.parsers.navdata.NavData + vis_posvel : gnss_lib_py.navdata.navdata.NavData SV states of satellites that are visible """ @@ -555,17 +554,17 @@ def find_sv_location(gps_millis, rx_ecef, ephem=None, sv_posvel=None, get_iono=F since start of GPS epoch [ms]. rx_ecef : np.ndarray 3x1 Receiver 3D ECEF position [m]. - ephem : gnss_lib_py.parsers.navdata.NavData + ephem : gnss_lib_py.navdata.navdata.NavData DataFrame containing all satellite ephemeris parameters ephemeris, as indicated in :code:`find_sv_states`. Use None if using precomputed satellite positions and velocities instead. - sv_posvel : gnss_lib_py.parsers.navdata.NavData + sv_posvel : gnss_lib_py.navdata.navdata.NavData Precomputed positions of satellites, use None if using broadcast ephemeris parameters instead. Returns ------- - sv_posvel : gnss_lib_py.parsers.navdata.NavData + sv_posvel : gnss_lib_py.navdata.navdata.NavData Satellite position and velocities (same if input). del_pos : np.ndarray Difference between satellite positions and receiver position. @@ -600,7 +599,7 @@ def _filter_ephemeris_measurements(measurements, constellations, Parameters ---------- - measurements : gnss_lib_py.parsers.navdata.NavData + measurements : gnss_lib_py.navdata.navdata.NavData Received measurements, that are filtered based on constellations. constellations : list List of strings indicating constellations required in output. @@ -609,9 +608,9 @@ def _filter_ephemeris_measurements(measurements, constellations, Returns ------- - measurements_subset : gnss_lib_py.parsers.navdata.NavData + measurements_subset : gnss_lib_py.navdata.navdata.NavData Measurements containing desired constellations - ephem : gnss_lib_py.parsers.navdata.NavData + ephem : gnss_lib_py.navdata.navdata.NavData Ephemeris parameters for received SVs and constellations """ measurements.in_rows(['gnss_id', 'sv_id', 'gps_millis']) @@ -660,7 +659,7 @@ def _combine_gnss_sv_ids(measurement_frame): Parameters ---------- - measurement_frame : gnss_lib_py.parsers.navdata.NavData + measurement_frame : gnss_lib_py.navdata.navdata.NavData NavData instance containing measurements including `gnss_id` and `sv_id`. @@ -688,15 +687,15 @@ def _sort_ephem_measures(measure_frame, ephem): Parameters ---------- - measure_frame : gnss_lib_py.parsers.navdata.NavData + measure_frame : gnss_lib_py.navdata.navdata.NavData Measurements received for a single time instance, to be sorted. - ephem : gnss_lib_py.parsers.navdata.NavData + ephem : gnss_lib_py.navdata.navdata.NavData Ephemeris parameters for all satellites for the closest time before the measurements were received. Returns ------- - rx_ephem : gnss_lib_py.parsers.navdata.NavData + rx_ephem : gnss_lib_py.navdata.navdata.NavData Ephemeris parameters for satellites from which measurements were received. Sorted by `gnss_sv_id`. sorted_sats_ind : np.ndarray @@ -719,7 +718,7 @@ def _extract_pos_vel_arr(sv_posvel): Parameters ---------- - sv_posvel : gnss_lib_py.parsers.navdata.NavData + sv_posvel : gnss_lib_py.navdata.navdata.NavData NavData containing satellite position and velocity states. Returns @@ -739,7 +738,7 @@ def _find_delxyz_range(sv_posvel, rx_ecef): Parameters ---------- - sv_posvel : gnss_lib_py.parsers.navdata.NavData + sv_posvel : gnss_lib_py.navdata.navdata.NavData Satellite position and velocities. rx_ecef : np.ndarray 3x1 Receiver 3D ECEF position [m]. @@ -769,7 +768,7 @@ def single_gnss_from_precise_eph(navdata, sp3_parsed_file, Parameters ---------- - navdata : gnss_lib_py.parsers.navdata.NavData + navdata : gnss_lib_py.navdata.navdata.NavData Instance of the NavData class that must include rows for ``gps_millis`` and ``gnss_sv_id``. sp3_parsed_file : gnss_lib_py.parsers.sp3.Sp3 @@ -786,7 +785,7 @@ def single_gnss_from_precise_eph(navdata, sp3_parsed_file, Returns ------- - navdata : gnss_lib_py.parsers.navdata.NavData + navdata : gnss_lib_py.navdata.navdata.NavData Updated NavData class with satellite information computed using precise ephemerides from .sp3 and .clk files """ diff --git a/gnss_lib_py/utils/visualizations.py b/gnss_lib_py/utils/visualizations.py deleted file mode 100644 index af0993c0..00000000 --- a/gnss_lib_py/utils/visualizations.py +++ /dev/null @@ -1,1039 +0,0 @@ -"""Visualization functions for GNSS data. - -""" - -__authors__ = "D. Knowles" -__date__ = "27 Jan 2022" - -import os -import pathlib -from math import floor -from multiprocessing import Process - -import numpy as np -import pandas as pd -from cycler import cycler -import plotly.express as px -import plotly.graph_objects as go -import matplotlib as mpl -import matplotlib.pyplot as plt -from matplotlib.collections import LineCollection -from matplotlib.colors import to_rgb, ListedColormap - -import gnss_lib_py.utils.file_operations as fo -from gnss_lib_py.parsers.navdata import NavData -from gnss_lib_py.utils.coordinates import add_el_az - -STANFORD_COLORS = [ - "#8C1515", # cardinal red - "#6FC3FF", # light digital blue - "#006F54", # dark digital green - "#620059", # plum - "#E98300", # poppy - "#FEDD5C", # illuminating - "#E04F39", # spirited - "#4298B5", # sky - "#8F993E", # olive - "#651C32", # brick - "#B1040E", # digital red - "#016895", # dark sky - "#279989", # palo verde - # "#67AFD2", # light sky - # "#008566", # digital green - ] -MARKERS = ["o","*","P","v","s","^","p","<","h",">","H","X","D"] - -GNSS_ORDER = ["gps","glonass","galileo","beidou","qzss","irnss","sbas", - "unknown"] - -mpl.rcParams['axes.prop_cycle'] = (cycler(color=STANFORD_COLORS) \ - + cycler(marker=MARKERS)) - -def plot_metric(navdata, *args, groupby=None, avg_y=False, fig=None, - title=None, save=False, prefix="", fname=None, - markeredgecolor="k", markeredgewidth=0.2, **kwargs): - """Plot specific metric from a row of the NavData class. - - Parameters - ---------- - navdata : gnss_lib_py.parsers.navdata.NavData - Instance of the NavData class - *args : tuple - Tuple of row names that are to be plotted. If one is given, that - value is plotted on the y-axis. If two values are given, the - first is plotted on the x-axis and the second on the y-axis. - groupby : string - Row name by which to group and label plots. - avg_y : bool - Whether or not to average across the y values for each x - timestep when doing groupby - fig : matplotlib.pyplot.Figure - Previous figure on which to add current plotting. Default of - None plots on a new figure. - title : string - Title for the plot. - save : bool - Saves figure if true to file specified by fname or defaults - to the Results folder otherwise. - prefix : string - File prefix to add to filename. - fname : string or path-like - Path to save figure. If not None, fname is passed directly - to matplotlib's savefig fname parameter and prefix will be - overwritten. - markeredgecolor : color - Marker edge color. - markeredgewidth : float - Marker edge width. - - Returns - ------- - fig : matplotlib.pyplot.Figure - Figure of plotted metrics. - - """ - - if not isinstance(navdata,NavData): - raise TypeError("first arg to plot_metrics must be a "\ - + "NavData object.") - - x_metric, y_metric = _parse_metric_args(navdata, *args) - - if groupby is not None: - navdata.in_rows(groupby) - if not isinstance(prefix, str): - raise TypeError("Prefix must be a string.") - - # create a new figure if none provided - fig, axes = _get_new_fig(fig) - - if x_metric is None: - x_data = None - xlabel = "INDEX" - if title is None: - title = _get_label({y_metric:y_metric}) - else: - if title is None: - title = _get_label({y_metric:y_metric}) + " vs. " \ - + _get_label({x_metric:x_metric}) - xlabel = _get_label({x_metric:x_metric}) - - if groupby is not None: - all_groups = np.unique(navdata[groupby]) - if groupby == "gnss_id": - all_groups = _sort_gnss_ids(all_groups) - for group in all_groups: - subset = navdata.where(groupby,group) - y_data = np.atleast_1d(subset[y_metric]) - if x_metric is None: - x_data = range(len(y_data)) - else: - x_data = np.atleast_1d(subset[x_metric]) - if avg_y: - # average y values for each x - x_unique = sorted(np.unique(x_data)) - y_avg = [] - for x_val in x_unique: - x_idxs = np.argwhere(x_data==x_val) - y_avg.append(np.mean(y_data[x_idxs])) - x_data = x_unique - y_data = y_avg - # change name - group = str(group) + "_avg" - axes.plot(x_data, y_data, - label=_get_label({groupby:group}), - markeredgecolor = markeredgecolor, - markeredgewidth = markeredgewidth, - **kwargs) - else: - y_data = np.atleast_1d(navdata[y_metric]) - if x_metric is None: - x_data = range(len(y_data)) - else: - x_data = np.atleast_1d(navdata[x_metric]) - axes.plot(x_data, y_data, - markeredgecolor = markeredgecolor, - markeredgewidth = markeredgewidth, - **kwargs) - - handles, _ = axes.get_legend_handles_labels() - if len(handles) > 0: - plt.legend(loc="upper left", bbox_to_anchor=(1.05, 1), - title=_get_label({groupby:groupby})) - - plt.title(title) - plt.xlabel(xlabel) - plt.ylabel(_get_label({y_metric:y_metric})) - fig.set_layout_engine(layout="tight") - - if save: # pragma: no cover - _save_figure(fig, title, prefix, fname) - return fig - -def plot_metric_by_constellation(navdata, *args, save=False, prefix="", - fname=None, **kwargs): - """Plot specific metric from a row of the NavData class. - - Breaks up metrics by constellation names in "gnss_id" and - additionally "signal_type" if the "signal_type" row exists. - - Plots will include a legend with satellite ID if the "sv_id" row - is present in navdata. - - Parameters - ---------- - navdata : gnss_lib_py.parsers.navdata.NavData - Instance of the NavData class. Must include ``gnss_id`` row and - optionally ``signal_type`` and ``sv_id`` for increased - labelling. - *args : tuple - Tuple of row names that are to be plotted. If one is given, that - value is plotted on the y-axis. If two values are given, the - first is plotted on the x-axis and the second on the y-axis. - save : bool - Saves figure if true to file specified by ``fname`` or defaults - to the Results folder otherwise. - prefix : string - File prefix to add to filename. - fname : string or path-like - Path to save figure to. If not None, ``fname`` is passed - directly to matplotlib's savefig fname parameter and prefix will - be overwritten. - - Returns - ------- - fig : list of matplotlib.pyplot.Figure objects - List of figures of plotted metrics. - - """ - - if not isinstance(navdata,NavData): - raise TypeError("first arg to plot_metric_by_constellation "\ - + "must be a NavData object.") - - x_metric, y_metric = _parse_metric_args(navdata, *args) - - if not isinstance(prefix, str): - raise TypeError("Prefix must be a string.") - if "gnss_id" not in navdata.rows: - raise KeyError("gnss_id row missing," \ - + " try using" \ - + " the plot_metric() function call instead") - - figs = [] - for constellation in _sort_gnss_ids(np.unique(navdata["gnss_id"])): - const_subset = navdata.where("gnss_id",constellation) - - if "signal_type" in const_subset.rows: - for signal in np.unique(const_subset["signal_type"]): - title = _get_label({"gnss_id":constellation,"signal_type":signal}) - signal_subset = const_subset.where("signal_type",signal) - if "sv_id" in signal_subset.rows: - # group by sv_id - fig = plot_metric(signal_subset,x_metric,y_metric, - groupby="sv_id", title=title, - save=save, prefix=prefix, - fname=fname, **kwargs) - figs.append(fig) - else: - fig = plot_metric(signal_subset,x_metric,y_metric, - title=title, save=save, - prefix=prefix, fname=fname, - **kwargs) - figs.append(fig) - else: - title = _get_label({"gnss_id":constellation}) - if "sv_id" in const_subset.rows: - # group by sv_id - fig = plot_metric(const_subset,x_metric,y_metric, - groupby="sv_id", title=title, - save=save, prefix=prefix, fname=fname, - **kwargs) - figs.append(fig) - else: - fig = plot_metric(const_subset,x_metric,y_metric, - title=title, save=save, prefix=prefix, - fname=fname, **kwargs) - figs.append(fig) - - return figs - -def plot_skyplot(navdata, receiver_state, - save=False, prefix="", fname=None, - add_sv_id_label=True, step = "auto", trim_options=None): - """Skyplot of satellite positions relative to receiver. - - First adds ``el_sv_deg`` and ``az_sv_deg`` rows to navdata if they - do not yet exist. - - Breaks up satellites by constellation names in ``gnss_id`` and the - ``sv_id`` if the row is present in navdata. - - Parameters - ---------- - navdata : gnss_lib_py.parsers.navdata.NavData - Instance of the NavData class. Must include ``gps_millis`` as - well as satellite ECEF positions as ``x_sv_m``, ``y_sv_m``, - ``z_sv_m``, ``gnss_id`` and ``sv_id``. - receiver_state : gnss_lib_py.parsers.navdata.NavData - Either estimated or ground truth receiver position in ECEF frame - in meters as an instance of the NavData class with the - following rows: ``x_rx*_m``, ``y_rx*_m``, ``z_rx*_m``, ``gps_millis``. - save : bool - Saves figure if true to file specified by ``fname`` or defaults - to the Results folder otherwise. - prefix : string - File prefix to add to filename. - fname : string or path-like - Path to save figure to. If not None, ``fname`` is passed - directly to matplotlib's savefig fname parameter and prefix will - be overwritten. - add_sv_id_label : bool - If the ``sv_id`` row is available, will add SV ID label near the - satellite trail. - step : int or "auto" - Skyplot plotting is sped up by only plotting a portion of the - satellite trajectories. If default is set to "auto" then it will - plot a maximum of 50 points across each satellite trajectory. If - the step variable is set to a positive integer ``n`` then only - every nth point will be used in the trajectory. Setting the - steps variable to 1 will plot every satellite trajectory point - and may be slow to plot. - trim_options : None or dict - The ``trim_options`` variables gives control for line segments - being trimmed between satellite points. For example, if 24 hours - of a satellite is plotted, often the satellite will come in and - out of view and the segment between when it was lost from view - and when the satellite comes back in view should be trimmed. - If trim_options is set to the default of None, then the default - is set of trimming according to az_and_el and gps_millis. The - current options for the trim_options dictionary are listed here. - {"az" : az_limit} means that if at two timesteps the azimuth - difference in degrees is greater than az_limit, then the line - segment will be trimmed. - {"az_and_el" : (az_limit,el_limit)} means that if at two - timesteps the azimuth difference in degrees is greater than - az_limit as well as the average of the elevation angle across - the two timesteps is less than el_limit in degrees, then the - line segment will be trimmed. The el_limit is because satellites - near 90 degrees elevation can traverse large amounts of degrees - in azimuth in a valid trajectory but at low elevations should - not have such large azimuth changes quickly. - {"gps_millis",gps_millis_limit} means that line segments will be - trimmed if the milliseconds between the two points is larger - than the gps_millis_limit. This option only works if the - gps_millis row is included in the ``navdata`` variable input. - Default options for the trim options are :code:`"az_and_el" : (15.,30.)` - and :code:`"gps_millis" : 3.6E6`. - - Returns - ------- - fig : matplotlib.pyplot.figure - Figure object of skyplot. - - """ - - if not isinstance(navdata,NavData): - raise TypeError("first arg to plot_skyplot "\ - + "must be a NavData object.") - - if not isinstance(prefix, str): - raise TypeError("Prefix must be a string.") - - # add elevation and azimuth data. - add_el_az(navdata, receiver_state, inplace=True) - - # create new figure - fig = plt.figure(figsize=(6,4.5)) - axes = fig.add_subplot(111, projection='polar') - - navdata = navdata.copy() - navdata["az_sv_rad"] = np.radians(navdata["az_sv_deg"]) - # remove SVs below horizon - navdata = navdata.where("el_sv_deg",0,"geq") - # remove np.nan values caused by potentially faulty data - navdata = navdata.where("az_sv_rad",np.nan,"neq") - navdata = navdata.where("el_sv_deg",np.nan,"neq") - - for c_idx, constellation in enumerate(_sort_gnss_ids(np.unique(navdata["gnss_id"]))): - const_subset = navdata.where("gnss_id",constellation) - color = "C" + str(c_idx % len(STANFORD_COLORS)) - cmap = _new_cmap(to_rgb(color)) - marker = MARKERS[c_idx % len(MARKERS)] - const_label_created = False - - # iterate through each satellite - for sv_name in np.unique(const_subset["sv_id"]): - sv_subset = const_subset.where("sv_id",sv_name) - - # only plot ~ 50 points for each sat to decrease time - # it takes to plot these line collections if step == "auto" - if isinstance(step,str) and step == "auto": - step = max(1,int(len(sv_subset)/50.)) - elif isinstance(step, int): - step = max(1,step) - else: - raise TypeError("step varaible must be 'auto' or int") - points = np.array([np.atleast_1d(sv_subset["az_sv_rad"])[::step], - np.atleast_1d(sv_subset["el_sv_deg"])[::step]]).T - points = np.reshape(points,(-1, 1, 2)) - segments = np.concatenate([points[:-1], points[1:]], axis=1) - norm = plt.Normalize(0,len(segments)) - - if trim_options is None: - trim_options = { - "az_and_el" : (15.,30.), - "gps_millis" : 3.6E6, - } - plotted_idxs = np.array([True] * len(segments)) - - if "az" in trim_options and len(segments) > 2: - # ignore segments that cross more than az_limit degrees - # in azimuth between timesteps - az_limit = np.radians(trim_options["az"]) - az_idxs = ~((np.abs(np.diff(np.unwrap(segments[:,:,0]))) >= az_limit)[:,0]) - plotted_idxs = np.bitwise_and(plotted_idxs, az_idxs) - if "az_and_el" in trim_options and len(segments) > 2: - # ignore segments that cross more than az_limit degrees - # in azimuth between timesteps and are at an elevation - # less than el_limit degrees. - # These satellites are assumed to be the satellites - # coming in and out of view in a later part of the orbit - az_limit = np.radians(trim_options["az_and_el"][0]) - el_limit = trim_options["az_and_el"][1] - az_and_el_idxs = ~(((np.abs(np.diff(np.unwrap(segments[:,:,0]))) >= az_limit)[:,0]) \ - & (np.mean(segments[:,:,1],axis=1) <= el_limit)) - plotted_idxs = np.bitwise_and(plotted_idxs, az_and_el_idxs) - if "gps_millis" in trim_options and "gps_millis" in sv_subset.rows \ - and len(segments) > 2: - # ignore segments if there is more than gps_millis_limit - # milliseconds between the time segments - gps_millis_limit = trim_options["gps_millis"] - - all_times = np.atleast_2d(sv_subset["gps_millis"][::step]).T - point_times = np.concatenate([all_times[:-1],all_times[1:]], - axis=1) - gps_millis_idxs = (np.abs(np.diff(point_times)) <= gps_millis_limit)[:,0] - plotted_idxs = np.bitwise_and(plotted_idxs, gps_millis_idxs) - - segments = segments[list(plotted_idxs)] - - local_coord = LineCollection(segments, cmap=cmap, - norm=norm, linewidths=(4,), - array = range(len(segments))) - axes.add_collection(local_coord) - if not const_label_created: - # plot with label - axes.plot(np.atleast_1d(sv_subset["az_sv_rad"])[-1], - np.atleast_1d(sv_subset["el_sv_deg"])[-1], - c=color, marker=marker, markersize=8, - label=_get_label({"gnss_id":constellation})) - const_label_created = True - else: - # plot without label - axes.plot(np.atleast_1d(sv_subset["az_sv_rad"])[-1], - np.atleast_1d(sv_subset["el_sv_deg"])[-1], - c=color, marker=marker, markersize=8) - if add_sv_id_label: - # offsets move label to the right of marker - az_offset = 3.*np.radians(np.cos(np.atleast_1d(sv_subset["az_sv_rad"])[-1])) - el_offset = -3.*np.sin(np.atleast_1d(sv_subset["az_sv_rad"])[-1]) - axes.text(np.atleast_1d(sv_subset["az_sv_rad"])[-1] \ - + az_offset, - np.atleast_1d(sv_subset["el_sv_deg"])[-1] \ - + el_offset, - str(int(sv_name)), - ) - - # updated axes for skyplot graph specifics - axes.set_theta_zero_location('N') - axes.set_theta_direction(-1) - axes.set_yticks(range(0, 60+10, 30)) # Define the yticks - axes.set_yticklabels(['',r'$30\degree$',r'$60\degree$']) - axes.set_ylim(90,0) - - handles, _ = axes.get_legend_handles_labels() - if len(handles) > 0: - axes.legend(loc="upper left", bbox_to_anchor=(1.05, 1), - title=_get_label({"constellation":"constellation"})) - - fig.set_layout_engine(layout='tight') - - if save: # pragma: no cover - _save_figure(fig, "skyplot", prefix=prefix, fnames=fname) - return fig - -def plot_map(*args, sections=0, save=False, prefix="", - fname=None, width=730, height=520, **kwargs): - """Map lat/lon trajectories on map. - - By increasing the ``sections`` parameter, it is possible to output - multiple zoom sections of the trajectories to see finer details. - - Parameters - ---------- - *args : gnss_lib_py.parsers.navdata.NavData - Tuple of gnss_lib_py.parsers.navdata.NavData objects. The - NavData objects should include row names for both latitude and - longitude in the form of ```lat_*_deg`` and ``lon_*_deg``. - Must also include ``gps_millis`` if sections >= 2. - sections : int - Number of zoomed in sections to make of data. Will only output - additional plots if sections >= 2. Creates sections by equal - timestamps using the ``gps_millis`` row. - save : bool - Save figure if true. Defaults to saving the figure in the - Results folder. - prefix : string - File prefix to add to filename. - fname : string or path-like - Path to save figure to. If not None, ``fname`` is passed - directly to matplotlib's savefig fname parameter and prefix will - be overwritten. - width : int - Figure width in pixels. - height : int - Figure height in pixels. - mapbox_style : str - Can optionally be included as one of the ``**kwargs`` - Free options include ``open-street-map``, ``white-bg``, - ``carto-positron``, ``carto-darkmatter``, ``stamen-terrain``, - ``stamen-toner``, and ``stamen-watercolor``. - - Returns - ------- - figs : single or list of plotly.graph_objects.Figure - Returns single plotly.graph_objects.Figure object if sections is - <= 1, otherwise returns list of Figure objects containing full - trajectory as well as zoomed in sections. - - """ - - figure_df = None # plotly works best passing in DataFrame - color_discrete_map = {} # discrete color map - - for idx, traj_data in enumerate(args): - if not isinstance(traj_data, NavData): - raise TypeError("Input(s) to plot_map() must be of type " \ - + "NavData.") - - # check for lat/lon indexes - traj_idxs = traj_data.find_wildcard_indexes( - wildcards=["lat_*_deg","lon_*_deg"], max_allow=1, - excludes=[["lat_sigma_*_deg"],["lon_sigma_*_deg"]]) - - label_name = _get_label({"":"_".join((traj_idxs["lat_*_deg"][0].split("_"))[1:-1])}) - - data = {"latitude" : traj_data[traj_idxs["lat_*_deg"][0]], - "longitude" : traj_data[traj_idxs["lon_*_deg"][0]], - "Trajectory" : [label_name] * len(traj_data), - } - if sections >= 2: - traj_data.in_rows("gps_millis") - data["gps_millis"] = traj_data["gps_millis"] - traj_df = pd.DataFrame.from_dict(data) - color_discrete_map[label_name] = \ - STANFORD_COLORS[idx % len(STANFORD_COLORS)] - if figure_df is None: - figure_df = traj_df - else: - figure_df = pd.concat([figure_df,traj_df]) - - zoom, center = _zoom_center(lats=figure_df["latitude"].to_numpy(), - lons=figure_df["longitude"].to_numpy(), - width_to_height=float(0.9*width)/height) - - fig = px.scatter_mapbox(figure_df, - lat="latitude", - lon="longitude", - color="Trajectory", - color_discrete_map=color_discrete_map, - zoom=zoom, - center=center, - width = width, - height = height, - ) - fig.update_layout(mapbox_style="open-street-map") - fig.update_layout(margin={"r": 0, "t": 0, "l": 0, "b": 0}) - fig.update_layout(**kwargs) - - if sections <= 1: - if save: # pragma: no cover - _save_plotly(fig, titles="map", prefix=prefix, fnames=fname, - width=width, height=height) - return fig - - figs = [fig] - titles = ["map_full"] - # break into zoom section of figures - time_groups = np.array_split(np.sort(figure_df["gps_millis"].unique()),sections) - for time_idx, time_group in enumerate(time_groups): - zoomed_df = figure_df[(figure_df["gps_millis"] >= min(time_group)) \ - & (figure_df["gps_millis"] <= max(time_group))] - - # calculate new zoom and center based on partial data - zoom, center = _zoom_center(lats=zoomed_df["latitude"].to_numpy(), - lons=zoomed_df["longitude"].to_numpy(), - width_to_height=float(0.9*width)/height) - fig = px.scatter_mapbox(figure_df, - lat="latitude", - lon="longitude", - color="Trajectory", - color_discrete_map=color_discrete_map, - zoom=zoom, - center=center, - width = width, - height = height, - ) - fig.update_layout(mapbox_style="open-street-map") - fig.update_layout(margin={"r": 0, "t": 0, "l": 0, "b": 0}) - fig.update_layout(**kwargs) - - figs.append(fig) - titles.append("map_section_" + str(time_idx + 1)) - - if save: # pragma: no cover - _save_plotly(figs, titles=titles, prefix=prefix, fnames=fname, - width=width, height=height) - return figs - -def close_figures(figs=None): - """Closes figures. - - If figs is None, then will attempt to close all matplotlib figures - with plt.close('all') - - Parameters - ---------- - figs : list or matplotlib.pyplot.figure or None - List of figures or single matplotlib figure object. - - """ - - if figs is None: - plt.close('all') - elif isinstance(figs,plt.Figure): - plt.close(figs) - elif isinstance(figs, list): - for fig in figs: - if isinstance(fig, plt.Figure): - plt.close(fig) - else: - raise TypeError("Must be either a single figure or list of figures.") - -def _get_new_fig(fig=None): - """Creates new default figure and axes. - - Parameters - ---------- - fig : matplotlib.pyplot.figure - Previous figure to format to style. - - Returns - ------- - fig : matplotlib.pyplot.figure - Default NavData figure. - axes : matplotlib.pyplot.axes - Default NavData axes. - - """ - - if fig is None: - fig = plt.figure() - axes = plt.gca() - elif len(fig.get_axes()) == 0: - axes = plt.gca() - else: - axes = fig.get_axes()[0] - - axes.ticklabel_format(useOffset=False) - fig.autofmt_xdate() # rotate x labels automatically - - return fig, axes - -def _get_label(inputs): - """Return label/title name from input dictionary. - - Parameters - ---------- - inputs : dict - Dictionary of {row_name : row_value} pairs to create name from. - - Returns - ------- - label : string - Properly formatted label/title for use in graphs. - - """ - - if not isinstance(inputs,dict): - raise TypeError("_get_label input must be dictionary.") - - # handle units specially. - units = {"m","km", - "deg","rad", - "millis","ms","sec","s","hr","min", - "mps","kmph","mph", - "dgps","radps", - "mps2", - } - unit_replacements = { - "ms" : "milliseconds", - "millis" : "milliseconds", - "mps" : "m/s", - "kmph" : "km/hr", - "mph" : "miles/hr", - "degps" : "deg/s", - "radps" : "rad/s", - "mps2" : "m/s^2", - } - - label = "" - for key, value in inputs.items(): - - if len(label) != 0: # add space between multiple inputs - value = " " + value - - if not isinstance(value,str): # convert numbers/arrays to string - value = str(value) - - try: # convert to integer if a numeric value - value = str(int(float(value))) - except ValueError: - pass - - # special exceptions for known times - if value in ("gps_millis","unix_millis"): - value = value.split("_")[0] + "_time_millis" - - value = value.split("_") - if value[-1] in units: - # make units lowercase and bracketed. - if value[-1] in unit_replacements: - value[-1] = unit_replacements[value[-1]] - value = " ".join(value[:-1]).upper() + " [" + value[-1] + "]" - else: - value = " ".join(value).upper() - - if key == "gnss_id": # use GNSS specific capitalization - constellation_map = {"GALILEO" : "Galileo", - "BEIDOU" : "BeiDou" - } - for old_value, new_value in constellation_map.items(): - value = value.replace(old_value,new_value) - - if key == "signal_type": - # replace with lowercase "i" for Beidou "I" signals for more - # legible name in the legend - if value[-1] == "I": - value = value[:-1] + "i" - - label += value - - return label - -def _sort_gnss_ids(unsorted_gnss_ids): - """Sort constellations by chronological availability. - - Order defined by `GNSS_ORDER` variable in header. - - Parameters - ---------- - unsorted_gnss_ids : list or array-like of strings. - Unsorted constellation names. - - Returns - ------- - sorted_gnss_ids : list or array-like of strings. - Sorted constellation names. - - """ - - sorted_gnss_ids = [] - unsorted_gnss_ids = list(unsorted_gnss_ids) - for gnss in GNSS_ORDER: - if gnss in unsorted_gnss_ids: - unsorted_gnss_ids.remove(gnss) - sorted_gnss_ids.append(gnss) - sorted_gnss_ids += sorted(unsorted_gnss_ids) - - return sorted_gnss_ids - -def _save_figure(figures, titles=None, prefix="", fnames=None): # pragma: no cover - """Saves figures to file. - - Parameters - ---------- - figures : single or list of matplotlib.pyplot.figure objects - Figures to be saved. - titles : string, path-like or list of strings - Titles for all plots. - prefix : string - File prefix to add to filename. - fnames : single or list of string or path-like - Path to save figure to. If not None, fname is passed directly - to matplotlib's savefig fname parameter and prefix will be - overwritten. - - """ - - if isinstance(figures, plt.Figure): - figures = [figures] - if isinstance(titles,str) or titles is None: - titles = [titles] - if isinstance(fnames, (str, pathlib.Path)) or fnames is None: - fnames = [fnames] - - for fig_idx, figure in enumerate(figures): - - if (len(fnames) == 1 and fnames[0] is None) \ - or fnames[fig_idx] is None: - # create results folder if it does not yet exist. - log_path = os.path.join(os.getcwd(),"results",fo.TIMESTAMP) - fo.make_dir(log_path) - - # make name path friendly - title = titles[fig_idx] - title = title.replace(" ","_") - title = title.replace(".","") - - if prefix != "" and not prefix.endswith('_'): - prefix += "_" - fname = os.path.join(log_path, prefix + title \ - + ".png") - else: - fname = fnames[fig_idx] - - figure.savefig(fname, - dpi=300., - format="png", - bbox_inches="tight") - -def _parse_metric_args(navdata, *args): - """Parses arguments and raises error if metrics are nonnumeric. - - Parameters - ---------- - navdata : gnss_lib_py.parsers.navdata.NavData - Instance of the NavData class - *args : tuple - Tuple of row names that are to be plotted. If one is given, that - value is plotted on the y-axis. If two values are given, the - first is plotted on the x-axis and the second on the y-axis. - - Returns - ------- - x_metric : string - Metric to be plotted on y-axis if y_metric is None, otherwise - x_metric is plotted on x axis. - y_metric : string or None - y_metric is plotted on the y axis. - - """ - - # parse arguments - if len(args)==1: - x_metric = None - y_metric = args[0] - elif len(args)==2: - x_metric = args[0] - y_metric = args[1] - else: - raise ValueError("Cannot plot more than one pair of x-y values") - for metric in [x_metric, y_metric]: - if metric is not None and navdata.is_str(metric): - raise KeyError(metric + " is a non-numeric row." \ - + "Unable to plot with plot_metric().") - - return x_metric, y_metric - -def _new_cmap(rgb_color): - """Return a new cmap from a color going to white. - - Given an RGB color, it creates a new color map that starts at white - then fades into the provided RGB color. - - Parameters - ---------- - rgb_color : tuple - color tuple of (red, green, blue) in floats between 0 and 1.0 - - Returns - ------- - cmap : ListedColormap - New color map made from the provided color. - - - Notes - ----- - More details and examples at the following link - https://matplotlib.org/3.1.0/tutorials/colors/colormap-manipulation.html - - """ - num_vals = 256 - vals = np.ones((num_vals, 4)) - - vals[:, 0] = np.linspace(1., rgb_color[0], num_vals) - vals[:, 1] = np.linspace(1., rgb_color[1], num_vals) - vals[:, 2] = np.linspace(1., rgb_color[2], num_vals) - cmap = ListedColormap(vals) - - return cmap - -def _zoom_center(lats, lons, width_to_height = 1.25): - """Finds optimal zoom and centering for a plotly mapbox. - - Assumed to use Mercator projection. - - Temporary solution copied from stackoverflow [1]_ and awaiting - official implementation [2]_. - - Parameters - -------- - lons: array-like, - Longitude component of each location. - lats: array-like - Latitude component of each location. - width_to_height: float, expected ratio of final graph's width to - height, used to select the constrained axis. - - Returns - ------- - zoom: float - Plotly zoom parameter from 1 to 20. - center: dict - Position with 'lon' and 'lat' keys for cetner of map. - - References - ---------- - .. [1] Richie V. https://stackoverflow.com/a/64148305/12995548. - .. [2] https://github.com/plotly/plotly.js/issues/3434 - - """ - - maxlon, minlon = max(lons), min(lons) - maxlat, minlat = max(lats), min(lats) - center = { - 'lon': round((maxlon + minlon) / 2, 6), - 'lat': round((maxlat + minlat) / 2, 6) - } - - # longitudinal range by zoom level (20 to 1) - # in degrees, if centered at equator - lon_zoom_range = np.array([ - 0.0007, 0.0014, 0.003, 0.006, 0.012, 0.024, 0.048, 0.096, - 0.192, 0.3712, 0.768, 1.536, 3.072, 6.144, 11.8784, 23.7568, - 47.5136, 98.304, 190.0544, 360.0 - ]) - - # assumed Mercator projection - margin = 2.5 - height = (maxlat - minlat) * margin * width_to_height - width = (maxlon - minlon) * margin - lon_zoom = np.interp(width , lon_zoom_range, range(20, 0, -1)) - lat_zoom = np.interp(height, lon_zoom_range, range(20, 0, -1)) - zoom = floor(min(lon_zoom, lat_zoom)) - # zoom level higher than 18 won't load load properly as of June 2023 - zoom = min(zoom, 18) - - return zoom, center - -def _save_plotly(figures, titles=None, prefix="", fnames=None, - width=730, height=520): # pragma: no cover - """Saves figures to file. - - Parameters - ---------- - figures : single or list of plotly.graph_objects.Figure objects to - be saved. - titles : string, path-like or list of strings - Titles for all plots. - prefix : string - File prefix to add to filename. - fnames : single or list of string or path-like - Path to save figure to. If not None, ``fname`` is passed - directly to plotly's write_image file parameter and prefix will - be overwritten. - width : int - Figure width in pixels. - height : int - Figure height in pixels. - - """ - - if isinstance(figures, go.Figure): - figures = [figures] - if isinstance(titles,str) or titles is None: - titles = [titles] - if isinstance(fnames, (str, pathlib.Path)) or fnames is None: - fnames = [fnames] - - for fig_idx, figure in enumerate(figures): - - if (len(fnames) == 1 and fnames[0] is None) \ - or fnames[fig_idx] is None: - # create results folder if it does not yet exist. - log_path = os.path.join(os.getcwd(),"results",fo.TIMESTAMP) - fo.make_dir(log_path) - - # make name path friendly - title = titles[fig_idx] - title = title.replace(" ","_") - title = title.replace(".","") - - if prefix != "" and not prefix.endswith('_'): - prefix += "_" - fname = os.path.join(log_path, prefix + title \ - + ".png") - else: - fname = fnames[fig_idx] - - while True: - # sometimes writing a plotly image hanges for an unknown - # reason. Hence, we call write_image in a process that is - # automatically terminated after 180 seconds if nothing - # happens. - process = Process(target=_write_plotly, - name="write_plotly", - args=(figure,fname,width,height)) - process.start() - - process.join(180) - if process.is_alive(): - process.terminate() - process.join() - continue - break - - -def _write_plotly(figure, fname, width, height): # pragma: no cover - """Saves figure to file. - - Automatically zooms out if plotly throws a ValueError when trying - to zoom in too much. - - Parameters - ---------- - figure : plotly.graph_objects.Figure - Object to save. - fname : string or path-like - Path to save figure to. - width : int - Figure width in pixels. - height : int - Figure height in pixels. - - """ - - while True: - try: - figure.write_image(fname, - width = width, - height = height, - ) - break - except ValueError as error: - figure.layout.mapbox.zoom -= 1 - if figure.layout.mapbox.zoom < 1: - print(error) - break diff --git a/gnss_lib_py/visualizations/plot_map.py b/gnss_lib_py/visualizations/plot_map.py new file mode 100644 index 00000000..2f7a0326 --- /dev/null +++ b/gnss_lib_py/visualizations/plot_map.py @@ -0,0 +1,313 @@ +"""Visualization functions for GNSS data. + +""" + +__authors__ = "D. Knowles" +__date__ = "27 Jan 2022" + +import os +import pathlib +from math import floor +from multiprocessing import Process + +import numpy as np +import pandas as pd +import plotly.express as px +import plotly.graph_objects as go + +from gnss_lib_py.visualizations import style +import gnss_lib_py.utils.file_operations as fo +from gnss_lib_py.navdata.navdata import NavData +from gnss_lib_py.navdata.operations import find_wildcard_indexes + +def plot_map(*args, sections=0, save=False, prefix="", + fname=None, width=730, height=520, **kwargs): + """Map lat/lon trajectories on map. + + By increasing the ``sections`` parameter, it is possible to output + multiple zoom sections of the trajectories to see finer details. + + Parameters + ---------- + *args : gnss_lib_py.navdata.navdata.NavData + Tuple of gnss_lib_py.navdata.navdata.NavData objects. The + NavData objects should include row names for both latitude and + longitude in the form of ```lat_*_deg`` and ``lon_*_deg``. + Must also include ``gps_millis`` if sections >= 2. + sections : int + Number of zoomed in sections to make of data. Will only output + additional plots if sections >= 2. Creates sections by equal + timestamps using the ``gps_millis`` row. + save : bool + Save figure if true. Defaults to saving the figure in the + Results folder. + prefix : string + File prefix to add to filename. + fname : string or path-like + Path to save figure to. If not None, ``fname`` is passed + directly to matplotlib's savefig fname parameter and prefix will + be overwritten. + width : int + Figure width in pixels. + height : int + Figure height in pixels. + mapbox_style : str + Can optionally be included as one of the ``**kwargs`` + Free options include ``open-street-map``, ``white-bg``, + ``carto-positron``, ``carto-darkmatter``, ``stamen-terrain``, + ``stamen-toner``, and ``stamen-watercolor``. + + Returns + ------- + figs : single or list of plotly.graph_objects.Figure + Returns single plotly.graph_objects.Figure object if sections is + <= 1, otherwise returns list of Figure objects containing full + trajectory as well as zoomed in sections. + + """ + + figure_df = None # plotly works best passing in DataFrame + color_discrete_map = {} # discrete color map + + for idx, traj_data in enumerate(args): + if not isinstance(traj_data, NavData): + raise TypeError("Input(s) to plot_map() must be of type " \ + + "NavData.") + + # check for lat/lon indexes + traj_idxs = find_wildcard_indexes(traj_data, + wildcards=["lat_*_deg","lon_*_deg"], max_allow=1, + excludes=[["lat_sigma_*_deg"],["lon_sigma_*_deg"]]) + + label_name = style.get_label({"":"_".join((traj_idxs["lat_*_deg"][0].split("_"))[1:-1])}) + + data = {"latitude" : traj_data[traj_idxs["lat_*_deg"][0]], + "longitude" : traj_data[traj_idxs["lon_*_deg"][0]], + "Trajectory" : [label_name] * len(traj_data), + } + if sections >= 2: + traj_data.in_rows("gps_millis") + data["gps_millis"] = traj_data["gps_millis"] + traj_df = pd.DataFrame.from_dict(data) + color_discrete_map[label_name] = \ + style.STANFORD_COLORS[idx % len(style.STANFORD_COLORS)] + if figure_df is None: + figure_df = traj_df + else: + figure_df = pd.concat([figure_df,traj_df]) + + zoom, center = _zoom_center(lats=figure_df["latitude"].to_numpy(), + lons=figure_df["longitude"].to_numpy(), + width_to_height=float(0.9*width)/height) + + fig = px.scatter_mapbox(figure_df, + lat="latitude", + lon="longitude", + color="Trajectory", + color_discrete_map=color_discrete_map, + zoom=zoom, + center=center, + width = width, + height = height, + ) + fig.update_layout(mapbox_style="open-street-map") + fig.update_layout(margin={"r": 0, "t": 0, "l": 0, "b": 0}) + fig.update_layout(**kwargs) + + if sections <= 1: + if save: # pragma: no cover + _save_plotly(fig, titles="map", prefix=prefix, fnames=fname, + width=width, height=height) + return fig + + figs = [fig] + titles = ["map_full"] + # break into zoom section of figures + time_groups = np.array_split(np.sort(figure_df["gps_millis"].unique()),sections) + for time_idx, time_group in enumerate(time_groups): + zoomed_df = figure_df[(figure_df["gps_millis"] >= min(time_group)) \ + & (figure_df["gps_millis"] <= max(time_group))] + + # calculate new zoom and center based on partial data + zoom, center = _zoom_center(lats=zoomed_df["latitude"].to_numpy(), + lons=zoomed_df["longitude"].to_numpy(), + width_to_height=float(0.9*width)/height) + fig = px.scatter_mapbox(figure_df, + lat="latitude", + lon="longitude", + color="Trajectory", + color_discrete_map=color_discrete_map, + zoom=zoom, + center=center, + width = width, + height = height, + ) + fig.update_layout(mapbox_style="open-street-map") + fig.update_layout(margin={"r": 0, "t": 0, "l": 0, "b": 0}) + fig.update_layout(**kwargs) + + figs.append(fig) + titles.append("map_section_" + str(time_idx + 1)) + + if save: # pragma: no cover + _save_plotly(figs, titles=titles, prefix=prefix, fnames=fname, + width=width, height=height) + return figs + + +def _zoom_center(lats, lons, width_to_height = 1.25): + """Finds optimal zoom and centering for a plotly mapbox. + + Assumed to use Mercator projection. + + Temporary solution copied from stackoverflow [1]_ and awaiting + official implementation [2]_. + + Parameters + -------- + lons: array-like, + Longitude component of each location. + lats: array-like + Latitude component of each location. + width_to_height: float, expected ratio of final graph's width to + height, used to select the constrained axis. + + Returns + ------- + zoom: float + Plotly zoom parameter from 1 to 20. + center: dict + Position with 'lon' and 'lat' keys for cetner of map. + + References + ---------- + .. [1] Richie V. https://stackoverflow.com/a/64148305/12995548. + .. [2] https://github.com/plotly/plotly.js/issues/3434 + + """ + + maxlon, minlon = max(lons), min(lons) + maxlat, minlat = max(lats), min(lats) + center = { + 'lon': round((maxlon + minlon) / 2, 6), + 'lat': round((maxlat + minlat) / 2, 6) + } + + # longitudinal range by zoom level (20 to 1) + # in degrees, if centered at equator + lon_zoom_range = np.array([ + 0.0007, 0.0014, 0.003, 0.006, 0.012, 0.024, 0.048, 0.096, + 0.192, 0.3712, 0.768, 1.536, 3.072, 6.144, 11.8784, 23.7568, + 47.5136, 98.304, 190.0544, 360.0 + ]) + + # assumed Mercator projection + margin = 2.5 + height = (maxlat - minlat) * margin * width_to_height + width = (maxlon - minlon) * margin + lon_zoom = np.interp(width , lon_zoom_range, range(20, 0, -1)) + lat_zoom = np.interp(height, lon_zoom_range, range(20, 0, -1)) + zoom = floor(min(lon_zoom, lat_zoom)) + # zoom level higher than 18 won't load load properly as of June 2023 + zoom = min(zoom, 18) + + return zoom, center + +def _save_plotly(figures, titles=None, prefix="", fnames=None, + width=730, height=520): # pragma: no cover + """Saves figures to file. + + Parameters + ---------- + figures : single or list of plotly.graph_objects.Figure objects to + be saved. + titles : string, path-like or list of strings + Titles for all plots. + prefix : string + File prefix to add to filename. + fnames : single or list of string or path-like + Path to save figure to. If not None, ``fname`` is passed + directly to plotly's write_image file parameter and prefix will + be overwritten. + width : int + Figure width in pixels. + height : int + Figure height in pixels. + + """ + + if isinstance(figures, go.Figure): + figures = [figures] + if isinstance(titles,str) or titles is None: + titles = [titles] + if isinstance(fnames, (str, pathlib.Path)) or fnames is None: + fnames = [fnames] + + for fig_idx, figure in enumerate(figures): + + if (len(fnames) == 1 and fnames[0] is None) \ + or fnames[fig_idx] is None: + # create results folder if it does not yet exist. + log_path = os.path.join(os.getcwd(),"results",fo.TIMESTAMP) + fo.make_dir(log_path) + + # make name path friendly + title = titles[fig_idx] + title = title.replace(" ","_") + title = title.replace(".","") + + if prefix != "" and not prefix.endswith('_'): + prefix += "_" + fname = os.path.join(log_path, prefix + title \ + + ".png") + else: + fname = fnames[fig_idx] + + while True: + # sometimes writing a plotly image hanges for an unknown + # reason. Hence, we call write_image in a process that is + # automatically terminated after 180 seconds if nothing + # happens. + process = Process(target=_write_plotly, + name="write_plotly", + args=(figure,fname,width,height)) + process.start() + + process.join(180) + if process.is_alive(): + process.terminate() + process.join() + continue + break + +def _write_plotly(figure, fname, width, height): # pragma: no cover + """Saves figure to file. + + Automatically zooms out if plotly throws a ValueError when trying + to zoom in too much. + + Parameters + ---------- + figure : plotly.graph_objects.Figure + Object to save. + fname : string or path-like + Path to save figure to. + width : int + Figure width in pixels. + height : int + Figure height in pixels. + + """ + + while True: + try: + figure.write_image(fname, + width = width, + height = height, + ) + break + except ValueError as error: + figure.layout.mapbox.zoom -= 1 + if figure.layout.mapbox.zoom < 1: + print(error) + break diff --git a/gnss_lib_py/visualizations/plot_metric.py b/gnss_lib_py/visualizations/plot_metric.py new file mode 100644 index 00000000..76763760 --- /dev/null +++ b/gnss_lib_py/visualizations/plot_metric.py @@ -0,0 +1,289 @@ +"""Visualization functions for GNSS data. + +""" + +__authors__ = "D. Knowles" +__date__ = "27 Jan 2022" + +import numpy as np +import matplotlib.pyplot as plt + +from gnss_lib_py.visualizations.style import * +from gnss_lib_py.navdata.navdata import NavData + +def plot_metric(navdata, *args, groupby=None, avg_y=False, fig=None, + title=None, save=False, prefix="", fname=None, + markeredgecolor="k", markeredgewidth=0.2, **kwargs): + """Plot specific metric from a row of the NavData class. + + Parameters + ---------- + navdata : gnss_lib_py.navdata.navdata.NavData + Instance of the NavData class + *args : tuple + Tuple of row names that are to be plotted. If one is given, that + value is plotted on the y-axis. If two values are given, the + first is plotted on the x-axis and the second on the y-axis. + groupby : string + Row name by which to group and label plots. + avg_y : bool + Whether or not to average across the y values for each x + timestep when doing groupby + fig : matplotlib.pyplot.Figure + Previous figure on which to add current plotting. Default of + None plots on a new figure. + title : string + Title for the plot. + save : bool + Saves figure if true to file specified by fname or defaults + to the Results folder otherwise. + prefix : string + File prefix to add to filename. + fname : string or path-like + Path to save figure. If not None, fname is passed directly + to matplotlib's savefig fname parameter and prefix will be + overwritten. + markeredgecolor : color + Marker edge color. + markeredgewidth : float + Marker edge width. + + Returns + ------- + fig : matplotlib.pyplot.Figure + Figure of plotted metrics. + + """ + + if not isinstance(navdata,NavData): + raise TypeError("first arg to plot_metrics must be a "\ + + "NavData object.") + + x_metric, y_metric = _parse_metric_args(navdata, *args) + + if groupby is not None: + navdata.in_rows(groupby) + if not isinstance(prefix, str): + raise TypeError("Prefix must be a string.") + + # create a new figure if none provided + fig, axes = _get_new_fig(fig) + + if x_metric is None: + x_data = None + xlabel = "INDEX" + if title is None: + title = get_label({y_metric:y_metric}) + else: + if title is None: + title = get_label({y_metric:y_metric}) + " vs. " \ + + get_label({x_metric:x_metric}) + xlabel = get_label({x_metric:x_metric}) + + if groupby is not None: + all_groups = np.unique(navdata[groupby]) + if groupby == "gnss_id": + all_groups = sort_gnss_ids(all_groups) + for group in all_groups: + subset = navdata.where(groupby,group) + y_data = np.atleast_1d(subset[y_metric]) + if x_metric is None: + x_data = range(len(y_data)) + else: + x_data = np.atleast_1d(subset[x_metric]) + if avg_y: + # average y values for each x + x_unique = sorted(np.unique(x_data)) + y_avg = [] + for x_val in x_unique: + x_idxs = np.argwhere(x_data==x_val) + y_avg.append(np.mean(y_data[x_idxs])) + x_data = x_unique + y_data = y_avg + # change name + group = str(group) + "_avg" + axes.plot(x_data, y_data, + label=get_label({groupby:group}), + markeredgecolor = markeredgecolor, + markeredgewidth = markeredgewidth, + **kwargs) + else: + y_data = np.atleast_1d(navdata[y_metric]) + if x_metric is None: + x_data = range(len(y_data)) + else: + x_data = np.atleast_1d(navdata[x_metric]) + axes.plot(x_data, y_data, + markeredgecolor = markeredgecolor, + markeredgewidth = markeredgewidth, + **kwargs) + + handles, _ = axes.get_legend_handles_labels() + if len(handles) > 0: + plt.legend(loc="upper left", bbox_to_anchor=(1.05, 1), + title=get_label({groupby:groupby})) + + plt.title(title) + plt.xlabel(xlabel) + plt.ylabel(get_label({y_metric:y_metric})) + fig.set_layout_engine(layout="tight") + + if save: # pragma: no cover + save_figure(fig, title, prefix, fname) + return fig + +def plot_metric_by_constellation(navdata, *args, save=False, prefix="", + fname=None, **kwargs): + """Plot specific metric from a row of the NavData class. + + Breaks up metrics by constellation names in "gnss_id" and + additionally "signal_type" if the "signal_type" row exists. + + Plots will include a legend with satellite ID if the "sv_id" row + is present in navdata. + + Parameters + ---------- + navdata : gnss_lib_py.navdata.navdata.NavData + Instance of the NavData class. Must include ``gnss_id`` row and + optionally ``signal_type`` and ``sv_id`` for increased + labelling. + *args : tuple + Tuple of row names that are to be plotted. If one is given, that + value is plotted on the y-axis. If two values are given, the + first is plotted on the x-axis and the second on the y-axis. + save : bool + Saves figure if true to file specified by ``fname`` or defaults + to the Results folder otherwise. + prefix : string + File prefix to add to filename. + fname : string or path-like + Path to save figure to. If not None, ``fname`` is passed + directly to matplotlib's savefig fname parameter and prefix will + be overwritten. + + Returns + ------- + fig : list of matplotlib.pyplot.Figure objects + List of figures of plotted metrics. + + """ + + if not isinstance(navdata,NavData): + raise TypeError("first arg to plot_metric_by_constellation "\ + + "must be a NavData object.") + + x_metric, y_metric = _parse_metric_args(navdata, *args) + + if not isinstance(prefix, str): + raise TypeError("Prefix must be a string.") + if "gnss_id" not in navdata.rows: + raise KeyError("gnss_id row missing," \ + + " try using" \ + + " the plot_metric() function call instead") + + figs = [] + for constellation in sort_gnss_ids(np.unique(navdata["gnss_id"])): + const_subset = navdata.where("gnss_id",constellation) + + if "signal_type" in const_subset.rows: + for signal in np.unique(const_subset["signal_type"]): + title = get_label({"gnss_id":constellation,"signal_type":signal}) + signal_subset = const_subset.where("signal_type",signal) + if "sv_id" in signal_subset.rows: + # group by sv_id + fig = plot_metric(signal_subset,x_metric,y_metric, + groupby="sv_id", title=title, + save=save, prefix=prefix, + fname=fname, **kwargs) + figs.append(fig) + else: + fig = plot_metric(signal_subset,x_metric,y_metric, + title=title, save=save, + prefix=prefix, fname=fname, + **kwargs) + figs.append(fig) + else: + title = get_label({"gnss_id":constellation}) + if "sv_id" in const_subset.rows: + # group by sv_id + fig = plot_metric(const_subset,x_metric,y_metric, + groupby="sv_id", title=title, + save=save, prefix=prefix, fname=fname, + **kwargs) + figs.append(fig) + else: + fig = plot_metric(const_subset,x_metric,y_metric, + title=title, save=save, prefix=prefix, + fname=fname, **kwargs) + figs.append(fig) + + return figs + +def _parse_metric_args(navdata, *args): + """Parses arguments and raises error if metrics are nonnumeric. + + Parameters + ---------- + navdata : gnss_lib_py.navdata.navdata.NavData + Instance of the NavData class + *args : tuple + Tuple of row names that are to be plotted. If one is given, that + value is plotted on the y-axis. If two values are given, the + first is plotted on the x-axis and the second on the y-axis. + + Returns + ------- + x_metric : string + Metric to be plotted on y-axis if y_metric is None, otherwise + x_metric is plotted on x axis. + y_metric : string or None + y_metric is plotted on the y axis. + + """ + + # parse arguments + if len(args)==1: + x_metric = None + y_metric = args[0] + elif len(args)==2: + x_metric = args[0] + y_metric = args[1] + else: + raise ValueError("Cannot plot more than one pair of x-y values") + for metric in [x_metric, y_metric]: + if metric is not None and navdata.is_str(metric): + raise KeyError(metric + " is a non-numeric row." \ + + "Unable to plot with plot_metric().") + + return x_metric, y_metric + +def _get_new_fig(fig=None): + """Creates new default figure and axes. + + Parameters + ---------- + fig : matplotlib.pyplot.figure + Previous figure to format to style. + + Returns + ------- + fig : matplotlib.pyplot.figure + Default NavData figure. + axes : matplotlib.pyplot.axes + Default NavData axes. + + """ + + if fig is None: + fig = plt.figure() + axes = plt.gca() + elif len(fig.get_axes()) == 0: + axes = plt.gca() + else: + axes = fig.get_axes()[0] + + axes.ticklabel_format(useOffset=False) + fig.autofmt_xdate() # rotate x labels automatically + + return fig, axes diff --git a/gnss_lib_py/visualizations/plot_skyplot.py b/gnss_lib_py/visualizations/plot_skyplot.py new file mode 100644 index 00000000..05c72414 --- /dev/null +++ b/gnss_lib_py/visualizations/plot_skyplot.py @@ -0,0 +1,220 @@ +"""Visualization functions for GNSS data. + +""" + +__authors__ = "D. Knowles" +__date__ = "27 Jan 2022" + +import numpy as np +import matplotlib.pyplot as plt +from matplotlib.colors import to_rgb +from matplotlib.collections import LineCollection + +from gnss_lib_py.visualizations.style import * +from gnss_lib_py.navdata.navdata import NavData +from gnss_lib_py.utils.coordinates import add_el_az + +def plot_skyplot(navdata, receiver_state, + save=False, prefix="", fname=None, + add_sv_id_label=True, step = "auto", trim_options=None): + """Skyplot of satellite positions relative to receiver. + + First adds ``el_sv_deg`` and ``az_sv_deg`` rows to navdata if they + do not yet exist. + + Breaks up satellites by constellation names in ``gnss_id`` and the + ``sv_id`` if the row is present in navdata. + + Parameters + ---------- + navdata : gnss_lib_py.navdata.navdata.NavData + Instance of the NavData class. Must include ``gps_millis`` as + well as satellite ECEF positions as ``x_sv_m``, ``y_sv_m``, + ``z_sv_m``, ``gnss_id`` and ``sv_id``. + receiver_state : gnss_lib_py.navdata.navdata.NavData + Either estimated or ground truth receiver position in ECEF frame + in meters as an instance of the NavData class with the + following rows: ``x_rx*_m``, ``y_rx*_m``, ``z_rx*_m``, ``gps_millis``. + save : bool + Saves figure if true to file specified by ``fname`` or defaults + to the Results folder otherwise. + prefix : string + File prefix to add to filename. + fname : string or path-like + Path to save figure to. If not None, ``fname`` is passed + directly to matplotlib's savefig fname parameter and prefix will + be overwritten. + add_sv_id_label : bool + If the ``sv_id`` row is available, will add SV ID label near the + satellite trail. + step : int or "auto" + Skyplot plotting is sped up by only plotting a portion of the + satellite trajectories. If default is set to "auto" then it will + plot a maximum of 50 points across each satellite trajectory. If + the step variable is set to a positive integer ``n`` then only + every nth point will be used in the trajectory. Setting the + steps variable to 1 will plot every satellite trajectory point + and may be slow to plot. + trim_options : None or dict + The ``trim_options`` variables gives control for line segments + being trimmed between satellite points. For example, if 24 hours + of a satellite is plotted, often the satellite will come in and + out of view and the segment between when it was lost from view + and when the satellite comes back in view should be trimmed. + If trim_options is set to the default of None, then the default + is set of trimming according to az_and_el and gps_millis. The + current options for the trim_options dictionary are listed here. + {"az" : az_limit} means that if at two timesteps the azimuth + difference in degrees is greater than az_limit, then the line + segment will be trimmed. + {"az_and_el" : (az_limit,el_limit)} means that if at two + timesteps the azimuth difference in degrees is greater than + az_limit as well as the average of the elevation angle across + the two timesteps is less than el_limit in degrees, then the + line segment will be trimmed. The el_limit is because satellites + near 90 degrees elevation can traverse large amounts of degrees + in azimuth in a valid trajectory but at low elevations should + not have such large azimuth changes quickly. + {"gps_millis",gps_millis_limit} means that line segments will be + trimmed if the milliseconds between the two points is larger + than the gps_millis_limit. This option only works if the + gps_millis row is included in the ``navdata`` variable input. + Default options for the trim options are :code:`"az_and_el" : (15.,30.)` + and :code:`"gps_millis" : 3.6E6`. + + Returns + ------- + fig : matplotlib.pyplot.figure + Figure object of skyplot. + + """ + + if not isinstance(navdata,NavData): + raise TypeError("first arg to plot_skyplot "\ + + "must be a NavData object.") + + if not isinstance(prefix, str): + raise TypeError("Prefix must be a string.") + + # add elevation and azimuth data. + add_el_az(navdata, receiver_state, inplace=True) + + # create new figure + fig = plt.figure(figsize=(6,4.5)) + axes = fig.add_subplot(111, projection='polar') + + navdata = navdata.copy() + navdata["az_sv_rad"] = np.radians(navdata["az_sv_deg"]) + # remove SVs below horizon + navdata = navdata.where("el_sv_deg",0,"geq") + # remove np.nan values caused by potentially faulty data + navdata = navdata.where("az_sv_rad",np.nan,"neq") + navdata = navdata.where("el_sv_deg",np.nan,"neq") + + for c_idx, constellation in enumerate(sort_gnss_ids(np.unique(navdata["gnss_id"]))): + const_subset = navdata.where("gnss_id",constellation) + color = "C" + str(c_idx % len(STANFORD_COLORS)) + cmap = new_cmap(to_rgb(color)) + marker = MARKERS[c_idx % len(MARKERS)] + const_label_created = False + + # iterate through each satellite + for sv_name in np.unique(const_subset["sv_id"]): + sv_subset = const_subset.where("sv_id",sv_name) + + # only plot ~ 50 points for each sat to decrease time + # it takes to plot these line collections if step == "auto" + if isinstance(step,str) and step == "auto": + step = max(1,int(len(sv_subset)/50.)) + elif isinstance(step, int): + step = max(1,step) + else: + raise TypeError("step varaible must be 'auto' or int") + points = np.array([np.atleast_1d(sv_subset["az_sv_rad"])[::step], + np.atleast_1d(sv_subset["el_sv_deg"])[::step]]).T + points = np.reshape(points,(-1, 1, 2)) + segments = np.concatenate([points[:-1], points[1:]], axis=1) + norm = plt.Normalize(0,len(segments)) + + if trim_options is None: + trim_options = { + "az_and_el" : (15.,30.), + "gps_millis" : 3.6E6, + } + plotted_idxs = np.array([True] * len(segments)) + + if "az" in trim_options and len(segments) > 2: + # ignore segments that cross more than az_limit degrees + # in azimuth between timesteps + az_limit = np.radians(trim_options["az"]) + az_idxs = ~((np.abs(np.diff(np.unwrap(segments[:,:,0]))) >= az_limit)[:,0]) + plotted_idxs = np.bitwise_and(plotted_idxs, az_idxs) + if "az_and_el" in trim_options and len(segments) > 2: + # ignore segments that cross more than az_limit degrees + # in azimuth between timesteps and are at an elevation + # less than el_limit degrees. + # These satellites are assumed to be the satellites + # coming in and out of view in a later part of the orbit + az_limit = np.radians(trim_options["az_and_el"][0]) + el_limit = trim_options["az_and_el"][1] + az_and_el_idxs = ~(((np.abs(np.diff(np.unwrap(segments[:,:,0]))) >= az_limit)[:,0]) \ + & (np.mean(segments[:,:,1],axis=1) <= el_limit)) + plotted_idxs = np.bitwise_and(plotted_idxs, az_and_el_idxs) + if "gps_millis" in trim_options and "gps_millis" in sv_subset.rows \ + and len(segments) > 2: + # ignore segments if there is more than gps_millis_limit + # milliseconds between the time segments + gps_millis_limit = trim_options["gps_millis"] + + all_times = np.atleast_2d(sv_subset["gps_millis"][::step]).T + point_times = np.concatenate([all_times[:-1],all_times[1:]], + axis=1) + gps_millis_idxs = (np.abs(np.diff(point_times)) <= gps_millis_limit)[:,0] + plotted_idxs = np.bitwise_and(plotted_idxs, gps_millis_idxs) + + segments = segments[list(plotted_idxs)] + + local_coord = LineCollection(segments, cmap=cmap, + norm=norm, linewidths=(4,), + array = range(len(segments))) + axes.add_collection(local_coord) + if not const_label_created: + # plot with label + axes.plot(np.atleast_1d(sv_subset["az_sv_rad"])[-1], + np.atleast_1d(sv_subset["el_sv_deg"])[-1], + c=color, marker=marker, markersize=8, + label=get_label({"gnss_id":constellation})) + const_label_created = True + else: + # plot without label + axes.plot(np.atleast_1d(sv_subset["az_sv_rad"])[-1], + np.atleast_1d(sv_subset["el_sv_deg"])[-1], + c=color, marker=marker, markersize=8) + if add_sv_id_label: + # offsets move label to the right of marker + az_offset = 3.*np.radians(np.cos(np.atleast_1d(sv_subset["az_sv_rad"])[-1])) + el_offset = -3.*np.sin(np.atleast_1d(sv_subset["az_sv_rad"])[-1]) + axes.text(np.atleast_1d(sv_subset["az_sv_rad"])[-1] \ + + az_offset, + np.atleast_1d(sv_subset["el_sv_deg"])[-1] \ + + el_offset, + str(int(sv_name)), + ) + + # updated axes for skyplot graph specifics + axes.set_theta_zero_location('N') + axes.set_theta_direction(-1) + axes.set_yticks(range(0, 60+10, 30)) # Define the yticks + axes.set_yticklabels(['',r'$30\degree$',r'$60\degree$']) + axes.set_ylim(90,0) + + handles, _ = axes.get_legend_handles_labels() + if len(handles) > 0: + axes.legend(loc="upper left", bbox_to_anchor=(1.05, 1), + title=get_label({"constellation":"constellation"})) + + fig.set_layout_engine(layout='tight') + + if save: # pragma: no cover + save_figure(fig, "skyplot", prefix=prefix, fnames=fname) + return fig diff --git a/gnss_lib_py/visualizations/style.py b/gnss_lib_py/visualizations/style.py new file mode 100644 index 00000000..bd53b745 --- /dev/null +++ b/gnss_lib_py/visualizations/style.py @@ -0,0 +1,256 @@ +"""Visualization functions for GNSS data. + +""" + +__authors__ = "D. Knowles" +__date__ = "27 Jan 2022" + +import os +import pathlib + +import numpy as np +from cycler import cycler +import matplotlib as mpl +import matplotlib.pyplot as plt +from matplotlib.colors import ListedColormap + +import gnss_lib_py.utils.file_operations as fo + +STANFORD_COLORS = [ + "#8C1515", # cardinal red + "#6FC3FF", # light digital blue + "#006F54", # dark digital green + "#620059", # plum + "#E98300", # poppy + "#FEDD5C", # illuminating + "#E04F39", # spirited + "#4298B5", # sky + "#8F993E", # olive + "#651C32", # brick + "#B1040E", # digital red + "#016895", # dark sky + "#279989", # palo verde + # "#67AFD2", # light sky + # "#008566", # digital green + ] +MARKERS = ["o","*","P","v","s","^","p","<","h",">","H","X","D"] + +GNSS_ORDER = ["gps","glonass","galileo","beidou","qzss","irnss","sbas", + "unknown"] + +mpl.rcParams['axes.prop_cycle'] = (cycler(color=STANFORD_COLORS) \ + + cycler(marker=MARKERS)) + +def close_figures(figs=None): + """Closes figures. + + If figs is None, then will attempt to close all matplotlib figures + with plt.close('all') + + Parameters + ---------- + figs : list or matplotlib.pyplot.figure or None + List of figures or single matplotlib figure object. + + """ + + if figs is None: + plt.close('all') + elif isinstance(figs,plt.Figure): + plt.close(figs) + elif isinstance(figs, list): + for fig in figs: + if isinstance(fig, plt.Figure): + plt.close(fig) + else: + raise TypeError("Must be either a single figure or list of figures.") + +def get_label(inputs): + """Return label/title name from input dictionary. + + Parameters + ---------- + inputs : dict + Dictionary of {row_name : row_value} pairs to create name from. + + Returns + ------- + label : string + Properly formatted label/title for use in graphs. + + """ + + if not isinstance(inputs,dict): + raise TypeError("get_label input must be dictionary.") + + # handle units specially. + units = {"m","km", + "deg","rad", + "millis","ms","sec","s","hr","min", + "mps","kmph","mph", + "dgps","radps", + "mps2", + } + unit_replacements = { + "ms" : "milliseconds", + "millis" : "milliseconds", + "mps" : "m/s", + "kmph" : "km/hr", + "mph" : "miles/hr", + "degps" : "deg/s", + "radps" : "rad/s", + "mps2" : "m/s^2", + } + + label = "" + for key, value in inputs.items(): + + if len(label) != 0: # add space between multiple inputs + value = " " + value + + if not isinstance(value,str): # convert numbers/arrays to string + value = str(value) + + try: # convert to integer if a numeric value + value = str(int(float(value))) + except ValueError: + pass + + # special exceptions for known times + if value in ("gps_millis","unix_millis"): + value = value.split("_")[0] + "_time_millis" + + value = value.split("_") + if value[-1] in units: + # make units lowercase and bracketed. + if value[-1] in unit_replacements: + value[-1] = unit_replacements[value[-1]] + value = " ".join(value[:-1]).upper() + " [" + value[-1] + "]" + else: + value = " ".join(value).upper() + + if key == "gnss_id": # use GNSS specific capitalization + constellation_map = {"GALILEO" : "Galileo", + "BEIDOU" : "BeiDou" + } + for old_value, new_value in constellation_map.items(): + value = value.replace(old_value,new_value) + + if key == "signal_type": + # replace with lowercase "i" for Beidou "I" signals for more + # legible name in the legend + if value[-1] == "I": + value = value[:-1] + "i" + + label += value + + return label + +def sort_gnss_ids(unsorted_gnss_ids): + """Sort constellations by chronological availability. + + Order defined by `GNSS_ORDER` variable in header. + + Parameters + ---------- + unsorted_gnss_ids : list or array-like of strings. + Unsorted constellation names. + + Returns + ------- + sorted_gnss_ids : list or array-like of strings. + Sorted constellation names. + + """ + + sorted_gnss_ids = [] + unsorted_gnss_ids = list(unsorted_gnss_ids) + for gnss in GNSS_ORDER: + if gnss in unsorted_gnss_ids: + unsorted_gnss_ids.remove(gnss) + sorted_gnss_ids.append(gnss) + sorted_gnss_ids += sorted(unsorted_gnss_ids) + + return sorted_gnss_ids + +def save_figure(figures, titles=None, prefix="", fnames=None): # pragma: no cover + """Saves figures to file. + + Parameters + ---------- + figures : single or list of matplotlib.pyplot.figure objects + Figures to be saved. + titles : string, path-like or list of strings + Titles for all plots. + prefix : string + File prefix to add to filename. + fnames : single or list of string or path-like + Path to save figure to. If not None, fname is passed directly + to matplotlib's savefig fname parameter and prefix will be + overwritten. + + """ + + if isinstance(figures, plt.Figure): + figures = [figures] + if isinstance(titles,str) or titles is None: + titles = [titles] + if isinstance(fnames, (str, pathlib.Path)) or fnames is None: + fnames = [fnames] + + for fig_idx, figure in enumerate(figures): + + if (len(fnames) == 1 and fnames[0] is None) \ + or fnames[fig_idx] is None: + # create results folder if it does not yet exist. + log_path = os.path.join(os.getcwd(),"results",fo.TIMESTAMP) + fo.make_dir(log_path) + + # make name path friendly + title = titles[fig_idx] + title = title.replace(" ","_") + title = title.replace(".","") + + if prefix != "" and not prefix.endswith('_'): + prefix += "_" + fname = os.path.join(log_path, prefix + title \ + + ".png") + else: + fname = fnames[fig_idx] + + figure.savefig(fname, + dpi=300., + format="png", + bbox_inches="tight") + +def new_cmap(rgb_color): + """Return a new cmap from a color going to white. + + Given an RGB color, it creates a new color map that starts at white + then fades into the provided RGB color. + + Parameters + ---------- + rgb_color : tuple + color tuple of (red, green, blue) in floats between 0 and 1.0 + + Returns + ------- + cmap : ListedColormap + New color map made from the provided color. + + Notes + ----- + More details and examples at the following link + https://matplotlib.org/3.1.0/tutorials/colors/colormap-manipulation.html + + """ + num_vals = 256 + vals = np.ones((num_vals, 4)) + + vals[:, 0] = np.linspace(1., rgb_color[0], num_vals) + vals[:, 1] = np.linspace(1., rgb_color[1], num_vals) + vals[:, 2] = np.linspace(1., rgb_color[2], num_vals) + cmap = ListedColormap(vals) + + return cmap diff --git a/notebooks/tutorials/fde.ipynb b/notebooks/tutorials/algorithms/fde.ipynb similarity index 98% rename from notebooks/tutorials/fde.ipynb rename to notebooks/tutorials/algorithms/fde.ipynb index 2dcb3e8b..e80268ba 100644 --- a/notebooks/tutorials/fde.ipynb +++ b/notebooks/tutorials/algorithms/fde.ipynb @@ -1,5 +1,13 @@ { "cells": [ + { + "cell_type": "markdown", + "id": "76ee7e5e", + "metadata": {}, + "source": [ + "# FDE: Fault Detection and Exclusion" + ] + }, { "cell_type": "markdown", "id": "2b7e92ce", @@ -19,8 +27,9 @@ "import gnss_lib_py as glp\n", "\n", "# load Android Google Challenge data\n", - "!wget https://raw.githubusercontent.com/Stanford-NavLab/gnss_lib_py/main/data/unit_test/google_decimeter_2022/device_gnss.csv --quiet -O \"device_gnss.csv\"\n", - "navdata = glp.AndroidDerived2022(\"device_gnss.csv\")" + "glp.make_dir(\"../data\")\n", + "!wget https://raw.githubusercontent.com/Stanford-NavLab/gnss_lib_py/main/data/unit_test/google_decimeter_2022/device_gnss.csv --quiet -nc -O \"../data/device_gnss.csv\"\n", + "navdata = glp.AndroidDerived2022(\"../data/device_gnss.csv\")" ] }, { @@ -41,14 +50,6 @@ "navdata = navdata.where('gps_millis', navdata['gps_millis', 0], 'eq')" ] }, - { - "cell_type": "markdown", - "id": "76ee7e5e", - "metadata": {}, - "source": [ - "# FDE: Fault Detection and Exclusion" - ] - }, { "cell_type": "markdown", "id": "b9f9b91d-0a3f-49aa-b380-fb6d57639fe6", diff --git a/notebooks/tutorials/algorithms/gnss_filters.ipynb b/notebooks/tutorials/algorithms/gnss_filters.ipynb new file mode 100644 index 00000000..518102d0 --- /dev/null +++ b/notebooks/tutorials/algorithms/gnss_filters.ipynb @@ -0,0 +1,48 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "28b068e2", + "metadata": {}, + "source": [ + "# GNSS Filters" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "60e040a3", + "metadata": {}, + "outputs": [], + "source": [ + "import gnss_lib_py as glp" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.9" + }, + "vscode": { + "interpreter": { + "hash": "c7717b1dd2ec65abd747d44a25869d062db68d19263f8e701e26dddb0b153342" + } + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/notebooks/tutorials/algorithms/residuals.ipynb b/notebooks/tutorials/algorithms/residuals.ipynb new file mode 100644 index 00000000..25a819e7 --- /dev/null +++ b/notebooks/tutorials/algorithms/residuals.ipynb @@ -0,0 +1,116 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "28b068e2", + "metadata": {}, + "source": [ + "# Residuals" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "60e040a3", + "metadata": {}, + "outputs": [], + "source": [ + "import gnss_lib_py as glp" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "12285e7e", + "metadata": {}, + "outputs": [], + "source": [ + "# load Android Google Challenge data\n", + "glp.make_dir(\"../data\")\n", + "!wget https://raw.githubusercontent.com/Stanford-NavLab/gnss_lib_py/main/data/unit_test/google_decimeter_2021/Pixel4XL_derived.csv --quiet -nc -O \"../data/Pixel4XL_derived.csv\"\n", + "derived_data = glp.AndroidDerived2021(\"../data/Pixel4XL_derived.csv\", remove_timing_outliers=False)" + ] + }, + { + "cell_type": "markdown", + "id": "33f7cc62-cd1a-4c2d-9f97-78eb0134bcdb", + "metadata": {}, + "source": [ + "## Calculating GNSS Pseudorange Residuals" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e92db703", + "metadata": {}, + "outputs": [], + "source": [ + "galileo_data = derived_data.where(\"gnss_id\",\"galileo\")" + ] + }, + { + "cell_type": "markdown", + "id": "a4ff4bdc", + "metadata": {}, + "source": [ + "Solve for residuals using the estimated state. A new \"residuals_m\" row is added to derived_data" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7ab38367", + "metadata": {}, + "outputs": [], + "source": [ + "state_wls = glp.solve_wls(derived_data)\n", + "glp.solve_residuals(galileo_data, state_wls, inplace=True)" + ] + }, + { + "cell_type": "markdown", + "id": "6def9fe1", + "metadata": {}, + "source": [ + "Plot the residuals using the `plot_residuals()` function. The residuals are broken up constellation and signal type and plotted on separate figures." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e12ccd20", + "metadata": {}, + "outputs": [], + "source": [ + "figs = glp.plot_metric_by_constellation(galileo_data, \"gps_millis\", \"residuals_m\")" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.9" + }, + "vscode": { + "interpreter": { + "hash": "c7717b1dd2ec65abd747d44a25869d062db68d19263f8e701e26dddb0b153342" + } + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/notebooks/tutorials/algorithms.ipynb b/notebooks/tutorials/algorithms/snapshot.ipynb similarity index 65% rename from notebooks/tutorials/algorithms.ipynb rename to notebooks/tutorials/algorithms/snapshot.ipynb index 54e81ed0..a5d9806f 100644 --- a/notebooks/tutorials/algorithms.ipynb +++ b/notebooks/tutorials/algorithms/snapshot.ipynb @@ -1,11 +1,27 @@ { "cells": [ + { + "cell_type": "markdown", + "id": "1c1399bb-63e0-41fc-b6ec-717cccdee270", + "metadata": {}, + "source": [ + "# Snapshot Localization Algorithms" + ] + }, + { + "cell_type": "markdown", + "id": "151c36e4-cad1-4d02-9292-d422d2339883", + "metadata": {}, + "source": [ + "These tutorials demonstrate the snapshot localization algorithms available in ``gnss_lib_py``." + ] + }, { "cell_type": "markdown", "id": "76ee7e5e", "metadata": {}, "source": [ - "# Weighted Least Squares" + "## Weighted Least Squares" ] }, { @@ -26,8 +42,9 @@ "outputs": [], "source": [ "# load Android Google Challenge data\n", - "!wget https://raw.githubusercontent.com/Stanford-NavLab/gnss_lib_py/main/data/unit_test/google_decimeter_2021/Pixel4XL_derived.csv --quiet -O \"Pixel4XL_derived.csv\"\n", - "derived_data = glp.AndroidDerived2021(\"Pixel4XL_derived.csv\", remove_timing_outliers=False)" + "glp.make_dir(\"../data\")\n", + "!wget https://raw.githubusercontent.com/Stanford-NavLab/gnss_lib_py/main/data/unit_test/google_decimeter_2021/Pixel4XL_derived.csv --quiet -nc -O \"../data/Pixel4XL_derived.csv\"\n", + "derived_data = glp.AndroidDerived2021(\"../data/Pixel4XL_derived.csv\", remove_timing_outliers=False)" ] }, { @@ -78,7 +95,7 @@ "id": "c68a3316", "metadata": {}, "source": [ - "# Extended Kalman Filter" + "## Extended Kalman Filter" ] }, { @@ -99,8 +116,9 @@ "outputs": [], "source": [ "# load Android Google Challenge data\n", - "!wget https://raw.githubusercontent.com/Stanford-NavLab/gnss_lib_py/main/data/unit_test/google_decimeter_2021/Pixel4XL_derived.csv --quiet -O \"Pixel4XL_derived.csv\"\n", - "derived_data = glp.AndroidDerived2021(\"Pixel4XL_derived.csv\", remove_timing_outliers=False)" + "glp.make_dir(\"../data\")\n", + "!wget https://raw.githubusercontent.com/Stanford-NavLab/gnss_lib_py/main/data/unit_test/google_decimeter_2021/Pixel4XL_derived.csv --quiet -nc -O \"../data/Pixel4XL_derived.csv\"\n", + "derived_data = glp.AndroidDerived2021(\"../data/Pixel4XL_derived.csv\", remove_timing_outliers=False)" ] }, { @@ -115,9 +133,7 @@ "cell_type": "code", "execution_count": null, "id": "2615edfb", - "metadata": { - "scrolled": false - }, + "metadata": {}, "outputs": [], "source": [ "state_ekf = glp.solve_gnss_ekf(derived_data)" @@ -140,90 +156,6 @@ "source": [ "glp.plot_map(state_ekf)" ] - }, - { - "cell_type": "markdown", - "id": "28b068e2", - "metadata": {}, - "source": [ - "# Residuals" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "60e040a3", - "metadata": {}, - "outputs": [], - "source": [ - "import gnss_lib_py as glp" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "12285e7e", - "metadata": {}, - "outputs": [], - "source": [ - "# load Android Google Challenge data\n", - "!wget https://raw.githubusercontent.com/Stanford-NavLab/gnss_lib_py/main/data/unit_test/google_decimeter_2021/Pixel4XL_derived.csv --quiet -O \"Pixel4XL_derived.csv\"\n", - "derived_data = glp.AndroidDerived2021(\"Pixel4XL_derived.csv\", remove_timing_outliers=False)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "e92db703", - "metadata": {}, - "outputs": [], - "source": [ - "galileo_data = derived_data.where(\"gnss_id\",\"galileo\")" - ] - }, - { - "cell_type": "markdown", - "id": "a4ff4bdc", - "metadata": {}, - "source": [ - "Solve for residuals using the estimated state. A new \"residuals_m\" row is added to derived_data" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "7ab38367", - "metadata": {}, - "outputs": [], - "source": [ - "glp.solve_residuals(galileo_data, state_wls, inplace=True)" - ] - }, - { - "cell_type": "markdown", - "id": "6def9fe1", - "metadata": {}, - "source": [ - "Plot the residuals using the `plot_residuals()` function. The residuals are broken up constellation and signal type and plotted on separate figures." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "e12ccd20", - "metadata": {}, - "outputs": [], - "source": [ - "figs = glp.plot_metric_by_constellation(galileo_data, \"gps_millis\", \"residuals_m\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "fdc6d9cb", - "metadata": {}, - "outputs": [], - "source": [] } ], "metadata": { diff --git a/notebooks/tutorials/myreceiver.csv b/notebooks/tutorials/data/myreceiver.csv similarity index 100% rename from notebooks/tutorials/myreceiver.csv rename to notebooks/tutorials/data/myreceiver.csv diff --git a/notebooks/tutorials/navdata.ipynb b/notebooks/tutorials/navdata/navdata.ipynb similarity index 85% rename from notebooks/tutorials/navdata.ipynb rename to notebooks/tutorials/navdata/navdata.ipynb index 4a2d6d5d..41c4e0a5 100644 --- a/notebooks/tutorials/navdata.ipynb +++ b/notebooks/tutorials/navdata/navdata.ipynb @@ -17,7 +17,7 @@ "import numpy as np\n", "import pandas as pd\n", "\n", - "from gnss_lib_py import NavData" + "import gnss_lib_py as glp" ] }, { @@ -27,8 +27,9 @@ "outputs": [], "source": [ "# Get data path of example file\n", - "!wget https://raw.githubusercontent.com/Stanford-NavLab/gnss_lib_py/main/notebooks/tutorials/myreceiver.csv --quiet -O \"myreceiver.csv\"\n", - "data_path = \"myreceiver.csv\"" + "glp.make_dir(\"../data\")\n", + "!wget https://raw.githubusercontent.com/Stanford-NavLab/gnss_lib_py/main/notebooks/tutorials/data/myreceiver.csv --quiet -nc -O \"../data/myreceiver.csv\"\n", + "data_path = \"../data/myreceiver.csv\"" ] }, { @@ -58,7 +59,7 @@ "metadata": {}, "outputs": [], "source": [ - "empty_nav_data = NavData()\n", + "empty_nav_data = glp.NavData()\n", "print(empty_nav_data)" ] }, @@ -75,7 +76,7 @@ "metadata": {}, "outputs": [], "source": [ - "nav_data_csv = NavData(csv_path=data_path)\n", + "nav_data_csv = glp.NavData(csv_path=data_path)\n", "print(nav_data_csv)" ] }, @@ -93,7 +94,7 @@ "outputs": [], "source": [ "pd_df = pd.read_csv(data_path)\n", - "nav_data_pd = NavData(pandas_df=pd_df)\n", + "nav_data_pd = glp.NavData(pandas_df=pd_df)\n", "print(nav_data_pd)" ] }, @@ -111,7 +112,7 @@ "outputs": [], "source": [ "np_array = np.eye(4)\n", - "nav_data_np = NavData(numpy_array=np_array)\n", + "nav_data_np = glp.NavData(numpy_array=np_array)\n", "print(nav_data_np)" ] }, @@ -317,48 +318,6 @@ "nav_data_csv['new_string_row']" ] }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Adding new columns" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "To add new columns, use the `NavData.concat()` method which concatenates two `NavData` instances." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "print(nav_data_np)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "nav_data_np[:]" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "nav_data_np.concat(NavData(numpy_array=np_array),inplace=True)\n", - "nav_data_np[:]" - ] - }, { "cell_type": "markdown", "metadata": {}, @@ -512,28 +471,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## Looping" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "You can use the `NavData.loop_time()` method to loop over groups of data that belong to same time stamp.\n", - "In this case, we will show the data for each timestep using `pandas_df()`, which allows us to show strings and numbers together" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "for timestamp, delta_t, nav_data_subset in nav_data_csv.loop_time('myTimestamp'):\n", - " print('Current timestamp: ', timestamp)\n", - " print('Difference between current and future time step', delta_t)\n", - " print('Current group of data')\n", - " print(nav_data_subset.pandas_df())" + "## Looping over columns" ] }, { @@ -552,7 +490,7 @@ "for col_idx, nav_data_col in enumerate(nav_data_pd):\n", " print('Current column number', col_idx)\n", " print('Current column')\n", - " print(nav_data_col.pandas_df())\n", + " print(nav_data_col)\n", " if col_idx >= 3:\n", " break" ] @@ -574,7 +512,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.10" + "version": "3.8.9" }, "vscode": { "interpreter": { @@ -583,5 +521,5 @@ } }, "nbformat": 4, - "nbformat_minor": 2 + "nbformat_minor": 4 } diff --git a/notebooks/tutorials/navdata/operations.ipynb b/notebooks/tutorials/navdata/operations.ipynb new file mode 100644 index 00000000..e4c7faa3 --- /dev/null +++ b/notebooks/tutorials/navdata/operations.ipynb @@ -0,0 +1,164 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This tutorial shows how to use the `NavData` class, including how to initialize\n", + "instances and perform basic operations." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import gnss_lib_py as glp" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Get data path of example file\n", + "glp.make_dir(\"../data\")\n", + "!wget https://raw.githubusercontent.com/Stanford-NavLab/gnss_lib_py/main/notebooks/tutorials/data/myreceiver.csv --quiet -nc -O \"../data/myreceiver.csv\"\n", + "data_path = \"../data/myreceiver.csv\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# NavData Operations" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Create a NavData class from a csv file" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "navdata = glp.NavData(csv_path=data_path)\n", + "print(navdata)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Looping across a Time Row" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "You can use the `NavData.loop_time()` method to loop over groups of data that belong to same time stamp." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "for timestamp, delta_t, navdata_subset in glp.loop_time(navdata,'myTimestamp'):\n", + " print('Current timestamp: ', timestamp)\n", + " print('Difference between current and future time step', delta_t)\n", + " print('Current group of data')\n", + " print(navdata_subset)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Concatenating NavData Instances" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Use the `glp.concat()` method to concatenate two or more `NavData` instances. Each type of data is included in a row, so adding new rows with ``axis=0``, means adding new types of data. Concat requires that the new NavData matches the length of the existing NavData. Row concatenation assumes the same ordering within rows across both NavData instances (e.g. sorted by timestamp) and does not perform any matching/sorting itself. If the concatenating navdatas share a column name with ``axis=0`` then concat will add a suffix to create a unique row name." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "double_navdata = glp.concat(navdata, navdata, axis=0)\n", + "double_navdata" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "You can also concatenate new data to existing rows with ``axis=1``. If the row names of the new NavData instance don't match the row names ofthe existing NavData instance, the mismatched values will be filled with np.nan." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "glp.concat(double_navdata, navdata, axis=1)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Sorting a NavData Instance based on Row Values" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Interpolate NaN values in a NavData Row" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.9" + }, + "vscode": { + "interpreter": { + "hash": "c7717b1dd2ec65abd747d44a25869d062db68d19263f8e701e26dddb0b153342" + } + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/notebooks/tutorials/parsers.ipynb b/notebooks/tutorials/parsers.ipynb deleted file mode 100644 index 9f935527..00000000 --- a/notebooks/tutorials/parsers.ipynb +++ /dev/null @@ -1,1079 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "e8cfabd7", - "metadata": {}, - "source": [ - "This tutorial explains details about existing parsers and how to create a new parser if necessary." - ] - }, - { - "cell_type": "markdown", - "id": "d5632d85", - "metadata": {}, - "source": [ - "Load `gnss_lib_py` into the Python workspace" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "f7468fd3", - "metadata": {}, - "outputs": [], - "source": [ - "import gnss_lib_py as glp" - ] - }, - { - "cell_type": "markdown", - "id": "76ee7e5e", - "metadata": {}, - "source": [ - "# 2023 Google Android Derived Dataset" - ] - }, - { - "cell_type": "markdown", - "id": "ceac98ce", - "metadata": {}, - "source": [ - "This data comes from the 2023 Google Smartphone Decimeter Challenge and can be downloaded from [Kaggle](https://www.kaggle.com/competitions/smartphone-decimeter-2023)." - ] - }, - { - "cell_type": "markdown", - "id": "e2ec49c6", - "metadata": {}, - "source": [ - "Loading the data into an instance of `NavData` is as easy as creating an instance of `AndroidDerived2023` with the relevant file path." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "c335608f", - "metadata": {}, - "outputs": [], - "source": [ - "# download Android data file\n", - "!wget https://raw.githubusercontent.com/Stanford-NavLab/gnss_lib_py/main/data/unit_test/google_decimeter_2023/2023-09-07-18-59-us-ca/pixel7pro/device_gnss.csv --quiet -O \"device_gnss.csv\"\n", - "# load Android Google Challenge data\n", - "derived_data = glp.AndroidDerived2023(\"device_gnss.csv\")" - ] - }, - { - "cell_type": "markdown", - "id": "eb53fe00", - "metadata": {}, - "source": [ - "We can verify that the data loaded correctly by printing the shape and rows of the imported data." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "01b5d56a", - "metadata": {}, - "outputs": [], - "source": [ - "derived_data.shape" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "cc2d26d9", - "metadata": {}, - "outputs": [], - "source": [ - "derived_data.rows" - ] - }, - { - "cell_type": "markdown", - "id": "076d5ea5", - "metadata": {}, - "source": [ - "# 2023 Google Android Ground Truth\n", - "We can similarly load in the ground truth data from the same 2023 Google Smartphone Decimeter Challenge." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "21581d69", - "metadata": {}, - "outputs": [], - "source": [ - "# download Android data file\n", - "!wget https://raw.githubusercontent.com/Stanford-NavLab/gnss_lib_py/main/data/unit_test/google_decimeter_2023/2023-09-07-18-59-us-ca/pixel7pro/ground_truth.csv --quiet -O \"ground_truth.csv\"\n", - "# load Android Google Challenge ground truth data\n", - "gt_data = glp.AndroidGroundTruth2023(\"ground_truth.csv\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "ef4a123a", - "metadata": {}, - "outputs": [], - "source": [ - "gt_data.shape" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "9f327d87", - "metadata": {}, - "outputs": [], - "source": [ - "gt_data.rows" - ] - }, - { - "cell_type": "markdown", - "id": "c849214b", - "metadata": {}, - "source": [ - "# 2023/2022Google Decimeter Kaggle Challenge\n", - "There are utility functions to prepare Kaggle submissions for the [2023 challenge](https://www.kaggle.com/competitions/smartphone-decimeter-2023) and [2022 Challenge](https://www.kaggle.com/c/smartphone-decimeter-2022)." - ] - }, - { - "cell_type": "markdown", - "id": "83ac7998", - "metadata": {}, - "source": [ - "We offer a function to convert the provided Weighted Least Squares baseline solution into the standard `state_estimate` format found throughout `gnss_lib_py`. Simply pass in the derived data NavData object." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "a990ec31", - "metadata": {}, - "outputs": [], - "source": [ - "state_estimate = glp.solve_kaggle_baseline(derived_data)\n", - "\n", - "print(state_estimate)" - ] - }, - { - "cell_type": "markdown", - "id": "f1504d5d", - "metadata": {}, - "source": [ - "`prepare_kaggle_submission` can be used to convert the standard `state_estimate` format to a NavData object with the same rows and row names which the 2023 and 2022 Kaggle competitions expect. The `trip_id` is a combination of the trajectory trace name and phone name." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "22f43b10", - "metadata": {}, - "outputs": [], - "source": [ - "solution = glp.prepare_kaggle_submission(state_estimate, trip_id = \"my_trace/my_phone\")\n", - "\n", - "print(solution)" - ] - }, - { - "cell_type": "markdown", - "id": "7e8879f2", - "metadata": {}, - "source": [ - "`solve_kaggle_dataset` can be used to automatically iterate through all trace trajectory names and phone names, estimate the state using the provided solver, and concatenate all state estimates together for a single submission. The `solver` variable could use `solve_kaggle_baseline`, `solve_wls`, or `solve_gnss_ekf` for example.\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "49659881", - "metadata": {}, - "outputs": [], - "source": [ - "# submission = glp.solve_kaggle_dataset(folder_path = \"/path/to/train/or/test/folder/\", \n", - "# solver = glp.solve_wls)\n", - "\n", - "# after the submission NavData object is created, save it to a csv with:\n", - "# submission.to_csv(\"file_path.csv\")" - ] - }, - { - "cell_type": "markdown", - "id": "bbcd42da-3966-430c-bf7b-1fe133048ffc", - "metadata": {}, - "source": [ - "# 2022 Google Android Derived Dataset" - ] - }, - { - "cell_type": "markdown", - "id": "0ac8bc0e-43f3-4038-922a-69a04bef40a9", - "metadata": {}, - "source": [ - "This data comes from the 2022 Google Smartphone Decimeter Challenge and can be downloaded from [Kaggle](https://www.kaggle.com/c/smartphone-decimeter-2022)." - ] - }, - { - "cell_type": "markdown", - "id": "eba0d425-5628-4974-a310-fe25ab3762d7", - "metadata": {}, - "source": [ - "Loading the data into an instance of `NavData` is as easy as creating an instance of `AndroidDerived2022` with the relevant file path." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "325b2cc3-eed0-43b3-8894-1587651796d6", - "metadata": {}, - "outputs": [], - "source": [ - "# download Android data file\n", - "!wget https://raw.githubusercontent.com/Stanford-NavLab/gnss_lib_py/main/data/unit_test/google_decimeter_2022/device_gnss.csv --quiet -O \"device_gnss.csv\"\n", - "# load Android Google Challenge data\n", - "derived_data = glp.AndroidDerived2022(\"device_gnss.csv\")" - ] - }, - { - "cell_type": "markdown", - "id": "87c424b4-b1a3-49c1-834e-467953535006", - "metadata": {}, - "source": [ - "We can verify that the data loaded correctly by printing the shape and rows of the imported data." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "86d1f429-758e-4329-b79e-164f8bfe221d", - "metadata": {}, - "outputs": [], - "source": [ - "derived_data.shape" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "1814049c-9c2c-4a0b-b924-1b688168f1b6", - "metadata": {}, - "outputs": [], - "source": [ - "derived_data.rows" - ] - }, - { - "cell_type": "markdown", - "id": "b9a17a27-3413-4e3d-bd88-fa931446ffc1", - "metadata": {}, - "source": [ - "# 2022 Google Android Ground Truth\n", - "We can similarly load in the ground truth data from the same 2022 Google Smartphone Decimeter Challenge." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "42223d7f-c7ce-409b-8526-c7736b759965", - "metadata": {}, - "outputs": [], - "source": [ - "# download Android data file\n", - "!wget https://raw.githubusercontent.com/Stanford-NavLab/gnss_lib_py/main/data/unit_test/google_decimeter_2022/ground_truth.csv --quiet -O \"ground_truth.csv\"\n", - "# load Android Google Challenge ground truth data\n", - "gt_data = glp.AndroidGroundTruth2022(\"ground_truth.csv\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "30548ab0-8706-48b0-ac68-69b7ce7d90f4", - "metadata": {}, - "outputs": [], - "source": [ - "gt_data.shape" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "1951e173-c17e-4d2e-8a99-81c7c0723442", - "metadata": {}, - "outputs": [], - "source": [ - "gt_data.rows" - ] - }, - { - "cell_type": "markdown", - "id": "0f78bfdb", - "metadata": {}, - "source": [ - "# 2021 Google Android Derived Dataset" - ] - }, - { - "cell_type": "markdown", - "id": "b43a523e", - "metadata": {}, - "source": [ - "This data comes from the 2021 Google Smartphone Decimeter Challenge and can be downloaded from [Kaggle](https://www.kaggle.com/c/google-smartphone-decimeter-challenge)." - ] - }, - { - "cell_type": "markdown", - "id": "e12cecb0", - "metadata": {}, - "source": [ - "Loading the data into an instance of `NavData` is as easy as creating an instance of `AndroidDerived2021` with the relevant file path." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "6447cd99", - "metadata": {}, - "outputs": [], - "source": [ - "# download Android data file\n", - "!wget https://raw.githubusercontent.com/Stanford-NavLab/gnss_lib_py/main/data/unit_test/google_decimeter_2021/Pixel4_derived.csv --quiet -O \"Pixel4_derived.csv\"\n", - "# load Android Google Challenge data\n", - "derived_data = glp.AndroidDerived2021(\"Pixel4_derived.csv\")" - ] - }, - { - "cell_type": "markdown", - "id": "00bc1b39", - "metadata": {}, - "source": [ - "We can verify that the data loaded correctly by printing the shape and rows of the imported data." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "407270ad", - "metadata": {}, - "outputs": [], - "source": [ - "derived_data.shape" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "2ac2fc75", - "metadata": {}, - "outputs": [], - "source": [ - "derived_data.rows" - ] - }, - { - "cell_type": "markdown", - "id": "602a7f1d", - "metadata": {}, - "source": [ - "# 2021 Google Android Ground Truth\n", - "We can similarly load in the ground truth data from the same 2021 Google Smartphone Decimeter Challenge." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "8721297a", - "metadata": {}, - "outputs": [], - "source": [ - "# download Android data file\n", - "!wget https://raw.githubusercontent.com/Stanford-NavLab/gnss_lib_py/main/data/unit_test/google_decimeter_2021/Pixel4_ground_truth.csv --quiet -O \"Pixel4_ground_truth.csv\"\n", - "# load Android Google Challenge ground truth data\n", - "gt_data = glp.AndroidGroundTruth2021(\"Pixel4_ground_truth.csv\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "35cba5a4", - "metadata": {}, - "outputs": [], - "source": [ - "gt_data.shape" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "a1b0e8b6", - "metadata": {}, - "outputs": [], - "source": [ - "gt_data.rows" - ] - }, - { - "cell_type": "markdown", - "id": "95e58b71", - "metadata": {}, - "source": [ - "# TU Chemnitz SmartLoc\n", - "This tutorial shows how to load data from TU Chemnitz's [smartLoc GNSS Dataset](https://www.tu-chemnitz.de/projekt/smartLoc/gnss_dataset.html.en#Datasets)." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "714dbb21", - "metadata": {}, - "outputs": [], - "source": [ - "import gnss_lib_py as glp" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "b369035d", - "metadata": {}, - "outputs": [], - "source": [ - "# download cropped SmartLoc data file\n", - "!wget https://raw.githubusercontent.com/Stanford-NavLab/gnss_lib_py/main/data/unit_test/smartloc/tu_chemnitz_berlin_1_raw.csv --quiet -O \"smartloc.csv\"\n", - " \n", - "# load smartLoc data into NavData object\n", - "smartloc_data = glp.SmartLocRaw(\"smartloc.csv\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "4dd67679", - "metadata": {}, - "outputs": [], - "source": [ - "# plot the pseudorange over time of each individual satellite\n", - "# SBAS 120 is the outlier with its larger orbit\n", - "fig = glp.plot_metric(smartloc_data, \"gps_millis\",\"raw_pr_m\", groupby=\"sv_id\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "fe8d92d7", - "metadata": { - "scrolled": true - }, - "outputs": [], - "source": [ - "# show the ground truth smartLoc data on a map\n", - "fig = glp.plot_map(smartloc_data)\n", - "fig.show()" - ] - }, - { - "cell_type": "markdown", - "id": "e93e4676", - "metadata": {}, - "source": [ - "# SP3 File Parsing" - ] - }, - { - "cell_type": "markdown", - "id": "d1ce069f", - "metadata": {}, - "source": [ - "This tutorial shows how to load SP3 files." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "c0756386", - "metadata": {}, - "outputs": [], - "source": [ - "# download an example .sp3 data file\n", - "!wget https://raw.githubusercontent.com/Stanford-NavLab/gnss_lib_py/main/data/unit_test/sp3/COD0MGXFIN_20211180000_01D_05M_ORB.SP3 --quiet -O \"COD0MGXFIN_20211180000_01D_05M_ORB.SP3\"\n", - "# Specify .sp3 file path to extract precise ephemerides\n", - "sp3_path = \"COD0MGXFIN_20211180000_01D_05M_ORB.SP3\"" - ] - }, - { - "cell_type": "markdown", - "id": "2ad4d614", - "metadata": {}, - "source": [ - "Use the SP3 class loader to load in the SP3 file. The class can also optionally take multiple files as a list." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "5906e3cb", - "metadata": {}, - "outputs": [], - "source": [ - "sp3 = glp.Sp3(sp3_path)\n", - "sp3" - ] - }, - { - "cell_type": "markdown", - "id": "73352028", - "metadata": {}, - "source": [ - "To visualize the results, we'll plot the ECEF x position of the first 10 GPS satellites." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "76834337", - "metadata": {}, - "outputs": [], - "source": [ - "sp3_first_ten_gps = sp3.where(\"gnss_id\",\"gps\").where(\"sv_id\",10,\"leq\")\n", - "fig = glp.plot_metric_by_constellation(sp3_first_ten_gps,\"gps_millis\",\"x_sv_m\")" - ] - }, - { - "cell_type": "markdown", - "id": "c351c2eb", - "metadata": {}, - "source": [ - "# CLK File Parsing" - ] - }, - { - "cell_type": "markdown", - "id": "9519121f", - "metadata": {}, - "source": [ - "This tutorial shows how to load CLK files." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "102f55d9", - "metadata": {}, - "outputs": [], - "source": [ - "# download an example .clk data file\n", - "!wget https://raw.githubusercontent.com/Stanford-NavLab/gnss_lib_py/main/data/unit_test/clk/COD0MGXFIN_20211180000_01D_30S_CLK.CLK --quiet -O \"COD0MGXFIN_20211180000_01D_30S_CLK.CLK\"\n", - "# Specify .clk file path to extract precise ephemerides\n", - "clk_path = \"COD0MGXFIN_20211180000_01D_30S_CLK.CLK\"" - ] - }, - { - "cell_type": "markdown", - "id": "b90a33e5", - "metadata": {}, - "source": [ - "Use the Clk class loader to load in the CLK file. The class can also optionally take multiple files as a list." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "6ab5eb6c", - "metadata": {}, - "outputs": [], - "source": [ - "clk = glp.Clk(clk_path)\n", - "clk" - ] - }, - { - "cell_type": "markdown", - "id": "b87c055c", - "metadata": {}, - "source": [ - "To visualize the results, we'll plot the clock bias of the first BeiDou satellites." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "b1a1b9b7", - "metadata": { - "scrolled": true - }, - "outputs": [], - "source": [ - "clk_first_beidou = clk.where(\"gnss_id\",\"beidou\").where(\"sv_id\",16,\"leq\")\n", - "fig = glp.plot_metric_by_constellation(clk_first_beidou,\"b_sv_m\")" - ] - }, - { - "cell_type": "markdown", - "id": "b0a917b6", - "metadata": {}, - "source": [ - "# NMEA File Parsing" - ] - }, - { - "cell_type": "markdown", - "id": "f166df97", - "metadata": {}, - "source": [ - "NMEA is a file standard for storing and transferring position data and GPS measurements.\n", - "`gnss_lib_py` has functionality for reading NMEA files and loading the data into a `NavData`, which we demonstrate next." - ] - }, - { - "cell_type": "markdown", - "id": "aa0ea320", - "metadata": {}, - "source": [ - "Each NMEA sentence has a header eg. `$GPGGA` which describes whether the message is propreitary or general purpose and the type of message.\n", - "In this case, the message is `GGA`. `gnss_lib_py` currently supports `GGA` and `RMC` message types.\n", - "\n", - "Each NMEA sentence also comes with a checksum, which may appear after the '*' in each sentence.\n", - "In case the checksums are to be checked, pass the parameter `check=True` to the `Nmea` initialization." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "cbae66a6", - "metadata": {}, - "outputs": [], - "source": [ - "# download NMEA data and load it into NavData instance\n", - "!wget https://raw.githubusercontent.com/Stanford-NavLab/gnss_lib_py/main/data/unit_test/nmea/nmea_w_correct_checksum.nmea --quiet -O \"nmea_w_correct_checksum.nmea\"\n", - "# Load the NMEA file into a NavData structure\n", - "nmea_navdata = glp.Nmea('nmea_w_correct_checksum.nmea')\n", - "print('Loaded NMEA data\\n', nmea_navdata)" - ] - }, - { - "cell_type": "markdown", - "id": "6606e32a", - "metadata": {}, - "source": [ - "If the checksum is not to be checked, pass the parameter `check=False` to the initialization." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "81034771", - "metadata": {}, - "outputs": [], - "source": [ - "!wget https://raw.githubusercontent.com/Stanford-NavLab/gnss_lib_py/main/data/unit_test/nmea/nmea_no_checksum.nmea --quiet -O \"nmea_w_no_checksum.nmea\"\n", - "# Load the NMEA file into a NavData structure\n", - "nmea_navdata = glp.Nmea('nmea_w_no_checksum.nmea', check=False)\n", - "print('Loaded NMEA data\\n', nmea_navdata)" - ] - }, - { - "cell_type": "markdown", - "id": "a87c37c2", - "metadata": {}, - "source": [ - "NMEA GGA and RMC sentences store latitude and longitude coordinates in a `ddmm.mmmmmmm` format along with a cardinal direction like `N` or `W`.\n", - "\n", - "By default, these coordinates are transformed into decimal degrees but the original data format can be retained in the final loaded `NavData`.\n", - "Also, the LLH coordinates can be transformed to ECEF coordinates." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "a74a2f16", - "metadata": {}, - "outputs": [], - "source": [ - "nmea_navdata = glp.Nmea('nmea_w_correct_checksum.nmea', keep_raw=True, include_ecef=True)\n", - "print('Loaded NMEA data with raw data and ECEF coordinates\\n', nmea_navdata)" - ] - }, - { - "cell_type": "markdown", - "id": "fe8e8d6a", - "metadata": {}, - "source": [ - "# Rinex Observation File Parsing" - ] - }, - { - "cell_type": "markdown", - "id": "af135db1", - "metadata": {}, - "source": [ - "Rinex is another file standard that is used in the GNSS community to store and transmit navigation information.\n", - "Files with the extension `.yyo`, where `yy` is the year in which the measurement was made, are used to store and transmit\n", - "measurements.\n", - "These measurement files can contain any constellation and each measurement usually contains the pseudorange, carrier phase (or difference from carrier frequency),\n", - "doppler, and signal-to-noise ratio measurements.\n", - "In the following lines, we show how to load a ``.o`` file into a NavData instance." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "9f279032", - "metadata": {}, - "outputs": [], - "source": [ - "# download Rinex obs file and load it into NavData instance\n", - "!wget https://raw.githubusercontent.com/Stanford-NavLab/gnss_lib_py/main/data/unit_test/rinex/obs/rinex_obs_mixed_types.20o --quiet -O \"rinex_obs_mixed_types.20o\"\n", - "rinex_obs_3 = glp.RinexObs(\"rinex_obs_mixed_types.20o\")\n", - "print('Loaded Rinex Obs 3 data for the first time instant\\n', \\\n", - " rinex_obs_3.where('gps_millis', rinex_obs_3['gps_millis', 0], 'eq'))" - ] - }, - { - "cell_type": "markdown", - "id": "8f1f2cd2", - "metadata": {}, - "source": [ - "# Rinex Navigation File Parsing" - ] - }, - { - "cell_type": "markdown", - "id": "63352eb8", - "metadata": {}, - "source": [ - "Rinex Navigation files can be loaded using `RinexNav`" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "b034c805", - "metadata": {}, - "outputs": [], - "source": [ - "# download example Rinex navigation file\n", - "!wget https://raw.githubusercontent.com/Stanford-NavLab/gnss_lib_py/main/data/unit_test/rinex/nav/brdc1370.20n --quiet -O \"brdc1370.20n\"" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "f14e71ef", - "metadata": {}, - "outputs": [], - "source": [ - "# load into NavData instance\n", - "rinex_nav = glp.RinexNav(\"brdc1370.20n\")\n", - "rinex_nav" - ] - }, - { - "cell_type": "markdown", - "id": "eb016e74", - "metadata": {}, - "source": [ - "# How to Create a New NavData Class" - ] - }, - { - "cell_type": "markdown", - "id": "28dad153", - "metadata": {}, - "source": [ - "The modular and versatile functionality of this gnss_lib_py repository is enabled by loading all data types into a custom Python NavData class. If you are using a type of data or dataset that is not yet supported, you will need to create a new child class of the NavData Python class. This tutorial will guide you on how to set up your new Python child class. Once complete, please feel free to submit a pull request to our GitHub repository so other users can make use of the added functionality." - ] - }, - { - "cell_type": "markdown", - "id": "69598dcc", - "metadata": {}, - "source": [ - "For this example, say that we have a new type of data called MyReceiver that is a csv file with columns of a timestamp, satellite identifier, and pseudorange. The contents of a sample `myreceiver.csv` is the following:\n", - "\n", - "| myTimestamp | mySatId | myPseudorange |\n", - "| ----------- | ------- | ------------- |\n", - "| 10 | 10 | 270000001 |\n", - "| 10 | 14 | 270000007 |\n", - "| 10 | 7 | 270000004 |\n", - "| 10 | 3 | 270000005 |\n", - "| 11 | 10 | 270000002 |\n", - "| 11 | 14 | 270000008 |\n", - "| 11 | 7 | 270000003 |\n", - "| 11 | 3 | 270000004 |" - ] - }, - { - "cell_type": "markdown", - "id": "fedf66f3", - "metadata": {}, - "source": [ - "The first step is importing the base `NavData` class and creating a new class type that inherits from `NavData`" - ] - }, - { - "cell_type": "markdown", - "id": "20c325d6", - "metadata": {}, - "source": [ - "```python\n", - "class MyReceiver(NavData):\n", - " \"\"\"Class handling measurements from MyReceiver.\n", - "\n", - " Inherits from NavData().\n", - " \"\"\"\n", - "```" - ] - }, - { - "cell_type": "markdown", - "id": "c60a6fe4", - "metadata": {}, - "source": [ - "The `__init__` function should have a call to the parent `NavData` `__init__` function. Based on your data input, you should call the corresponding initializer.\n", - "\n", - "For csv files, call: `super().__init__(csv_path=input_path)` \n", - "For pandas DataFrames, call `super().__init__(pandas_df=input_path)` \n", - "For numpy ndarrays, call `super().__init__(numpy_array=input_path)` \n", - "\n", - "In our case, we have a csv file, so our `__init__` function looks like the following:" - ] - }, - { - "cell_type": "markdown", - "id": "d51dff11", - "metadata": {}, - "source": [ - "```python\n", - "def __init__(self, input_path):\n", - " \"\"\"MyReceive specific loading and preprocessing\n", - "\n", - " Parameters\n", - " ----------\n", - " input_path : string\n", - " Path to MyReceiver csv file\n", - "\n", - " \"\"\"\n", - "\n", - " # call NavData initialization with csv path\n", - " super().__init__(csv_path=input_path)\n", - "```" - ] - }, - { - "cell_type": "markdown", - "id": "4d8dc67f", - "metadata": {}, - "source": [ - "After our data is loaded, we may want to make known changes to our data. We can make those changes by defining a `postprocess` function. NavData's `__init__` function that we call in our initializer already makes a call to the `postprocess` function, so we don't have to repeat that call in `MyReceiver`'s `__init__` function." - ] - }, - { - "cell_type": "markdown", - "id": "c7d9b213", - "metadata": {}, - "source": [ - "One thing that we need to do to make use of the common functionality of `gnss_lib_py` is to standardize the names of our variables. See the [Standard Naming Conventions](https://gnss-lib-py.readthedocs.io/en/latest/reference/reference.html#standard-naming-conventions) section in the Reference tab of the documentation for the list of standardized names.\n", - "\n", - "In our case, we will convert `mySatId` to `sv_id` and `myPseudorange` to `raw_pr_m`. We make these conversions by simply updating the `_row_map` function." - ] - }, - { - "cell_type": "markdown", - "id": "bee9b8a1", - "metadata": {}, - "source": [ - "```python\n", - "\n", - "\n", - "@staticmethod\n", - "def _row_map():\n", - " \"\"\"Map of row names from loaded to gnss_lib_py standard\n", - "\n", - " Returns\n", - " -------\n", - " row_map : Dict\n", - " Dictionary of the form {old_name : new_name}\n", - " \"\"\"\n", - " row_map = {'mySatId' : 'sv_id',\n", - " 'myPseudorange' : 'raw_pr_m',\n", - " }\n", - " return row_map\n", - "```" - ] - }, - { - "cell_type": "markdown", - "id": "dbbe8d55", - "metadata": {}, - "source": [ - "As an additional postprocessing step, we may want to offset our pseudorange due to a known error or create the common timestamp variable `gps_millis` based on our unique timestamp row. Adding the `gps_millis` row enables the use of some of the common algorithms. The [time conversion utilities](https://gnss-lib-py.readthedocs.io/en/latest/tutorials/tutorials_utilities_notebook.html) can be used to create `gps_millis` from the GPS Week & Time of week, GPS milliseconds, or a datetime object." - ] - }, - { - "cell_type": "markdown", - "id": "97fbeb7b", - "metadata": {}, - "source": [ - "```python\n", - "# correct pseudorange\n", - "self['corr_pr_m'] = self['raw_pr_m'] + 100.\n", - "\n", - "# create common timestamp\n", - "self['gps_millis'] = self['myTimestamp'] + 5629719023\n", - "```" - ] - }, - { - "cell_type": "markdown", - "id": "72884667", - "metadata": {}, - "source": [ - "Putting it all together, we have:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "956af339", - "metadata": {}, - "outputs": [], - "source": [ - "from gnss_lib_py import NavData\n", - "\n", - "class MyReceiver(NavData):\n", - " \"\"\"Class handling measurements from MyReceiver.\n", - "\n", - " Inherits from NavData().\n", - " \"\"\"\n", - " def __init__(self, input_path):\n", - " \"\"\"MyReceive specific loading and preprocessing\n", - "\n", - " Parameters\n", - " ----------\n", - " input_path : string\n", - " Path to MyReceiver csv file\n", - "\n", - " \"\"\"\n", - " \n", - " # call NavData initialization with csv path\n", - " super().__init__(csv_path=input_path)\n", - " \n", - " def postprocess(self):\n", - " \"\"\"MyReceiver specific postprocessing\n", - "\n", - " \"\"\"\n", - "\n", - " # correct pseudorange\n", - " self['corr_pr_m'] = self['raw_pr_m'] + 100.\n", - " \n", - " # create common timestamp\n", - " self['gps_millis'] = self['myTimestamp'] + 1659075505350\n", - " \n", - "\n", - " @staticmethod\n", - " def _row_map():\n", - " \"\"\"Map of row names from loaded to gnss_lib_py standard\n", - "\n", - " Returns\n", - " -------\n", - " row_map : Dict\n", - " Dictionary of the form {old_name : new_name}\n", - " \"\"\"\n", - " row_map = {'mySatId' : 'sv_id',\n", - " 'myPseudorange' : 'raw_pr_m',\n", - " }\n", - " return row_map" - ] - }, - { - "cell_type": "markdown", - "id": "1ccadb47", - "metadata": {}, - "source": [ - "We can now create a instance of our new `MyReceiver` class with the path to our csv called `myreceiver.csv`." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "b611a39f", - "metadata": {}, - "outputs": [], - "source": [ - "# download myreceiver.csv file\n", - "!wget https://raw.githubusercontent.com/Stanford-NavLab/gnss_lib_py/main/notebooks/tutorials/myreceiver.csv --quiet -O \"myreceiver.csv\"\n", - "\n", - "# create instance of MyReceiver\n", - "my_receiver_data = MyReceiver(\"myreceiver.csv\")" - ] - }, - { - "cell_type": "markdown", - "id": "52b64751", - "metadata": {}, - "source": [ - "Let's print out our corrected pseudorange to make sure everything worked correctly." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "bae52fb6", - "metadata": {}, - "outputs": [], - "source": [ - "my_receiver_data[\"corr_pr_m\"]" - ] - }, - { - "cell_type": "markdown", - "id": "fd3eea3d", - "metadata": {}, - "source": [ - "We can now take advantage of all the tools `gnss_lib_py` has to offer!" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "bad31b8a", - "metadata": {}, - "outputs": [], - "source": [ - "fig = glp.plot_metric(my_receiver_data,\"gps_millis\",\"corr_pr_m\",groupby=\"sv_id\")" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.8.9" - }, - "vscode": { - "interpreter": { - "hash": "c7717b1dd2ec65abd747d44a25869d062db68d19263f8e701e26dddb0b153342" - } - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/notebooks/tutorials/android.ipynb b/notebooks/tutorials/parsers/android.ipynb similarity index 96% rename from notebooks/tutorials/android.ipynb rename to notebooks/tutorials/parsers/android.ipynb index 91abb18b..376d4d95 100644 --- a/notebooks/tutorials/android.ipynb +++ b/notebooks/tutorials/parsers/android.ipynb @@ -73,7 +73,8 @@ "metadata": {}, "outputs": [], "source": [ - "!wget https://raw.githubusercontent.com/Stanford-NavLab/gnss_lib_py/main/data/unit_test/android/measurements/pixel6.txt --quiet -O \"gnss_log.txt\"" + "glp.make_dir(\"../data\")\n", + "!wget https://raw.githubusercontent.com/Stanford-NavLab/gnss_lib_py/main/data/unit_test/android/measurements/pixel6.txt --quiet -nc -O \"../data/gnss_log.txt\"" ] }, { @@ -91,7 +92,7 @@ "metadata": {}, "outputs": [], "source": [ - "fix_data = glp.AndroidRawFixes(\"gnss_log.txt\")" + "fix_data = glp.AndroidRawFixes(\"../data/gnss_log.txt\")" ] }, { @@ -190,7 +191,7 @@ "metadata": {}, "outputs": [], "source": [ - "raw_data = glp.AndroidRawGnss(input_path=\"gnss_log.txt\", \n", + "raw_data = glp.AndroidRawGnss(input_path=\"../data/gnss_log.txt\", \n", " filter_measurements=True, \n", " measurement_filters={\"sv_time_uncertainty\" : 500.},\n", " verbose=True)" @@ -349,7 +350,8 @@ "metadata": {}, "outputs": [], "source": [ - "!wget https://raw.githubusercontent.com/Stanford-NavLab/gnss_lib_py/main/data/unit_test/android/nmea/pixel6.nmea --quiet -O \"gnss_log.nmea\"" + "glp.make_dir(\"../data\")\n", + "!wget https://raw.githubusercontent.com/Stanford-NavLab/gnss_lib_py/main/data/unit_test/android/nmea/pixel6.nmea --quiet -nc -O \"../data/gnss_log.nmea\"" ] }, { @@ -367,7 +369,7 @@ "metadata": {}, "outputs": [], "source": [ - "nmea_data = glp.Nmea(\"gnss_log.nmea\")" + "nmea_data = glp.Nmea(\"../data/gnss_log.nmea\")" ] }, { @@ -452,7 +454,8 @@ "metadata": {}, "outputs": [], "source": [ - "!wget https://raw.githubusercontent.com/Stanford-NavLab/gnss_lib_py/main/data/unit_test/android/rinex_obs/pixel6.23o --quiet -O \"gnss_log.23o\"" + "glp.make_dir(\"../data\")\n", + "!wget https://raw.githubusercontent.com/Stanford-NavLab/gnss_lib_py/main/data/unit_test/android/rinex_obs/pixel6.23o --quiet -nc -O \"../data/gnss_log.23o\"" ] }, { @@ -470,7 +473,7 @@ "metadata": {}, "outputs": [], "source": [ - "rinex_data = glp.RinexObs(\"gnss_log.23o\")" + "rinex_data = glp.RinexObs(\"../data/gnss_log.23o\")" ] }, { @@ -614,7 +617,7 @@ "metadata": {}, "outputs": [], "source": [ - "raw_data = glp.AndroidRawAccel(input_path=\"gnss_log.txt\")" + "raw_data = glp.AndroidRawAccel(input_path=\"../data/gnss_log.txt\")" ] }, { @@ -646,7 +649,7 @@ "metadata": {}, "outputs": [], "source": [ - "raw_data = glp.AndroidRawGyro(input_path=\"gnss_log.txt\")" + "raw_data = glp.AndroidRawGyro(input_path=\"../data/gnss_log.txt\")" ] }, { @@ -678,7 +681,7 @@ "metadata": {}, "outputs": [], "source": [ - "raw_data = glp.AndroidRawMag(input_path=\"gnss_log.txt\")" + "raw_data = glp.AndroidRawMag(input_path=\"../data/gnss_log.txt\")" ] }, { @@ -710,7 +713,7 @@ "metadata": {}, "outputs": [], "source": [ - "raw_data = glp.AndroidRawOrientation(input_path=\"gnss_log.txt\")" + "raw_data = glp.AndroidRawOrientation(input_path=\"../data/gnss_log.txt\")" ] }, { diff --git a/notebooks/tutorials/parsers/clk.ipynb b/notebooks/tutorials/parsers/clk.ipynb new file mode 100644 index 00000000..bf1f9ab4 --- /dev/null +++ b/notebooks/tutorials/parsers/clk.ipynb @@ -0,0 +1,118 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "d5632d85", + "metadata": {}, + "source": [ + "Load `gnss_lib_py` into the Python workspace" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f7468fd3", + "metadata": {}, + "outputs": [], + "source": [ + "import gnss_lib_py as glp" + ] + }, + { + "cell_type": "markdown", + "id": "c351c2eb", + "metadata": {}, + "source": [ + "# CLK File Parsing" + ] + }, + { + "cell_type": "markdown", + "id": "9519121f", + "metadata": {}, + "source": [ + "This tutorial shows how to load CLK files." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "102f55d9", + "metadata": {}, + "outputs": [], + "source": [ + "# download an example .clk data file\n", + "glp.make_dir(\"../data\")\n", + "!wget https://raw.githubusercontent.com/Stanford-NavLab/gnss_lib_py/main/data/unit_test/clk/COD0MGXFIN_20211180000_01D_30S_CLK.CLK --quiet -nc -O \"../data/COD0MGXFIN_20211180000_01D_30S_CLK.CLK\"\n", + "# Specify .clk file path to extract precise ephemerides\n", + "clk_path = \"../data/COD0MGXFIN_20211180000_01D_30S_CLK.CLK\"" + ] + }, + { + "cell_type": "markdown", + "id": "b90a33e5", + "metadata": {}, + "source": [ + "Use the Clk class loader to load in the CLK file. The class can also optionally take multiple files as a list." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6ab5eb6c", + "metadata": {}, + "outputs": [], + "source": [ + "clk = glp.Clk(clk_path)\n", + "clk" + ] + }, + { + "cell_type": "markdown", + "id": "b87c055c", + "metadata": {}, + "source": [ + "To visualize the results, we'll plot the clock bias of the first BeiDou satellites." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b1a1b9b7", + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "clk_first_beidou = clk.where(\"gnss_id\",\"beidou\").where(\"sv_id\",16,\"leq\")\n", + "fig = glp.plot_metric_by_constellation(clk_first_beidou,\"b_sv_m\")" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.9" + }, + "vscode": { + "interpreter": { + "hash": "c7717b1dd2ec65abd747d44a25869d062db68d19263f8e701e26dddb0b153342" + } + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/notebooks/tutorials/parsers/google_decimeter.ipynb b/notebooks/tutorials/parsers/google_decimeter.ipynb new file mode 100644 index 00000000..875fccef --- /dev/null +++ b/notebooks/tutorials/parsers/google_decimeter.ipynb @@ -0,0 +1,462 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "3a8febc1-db4b-4f02-9ac6-5d726b96996c", + "metadata": {}, + "source": [ + "# Google Decimeter Challenge Datasets" + ] + }, + { + "cell_type": "markdown", + "id": "e8cfabd7", + "metadata": {}, + "source": [ + "This tutorial explains details about to use parsers and functions made for the 2021, 2022, and 2023 Google Smartphone Decimeter Challege datasets." + ] + }, + { + "cell_type": "markdown", + "id": "d5632d85", + "metadata": {}, + "source": [ + "Load `gnss_lib_py` into the Python workspace" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f7468fd3", + "metadata": {}, + "outputs": [], + "source": [ + "import gnss_lib_py as glp" + ] + }, + { + "cell_type": "markdown", + "id": "76ee7e5e", + "metadata": {}, + "source": [ + "## 2023 Google Android Derived Dataset" + ] + }, + { + "cell_type": "markdown", + "id": "ceac98ce", + "metadata": {}, + "source": [ + "This data comes from the 2023 Google Smartphone Decimeter Challenge and can be downloaded from [Kaggle](https://www.kaggle.com/competitions/smartphone-decimeter-2023)." + ] + }, + { + "cell_type": "markdown", + "id": "e2ec49c6", + "metadata": {}, + "source": [ + "Loading the data into an instance of `NavData` is as easy as creating an instance of `AndroidDerived2023` with the relevant file path." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c335608f", + "metadata": {}, + "outputs": [], + "source": [ + "# download Android data file\n", + "glp.make_dir(\"../data\")\n", + "!wget https://raw.githubusercontent.com/Stanford-NavLab/gnss_lib_py/main/data/unit_test/google_decimeter_2023/2023-09-07-18-59-us-ca/pixel7pro/device_gnss.csv --quiet -nc -O \"../data/device_gnss.csv\"\n", + "# load Android Google Challenge data\n", + "derived_data = glp.AndroidDerived2023(\"../data/device_gnss.csv\")" + ] + }, + { + "cell_type": "markdown", + "id": "eb53fe00", + "metadata": {}, + "source": [ + "We can verify that the data loaded correctly by printing the shape and rows of the imported data." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "01b5d56a", + "metadata": {}, + "outputs": [], + "source": [ + "derived_data.shape" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cc2d26d9", + "metadata": {}, + "outputs": [], + "source": [ + "derived_data.rows" + ] + }, + { + "cell_type": "markdown", + "id": "076d5ea5", + "metadata": {}, + "source": [ + "## 2023 Google Android Ground Truth\n", + "We can similarly load in the ground truth data from the same 2023 Google Smartphone Decimeter Challenge." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "21581d69", + "metadata": {}, + "outputs": [], + "source": [ + "# download Android data file\n", + "glp.make_dir(\"../data\")\n", + "!wget https://raw.githubusercontent.com/Stanford-NavLab/gnss_lib_py/main/data/unit_test/google_decimeter_2023/2023-09-07-18-59-us-ca/pixel7pro/ground_truth.csv --quiet -nc -O \"../data/ground_truth.csv\"\n", + "# load Android Google Challenge ground truth data\n", + "gt_data = glp.AndroidGroundTruth2023(\"../data/ground_truth.csv\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ef4a123a", + "metadata": {}, + "outputs": [], + "source": [ + "gt_data.shape" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9f327d87", + "metadata": {}, + "outputs": [], + "source": [ + "gt_data.rows" + ] + }, + { + "cell_type": "markdown", + "id": "c849214b", + "metadata": {}, + "source": [ + "## 2023/2022Google Decimeter Kaggle Challenge\n", + "There are utility functions to prepare Kaggle submissions for the [2023 challenge](https://www.kaggle.com/competitions/smartphone-decimeter-2023) and [2022 Challenge](https://www.kaggle.com/c/smartphone-decimeter-2022)." + ] + }, + { + "cell_type": "markdown", + "id": "83ac7998", + "metadata": {}, + "source": [ + "We offer a function to convert the provided Weighted Least Squares baseline solution into the standard `state_estimate` format found throughout `gnss_lib_py`. Simply pass in the derived data NavData object." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a990ec31", + "metadata": {}, + "outputs": [], + "source": [ + "state_estimate = glp.solve_kaggle_baseline(derived_data)\n", + "\n", + "print(state_estimate)" + ] + }, + { + "cell_type": "markdown", + "id": "f1504d5d", + "metadata": {}, + "source": [ + "`prepare_kaggle_submission` can be used to convert the standard `state_estimate` format to a NavData object with the same rows and row names which the 2023 and 2022 Kaggle competitions expect. The `trip_id` is a combination of the trajectory trace name and phone name." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "22f43b10", + "metadata": {}, + "outputs": [], + "source": [ + "solution = glp.prepare_kaggle_submission(state_estimate, trip_id = \"my_trace/my_phone\")\n", + "\n", + "print(solution)" + ] + }, + { + "cell_type": "markdown", + "id": "7e8879f2", + "metadata": {}, + "source": [ + "`solve_kaggle_dataset` can be used to automatically iterate through all trace trajectory names and phone names, estimate the state using the provided solver, and concatenate all state estimates together for a single submission. The `solver` variable could use `solve_kaggle_baseline`, `solve_wls`, or `solve_gnss_ekf` for example.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "49659881", + "metadata": {}, + "outputs": [], + "source": [ + "# submission = glp.solve_kaggle_dataset(folder_path = \"/path/to/train/or/test/folder/\", \n", + "# solver = glp.solve_wls)\n", + "\n", + "# after the submission NavData object is created, save it to a csv with:\n", + "# submission.to_csv(\"file_path.csv\")" + ] + }, + { + "cell_type": "markdown", + "id": "bbcd42da-3966-430c-bf7b-1fe133048ffc", + "metadata": {}, + "source": [ + "## 2022 Google Android Derived Dataset" + ] + }, + { + "cell_type": "markdown", + "id": "0ac8bc0e-43f3-4038-922a-69a04bef40a9", + "metadata": {}, + "source": [ + "This data comes from the 2022 Google Smartphone Decimeter Challenge and can be downloaded from [Kaggle](https://www.kaggle.com/c/smartphone-decimeter-2022)." + ] + }, + { + "cell_type": "markdown", + "id": "eba0d425-5628-4974-a310-fe25ab3762d7", + "metadata": {}, + "source": [ + "Loading the data into an instance of `NavData` is as easy as creating an instance of `AndroidDerived2022` with the relevant file path." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "325b2cc3-eed0-43b3-8894-1587651796d6", + "metadata": {}, + "outputs": [], + "source": [ + "# download Android data file\n", + "glp.make_dir(\"../data\")\n", + "!wget https://raw.githubusercontent.com/Stanford-NavLab/gnss_lib_py/main/data/unit_test/google_decimeter_2022/device_gnss.csv --quiet -nc -O \"../data/device_gnss.csv\"\n", + "# load Android Google Challenge data\n", + "derived_data = glp.AndroidDerived2022(\"../data/device_gnss.csv\")" + ] + }, + { + "cell_type": "markdown", + "id": "87c424b4-b1a3-49c1-834e-467953535006", + "metadata": {}, + "source": [ + "We can verify that the data loaded correctly by printing the shape and rows of the imported data." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "86d1f429-758e-4329-b79e-164f8bfe221d", + "metadata": {}, + "outputs": [], + "source": [ + "derived_data.shape" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1814049c-9c2c-4a0b-b924-1b688168f1b6", + "metadata": {}, + "outputs": [], + "source": [ + "derived_data.rows" + ] + }, + { + "cell_type": "markdown", + "id": "b9a17a27-3413-4e3d-bd88-fa931446ffc1", + "metadata": {}, + "source": [ + "## 2022 Google Android Ground Truth\n", + "We can similarly load in the ground truth data from the same 2022 Google Smartphone Decimeter Challenge." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "42223d7f-c7ce-409b-8526-c7736b759965", + "metadata": {}, + "outputs": [], + "source": [ + "# download Android data file\n", + "glp.make_dir(\"../data\")\n", + "!wget https://raw.githubusercontent.com/Stanford-NavLab/gnss_lib_py/main/data/unit_test/google_decimeter_2022/ground_truth.csv --quiet -nc -O \"../data/ground_truth.csv\"\n", + "# load Android Google Challenge ground truth data\n", + "gt_data = glp.AndroidGroundTruth2022(\"../data/ground_truth.csv\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "30548ab0-8706-48b0-ac68-69b7ce7d90f4", + "metadata": {}, + "outputs": [], + "source": [ + "gt_data.shape" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1951e173-c17e-4d2e-8a99-81c7c0723442", + "metadata": {}, + "outputs": [], + "source": [ + "gt_data.rows" + ] + }, + { + "cell_type": "markdown", + "id": "0f78bfdb", + "metadata": {}, + "source": [ + "## 2021 Google Android Derived Dataset" + ] + }, + { + "cell_type": "markdown", + "id": "b43a523e", + "metadata": {}, + "source": [ + "This data comes from the 2021 Google Smartphone Decimeter Challenge and can be downloaded from [Kaggle](https://www.kaggle.com/c/google-smartphone-decimeter-challenge)." + ] + }, + { + "cell_type": "markdown", + "id": "e12cecb0", + "metadata": {}, + "source": [ + "Loading the data into an instance of `NavData` is as easy as creating an instance of `AndroidDerived2021` with the relevant file path." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6447cd99", + "metadata": {}, + "outputs": [], + "source": [ + "# download Android data file\n", + "glp.make_dir(\"../data\")\n", + "!wget https://raw.githubusercontent.com/Stanford-NavLab/gnss_lib_py/main/data/unit_test/google_decimeter_2021/Pixel4_derived.csv --quiet -nc -O \"../data/Pixel4_derived.csv\"\n", + "# load Android Google Challenge data\n", + "derived_data = glp.AndroidDerived2021(\"../data/Pixel4_derived.csv\")" + ] + }, + { + "cell_type": "markdown", + "id": "00bc1b39", + "metadata": {}, + "source": [ + "We can verify that the data loaded correctly by printing the shape and rows of the imported data." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "407270ad", + "metadata": {}, + "outputs": [], + "source": [ + "derived_data.shape" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2ac2fc75", + "metadata": {}, + "outputs": [], + "source": [ + "derived_data.rows" + ] + }, + { + "cell_type": "markdown", + "id": "602a7f1d", + "metadata": {}, + "source": [ + "## 2021 Google Android Ground Truth\n", + "We can similarly load in the ground truth data from the same 2021 Google Smartphone Decimeter Challenge." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8721297a", + "metadata": {}, + "outputs": [], + "source": [ + "# download Android data file\n", + "glp.make_dir(\"../data\")\n", + "!wget https://raw.githubusercontent.com/Stanford-NavLab/gnss_lib_py/main/data/unit_test/google_decimeter_2021/Pixel4_ground_truth.csv --quiet -nc -O \"../data/Pixel4_ground_truth.csv\"\n", + "# load Android Google Challenge ground truth data\n", + "gt_data = glp.AndroidGroundTruth2021(\"../data/Pixel4_ground_truth.csv\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "35cba5a4", + "metadata": {}, + "outputs": [], + "source": [ + "gt_data.shape" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a1b0e8b6", + "metadata": {}, + "outputs": [], + "source": [ + "gt_data.rows" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.9" + }, + "vscode": { + "interpreter": { + "hash": "c7717b1dd2ec65abd747d44a25869d062db68d19263f8e701e26dddb0b153342" + } + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/notebooks/tutorials/parsers/new_parsers.ipynb b/notebooks/tutorials/parsers/new_parsers.ipynb new file mode 100644 index 00000000..07f04858 --- /dev/null +++ b/notebooks/tutorials/parsers/new_parsers.ipynb @@ -0,0 +1,331 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "eb016e74", + "metadata": {}, + "source": [ + "# How to Create a New NavData Class" + ] + }, + { + "cell_type": "markdown", + "id": "e8cfabd7", + "metadata": {}, + "source": [ + "This tutorial explains how to create a new parser if necessary." + ] + }, + { + "cell_type": "markdown", + "id": "d5632d85", + "metadata": {}, + "source": [ + "Load `gnss_lib_py` into the Python workspace" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f7468fd3", + "metadata": {}, + "outputs": [], + "source": [ + "import gnss_lib_py as glp" + ] + }, + { + "cell_type": "markdown", + "id": "28dad153", + "metadata": {}, + "source": [ + "The modular and versatile functionality of this gnss_lib_py repository is enabled by loading all data types into a custom Python NavData class. If you are using a type of data or dataset that is not yet supported, you will need to create a new child class of the NavData Python class. This tutorial will guide you on how to set up your new Python child class. Once complete, please feel free to submit a pull request to our GitHub repository so other users can make use of the added functionality." + ] + }, + { + "cell_type": "markdown", + "id": "69598dcc", + "metadata": {}, + "source": [ + "For this example, say that we have a new type of data called MyReceiver that is a csv file with columns of a timestamp, satellite identifier, and pseudorange. The contents of a sample `myreceiver.csv` is the following:\n", + "\n", + "| myTimestamp | mySatId | myPseudorange |\n", + "| ----------- | ------- | ------------- |\n", + "| 10 | 10 | 270000001 |\n", + "| 10 | 14 | 270000007 |\n", + "| 10 | 7 | 270000004 |\n", + "| 10 | 3 | 270000005 |\n", + "| 11 | 10 | 270000002 |\n", + "| 11 | 14 | 270000008 |\n", + "| 11 | 7 | 270000003 |\n", + "| 11 | 3 | 270000004 |" + ] + }, + { + "cell_type": "markdown", + "id": "fedf66f3", + "metadata": {}, + "source": [ + "The first step is importing the base `NavData` class and creating a new class type that inherits from `NavData`" + ] + }, + { + "cell_type": "markdown", + "id": "20c325d6", + "metadata": {}, + "source": [ + "```python\n", + "class MyReceiver(NavData):\n", + " \"\"\"Class handling measurements from MyReceiver.\n", + "\n", + " Inherits from NavData().\n", + " \"\"\"\n", + "```" + ] + }, + { + "cell_type": "markdown", + "id": "c60a6fe4", + "metadata": {}, + "source": [ + "The `__init__` function should have a call to the parent `NavData` `__init__` function. Based on your data input, you should call the corresponding initializer.\n", + "\n", + "For csv files, call: `super().__init__(csv_path=input_path)` \n", + "For pandas DataFrames, call `super().__init__(pandas_df=input_path)` \n", + "For numpy ndarrays, call `super().__init__(numpy_array=input_path)` \n", + "\n", + "In our case, we have a csv file, so our `__init__` function looks like the following:" + ] + }, + { + "cell_type": "markdown", + "id": "d51dff11", + "metadata": {}, + "source": [ + "```python\n", + "def __init__(self, input_path):\n", + " \"\"\"MyReceive specific loading and preprocessing\n", + "\n", + " Parameters\n", + " ----------\n", + " input_path : string\n", + " Path to MyReceiver csv file\n", + "\n", + " \"\"\"\n", + "\n", + " # call NavData initialization with csv path\n", + " super().__init__(csv_path=input_path)\n", + "```" + ] + }, + { + "cell_type": "markdown", + "id": "4d8dc67f", + "metadata": {}, + "source": [ + "After our data is loaded, we may want to make known changes to our data. We can make those changes by defining a `postprocess` function. NavData's `__init__` function that we call in our initializer already makes a call to the `postprocess` function, so we don't have to repeat that call in `MyReceiver`'s `__init__` function." + ] + }, + { + "cell_type": "markdown", + "id": "c7d9b213", + "metadata": {}, + "source": [ + "One thing that we need to do to make use of the common functionality of `gnss_lib_py` is to standardize the names of our variables. See the [Standard Naming Conventions](https://gnss-lib-py.readthedocs.io/en/latest/reference/reference.html#standard-naming-conventions) section in the Reference tab of the documentation for the list of standardized names.\n", + "\n", + "In our case, we will convert `mySatId` to `sv_id` and `myPseudorange` to `raw_pr_m`. We make these conversions by simply updating the `_row_map` function." + ] + }, + { + "cell_type": "markdown", + "id": "bee9b8a1", + "metadata": {}, + "source": [ + "```python\n", + "\n", + "\n", + "@staticmethod\n", + "def _row_map():\n", + " \"\"\"Map of column names from loaded to gnss_lib_py standard\n", + "\n", + " Returns\n", + " -------\n", + " row_map : Dict\n", + " Dictionary of the form {old_name : new_name}\n", + " \"\"\"\n", + " row_map = {'mySatId' : 'sv_id',\n", + " 'myPseudorange' : 'raw_pr_m',\n", + " }\n", + " return row_map\n", + "```" + ] + }, + { + "cell_type": "markdown", + "id": "dbbe8d55", + "metadata": {}, + "source": [ + "As an additional postprocessing step, we may want to offset our pseudorange due to a known error or create the common timestamp variable `gps_millis` based on our unique timestamp row. Adding the `gps_millis` row enables the use of some of the common algorithms. The [time conversion utilities](https://gnss-lib-py.readthedocs.io/en/latest/tutorials/tutorials_utilities_notebook.html) can be used to create `gps_millis` from the GPS Week & Time of week, GPS milliseconds, or a datetime object." + ] + }, + { + "cell_type": "markdown", + "id": "97fbeb7b", + "metadata": {}, + "source": [ + "```python\n", + "# correct pseudorange\n", + "self['corr_pr_m'] = self['raw_pr_m'] + 100.\n", + "\n", + "# create common timestamp\n", + "self['gps_millis'] = self['myTimestamp'] + 5629719023\n", + "```" + ] + }, + { + "cell_type": "markdown", + "id": "72884667", + "metadata": {}, + "source": [ + "Putting it all together, we have:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "956af339", + "metadata": {}, + "outputs": [], + "source": [ + "class MyReceiver(glp.NavData):\n", + " \"\"\"Class handling measurements from MyReceiver.\n", + "\n", + " Inherits from NavData().\n", + " \"\"\"\n", + " def __init__(self, input_path):\n", + " \"\"\"MyReceive specific loading and preprocessing\n", + "\n", + " Parameters\n", + " ----------\n", + " input_path : string\n", + " Path to MyReceiver csv file\n", + "\n", + " \"\"\"\n", + " \n", + " # call NavData initialization with csv path\n", + " super().__init__(csv_path=input_path)\n", + " \n", + " def postprocess(self):\n", + " \"\"\"MyReceiver specific postprocessing\n", + "\n", + " \"\"\"\n", + "\n", + " # correct pseudorange\n", + " self['corr_pr_m'] = self['raw_pr_m'] + 100.\n", + " \n", + " # create common timestamp\n", + " self['gps_millis'] = self['myTimestamp'] + 1659075505350\n", + " \n", + "\n", + " @staticmethod\n", + " def _row_map():\n", + " \"\"\"Map of column names from loaded to gnss_lib_py standard\n", + "\n", + " Returns\n", + " -------\n", + " row_map : Dict\n", + " Dictionary of the form {old_name : new_name}\n", + " \"\"\"\n", + " row_map = {'mySatId' : 'sv_id',\n", + " 'myPseudorange' : 'raw_pr_m',\n", + " }\n", + " return row_map" + ] + }, + { + "cell_type": "markdown", + "id": "1ccadb47", + "metadata": {}, + "source": [ + "We can now create a instance of our new `MyReceiver` class with the path to our csv called `myreceiver.csv`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b611a39f", + "metadata": {}, + "outputs": [], + "source": [ + "# download myreceiver.csv file\n", + "glp.make_dir(\"../data\")\n", + "!wget https://raw.githubusercontent.com/Stanford-NavLab/gnss_lib_py/main/notebooks/tutorials/data/myreceiver.csv --quiet -O \"../data/myreceiver.csv\"\n", + "\n", + "# create instance of MyReceiver\n", + "my_receiver_data = MyReceiver(\"../data/myreceiver.csv\")" + ] + }, + { + "cell_type": "markdown", + "id": "52b64751", + "metadata": {}, + "source": [ + "Let's print out our corrected pseudorange to make sure everything worked correctly." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "bae52fb6", + "metadata": {}, + "outputs": [], + "source": [ + "my_receiver_data[\"corr_pr_m\"]" + ] + }, + { + "cell_type": "markdown", + "id": "fd3eea3d", + "metadata": {}, + "source": [ + "We can now take advantage of all the tools `gnss_lib_py` has to offer!" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "bad31b8a", + "metadata": {}, + "outputs": [], + "source": [ + "fig = glp.plot_metric(my_receiver_data,\"gps_millis\",\"corr_pr_m\",groupby=\"sv_id\")" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.9" + }, + "vscode": { + "interpreter": { + "hash": "c7717b1dd2ec65abd747d44a25869d062db68d19263f8e701e26dddb0b153342" + } + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/notebooks/tutorials/parsers/nmea.ipynb b/notebooks/tutorials/parsers/nmea.ipynb new file mode 100644 index 00000000..4f78b42f --- /dev/null +++ b/notebooks/tutorials/parsers/nmea.ipynb @@ -0,0 +1,136 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "d5632d85", + "metadata": {}, + "source": [ + "Load `gnss_lib_py` into the Python workspace" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f7468fd3", + "metadata": {}, + "outputs": [], + "source": [ + "import gnss_lib_py as glp" + ] + }, + { + "cell_type": "markdown", + "id": "b0a917b6", + "metadata": {}, + "source": [ + "# NMEA File Parsing" + ] + }, + { + "cell_type": "markdown", + "id": "f166df97", + "metadata": {}, + "source": [ + "NMEA is a file standard for storing and transferring position data and GPS measurements.\n", + "`gnss_lib_py` has functionality for reading NMEA files and loading the data into a `NavData`, which we demonstrate next." + ] + }, + { + "cell_type": "markdown", + "id": "aa0ea320", + "metadata": {}, + "source": [ + "Each NMEA sentence has a header eg. `$GPGGA` which describes whether the message is propreitary or general purpose and the type of message.\n", + "In this case, the message is `GGA`. `gnss_lib_py` currently supports `GGA` and `RMC` message types.\n", + "\n", + "Each NMEA sentence also comes with a checksum, which may appear after the '*' in each sentence.\n", + "In case the checksums are to be checked, pass the parameter `check=True` to the `Nmea` initialization." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cbae66a6", + "metadata": {}, + "outputs": [], + "source": [ + "# download NMEA data and load it into NavData instance\n", + "glp.make_dir(\"../data\")\n", + "!wget https://raw.githubusercontent.com/Stanford-NavLab/gnss_lib_py/main/data/unit_test/nmea/nmea_w_correct_checksum.nmea --quiet -nc -O \"../data/nmea_w_correct_checksum.nmea\"\n", + "# Load the NMEA file into a NavData structure\n", + "nmea_navdata = glp.Nmea('../data/nmea_w_correct_checksum.nmea')\n", + "print('Loaded NMEA data\\n', nmea_navdata)" + ] + }, + { + "cell_type": "markdown", + "id": "6606e32a", + "metadata": {}, + "source": [ + "If the checksum is not to be checked, pass the parameter `check=False` to the initialization." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "81034771", + "metadata": {}, + "outputs": [], + "source": [ + "glp.make_dir(\"../data\")\n", + "!wget https://raw.githubusercontent.com/Stanford-NavLab/gnss_lib_py/main/data/unit_test/nmea/nmea_no_checksum.nmea --quiet -nc -O \"../data/nmea_w_no_checksum.nmea\"\n", + "# Load the NMEA file into a NavData structure\n", + "nmea_navdata = glp.Nmea('../data/nmea_w_no_checksum.nmea', check=False)\n", + "print('Loaded NMEA data\\n', nmea_navdata)" + ] + }, + { + "cell_type": "markdown", + "id": "a87c37c2", + "metadata": {}, + "source": [ + "NMEA GGA and RMC sentences store latitude and longitude coordinates in a `ddmm.mmmmmmm` format along with a cardinal direction like `N` or `W`.\n", + "\n", + "By default, these coordinates are transformed into decimal degrees but the original data format can be retained in the final loaded `NavData`.\n", + "Also, the LLH coordinates can be transformed to ECEF coordinates." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a74a2f16", + "metadata": {}, + "outputs": [], + "source": [ + "nmea_navdata = glp.Nmea('../data/nmea_w_correct_checksum.nmea', keep_raw=True, include_ecef=True)\n", + "print('Loaded NMEA data with raw data and ECEF coordinates\\n', nmea_navdata)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.9" + }, + "vscode": { + "interpreter": { + "hash": "c7717b1dd2ec65abd747d44a25869d062db68d19263f8e701e26dddb0b153342" + } + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/notebooks/tutorials/parsers/rinex_nav.ipynb b/notebooks/tutorials/parsers/rinex_nav.ipynb new file mode 100644 index 00000000..62b9adc2 --- /dev/null +++ b/notebooks/tutorials/parsers/rinex_nav.ipynb @@ -0,0 +1,88 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "d5632d85", + "metadata": {}, + "source": [ + "Load `gnss_lib_py` into the Python workspace" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f7468fd3", + "metadata": {}, + "outputs": [], + "source": [ + "import gnss_lib_py as glp" + ] + }, + { + "cell_type": "markdown", + "id": "8f1f2cd2", + "metadata": {}, + "source": [ + "# Rinex Navigation File Parsing" + ] + }, + { + "cell_type": "markdown", + "id": "63352eb8", + "metadata": {}, + "source": [ + "Rinex Navigation files can be loaded using `RinexNav`" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b034c805", + "metadata": {}, + "outputs": [], + "source": [ + "# download example Rinex navigation file\n", + "glp.make_dir(\"../data\")\n", + "!wget https://raw.githubusercontent.com/Stanford-NavLab/gnss_lib_py/main/data/unit_test/rinex/nav/brdc1370.20n --quiet -nc -O \"../data/brdc1370.20n\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f14e71ef", + "metadata": {}, + "outputs": [], + "source": [ + "# load into NavData instance\n", + "rinex_nav = glp.RinexNav(\"../data/brdc1370.20n\")\n", + "rinex_nav" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.9" + }, + "vscode": { + "interpreter": { + "hash": "c7717b1dd2ec65abd747d44a25869d062db68d19263f8e701e26dddb0b153342" + } + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/notebooks/tutorials/parsers/rinex_obs.ipynb b/notebooks/tutorials/parsers/rinex_obs.ipynb new file mode 100644 index 00000000..0f7d189f --- /dev/null +++ b/notebooks/tutorials/parsers/rinex_obs.ipynb @@ -0,0 +1,84 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "d5632d85", + "metadata": {}, + "source": [ + "Load `gnss_lib_py` into the Python workspace" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f7468fd3", + "metadata": {}, + "outputs": [], + "source": [ + "import gnss_lib_py as glp" + ] + }, + { + "cell_type": "markdown", + "id": "fe8e8d6a", + "metadata": {}, + "source": [ + "# Rinex Observation File Parsing" + ] + }, + { + "cell_type": "markdown", + "id": "af135db1", + "metadata": {}, + "source": [ + "Rinex is another file standard that is used in the GNSS community to store and transmit navigation information.\n", + "Files with the extension `.yyo`, where `yy` is the year in which the measurement was made, are used to store and transmit\n", + "measurements.\n", + "These measurement files can contain any constellation and each measurement usually contains the pseudorange, carrier phase (or difference from carrier frequency),\n", + "doppler, and signal-to-noise ratio measurements.\n", + "In the following lines, we show how to load a ``.o`` file into a NavData instance." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9f279032", + "metadata": {}, + "outputs": [], + "source": [ + "# download Rinex obs file and load it into NavData instance\n", + "glp.make_dir(\"../data\")\n", + "!wget https://raw.githubusercontent.com/Stanford-NavLab/gnss_lib_py/main/data/unit_test/rinex/obs/rinex_obs_mixed_types.20o --quiet -nc -O \"../data/rinex_obs_mixed_types.20o\"\n", + "rinex_obs_3 = glp.RinexObs(\"../data/rinex_obs_mixed_types.20o\")\n", + "print('Loaded Rinex Obs 3 data for the first time instant\\n', \\\n", + " rinex_obs_3.where('gps_millis', rinex_obs_3['gps_millis', 0], 'eq'))" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.9" + }, + "vscode": { + "interpreter": { + "hash": "c7717b1dd2ec65abd747d44a25869d062db68d19263f8e701e26dddb0b153342" + } + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/notebooks/tutorials/parsers/smartloc.ipynb b/notebooks/tutorials/parsers/smartloc.ipynb new file mode 100644 index 00000000..0f956963 --- /dev/null +++ b/notebooks/tutorials/parsers/smartloc.ipynb @@ -0,0 +1,108 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "d5632d85", + "metadata": {}, + "source": [ + "Load `gnss_lib_py` into the Python workspace" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f7468fd3", + "metadata": {}, + "outputs": [], + "source": [ + "import gnss_lib_py as glp" + ] + }, + { + "cell_type": "markdown", + "id": "95e58b71", + "metadata": {}, + "source": [ + "# TU Chemnitz SmartLoc\n", + "This tutorial shows how to load data from TU Chemnitz's [smartLoc GNSS Dataset](https://www.tu-chemnitz.de/projekt/smartLoc/gnss_dataset.html.en#Datasets)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "714dbb21", + "metadata": {}, + "outputs": [], + "source": [ + "import gnss_lib_py as glp" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b369035d", + "metadata": {}, + "outputs": [], + "source": [ + "# download cropped SmartLoc data file\n", + "glp.make_dir(\"../data\")\n", + "!wget https://raw.githubusercontent.com/Stanford-NavLab/gnss_lib_py/main/data/unit_test/smartloc/tu_chemnitz_berlin_1_raw.csv --quiet -nc -O \"../data/smartloc.csv\"\n", + " \n", + "# load smartLoc data into NavData object\n", + "smartloc_data = glp.SmartLocRaw(\"../data/smartloc.csv\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4dd67679", + "metadata": {}, + "outputs": [], + "source": [ + "# plot the pseudorange over time of each individual satellite\n", + "# SBAS 120 is the outlier with its larger orbit\n", + "fig = glp.plot_metric(smartloc_data, \"gps_millis\",\"raw_pr_m\", groupby=\"sv_id\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "fe8d92d7", + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "# show the ground truth smartLoc data on a map\n", + "fig = glp.plot_map(smartloc_data)\n", + "fig.show()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.9" + }, + "vscode": { + "interpreter": { + "hash": "c7717b1dd2ec65abd747d44a25869d062db68d19263f8e701e26dddb0b153342" + } + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/notebooks/tutorials/parsers/sp3.ipynb b/notebooks/tutorials/parsers/sp3.ipynb new file mode 100644 index 00000000..e150e941 --- /dev/null +++ b/notebooks/tutorials/parsers/sp3.ipynb @@ -0,0 +1,116 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "d5632d85", + "metadata": {}, + "source": [ + "Load `gnss_lib_py` into the Python workspace" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f7468fd3", + "metadata": {}, + "outputs": [], + "source": [ + "import gnss_lib_py as glp" + ] + }, + { + "cell_type": "markdown", + "id": "e93e4676", + "metadata": {}, + "source": [ + "# SP3 File Parsing" + ] + }, + { + "cell_type": "markdown", + "id": "d1ce069f", + "metadata": {}, + "source": [ + "This tutorial shows how to load SP3 files." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c0756386", + "metadata": {}, + "outputs": [], + "source": [ + "# download an example .sp3 data file\n", + "glp.make_dir(\"../data\")\n", + "!wget https://raw.githubusercontent.com/Stanford-NavLab/gnss_lib_py/main/data/unit_test/sp3/COD0MGXFIN_20211180000_01D_05M_ORB.SP3 --quiet -nc -O \"../data/COD0MGXFIN_20211180000_01D_05M_ORB.SP3\"\n", + "# Specify .sp3 file path to extract precise ephemerides\n", + "sp3_path = \"../data/COD0MGXFIN_20211180000_01D_05M_ORB.SP3\"" + ] + }, + { + "cell_type": "markdown", + "id": "2ad4d614", + "metadata": {}, + "source": [ + "Use the SP3 class loader to load in the SP3 file. The class can also optionally take multiple files as a list." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5906e3cb", + "metadata": {}, + "outputs": [], + "source": [ + "sp3 = glp.Sp3(sp3_path)\n", + "sp3" + ] + }, + { + "cell_type": "markdown", + "id": "73352028", + "metadata": {}, + "source": [ + "To visualize the results, we'll plot the ECEF x position of the first 10 GPS satellites." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "76834337", + "metadata": {}, + "outputs": [], + "source": [ + "sp3_first_ten_gps = sp3.where(\"gnss_id\",\"gps\").where(\"sv_id\",10,\"leq\")\n", + "fig = glp.plot_metric_by_constellation(sp3_first_ten_gps,\"gps_millis\",\"x_sv_m\")" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.9" + }, + "vscode": { + "interpreter": { + "hash": "c7717b1dd2ec65abd747d44a25869d062db68d19263f8e701e26dddb0b153342" + } + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/notebooks/tutorials/utils/constants.ipynb b/notebooks/tutorials/utils/constants.ipynb new file mode 100644 index 00000000..4a4db323 --- /dev/null +++ b/notebooks/tutorials/utils/constants.ipynb @@ -0,0 +1,53 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# GNSS Constants" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Load `gnss_lib_py` into the Python workspace" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import gnss_lib_py as glp" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.9" + }, + "vscode": { + "interpreter": { + "hash": "c7717b1dd2ec65abd747d44a25869d062db68d19263f8e701e26dddb0b153342" + } + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/notebooks/tutorials/coordinates.ipynb b/notebooks/tutorials/utils/coordinates.ipynb similarity index 92% rename from notebooks/tutorials/coordinates.ipynb rename to notebooks/tutorials/utils/coordinates.ipynb index 7496add3..f2f91ba7 100644 --- a/notebooks/tutorials/coordinates.ipynb +++ b/notebooks/tutorials/utils/coordinates.ipynb @@ -4,7 +4,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# Global Coordinate Conversions" + "# Coordinate Conversions" ] }, { @@ -45,7 +45,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# Local NED Frame Conversions" + "## Local NED Frame Conversions" ] }, { @@ -68,7 +68,7 @@ "print('NED to ECEF conversion matrix for initialized local frame')\n", "print(local_frame.ned_to_ecef_matrix)\n", "\n", - "local_frame = glp.LocalCoord.from_geodetic(x_ecef)\n", + "local_frame = glp.LocalCoord.from_ecef(x_ecef)\n", "print('NED to ECEF conversion matrix for initialized local frame')\n", "print(local_frame.ned_to_ecef_matrix)" ] @@ -127,7 +127,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# Elevation and Aziumth from ECEF Positions" + "## Elevation and Aziumth from ECEF Positions" ] }, { @@ -144,8 +144,9 @@ "outputs": [], "source": [ "# load Android Google Challenge data\n", - "!wget https://raw.githubusercontent.com/Stanford-NavLab/gnss_lib_py/main/data/unit_test/google_decimeter_2022/device_gnss.csv --quiet -O \"device_gnss.csv\"\n", - "navdata = glp.AndroidDerived2022(\"device_gnss.csv\")\n", + "glp.make_dir(\"../data\")\n", + "!wget https://raw.githubusercontent.com/Stanford-NavLab/gnss_lib_py/main/data/unit_test/google_decimeter_2022/device_gnss.csv --quiet -nc -O \"../data/device_gnss.csv\"\n", + "navdata = glp.AndroidDerived2022(\"../data/device_gnss.csv\")\n", "navdata_subset = navdata.where(\"gps_millis\",navdata[\"gps_millis\",0]) # only use data from first timestep" ] }, @@ -187,18 +188,11 @@ " print(f\"Calculated elevation: {calculated_el_az[0, sat_idx]}, Truth elevation: {truth_el_az[0, sat_idx]}\")\n", " print(f\"Calculated azimuth: {calculated_el_az[1, sat_idx]}, Truth azimuth: {truth_el_az[1, sat_idx]}\")" ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] } ], "metadata": { "kernelspec": { - "display_name": "Python 3", + "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, @@ -214,7 +208,6 @@ "pygments_lexer": "ipython3", "version": "3.8.9" }, - "orig_nbformat": 4, "vscode": { "interpreter": { "hash": "c7717b1dd2ec65abd747d44a25869d062db68d19263f8e701e26dddb0b153342" @@ -222,5 +215,5 @@ } }, "nbformat": 4, - "nbformat_minor": 2 + "nbformat_minor": 4 } diff --git a/notebooks/tutorials/ephemeris_downloader.ipynb b/notebooks/tutorials/utils/ephemeris_downloader.ipynb similarity index 100% rename from notebooks/tutorials/ephemeris_downloader.ipynb rename to notebooks/tutorials/utils/ephemeris_downloader.ipynb diff --git a/notebooks/tutorials/utils/file_operations.ipynb b/notebooks/tutorials/utils/file_operations.ipynb new file mode 100644 index 00000000..cf6a993d --- /dev/null +++ b/notebooks/tutorials/utils/file_operations.ipynb @@ -0,0 +1,53 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# File Operations" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Load `gnss_lib_py` into the Python workspace" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import gnss_lib_py as glp" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.9" + }, + "vscode": { + "interpreter": { + "hash": "c7717b1dd2ec65abd747d44a25869d062db68d19263f8e701e26dddb0b153342" + } + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/notebooks/tutorials/utils/filters.ipynb b/notebooks/tutorials/utils/filters.ipynb new file mode 100644 index 00000000..a5ccbfc4 --- /dev/null +++ b/notebooks/tutorials/utils/filters.ipynb @@ -0,0 +1,53 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Filters" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Load `gnss_lib_py` into the Python workspace" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import gnss_lib_py as glp" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.9" + }, + "vscode": { + "interpreter": { + "hash": "c7717b1dd2ec65abd747d44a25869d062db68d19263f8e701e26dddb0b153342" + } + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/notebooks/tutorials/utils/gnss_models.ipynb b/notebooks/tutorials/utils/gnss_models.ipynb new file mode 100644 index 00000000..22e94452 --- /dev/null +++ b/notebooks/tutorials/utils/gnss_models.ipynb @@ -0,0 +1,53 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# GNSS Models" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Load `gnss_lib_py` into the Python workspace" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import gnss_lib_py as glp" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.9" + }, + "vscode": { + "interpreter": { + "hash": "c7717b1dd2ec65abd747d44a25869d062db68d19263f8e701e26dddb0b153342" + } + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/notebooks/tutorials/sv_models.ipynb b/notebooks/tutorials/utils/sv_models.ipynb similarity index 92% rename from notebooks/tutorials/sv_models.ipynb rename to notebooks/tutorials/utils/sv_models.ipynb index 052c6bc2..d33f4f43 100644 --- a/notebooks/tutorials/sv_models.ipynb +++ b/notebooks/tutorials/utils/sv_models.ipynb @@ -1,5 +1,12 @@ { "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# SV (Space Vehicle) Models" + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -20,7 +27,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# Adding SV States with Precise Ephemerides (SP3 & CLK)" + "## Adding SV States with Precise Ephemerides (SP3 & CLK)" ] }, { @@ -51,8 +58,9 @@ "import numpy as np\n", "\n", "# load Android Google Challenge data\n", - "!wget https://raw.githubusercontent.com/Stanford-NavLab/gnss_lib_py/main/data/unit_test/google_decimeter_2021/Pixel4_derived_clkdiscnt.csv --quiet -O \"Pixel4_derived_clkdiscnt.csv\"\n", - "derived_data = glp.AndroidDerived2021(\"Pixel4_derived_clkdiscnt.csv\", remove_timing_outliers=False)\n", + "glp.make_dir(\"../data\")\n", + "!wget https://raw.githubusercontent.com/Stanford-NavLab/gnss_lib_py/main/data/unit_test/google_decimeter_2021/Pixel4_derived_clkdiscnt.csv --quiet -nc -O \"../data/Pixel4_derived_clkdiscnt.csv\"\n", + "derived_data = glp.AndroidDerived2021(\"../data/Pixel4_derived_clkdiscnt.csv\", remove_timing_outliers=False)\n", "# Define the keys relevant for satellite information, and remove the data within these fields\n", "SV_KEYS = ['x_sv_m', 'y_sv_m', 'z_sv_m', \\\n", " 'vx_sv_mps','vy_sv_mps','vz_sv_mps', \\\n", @@ -74,14 +82,14 @@ "outputs": [], "source": [ "# download .sp3 data file\n", - "!wget https://raw.githubusercontent.com/Stanford-NavLab/gnss_lib_py/main/data/unit_test/sp3/COD0MGXFIN_20211180000_01D_05M_ORB.SP3 --quiet -O \"COD0MGXFIN_20211180000_01D_05M_ORB.SP3\"\n", + "!wget https://raw.githubusercontent.com/Stanford-NavLab/gnss_lib_py/main/data/unit_test/sp3/COD0MGXFIN_20211180000_01D_05M_ORB.SP3 --quiet -nc -O \"../data/COD0MGXFIN_20211180000_01D_05M_ORB.SP3\"\n", "# Specify .sp3 file path to extract precise ephemerides\n", - "sp3_path = \"COD0MGXFIN_20211180000_01D_05M_ORB.SP3\"\n", + "sp3_path = \"../data/COD0MGXFIN_20211180000_01D_05M_ORB.SP3\"\n", "\n", "# download .clk data file\n", - "!wget https://raw.githubusercontent.com/Stanford-NavLab/gnss_lib_py/main/data/unit_test/clk/COD0MGXFIN_20211180000_01D_30S_CLK.CLK --quiet -O \"COD0MGXFIN_20211180000_01D_30S_CLK.CLK\"\n", + "!wget https://raw.githubusercontent.com/Stanford-NavLab/gnss_lib_py/main/data/unit_test/clk/COD0MGXFIN_20211180000_01D_30S_CLK.CLK --quiet -nc -O \"../data/COD0MGXFIN_20211180000_01D_30S_CLK.CLK\"\n", "# Specify .clk file path to extract precise ephemerides\n", - "clk_path = \"COD0MGXFIN_20211180000_01D_30S_CLK.CLK\"" + "clk_path = \"../data/COD0MGXFIN_20211180000_01D_30S_CLK.CLK\"" ] }, { @@ -128,7 +136,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# Adding SV states to `NavData` with received measurements using broadcast ephemeris" + "## Adding SV states to `NavData` with received measurements using broadcast ephemeris" ] }, { @@ -155,9 +163,10 @@ "source": [ "import numpy as np\n", "# download Android data file\n", - "!wget https://raw.githubusercontent.com/Stanford-NavLab/gnss_lib_py/main/data/unit_test/google_decimeter_2022/device_gnss.csv --quiet -O \"device_gnss.csv\"\n", + "glp.make_dir(\"../data\")\n", + "!wget https://raw.githubusercontent.com/Stanford-NavLab/gnss_lib_py/main/data/unit_test/google_decimeter_2022/device_gnss.csv --quiet -nc -O \"../data/device_gnss.csv\"\n", "# load Android Google Challenge data\n", - "derived_data = glp.AndroidDerived2022(\"device_gnss.csv\")" + "derived_data = glp.AndroidDerived2022(\"../data/device_gnss.csv\")" ] }, { @@ -220,7 +229,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# Add SV states for visible satellites given a series of times and positions" + "## Add SV states for visible satellites given a series of times and positions" ] }, { @@ -335,7 +344,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# Finding PRNs and states for visible SVs for a given position and time" + "## Finding PRNs and states for visible SVs for a given position and time" ] }, { @@ -373,7 +382,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# Finding SV states at given time and for specific PRNs" + "## Finding SV states at given time and for specific PRNs" ] }, { @@ -423,7 +432,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# Simulating SV positions given elevation and azimuth" + "## Simulating SV positions given elevation and azimuth" ] }, { @@ -475,5 +484,5 @@ } }, "nbformat": 4, - "nbformat_minor": 2 + "nbformat_minor": 4 } diff --git a/notebooks/tutorials/utilities.ipynb b/notebooks/tutorials/utils/time_conversions.ipynb similarity index 100% rename from notebooks/tutorials/utilities.ipynb rename to notebooks/tutorials/utils/time_conversions.ipynb diff --git a/notebooks/tutorials/visualizations/plot_map.ipynb b/notebooks/tutorials/visualizations/plot_map.ipynb new file mode 100644 index 00000000..baa6aad7 --- /dev/null +++ b/notebooks/tutorials/visualizations/plot_map.ipynb @@ -0,0 +1,102 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "de1216b7-0dd0-4347-82ef-70ef24894508", + "metadata": {}, + "source": [ + "# Plot Map" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cce0e69e", + "metadata": {}, + "outputs": [], + "source": [ + "import gnss_lib_py as glp\n", + "\n", + "# load Android Google Challenge data\n", + "glp.make_dir(\"../data\")\n", + "!wget https://raw.githubusercontent.com/Stanford-NavLab/gnss_lib_py/main/data/unit_test/google_decimeter_2021/Pixel4XL_derived.csv --quiet -nc -O \"../data/Pixel4XL_derived.csv\"\n", + "derived_data = glp.AndroidDerived2021(\"../data/Pixel4XL_derived.csv\", remove_timing_outliers=False)" + ] + }, + { + "cell_type": "markdown", + "id": "f5065f2b-9773-483e-bdac-3b6f36f11090", + "metadata": {}, + "source": [ + "**Note:** In this case, the example data is filtered to be seconds apart, in the regular\n", + "setting, such measurements would be removed. To prevent this from happening,\n", + "we set remove_timing_outliers to False here. For the full dataset, set this flag to True" + ] + }, + { + "cell_type": "markdown", + "id": "9f18ae5a", + "metadata": {}, + "source": [ + "The `plot_map` function allows you to plot latitude and longitude rows of data on a map. The rows must match the standard naming style of `lat_*_deg` and `lon_*_deg` where `*` can be replaced with an arbitrary string." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7849bf4d", + "metadata": {}, + "outputs": [], + "source": [ + "state_estimate = glp.solve_wls(derived_data)\n", + "fig = glp.plot_map(state_estimate)\n", + "fig.show()" + ] + }, + { + "cell_type": "markdown", + "id": "287beec9", + "metadata": {}, + "source": [ + "You can plot multiple data traces on the same graph as long as the `*` in the `lat_*_deg` and `lon_*_deg` fields is different for each data trace." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "bd7fbb4b", + "metadata": {}, + "outputs": [], + "source": [ + "# load a separate data file\n", + "glp.make_dir(\"../data\")\n", + "!wget https://raw.githubusercontent.com/Stanford-NavLab/gnss_lib_py/main/data/unit_test/google_decimeter_2021/Pixel4_ground_truth.csv --quiet -nc -O \"../data/Pixel4_truth.csv\"\n", + "truth_data_second_trace = glp.AndroidGroundTruth2021(\"../data/Pixel4_truth.csv\")\n", + "\n", + "fig = glp.plot_map(state_estimate, truth_data_second_trace)\n", + "fig.show()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.9" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/notebooks/tutorials/visualizations.ipynb b/notebooks/tutorials/visualizations/plot_metric.ipynb similarity index 58% rename from notebooks/tutorials/visualizations.ipynb rename to notebooks/tutorials/visualizations/plot_metric.ipynb index 97bd216d..2552b71a 100644 --- a/notebooks/tutorials/visualizations.ipynb +++ b/notebooks/tutorials/visualizations/plot_metric.ipynb @@ -2,22 +2,10 @@ "cells": [ { "cell_type": "markdown", - "id": "2b7e92ce", - "metadata": {}, - "source": [ - "This tutorial illustrates a few of the visualization capabilities from `utils/visualizations.py`." - ] - }, - { - "cell_type": "markdown", - "id": "952766c1", + "id": "0194ecce", "metadata": {}, "source": [ - "The `visualizations.py` file contains several plotting functionalities. We'll use some existing data to demonstrate their functionality.\n", - "\n", - "**Note:** In this case, the example data is filtered to be seconds apart, in the regular\n", - "setting, such measurements would be removed. To prevent this from happening,\n", - "we set remove_timing_outliers to False here. For the full dataset, set this flag to True" + "# Plot Metric" ] }, { @@ -30,52 +18,24 @@ "import gnss_lib_py as glp\n", "\n", "# load Android Google Challenge data\n", - "!wget https://raw.githubusercontent.com/Stanford-NavLab/gnss_lib_py/main/data/unit_test/google_decimeter_2021/Pixel4XL_derived.csv --quiet -O \"Pixel4XL_derived.csv\"\n", - "derived_data = glp.AndroidDerived2021(\"Pixel4XL_derived.csv\", remove_timing_outliers=False)" + "glp.make_dir(\"../data\")\n", + "!wget https://raw.githubusercontent.com/Stanford-NavLab/gnss_lib_py/main/data/unit_test/google_decimeter_2021/Pixel4XL_derived.csv --quiet -nc -O \"../data/Pixel4XL_derived.csv\"\n", + "derived_data = glp.AndroidDerived2021(\"../data/Pixel4XL_derived.csv\", remove_timing_outliers=False)" ] }, { "cell_type": "markdown", - "id": "3673683a", - "metadata": {}, - "source": [ - "Since `NavData` is simply a data structure, you can still pull values from it and use standard Python plotting tools like `matplotlib`." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "47b4753e", - "metadata": {}, - "outputs": [], - "source": [ - "import matplotlib.pyplot as plt\n", - "plt.scatter(derived_data[\"gps_millis\"],derived_data[\"raw_pr_m\"])\n", - "plt.xlabel(\"GPS Milliseconds\")\n", - "plt.ylabel(\"Raw Pseudorange [m]\")\n", - "plt.title(\"Matplotlib Plotting Example\")\n", - "plt.show()" - ] - }, - { - "cell_type": "markdown", - "id": "76ee7e5e", + "id": "952766c1", "metadata": {}, "source": [ - "# Visualizations" + "**Note:** In this case, the example data is filtered to be seconds apart, in the regular\n", + "setting, such measurements would be removed. To prevent this from happening,\n", + "we set remove_timing_outliers to False here. For the full dataset, set this flag to True" ] }, { "cell_type": "markdown", - "id": "45ce2307", - "metadata": {}, - "source": [ - "`gnss_lib_py` extends `matplotlib` and `plotly` functionaltiy to allow quick visuialization of data rows through simple to use function calls." - ] - }, - { - "cell_type": "markdown", - "id": "0194ecce", + "id": "95499a4e-9fc8-4412-904f-1cec5eed7d55", "metadata": {}, "source": [ "## Plot Metric" @@ -192,89 +152,11 @@ "galileo_data = derived_data.where(\"gnss_id\",\"galileo\")\n", "fig = glp.plot_metric_by_constellation(galileo_data, \"gps_millis\", \"raw_pr_m\")" ] - }, - { - "cell_type": "markdown", - "id": "49ad4ff3", - "metadata": {}, - "source": [ - "## Plot Skyplot" - ] - }, - { - "cell_type": "markdown", - "id": "f3c75306", - "metadata": {}, - "source": [ - "The `plot_skyplot` function plots the satellite skyplot using the satellite positions and estimate receiver position." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "56a5e4c8", - "metadata": {}, - "outputs": [], - "source": [ - "state_estimate = glp.solve_wls(derived_data)\n", - "\n", - "fig = glp.plot_skyplot(derived_data, state_estimate)" - ] - }, - { - "cell_type": "markdown", - "id": "6320838e", - "metadata": {}, - "source": [ - "## Plot Map" - ] - }, - { - "cell_type": "markdown", - "id": "9f18ae5a", - "metadata": {}, - "source": [ - "The `plot_map` function allows you to plot latitude and longitude rows of data on a map. The rows must match the standard naming style of `lat_*_deg` and `lon_*_deg` where `*` can be replaced with an arbitrary string." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "7849bf4d", - "metadata": {}, - "outputs": [], - "source": [ - "fig = glp.plot_map(state_estimate)\n", - "fig.show()" - ] - }, - { - "cell_type": "markdown", - "id": "287beec9", - "metadata": {}, - "source": [ - "You can plot multiple data traces on the same graph as long as the `*` in the `lat_*_deg` and `lon_*_deg` fields is different for each data trace." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "bd7fbb4b", - "metadata": {}, - "outputs": [], - "source": [ - "# load a separate data file\n", - "!wget https://raw.githubusercontent.com/Stanford-NavLab/gnss_lib_py/main/data/unit_test/google_decimeter_2021/Pixel4_ground_truth.csv --quiet -O \"Pixel4_truth.csv\"\n", - "truth_data_second_trace = glp.AndroidGroundTruth2021(\"Pixel4_truth.csv\")\n", - "\n", - "fig = glp.plot_map(state_estimate, truth_data_second_trace)\n", - "fig.show()" - ] } ], "metadata": { "kernelspec": { - "display_name": "Python 3", + "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, diff --git a/notebooks/tutorials/visualizations/plot_skyplot.ipynb b/notebooks/tutorials/visualizations/plot_skyplot.ipynb new file mode 100644 index 00000000..7d0fdeda --- /dev/null +++ b/notebooks/tutorials/visualizations/plot_skyplot.ipynb @@ -0,0 +1,78 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "49ad4ff3", + "metadata": {}, + "source": [ + "# Plot Skyplot" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cce0e69e", + "metadata": {}, + "outputs": [], + "source": [ + "import gnss_lib_py as glp\n", + "\n", + "# load Android Google Challenge data\n", + "glp.make_dir(\"../data\")\n", + "!wget https://raw.githubusercontent.com/Stanford-NavLab/gnss_lib_py/main/data/unit_test/google_decimeter_2021/Pixel4XL_derived.csv --quiet -nc -O \"../data/Pixel4XL_derived.csv\"\n", + "derived_data = glp.AndroidDerived2021(\"../data/Pixel4XL_derived.csv\", remove_timing_outliers=False)" + ] + }, + { + "cell_type": "markdown", + "id": "952766c1", + "metadata": {}, + "source": [ + "**Note:** In this case, the example data is filtered to be seconds apart, in the regular\n", + "setting, such measurements would be removed. To prevent this from happening,\n", + "we set remove_timing_outliers to False here. For the full dataset, set this flag to True" + ] + }, + { + "cell_type": "markdown", + "id": "f3c75306", + "metadata": {}, + "source": [ + "The `plot_skyplot` function plots the satellite skyplot using the satellite positions and estimate receiver position." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "56a5e4c8", + "metadata": {}, + "outputs": [], + "source": [ + "state_estimate = glp.solve_wls(derived_data)\n", + "\n", + "fig = glp.plot_skyplot(derived_data, state_estimate)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.9" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/notebooks/tutorials/visualizations/style.ipynb b/notebooks/tutorials/visualizations/style.ipynb new file mode 100644 index 00000000..7a4b650c --- /dev/null +++ b/notebooks/tutorials/visualizations/style.ipynb @@ -0,0 +1,85 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "9e484cdf-e507-4ad2-ada0-1df5c2914275", + "metadata": {}, + "source": [ + "# Plotting Style" + ] + }, + { + "cell_type": "markdown", + "id": "952766c1", + "metadata": {}, + "source": [ + "The `visualizations.py` file contains several plotting functionalities. We'll use some existing data to demonstrate their functionality.\n", + "\n", + "**Note:** In this case, the example data is filtered to be seconds apart, in the regular\n", + "setting, such measurements would be removed. To prevent this from happening,\n", + "we set remove_timing_outliers to False here. For the full dataset, set this flag to True" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cce0e69e", + "metadata": {}, + "outputs": [], + "source": [ + "import gnss_lib_py as glp\n", + "\n", + "# load Android Google Challenge data\n", + "glp.make_dir(\"../data\")\n", + "!wget https://raw.githubusercontent.com/Stanford-NavLab/gnss_lib_py/main/data/unit_test/google_decimeter_2021/Pixel4XL_derived.csv --quiet -nc -O \"../data/Pixel4XL_derived.csv\"\n", + "derived_data = glp.AndroidDerived2021(\"../data/Pixel4XL_derived.csv\", remove_timing_outliers=False)" + ] + }, + { + "cell_type": "markdown", + "id": "3673683a", + "metadata": {}, + "source": [ + "Since `NavData` is simply a data structure, you can still pull values from it and use standard Python plotting tools like `matplotlib`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "47b4753e", + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "import matplotlib.pyplot as plt\n", + "plt.scatter(derived_data[\"gps_millis\"],derived_data[\"raw_pr_m\"])\n", + "plt.xlabel(\"GPS Milliseconds\")\n", + "plt.ylabel(\"Raw Pseudorange [m]\")\n", + "plt.title(\"Matplotlib Plotting Example\")\n", + "plt.show()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.9" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/poetry.lock b/poetry.lock index 96959afb..feaddf64 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. [[package]] name = "alabaster" @@ -13,24 +13,25 @@ files = [ [[package]] name = "anyio" -version = "4.0.0" +version = "4.2.0" description = "High level compatibility layer for multiple asynchronous event loop implementations" optional = false python-versions = ">=3.8" files = [ - {file = "anyio-4.0.0-py3-none-any.whl", hash = "sha256:cfdb2b588b9fc25ede96d8db56ed50848b0b649dca3dd1df0b11f683bb9e0b5f"}, - {file = "anyio-4.0.0.tar.gz", hash = "sha256:f7ed51751b2c2add651e5747c891b47e26d2a21be5d32d9311dfe9692f3e5d7a"}, + {file = "anyio-4.2.0-py3-none-any.whl", hash = "sha256:745843b39e829e108e518c489b31dc757de7d2131d53fac32bd8df268227bfee"}, + {file = "anyio-4.2.0.tar.gz", hash = "sha256:e1875bb4b4e2de1669f4bc7869b6d3f54231cdced71605e6e64c9be77e3be50f"}, ] [package.dependencies] exceptiongroup = {version = ">=1.0.2", markers = "python_version < \"3.11\""} idna = ">=2.8" sniffio = ">=1.1" +typing-extensions = {version = ">=4.1", markers = "python_version < \"3.11\""} [package.extras] -doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)"] -test = ["anyio[trio]", "coverage[toml] (>=7)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"] -trio = ["trio (>=0.22)"] +doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"] +test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"] +trio = ["trio (>=0.23)"] [[package]] name = "appnope" @@ -172,31 +173,32 @@ typing-extensions = {version = ">=4.0.0", markers = "python_version < \"3.11\""} [[package]] name = "attrs" -version = "23.1.0" +version = "23.2.0" description = "Classes Without Boilerplate" optional = false python-versions = ">=3.7" files = [ - {file = "attrs-23.1.0-py3-none-any.whl", hash = "sha256:1f28b4522cdc2fb4256ac1a020c78acf9cba2c6b461ccd2c126f3aa8e8335d04"}, - {file = "attrs-23.1.0.tar.gz", hash = "sha256:6279836d581513a26f1bf235f9acd333bc9115683f14f7e8fae46c98fc50e015"}, + {file = "attrs-23.2.0-py3-none-any.whl", hash = "sha256:99b87a485a5820b23b879f04c2305b44b951b502fd64be915879d77a7e8fc6f1"}, + {file = "attrs-23.2.0.tar.gz", hash = "sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30"}, ] [package.extras] cov = ["attrs[tests]", "coverage[toml] (>=5.3)"] -dev = ["attrs[docs,tests]", "pre-commit"] +dev = ["attrs[tests]", "pre-commit"] docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope-interface"] tests = ["attrs[tests-no-zope]", "zope-interface"] -tests-no-zope = ["cloudpickle", "hypothesis", "mypy (>=1.1.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +tests-mypy = ["mypy (>=1.6)", "pytest-mypy-plugins"] +tests-no-zope = ["attrs[tests-mypy]", "cloudpickle", "hypothesis", "pympler", "pytest (>=4.3.0)", "pytest-xdist[psutil]"] [[package]] name = "babel" -version = "2.13.1" +version = "2.14.0" description = "Internationalization utilities" optional = false python-versions = ">=3.7" files = [ - {file = "Babel-2.13.1-py3-none-any.whl", hash = "sha256:7077a4984b02b6727ac10f1f7294484f737443d7e2e66c5e4380e41a3ae0b4ed"}, - {file = "Babel-2.13.1.tar.gz", hash = "sha256:33e0952d7dd6374af8dbf6768cc4ddf3ccfefc244f9986d4074704f2fbd18900"}, + {file = "Babel-2.14.0-py3-none-any.whl", hash = "sha256:efb1a25b7118e67ce3a259bed20545c29cb68be8ad2c784c83689981b7a57287"}, + {file = "Babel-2.14.0.tar.gz", hash = "sha256:6919867db036398ba21eb5c7a0f6b28ab8cbc3ae7a73a44ebe34ae74a4e7d363"}, ] [package.dependencies] @@ -218,19 +220,22 @@ files = [ [[package]] name = "beautifulsoup4" -version = "4.12.2" +version = "4.12.3" description = "Screen-scraping library" optional = false python-versions = ">=3.6.0" files = [ - {file = "beautifulsoup4-4.12.2-py3-none-any.whl", hash = "sha256:bd2520ca0d9d7d12694a53d44ac482d181b4ec1888909b035a3dbf40d0f57d4a"}, - {file = "beautifulsoup4-4.12.2.tar.gz", hash = "sha256:492bbc69dca35d12daac71c4db1bfff0c876c00ef4a2ffacce226d4638eb72da"}, + {file = "beautifulsoup4-4.12.3-py3-none-any.whl", hash = "sha256:b80878c9f40111313e55da8ba20bdba06d8fa3969fc68304167741bbf9e082ed"}, + {file = "beautifulsoup4-4.12.3.tar.gz", hash = "sha256:74e3d1928edc070d21748185c46e3fb33490f22f52a3addee9aee0f4f7781051"}, ] [package.dependencies] soupsieve = ">1.2" [package.extras] +cchardet = ["cchardet"] +chardet = ["chardet"] +charset-normalizer = ["charset-normalizer"] html5lib = ["html5lib"] lxml = ["lxml"] @@ -254,13 +259,13 @@ css = ["tinycss2 (>=1.1.0,<1.3)"] [[package]] name = "certifi" -version = "2023.7.22" +version = "2023.11.17" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" files = [ - {file = "certifi-2023.7.22-py3-none-any.whl", hash = "sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9"}, - {file = "certifi-2023.7.22.tar.gz", hash = "sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082"}, + {file = "certifi-2023.11.17-py3-none-any.whl", hash = "sha256:e036ab49d5b79556f99cfc2d9320b34cfbe5be05c5871b51de9329f0603b0474"}, + {file = "certifi-2023.11.17.tar.gz", hash = "sha256:9b469f3a900bf28dc19b8cfbf8019bf47f7fdd1a65a1d4ffb98fc14166beb4d1"}, ] [[package]] @@ -439,13 +444,13 @@ files = [ [[package]] name = "comm" -version = "0.2.0" +version = "0.2.1" description = "Jupyter Python Comm implementation, for usage in ipykernel, xeus-python etc." optional = false python-versions = ">=3.8" files = [ - {file = "comm-0.2.0-py3-none-any.whl", hash = "sha256:2da8d9ebb8dd7bfc247adaff99f24dce705638a8042b85cb995066793e391001"}, - {file = "comm-0.2.0.tar.gz", hash = "sha256:a517ea2ca28931c7007a7a99c562a0fa5883cfb48963140cf642c41c948498be"}, + {file = "comm-0.2.1-py3-none-any.whl", hash = "sha256:87928485c0dfc0e7976fd89fc1e187023cf587e7c353e4a9b417555b44adf021"}, + {file = "comm-0.2.1.tar.gz", hash = "sha256:0bc91edae1344d39d3661dcbc36937181fdaddb304790458f8b044dbc064b89a"}, ] [package.dependencies] @@ -527,63 +532,63 @@ test-no-images = ["pytest", "pytest-cov", "wurlitzer"] [[package]] name = "coverage" -version = "7.3.2" +version = "7.4.0" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.8" files = [ - {file = "coverage-7.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d872145f3a3231a5f20fd48500274d7df222e291d90baa2026cc5152b7ce86bf"}, - {file = "coverage-7.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:310b3bb9c91ea66d59c53fa4989f57d2436e08f18fb2f421a1b0b6b8cc7fffda"}, - {file = "coverage-7.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f47d39359e2c3779c5331fc740cf4bce6d9d680a7b4b4ead97056a0ae07cb49a"}, - {file = "coverage-7.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aa72dbaf2c2068404b9870d93436e6d23addd8bbe9295f49cbca83f6e278179c"}, - {file = "coverage-7.3.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:beaa5c1b4777f03fc63dfd2a6bd820f73f036bfb10e925fce067b00a340d0f3f"}, - {file = "coverage-7.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:dbc1b46b92186cc8074fee9d9fbb97a9dd06c6cbbef391c2f59d80eabdf0faa6"}, - {file = "coverage-7.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:315a989e861031334d7bee1f9113c8770472db2ac484e5b8c3173428360a9148"}, - {file = "coverage-7.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d1bc430677773397f64a5c88cb522ea43175ff16f8bfcc89d467d974cb2274f9"}, - {file = "coverage-7.3.2-cp310-cp310-win32.whl", hash = "sha256:a889ae02f43aa45032afe364c8ae84ad3c54828c2faa44f3bfcafecb5c96b02f"}, - {file = "coverage-7.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:c0ba320de3fb8c6ec16e0be17ee1d3d69adcda99406c43c0409cb5c41788a611"}, - {file = "coverage-7.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ac8c802fa29843a72d32ec56d0ca792ad15a302b28ca6203389afe21f8fa062c"}, - {file = "coverage-7.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:89a937174104339e3a3ffcf9f446c00e3a806c28b1841c63edb2b369310fd074"}, - {file = "coverage-7.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e267e9e2b574a176ddb983399dec325a80dbe161f1a32715c780b5d14b5f583a"}, - {file = "coverage-7.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2443cbda35df0d35dcfb9bf8f3c02c57c1d6111169e3c85fc1fcc05e0c9f39a3"}, - {file = "coverage-7.3.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4175e10cc8dda0265653e8714b3174430b07c1dca8957f4966cbd6c2b1b8065a"}, - {file = "coverage-7.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0cbf38419fb1a347aaf63481c00f0bdc86889d9fbf3f25109cf96c26b403fda1"}, - {file = "coverage-7.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:5c913b556a116b8d5f6ef834038ba983834d887d82187c8f73dec21049abd65c"}, - {file = "coverage-7.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1981f785239e4e39e6444c63a98da3a1db8e971cb9ceb50a945ba6296b43f312"}, - {file = "coverage-7.3.2-cp311-cp311-win32.whl", hash = "sha256:43668cabd5ca8258f5954f27a3aaf78757e6acf13c17604d89648ecc0cc66640"}, - {file = "coverage-7.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10c39c0452bf6e694511c901426d6b5ac005acc0f78ff265dbe36bf81f808a2"}, - {file = "coverage-7.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:4cbae1051ab791debecc4a5dcc4a1ff45fc27b91b9aee165c8a27514dd160836"}, - {file = "coverage-7.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:12d15ab5833a997716d76f2ac1e4b4d536814fc213c85ca72756c19e5a6b3d63"}, - {file = "coverage-7.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c7bba973ebee5e56fe9251300c00f1579652587a9f4a5ed8404b15a0471f216"}, - {file = "coverage-7.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fe494faa90ce6381770746077243231e0b83ff3f17069d748f645617cefe19d4"}, - {file = "coverage-7.3.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6e9589bd04d0461a417562649522575d8752904d35c12907d8c9dfeba588faf"}, - {file = "coverage-7.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d51ac2a26f71da1b57f2dc81d0e108b6ab177e7d30e774db90675467c847bbdf"}, - {file = "coverage-7.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:99b89d9f76070237975b315b3d5f4d6956ae354a4c92ac2388a5695516e47c84"}, - {file = "coverage-7.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fa28e909776dc69efb6ed975a63691bc8172b64ff357e663a1bb06ff3c9b589a"}, - {file = "coverage-7.3.2-cp312-cp312-win32.whl", hash = "sha256:289fe43bf45a575e3ab10b26d7b6f2ddb9ee2dba447499f5401cfb5ecb8196bb"}, - {file = "coverage-7.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:7dbc3ed60e8659bc59b6b304b43ff9c3ed858da2839c78b804973f613d3e92ed"}, - {file = "coverage-7.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f94b734214ea6a36fe16e96a70d941af80ff3bfd716c141300d95ebc85339738"}, - {file = "coverage-7.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:af3d828d2c1cbae52d34bdbb22fcd94d1ce715d95f1a012354a75e5913f1bda2"}, - {file = "coverage-7.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:630b13e3036e13c7adc480ca42fa7afc2a5d938081d28e20903cf7fd687872e2"}, - {file = "coverage-7.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c9eacf273e885b02a0273bb3a2170f30e2d53a6d53b72dbe02d6701b5296101c"}, - {file = "coverage-7.3.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d8f17966e861ff97305e0801134e69db33b143bbfb36436efb9cfff6ec7b2fd9"}, - {file = "coverage-7.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b4275802d16882cf9c8b3d057a0839acb07ee9379fa2749eca54efbce1535b82"}, - {file = "coverage-7.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:72c0cfa5250f483181e677ebc97133ea1ab3eb68645e494775deb6a7f6f83901"}, - {file = "coverage-7.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:cb536f0dcd14149425996821a168f6e269d7dcd2c273a8bff8201e79f5104e76"}, - {file = "coverage-7.3.2-cp38-cp38-win32.whl", hash = "sha256:307adb8bd3abe389a471e649038a71b4eb13bfd6b7dd9a129fa856f5c695cf92"}, - {file = "coverage-7.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:88ed2c30a49ea81ea3b7f172e0269c182a44c236eb394718f976239892c0a27a"}, - {file = "coverage-7.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b631c92dfe601adf8f5ebc7fc13ced6bb6e9609b19d9a8cd59fa47c4186ad1ce"}, - {file = "coverage-7.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d3d9df4051c4a7d13036524b66ecf7a7537d14c18a384043f30a303b146164e9"}, - {file = "coverage-7.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f7363d3b6a1119ef05015959ca24a9afc0ea8a02c687fe7e2d557705375c01f"}, - {file = "coverage-7.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2f11cc3c967a09d3695d2a6f03fb3e6236622b93be7a4b5dc09166a861be6d25"}, - {file = "coverage-7.3.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:149de1d2401ae4655c436a3dced6dd153f4c3309f599c3d4bd97ab172eaf02d9"}, - {file = "coverage-7.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:3a4006916aa6fee7cd38db3bfc95aa9c54ebb4ffbfc47c677c8bba949ceba0a6"}, - {file = "coverage-7.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9028a3871280110d6e1aa2df1afd5ef003bab5fb1ef421d6dc748ae1c8ef2ebc"}, - {file = "coverage-7.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9f805d62aec8eb92bab5b61c0f07329275b6f41c97d80e847b03eb894f38d083"}, - {file = "coverage-7.3.2-cp39-cp39-win32.whl", hash = "sha256:d1c88ec1a7ff4ebca0219f5b1ef863451d828cccf889c173e1253aa84b1e07ce"}, - {file = "coverage-7.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b4767da59464bb593c07afceaddea61b154136300881844768037fd5e859353f"}, - {file = "coverage-7.3.2-pp38.pp39.pp310-none-any.whl", hash = "sha256:ae97af89f0fbf373400970c0a21eef5aa941ffeed90aee43650b81f7d7f47637"}, - {file = "coverage-7.3.2.tar.gz", hash = "sha256:be32ad29341b0170e795ca590e1c07e81fc061cb5b10c74ce7203491484404ef"}, + {file = "coverage-7.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:36b0ea8ab20d6a7564e89cb6135920bc9188fb5f1f7152e94e8300b7b189441a"}, + {file = "coverage-7.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0676cd0ba581e514b7f726495ea75aba3eb20899d824636c6f59b0ed2f88c471"}, + {file = "coverage-7.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0ca5c71a5a1765a0f8f88022c52b6b8be740e512980362f7fdbb03725a0d6b9"}, + {file = "coverage-7.4.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a7c97726520f784239f6c62506bc70e48d01ae71e9da128259d61ca5e9788516"}, + {file = "coverage-7.4.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:815ac2d0f3398a14286dc2cea223a6f338109f9ecf39a71160cd1628786bc6f5"}, + {file = "coverage-7.4.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:80b5ee39b7f0131ebec7968baa9b2309eddb35b8403d1869e08f024efd883566"}, + {file = "coverage-7.4.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:5b2ccb7548a0b65974860a78c9ffe1173cfb5877460e5a229238d985565574ae"}, + {file = "coverage-7.4.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:995ea5c48c4ebfd898eacb098164b3cc826ba273b3049e4a889658548e321b43"}, + {file = "coverage-7.4.0-cp310-cp310-win32.whl", hash = "sha256:79287fd95585ed36e83182794a57a46aeae0b64ca53929d1176db56aacc83451"}, + {file = "coverage-7.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:5b14b4f8760006bfdb6e08667af7bc2d8d9bfdb648351915315ea17645347137"}, + {file = "coverage-7.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:04387a4a6ecb330c1878907ce0dc04078ea72a869263e53c72a1ba5bbdf380ca"}, + {file = "coverage-7.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ea81d8f9691bb53f4fb4db603203029643caffc82bf998ab5b59ca05560f4c06"}, + {file = "coverage-7.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:74775198b702868ec2d058cb92720a3c5a9177296f75bd97317c787daf711505"}, + {file = "coverage-7.4.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:76f03940f9973bfaee8cfba70ac991825611b9aac047e5c80d499a44079ec0bc"}, + {file = "coverage-7.4.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:485e9f897cf4856a65a57c7f6ea3dc0d4e6c076c87311d4bc003f82cfe199d25"}, + {file = "coverage-7.4.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:6ae8c9d301207e6856865867d762a4b6fd379c714fcc0607a84b92ee63feff70"}, + {file = "coverage-7.4.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:bf477c355274a72435ceb140dc42de0dc1e1e0bf6e97195be30487d8eaaf1a09"}, + {file = "coverage-7.4.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:83c2dda2666fe32332f8e87481eed056c8b4d163fe18ecc690b02802d36a4d26"}, + {file = "coverage-7.4.0-cp311-cp311-win32.whl", hash = "sha256:697d1317e5290a313ef0d369650cfee1a114abb6021fa239ca12b4849ebbd614"}, + {file = "coverage-7.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:26776ff6c711d9d835557ee453082025d871e30b3fd6c27fcef14733f67f0590"}, + {file = "coverage-7.4.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:13eaf476ec3e883fe3e5fe3707caeb88268a06284484a3daf8250259ef1ba143"}, + {file = "coverage-7.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846f52f46e212affb5bcf131c952fb4075b55aae6b61adc9856222df89cbe3e2"}, + {file = "coverage-7.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:26f66da8695719ccf90e794ed567a1549bb2644a706b41e9f6eae6816b398c4a"}, + {file = "coverage-7.4.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:164fdcc3246c69a6526a59b744b62e303039a81e42cfbbdc171c91a8cc2f9446"}, + {file = "coverage-7.4.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:316543f71025a6565677d84bc4df2114e9b6a615aa39fb165d697dba06a54af9"}, + {file = "coverage-7.4.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:bb1de682da0b824411e00a0d4da5a784ec6496b6850fdf8c865c1d68c0e318dd"}, + {file = "coverage-7.4.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:0e8d06778e8fbffccfe96331a3946237f87b1e1d359d7fbe8b06b96c95a5407a"}, + {file = "coverage-7.4.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a56de34db7b7ff77056a37aedded01b2b98b508227d2d0979d373a9b5d353daa"}, + {file = "coverage-7.4.0-cp312-cp312-win32.whl", hash = "sha256:51456e6fa099a8d9d91497202d9563a320513fcf59f33991b0661a4a6f2ad450"}, + {file = "coverage-7.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:cd3c1e4cb2ff0083758f09be0f77402e1bdf704adb7f89108007300a6da587d0"}, + {file = "coverage-7.4.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e9d1bf53c4c8de58d22e0e956a79a5b37f754ed1ffdbf1a260d9dcfa2d8a325e"}, + {file = "coverage-7.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:109f5985182b6b81fe33323ab4707011875198c41964f014579cf82cebf2bb85"}, + {file = "coverage-7.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3cc9d4bc55de8003663ec94c2f215d12d42ceea128da8f0f4036235a119c88ac"}, + {file = "coverage-7.4.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cc6d65b21c219ec2072c1293c505cf36e4e913a3f936d80028993dd73c7906b1"}, + {file = "coverage-7.4.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5a10a4920def78bbfff4eff8a05c51be03e42f1c3735be42d851f199144897ba"}, + {file = "coverage-7.4.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b8e99f06160602bc64da35158bb76c73522a4010f0649be44a4e167ff8555952"}, + {file = "coverage-7.4.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:7d360587e64d006402b7116623cebf9d48893329ef035278969fa3bbf75b697e"}, + {file = "coverage-7.4.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:29f3abe810930311c0b5d1a7140f6395369c3db1be68345638c33eec07535105"}, + {file = "coverage-7.4.0-cp38-cp38-win32.whl", hash = "sha256:5040148f4ec43644702e7b16ca864c5314ccb8ee0751ef617d49aa0e2d6bf4f2"}, + {file = "coverage-7.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:9864463c1c2f9cb3b5db2cf1ff475eed2f0b4285c2aaf4d357b69959941aa555"}, + {file = "coverage-7.4.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:936d38794044b26c99d3dd004d8af0035ac535b92090f7f2bb5aa9c8e2f5cd42"}, + {file = "coverage-7.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:799c8f873794a08cdf216aa5d0531c6a3747793b70c53f70e98259720a6fe2d7"}, + {file = "coverage-7.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e7defbb9737274023e2d7af02cac77043c86ce88a907c58f42b580a97d5bcca9"}, + {file = "coverage-7.4.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a1526d265743fb49363974b7aa8d5899ff64ee07df47dd8d3e37dcc0818f09ed"}, + {file = "coverage-7.4.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf635a52fc1ea401baf88843ae8708591aa4adff875e5c23220de43b1ccf575c"}, + {file = "coverage-7.4.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:756ded44f47f330666843b5781be126ab57bb57c22adbb07d83f6b519783b870"}, + {file = "coverage-7.4.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:0eb3c2f32dabe3a4aaf6441dde94f35687224dfd7eb2a7f47f3fd9428e421058"}, + {file = "coverage-7.4.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:bfd5db349d15c08311702611f3dccbef4b4e2ec148fcc636cf8739519b4a5c0f"}, + {file = "coverage-7.4.0-cp39-cp39-win32.whl", hash = "sha256:53d7d9158ee03956e0eadac38dfa1ec8068431ef8058fe6447043db1fb40d932"}, + {file = "coverage-7.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:cfd2a8b6b0d8e66e944d47cdec2f47c48fef2ba2f2dff5a9a75757f64172857e"}, + {file = "coverage-7.4.0-pp38.pp39.pp310-none-any.whl", hash = "sha256:c530833afc4707fe48524a44844493f36d8727f04dcce91fb978c414a8556cc6"}, + {file = "coverage-7.4.0.tar.gz", hash = "sha256:707c0f58cb1712b8809ece32b68996ee1e609f71bd14615bd8f87a1293cb610e"}, ] [package.dependencies] @@ -683,13 +688,13 @@ files = [ [[package]] name = "exceptiongroup" -version = "1.1.3" +version = "1.2.0" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" files = [ - {file = "exceptiongroup-1.1.3-py3-none-any.whl", hash = "sha256:343280667a4585d195ca1cf9cef84a4e178c4b6cf2274caef9859782b567d5e3"}, - {file = "exceptiongroup-1.1.3.tar.gz", hash = "sha256:097acd85d473d75af5bb98e41b61ff7fe35efe6675e4f9370ec6ec5126d160e9"}, + {file = "exceptiongroup-1.2.0-py3-none-any.whl", hash = "sha256:4bfd3996ac73b41e9b9628b04e079f193850720ea5945fc96a08633c66912f14"}, + {file = "exceptiongroup-1.2.0.tar.gz", hash = "sha256:91f5c769735f051a4290d52edd0858999b57e5876e9f85937691bd4c9fa3ed68"}, ] [package.extras] @@ -711,13 +716,13 @@ tests = ["asttokens (>=2.1.0)", "coverage", "coverage-enable-subprocess", "ipyth [[package]] name = "fastjsonschema" -version = "2.19.0" +version = "2.19.1" description = "Fastest Python implementation of JSON schema" optional = false python-versions = "*" files = [ - {file = "fastjsonschema-2.19.0-py3-none-any.whl", hash = "sha256:b9fd1a2dd6971dbc7fee280a95bd199ae0dd9ce22beb91cc75e9c1c528a5170e"}, - {file = "fastjsonschema-2.19.0.tar.gz", hash = "sha256:e25df6647e1bc4a26070b700897b07b542ec898dd4f1f6ea013e7f6a88417225"}, + {file = "fastjsonschema-2.19.1-py3-none-any.whl", hash = "sha256:3672b47bc94178c9f23dbb654bf47440155d4db9df5f7bc47643315f9c405cd0"}, + {file = "fastjsonschema-2.19.1.tar.gz", hash = "sha256:e3126a94bdc4623d3de4485f8d468a12f02a67921315ddc87836d6e456dc789d"}, ] [package.extras] @@ -725,59 +730,59 @@ devel = ["colorama", "json-spec", "jsonschema", "pylint", "pytest", "pytest-benc [[package]] name = "fonttools" -version = "4.44.3" +version = "4.47.2" description = "Tools to manipulate font files" optional = false python-versions = ">=3.8" files = [ - {file = "fonttools-4.44.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:192ebdb3bb1882b7ed3ad4b949a106ddd8b428d046ddce64df2d459f7a2db31b"}, - {file = "fonttools-4.44.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:20898476cf9c61795107b91409f4b1cf86de6e92b41095bbe900c05b5b117c96"}, - {file = "fonttools-4.44.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:437204780611f9f80f74cd4402fa451e920d1c4b6cb474a0818a734b4affc477"}, - {file = "fonttools-4.44.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50152205ed3e16c5878a006ee53ecc402acac9af68357343be1e5c36f66ccb24"}, - {file = "fonttools-4.44.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ba9c407d8bd63b21910b98399aeec87e24ca9c3e62ea60c246e505c4a4df6c27"}, - {file = "fonttools-4.44.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:79a6babb87d7f70f8aed88f157bbdc5d2f01ad8b01e9535ff07e43e96ad25548"}, - {file = "fonttools-4.44.3-cp310-cp310-win32.whl", hash = "sha256:32e8a5cebfe8f797461b02084104053b2690ebf0cc38eda5beb9ba24ce43c349"}, - {file = "fonttools-4.44.3-cp310-cp310-win_amd64.whl", hash = "sha256:c26649a6ce6f1ce4dd6748f64b18f70e39c618c6188286ab9534a949da28164c"}, - {file = "fonttools-4.44.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5cd114cb20b491f6812aa397040b06a469563c1a01ec94c8c5d96b76d84916db"}, - {file = "fonttools-4.44.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e84084cc325f888c3495df7ec25f6133be0f606efb80a9c9e072ea6064ede9ac"}, - {file = "fonttools-4.44.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:877e36afce69cfdbd0453a4f44b16e865ac29f06df29f10f0b822a68ab858e86"}, - {file = "fonttools-4.44.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1c2cb1e2a7cfeaeb40b8823f238d7e02929b3a0b53e133e757dec5e99c327c9"}, - {file = "fonttools-4.44.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:dd752b778b37863cf5146d0112aafcd5693235831f09303809ab9c1e564c236b"}, - {file = "fonttools-4.44.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8f4e22c5128cb604d3b0b869eb8d3092a1c10cbe6def402ff46bb920f7169374"}, - {file = "fonttools-4.44.3-cp311-cp311-win32.whl", hash = "sha256:4831d948bc3cea9cd8bf0c92a087f4392068bcac3b584a61a4c837c48a012337"}, - {file = "fonttools-4.44.3-cp311-cp311-win_amd64.whl", hash = "sha256:948b35e54b0c1b6acf9d63c70515051b7d400d69b61c91377cf0e8742d71c44d"}, - {file = "fonttools-4.44.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:fad1c74aa10b77764d3cdf3481bd181d4949e0b46f2da6f9e57543d4adbda177"}, - {file = "fonttools-4.44.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b6a77e3b994649f72fb46b0b8cfe64481b5640e5aecc2d77961300a34fe1dc4f"}, - {file = "fonttools-4.44.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2bff4f9d5edc10b29d2a2daeefd78a47289ba2f751c9bf247925b9d43c6efd79"}, - {file = "fonttools-4.44.3-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3302998e02a854a41c930f9f1366eb8092dbc5fe7ff636d86aeb28d232f4610a"}, - {file = "fonttools-4.44.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:8c7985017e7fb2c2613fa5c440457cd45a6ea808f8d08ed70c27e02e6862cbbe"}, - {file = "fonttools-4.44.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:35d88af2b63060ed2b50aa00d38f60edf4c0b9275a77ae1a98e8d2c03540c617"}, - {file = "fonttools-4.44.3-cp312-cp312-win32.whl", hash = "sha256:5478a77a15d01a21c569fc4ab6f2faba852a21d0932eef02ac4c4a4b50af8070"}, - {file = "fonttools-4.44.3-cp312-cp312-win_amd64.whl", hash = "sha256:979fc845703e0d9b35bc65379fcf34d050e04c3e0b3381a0f66b0be33183da1c"}, - {file = "fonttools-4.44.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:7a8b9f22d3c147ecdc7be46f9f1e1df0523541df0535fac5bdd653726218d068"}, - {file = "fonttools-4.44.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bcb0fde94374ba00c118d632b0b5f1f4447401313166bcb14d737322928e358f"}, - {file = "fonttools-4.44.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3eb365cd8ae4765973fa036aed0077ac26f37b2f8240a72c4a29cd9d8a31027f"}, - {file = "fonttools-4.44.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c329e21502c894fe4c800e32bc3ce37c6b5ca95778d32dff17d7ebf5cac94efa"}, - {file = "fonttools-4.44.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:345a30db8adfbb868221234fb434dd2fc5bfe27baafbaf418528f6c5a5a95584"}, - {file = "fonttools-4.44.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2fe4eed749de2e6bf3aa05d18df04231a712a16c08974af5e67bb9f75a25d10f"}, - {file = "fonttools-4.44.3-cp38-cp38-win32.whl", hash = "sha256:3b179a284b73802edd6d910e6384f28098cb03bd263fd87db6abb31679f68863"}, - {file = "fonttools-4.44.3-cp38-cp38-win_amd64.whl", hash = "sha256:4c805a0b0545fd9becf6dfe8d57e45a7c1af7fdbfd0a7d776c5e999e4edec9f5"}, - {file = "fonttools-4.44.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:f647d270ee90f70acbf5b31a53d486ba0897624236f9056d624c4e436386a14e"}, - {file = "fonttools-4.44.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ba82ee938bd7ea16762124a650bf2529f67dfe9999f64e0ebe1ef0a04baceafd"}, - {file = "fonttools-4.44.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e3bbca4f873d96c20757c24c70a903251a8998e1931bd888b49956f21d94b441"}, - {file = "fonttools-4.44.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50b43fd55089ae850a050f0c382f13fc9586279a540b646b28b9e93fbc05b8a3"}, - {file = "fonttools-4.44.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:cde83f83919ae7569a0316e093e04022dbb8ae5217f41cf591f125dd35d4dc0d"}, - {file = "fonttools-4.44.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:72ec91b85391dd4b06991c0919215ecf910554df2842df32e928155ea5b74aef"}, - {file = "fonttools-4.44.3-cp39-cp39-win32.whl", hash = "sha256:367aa3e81a096e9a95dfc0d5afcbd0a299d857bac6d0fe5f1614c6f3e53f447f"}, - {file = "fonttools-4.44.3-cp39-cp39-win_amd64.whl", hash = "sha256:718599de63b337518bfa5ce67e4ae462da3dd582a74fbe805f56b3704eb334a1"}, - {file = "fonttools-4.44.3-py3-none-any.whl", hash = "sha256:42eefbb1babf81de40ab4a6ace6018c8c5a0d79ece0f986f73a9904b26ee511b"}, - {file = "fonttools-4.44.3.tar.gz", hash = "sha256:f77b6c0add23a3f1ec8eda40015bcb8e92796f7d06a074de102a31c7d007c05b"}, -] - -[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.1.0)", "xattr", "zopfli (>=0.1.4)"] + {file = "fonttools-4.47.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3b629108351d25512d4ea1a8393a2dba325b7b7d7308116b605ea3f8e1be88df"}, + {file = "fonttools-4.47.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c19044256c44fe299d9a73456aabee4b4d06c6b930287be93b533b4737d70aa1"}, + {file = "fonttools-4.47.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b8be28c036b9f186e8c7eaf8a11b42373e7e4949f9e9f370202b9da4c4c3f56c"}, + {file = "fonttools-4.47.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f83a4daef6d2a202acb9bf572958f91cfde5b10c8ee7fb1d09a4c81e5d851fd8"}, + {file = "fonttools-4.47.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4a5a5318ba5365d992666ac4fe35365f93004109d18858a3e18ae46f67907670"}, + {file = "fonttools-4.47.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8f57ecd742545362a0f7186774b2d1c53423ed9ece67689c93a1055b236f638c"}, + {file = "fonttools-4.47.2-cp310-cp310-win32.whl", hash = "sha256:a1c154bb85dc9a4cf145250c88d112d88eb414bad81d4cb524d06258dea1bdc0"}, + {file = "fonttools-4.47.2-cp310-cp310-win_amd64.whl", hash = "sha256:3e2b95dce2ead58fb12524d0ca7d63a63459dd489e7e5838c3cd53557f8933e1"}, + {file = "fonttools-4.47.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:29495d6d109cdbabe73cfb6f419ce67080c3ef9ea1e08d5750240fd4b0c4763b"}, + {file = "fonttools-4.47.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0a1d313a415eaaba2b35d6cd33536560deeebd2ed758b9bfb89ab5d97dc5deac"}, + {file = "fonttools-4.47.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:90f898cdd67f52f18049250a6474185ef6544c91f27a7bee70d87d77a8daf89c"}, + {file = "fonttools-4.47.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3480eeb52770ff75140fe7d9a2ec33fb67b07efea0ab5129c7e0c6a639c40c70"}, + {file = "fonttools-4.47.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0255dbc128fee75fb9be364806b940ed450dd6838672a150d501ee86523ac61e"}, + {file = "fonttools-4.47.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f791446ff297fd5f1e2247c188de53c1bfb9dd7f0549eba55b73a3c2087a2703"}, + {file = "fonttools-4.47.2-cp311-cp311-win32.whl", hash = "sha256:740947906590a878a4bde7dd748e85fefa4d470a268b964748403b3ab2aeed6c"}, + {file = "fonttools-4.47.2-cp311-cp311-win_amd64.whl", hash = "sha256:63fbed184979f09a65aa9c88b395ca539c94287ba3a364517698462e13e457c9"}, + {file = "fonttools-4.47.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:4ec558c543609e71b2275c4894e93493f65d2f41c15fe1d089080c1d0bb4d635"}, + {file = "fonttools-4.47.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:e040f905d542362e07e72e03612a6270c33d38281fd573160e1003e43718d68d"}, + {file = "fonttools-4.47.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6dd58cc03016b281bd2c74c84cdaa6bd3ce54c5a7f47478b7657b930ac3ed8eb"}, + {file = "fonttools-4.47.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:32ab2e9702dff0dd4510c7bb958f265a8d3dd5c0e2547e7b5f7a3df4979abb07"}, + {file = "fonttools-4.47.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:3a808f3c1d1df1f5bf39be869b6e0c263570cdafb5bdb2df66087733f566ea71"}, + {file = "fonttools-4.47.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ac71e2e201df041a2891067dc36256755b1229ae167edbdc419b16da78732c2f"}, + {file = "fonttools-4.47.2-cp312-cp312-win32.whl", hash = "sha256:69731e8bea0578b3c28fdb43dbf95b9386e2d49a399e9a4ad736b8e479b08085"}, + {file = "fonttools-4.47.2-cp312-cp312-win_amd64.whl", hash = "sha256:b3e1304e5f19ca861d86a72218ecce68f391646d85c851742d265787f55457a4"}, + {file = "fonttools-4.47.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:254d9a6f7be00212bf0c3159e0a420eb19c63793b2c05e049eb337f3023c5ecc"}, + {file = "fonttools-4.47.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:eabae77a07c41ae0b35184894202305c3ad211a93b2eb53837c2a1143c8bc952"}, + {file = "fonttools-4.47.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a86a5ab2873ed2575d0fcdf1828143cfc6b977ac448e3dc616bb1e3d20efbafa"}, + {file = "fonttools-4.47.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13819db8445a0cec8c3ff5f243af6418ab19175072a9a92f6cc8ca7d1452754b"}, + {file = "fonttools-4.47.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:4e743935139aa485fe3253fc33fe467eab6ea42583fa681223ea3f1a93dd01e6"}, + {file = "fonttools-4.47.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d49ce3ea7b7173faebc5664872243b40cf88814ca3eb135c4a3cdff66af71946"}, + {file = "fonttools-4.47.2-cp38-cp38-win32.whl", hash = "sha256:94208ea750e3f96e267f394d5588579bb64cc628e321dbb1d4243ffbc291b18b"}, + {file = "fonttools-4.47.2-cp38-cp38-win_amd64.whl", hash = "sha256:0f750037e02beb8b3569fbff701a572e62a685d2a0e840d75816592280e5feae"}, + {file = "fonttools-4.47.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:3d71606c9321f6701642bd4746f99b6089e53d7e9817fc6b964e90d9c5f0ecc6"}, + {file = "fonttools-4.47.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:86e0427864c6c91cf77f16d1fb9bf1bbf7453e824589e8fb8461b6ee1144f506"}, + {file = "fonttools-4.47.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a00bd0e68e88987dcc047ea31c26d40a3c61185153b03457956a87e39d43c37"}, + {file = "fonttools-4.47.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a5d77479fb885ef38a16a253a2f4096bc3d14e63a56d6246bfdb56365a12b20c"}, + {file = "fonttools-4.47.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:5465df494f20a7d01712b072ae3ee9ad2887004701b95cb2cc6dcb9c2c97a899"}, + {file = "fonttools-4.47.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4c811d3c73b6abac275babb8aa439206288f56fdb2c6f8835e3d7b70de8937a7"}, + {file = "fonttools-4.47.2-cp39-cp39-win32.whl", hash = "sha256:5b60e3afa9635e3dfd3ace2757039593e3bd3cf128be0ddb7a1ff4ac45fa5a50"}, + {file = "fonttools-4.47.2-cp39-cp39-win_amd64.whl", hash = "sha256:7ee48bd9d6b7e8f66866c9090807e3a4a56cf43ffad48962725a190e0dd774c8"}, + {file = "fonttools-4.47.2-py3-none-any.whl", hash = "sha256:7eb7ad665258fba68fd22228a09f347469d95a97fb88198e133595947a20a184"}, + {file = "fonttools-4.47.2.tar.gz", hash = "sha256:7df26dd3650e98ca45f1e29883c96a0b9f5bb6af8d632a6a108bc744fa0bd9b3"}, +] + +[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", "pycairo", "scipy", "skia-pathops (>=0.5.0)", "sympy", "uharfbuzz (>=0.23.0)", "unicodedata2 (>=15.1.0)", "xattr", "zopfli (>=0.1.4)"] graphite = ["lz4 (>=1.7.4.2)"] -interpolatable = ["munkres", "scipy"] +interpolatable = ["munkres", "pycairo", "scipy"] lxml = ["lxml (>=4.0,<5)"] pathops = ["skia-pathops (>=0.5.0)"] plot = ["matplotlib"] @@ -858,13 +863,13 @@ tests = ["pytest"] [[package]] name = "idna" -version = "3.4" +version = "3.6" description = "Internationalized Domain Names in Applications (IDNA)" 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"}, + {file = "idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f"}, + {file = "idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca"}, ] [[package]] @@ -880,20 +885,20 @@ files = [ [[package]] name = "importlib-metadata" -version = "6.8.0" +version = "7.0.1" description = "Read metadata from Python packages" optional = false python-versions = ">=3.8" files = [ - {file = "importlib_metadata-6.8.0-py3-none-any.whl", hash = "sha256:3ebb78df84a805d7698245025b975d9d67053cd94c79245ba4b3eb694abe68bb"}, - {file = "importlib_metadata-6.8.0.tar.gz", hash = "sha256:dbace7892d8c0c4ac1ad096662232f831d4e64f4c4545bd53016a3e9d4654743"}, + {file = "importlib_metadata-7.0.1-py3-none-any.whl", hash = "sha256:4805911c3a4ec7c3966410053e9ec6a1fecd629117df5adee56dfc9432a1081e"}, + {file = "importlib_metadata-7.0.1.tar.gz", hash = "sha256:f238736bb06590ae52ac1fab06a3a9ef1d8dce2b7a35b5ab329371d6c8f5d2cc"}, ] [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"] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] perf = ["ipython"] testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)", "pytest-ruff"] @@ -928,13 +933,13 @@ files = [ [[package]] name = "ipykernel" -version = "6.26.0" +version = "6.29.0" description = "IPython Kernel for Jupyter" optional = false python-versions = ">=3.8" files = [ - {file = "ipykernel-6.26.0-py3-none-any.whl", hash = "sha256:3ba3dc97424b87b31bb46586b5167b3161b32d7820b9201a9e698c71e271602c"}, - {file = "ipykernel-6.26.0.tar.gz", hash = "sha256:553856658eb8430bbe9653ea041a41bff63e9606fc4628873fc92a6cf3abd404"}, + {file = "ipykernel-6.29.0-py3-none-any.whl", hash = "sha256:076663ca68492576f051e4af7720d33f34383e655f2be0d544c8b1c9de915b2f"}, + {file = "ipykernel-6.29.0.tar.gz", hash = "sha256:b5dd3013cab7b330df712891c96cd1ab868c27a7159e606f762015e9bf8ceb3f"}, ] [package.dependencies] @@ -948,7 +953,7 @@ matplotlib-inline = ">=0.1" nest-asyncio = "*" packaging = "*" psutil = "*" -pyzmq = ">=20" +pyzmq = ">=24" tornado = ">=6.1" traitlets = ">=5.4.0" @@ -957,7 +962,7 @@ 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"] +test = ["flaky", "ipyparallel", "pre-commit", "pytest (>=7.0)", "pytest-asyncio (==0.23.2)", "pytest-cov", "pytest-timeout"] [[package]] name = "ipython" @@ -1035,20 +1040,17 @@ arrow = ">=0.15.0" [[package]] name = "isort" -version = "5.12.0" +version = "5.13.2" description = "A Python utility / library to sort Python imports." 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"}, + {file = "isort-5.13.2-py3-none-any.whl", hash = "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6"}, + {file = "isort-5.13.2.tar.gz", hash = "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109"}, ] [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"] +colors = ["colorama (>=0.4.6)"] [[package]] name = "jedi" @@ -1071,13 +1073,13 @@ testing = ["Django", "attrs", "colorama", "docopt", "pytest (<7.0.0)"] [[package]] name = "jinja2" -version = "3.1.2" +version = "3.1.3" description = "A very fast and expressive template engine." 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"}, + {file = "Jinja2-3.1.3-py3-none-any.whl", hash = "sha256:7d6d50dd97d52cbc355597bd845fabfbac3f551e1f99619e39a35ce8c370b5fa"}, + {file = "Jinja2-3.1.3.tar.gz", hash = "sha256:ac8bd6544d4bb2c9792bf3a159e80bba8fda7f07e81bc3aed565432d5925ba90"}, ] [package.dependencies] @@ -1113,13 +1115,13 @@ files = [ [[package]] name = "jsonschema" -version = "4.20.0" +version = "4.21.1" description = "An implementation of JSON Schema validation for Python" optional = false python-versions = ">=3.8" files = [ - {file = "jsonschema-4.20.0-py3-none-any.whl", hash = "sha256:ed6231f0429ecf966f5bc8dfef245998220549cbbcf140f913b7464c52c3b6b3"}, - {file = "jsonschema-4.20.0.tar.gz", hash = "sha256:4f614fd46d8d61258610998997743ec5492a648b33cf478c1ddc23ed4598a5fa"}, + {file = "jsonschema-4.21.1-py3-none-any.whl", hash = "sha256:7996507afae316306f9e2290407761157c6f78002dcf7419acb99822143d1c6f"}, + {file = "jsonschema-4.21.1.tar.gz", hash = "sha256:85727c00279f5fa6bedbe6238d2aa6403bedd8b4864ab11207d07df3cc1b2ee5"}, ] [package.dependencies] @@ -1144,13 +1146,13 @@ format-nongpl = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339- [[package]] name = "jsonschema-specifications" -version = "2023.11.1" +version = "2023.12.1" description = "The JSON Schema meta-schemas and vocabularies, exposed as a Registry" optional = false python-versions = ">=3.8" files = [ - {file = "jsonschema_specifications-2023.11.1-py3-none-any.whl", hash = "sha256:f596778ab612b3fd29f72ea0d990393d0540a5aab18bf0407a46632eab540779"}, - {file = "jsonschema_specifications-2023.11.1.tar.gz", hash = "sha256:c9b234904ffe02f079bf91b14d79987faa685fd4b39c377a0996954c0090b9ca"}, + {file = "jsonschema_specifications-2023.12.1-py3-none-any.whl", hash = "sha256:87e4fdf3a94858b8a2ba2778d9ba57d8a9cafca7c7489c46ba0d30a8bc6a9c3c"}, + {file = "jsonschema_specifications-2023.12.1.tar.gz", hash = "sha256:48a76787b3e70f5ed53f1160d2b81f586e4ca6d1548c5de7085d1682674764cc"}, ] [package.dependencies] @@ -1226,13 +1228,13 @@ test = ["flaky", "pexpect", "pytest"] [[package]] name = "jupyter-core" -version = "5.5.0" +version = "5.7.1" description = "Jupyter core package. A base package on which Jupyter projects rely." optional = false python-versions = ">=3.8" files = [ - {file = "jupyter_core-5.5.0-py3-none-any.whl", hash = "sha256:e11e02cd8ae0a9de5c6c44abf5727df9f2581055afe00b22183f621ba3585805"}, - {file = "jupyter_core-5.5.0.tar.gz", hash = "sha256:880b86053bf298a8724994f95e99b99130659022a4f7f45f563084b6223861d3"}, + {file = "jupyter_core-5.7.1-py3-none-any.whl", hash = "sha256:c65c82126453a723a2804aa52409930434598fd9d35091d63dfb919d2b765bb7"}, + {file = "jupyter_core-5.7.1.tar.gz", hash = "sha256:de61a9d7fc71240f688b2fb5ab659fbb56979458dc66a71decd098e03c79e218"}, ] [package.dependencies] @@ -1271,13 +1273,13 @@ test = ["click", "pre-commit", "pytest (>=7.0)", "pytest-asyncio (>=0.19.0)", "p [[package]] name = "jupyter-lsp" -version = "2.2.0" +version = "2.2.2" description = "Multi-Language Server WebSocket proxy for Jupyter Notebook/Lab server" optional = false python-versions = ">=3.8" files = [ - {file = "jupyter-lsp-2.2.0.tar.gz", hash = "sha256:8ebbcb533adb41e5d635eb8fe82956b0aafbf0fd443b6c4bfa906edeeb8635a1"}, - {file = "jupyter_lsp-2.2.0-py3-none-any.whl", hash = "sha256:9e06b8b4f7dd50300b70dd1a78c0c3b0c3d8fa68e0f2d8a5d1fbab62072aca3f"}, + {file = "jupyter-lsp-2.2.2.tar.gz", hash = "sha256:256d24620542ae4bba04a50fc1f6ffe208093a07d8e697fea0a8d1b8ca1b7e5b"}, + {file = "jupyter_lsp-2.2.2-py3-none-any.whl", hash = "sha256:3b95229e4168355a8c91928057c1621ac3510ba98b2a925e82ebd77f078b1aa5"}, ] [package.dependencies] @@ -1286,13 +1288,13 @@ jupyter-server = ">=1.1.2" [[package]] name = "jupyter-server" -version = "2.10.1" +version = "2.12.5" description = "The backend—i.e. core services, APIs, and REST endpoints—to Jupyter web applications." optional = false python-versions = ">=3.8" files = [ - {file = "jupyter_server-2.10.1-py3-none-any.whl", hash = "sha256:20519e355d951fc5e1b6ac5952854fe7620d0cfb56588fa4efe362a758977ed3"}, - {file = "jupyter_server-2.10.1.tar.gz", hash = "sha256:e6da2657a954a7879eed28cc08e0817b01ffd81d7eab8634660397b55f926472"}, + {file = "jupyter_server-2.12.5-py3-none-any.whl", hash = "sha256:184a0f82809a8522777cfb6b760ab6f4b1bb398664c5860a27cec696cb884923"}, + {file = "jupyter_server-2.12.5.tar.gz", hash = "sha256:0edb626c94baa22809be1323f9770cf1c00a952b17097592e40d03e6a3951689"}, ] [package.dependencies] @@ -1322,13 +1324,13 @@ test = ["flaky", "ipykernel", "pre-commit", "pytest (>=7.0)", "pytest-console-sc [[package]] name = "jupyter-server-terminals" -version = "0.4.4" +version = "0.5.1" description = "A Jupyter Server Extension Providing Terminals." 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"}, + {file = "jupyter_server_terminals-0.5.1-py3-none-any.whl", hash = "sha256:5e63e947ddd97bb2832db5ef837a258d9ccd4192cd608c1270850ad947ae5dd7"}, + {file = "jupyter_server_terminals-0.5.1.tar.gz", hash = "sha256:16d3be9cf48be6a1f943f3a6c93c033be259cf4779184c66421709cf63dccfea"}, ] [package.dependencies] @@ -1336,18 +1338,18 @@ 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"] +docs = ["jinja2", "jupyter-server", "mistune (<4.0)", "myst-parser", "nbformat", "packaging", "pydata-sphinx-theme", "sphinxcontrib-github-alt", "sphinxcontrib-openapi", "sphinxcontrib-spelling", "sphinxemoji", "tornado"] +test = ["jupyter-server (>=2.0.0)", "pytest (>=7.0)", "pytest-jupyter[server] (>=0.5.3)", "pytest-timeout"] [[package]] name = "jupyterlab" -version = "4.0.8" +version = "4.0.11" description = "JupyterLab computational environment" optional = false python-versions = ">=3.8" files = [ - {file = "jupyterlab-4.0.8-py3-none-any.whl", hash = "sha256:2ff5aa2a51eb21df241d6011c236e88bd1ff9a5dbb75bebc54472f9c18bfffa4"}, - {file = "jupyterlab-4.0.8.tar.gz", hash = "sha256:c4fe93f977bcc987bd395d7fae5ab02e0c042bf4e0f7c95196f3e2e578c2fb3a"}, + {file = "jupyterlab-4.0.11-py3-none-any.whl", hash = "sha256:536bf0e78723153a5016ca7efb88ed0ecd7070d3f1555d5b0e2770658f900a3c"}, + {file = "jupyterlab-4.0.11.tar.gz", hash = "sha256:d1aec24712566bc25a36229788242778e498ca4088028e2f9aa156b8b7fdc8fc"}, ] [package.dependencies] @@ -1367,31 +1369,31 @@ tornado = ">=6.2.0" traitlets = "*" [package.extras] -dev = ["black[jupyter] (==23.10.1)", "build", "bump2version", "coverage", "hatch", "pre-commit", "pytest-cov", "ruff (==0.0.292)"] +dev = ["build", "bump2version", "coverage", "hatch", "pre-commit", "pytest-cov", "ruff (==0.1.6)"] docs = ["jsx-lexer", "myst-parser", "pydata-sphinx-theme (>=0.13.0)", "pytest", "pytest-check-links", "pytest-tornasync", "sphinx (>=1.8,<7.2.0)", "sphinx-copybutton"] docs-screenshots = ["altair (==5.0.1)", "ipython (==8.14.0)", "ipywidgets (==8.0.6)", "jupyterlab-geojson (==3.4.0)", "jupyterlab-language-pack-zh-cn (==4.0.post0)", "matplotlib (==3.7.1)", "nbconvert (>=7.0.0)", "pandas (==2.0.2)", "scipy (==1.10.1)", "vega-datasets (==0.9.0)"] test = ["coverage", "pytest (>=7.0)", "pytest-check-links (>=0.7)", "pytest-console-scripts", "pytest-cov", "pytest-jupyter (>=0.5.3)", "pytest-timeout", "pytest-tornasync", "requests", "requests-cache", "virtualenv"] [[package]] name = "jupyterlab-pygments" -version = "0.2.2" +version = "0.3.0" description = "Pygments theme using JupyterLab CSS variables" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" 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"}, + {file = "jupyterlab_pygments-0.3.0-py3-none-any.whl", hash = "sha256:841a89020971da1d8693f1a99997aefc5dc424bb1b251fd6322462a1b8842780"}, + {file = "jupyterlab_pygments-0.3.0.tar.gz", hash = "sha256:721aca4d9029252b11cfa9d185e5b5af4d54772bb8072f9b7036f4170054d35d"}, ] [[package]] name = "jupyterlab-server" -version = "2.25.1" +version = "2.25.2" description = "A set of server components for JupyterLab and JupyterLab like applications." optional = false python-versions = ">=3.8" files = [ - {file = "jupyterlab_server-2.25.1-py3-none-any.whl", hash = "sha256:dce9714d91fb3e53d2b37d0e0619fa26ed223c8e7b8c81cca112926de19b53a4"}, - {file = "jupyterlab_server-2.25.1.tar.gz", hash = "sha256:6491283b0000698eae1a38c48507930560dfcf7461aea0015368698aab34dd9c"}, + {file = "jupyterlab_server-2.25.2-py3-none-any.whl", hash = "sha256:5b1798c9cc6a44f65c757de9f97fc06fc3d42535afbf47d2ace5e964ab447aaf"}, + {file = "jupyterlab_server-2.25.2.tar.gz", hash = "sha256:bd0ec7a99ebcedc8bcff939ef86e52c378e44c2707e053fcd81d046ce979ee63"}, ] [package.dependencies] @@ -1550,47 +1552,48 @@ files = [ [[package]] name = "lazy-object-proxy" -version = "1.9.0" +version = "1.10.0" description = "A fast and thorough lazy object proxy." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" 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"}, + {file = "lazy-object-proxy-1.10.0.tar.gz", hash = "sha256:78247b6d45f43a52ef35c25b5581459e85117225408a4128a3daf8bf9648ac69"}, + {file = "lazy_object_proxy-1.10.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:855e068b0358ab916454464a884779c7ffa312b8925c6f7401e952dcf3b89977"}, + {file = "lazy_object_proxy-1.10.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ab7004cf2e59f7c2e4345604a3e6ea0d92ac44e1c2375527d56492014e690c3"}, + {file = "lazy_object_proxy-1.10.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc0d2fc424e54c70c4bc06787e4072c4f3b1aa2f897dfdc34ce1013cf3ceef05"}, + {file = "lazy_object_proxy-1.10.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e2adb09778797da09d2b5ebdbceebf7dd32e2c96f79da9052b2e87b6ea495895"}, + {file = "lazy_object_proxy-1.10.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b1f711e2c6dcd4edd372cf5dec5c5a30d23bba06ee012093267b3376c079ec83"}, + {file = "lazy_object_proxy-1.10.0-cp310-cp310-win32.whl", hash = "sha256:76a095cfe6045c7d0ca77db9934e8f7b71b14645f0094ffcd842349ada5c5fb9"}, + {file = "lazy_object_proxy-1.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:b4f87d4ed9064b2628da63830986c3d2dca7501e6018347798313fcf028e2fd4"}, + {file = "lazy_object_proxy-1.10.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:fec03caabbc6b59ea4a638bee5fce7117be8e99a4103d9d5ad77f15d6f81020c"}, + {file = "lazy_object_proxy-1.10.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:02c83f957782cbbe8136bee26416686a6ae998c7b6191711a04da776dc9e47d4"}, + {file = "lazy_object_proxy-1.10.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:009e6bb1f1935a62889ddc8541514b6a9e1fcf302667dcb049a0be5c8f613e56"}, + {file = "lazy_object_proxy-1.10.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:75fc59fc450050b1b3c203c35020bc41bd2695ed692a392924c6ce180c6f1dc9"}, + {file = "lazy_object_proxy-1.10.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:782e2c9b2aab1708ffb07d4bf377d12901d7a1d99e5e410d648d892f8967ab1f"}, + {file = "lazy_object_proxy-1.10.0-cp311-cp311-win32.whl", hash = "sha256:edb45bb8278574710e68a6b021599a10ce730d156e5b254941754a9cc0b17d03"}, + {file = "lazy_object_proxy-1.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:e271058822765ad5e3bca7f05f2ace0de58a3f4e62045a8c90a0dfd2f8ad8cc6"}, + {file = "lazy_object_proxy-1.10.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:e98c8af98d5707dcdecc9ab0863c0ea6e88545d42ca7c3feffb6b4d1e370c7ba"}, + {file = "lazy_object_proxy-1.10.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:952c81d415b9b80ea261d2372d2a4a2332a3890c2b83e0535f263ddfe43f0d43"}, + {file = "lazy_object_proxy-1.10.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80b39d3a151309efc8cc48675918891b865bdf742a8616a337cb0090791a0de9"}, + {file = "lazy_object_proxy-1.10.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:e221060b701e2aa2ea991542900dd13907a5c90fa80e199dbf5a03359019e7a3"}, + {file = "lazy_object_proxy-1.10.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:92f09ff65ecff3108e56526f9e2481b8116c0b9e1425325e13245abfd79bdb1b"}, + {file = "lazy_object_proxy-1.10.0-cp312-cp312-win32.whl", hash = "sha256:3ad54b9ddbe20ae9f7c1b29e52f123120772b06dbb18ec6be9101369d63a4074"}, + {file = "lazy_object_proxy-1.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:127a789c75151db6af398b8972178afe6bda7d6f68730c057fbbc2e96b08d282"}, + {file = "lazy_object_proxy-1.10.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9e4ed0518a14dd26092614412936920ad081a424bdcb54cc13349a8e2c6d106a"}, + {file = "lazy_object_proxy-1.10.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5ad9e6ed739285919aa9661a5bbed0aaf410aa60231373c5579c6b4801bd883c"}, + {file = "lazy_object_proxy-1.10.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2fc0a92c02fa1ca1e84fc60fa258458e5bf89d90a1ddaeb8ed9cc3147f417255"}, + {file = "lazy_object_proxy-1.10.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:0aefc7591920bbd360d57ea03c995cebc204b424524a5bd78406f6e1b8b2a5d8"}, + {file = "lazy_object_proxy-1.10.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5faf03a7d8942bb4476e3b62fd0f4cf94eaf4618e304a19865abf89a35c0bbee"}, + {file = "lazy_object_proxy-1.10.0-cp38-cp38-win32.whl", hash = "sha256:e333e2324307a7b5d86adfa835bb500ee70bfcd1447384a822e96495796b0ca4"}, + {file = "lazy_object_proxy-1.10.0-cp38-cp38-win_amd64.whl", hash = "sha256:cb73507defd385b7705c599a94474b1d5222a508e502553ef94114a143ec6696"}, + {file = "lazy_object_proxy-1.10.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:366c32fe5355ef5fc8a232c5436f4cc66e9d3e8967c01fb2e6302fd6627e3d94"}, + {file = "lazy_object_proxy-1.10.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2297f08f08a2bb0d32a4265e98a006643cd7233fb7983032bd61ac7a02956b3b"}, + {file = "lazy_object_proxy-1.10.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18dd842b49456aaa9a7cf535b04ca4571a302ff72ed8740d06b5adcd41fe0757"}, + {file = "lazy_object_proxy-1.10.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:217138197c170a2a74ca0e05bddcd5f1796c735c37d0eee33e43259b192aa424"}, + {file = "lazy_object_proxy-1.10.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9a3a87cf1e133e5b1994144c12ca4aa3d9698517fe1e2ca82977781b16955658"}, + {file = "lazy_object_proxy-1.10.0-cp39-cp39-win32.whl", hash = "sha256:30b339b2a743c5288405aa79a69e706a06e02958eab31859f7f3c04980853b70"}, + {file = "lazy_object_proxy-1.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:a899b10e17743683b293a729d3a11f2f399e8a90c73b089e29f5d0fe3509f0dd"}, + {file = "lazy_object_proxy-1.10.0-pp310.pp311.pp312.pp38.pp39-none-any.whl", hash = "sha256:80fa48bd89c8f2f456fc0765c11c23bf5af827febacd2f523ca5bc1893fcc09d"}, ] [[package]] @@ -1664,58 +1667,58 @@ files = [ [[package]] name = "matplotlib" -version = "3.7.3" +version = "3.7.4" description = "Python plotting package" optional = false python-versions = ">=3.8" files = [ - {file = "matplotlib-3.7.3-cp310-cp310-macosx_10_12_universal2.whl", hash = "sha256:085c33b27561d9c04386789d5aa5eb4a932ddef43cfcdd0e01735f9a6e85ce0c"}, - {file = "matplotlib-3.7.3-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:c568e80e1c17f68a727f30f591926751b97b98314d8e59804f54f86ae6fa6a22"}, - {file = "matplotlib-3.7.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7baf98c5ad59c5c4743ea884bb025cbffa52dacdfdac0da3e6021a285a90377e"}, - {file = "matplotlib-3.7.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:236024f582e40dac39bca592258888b38ae47a9fed7b8de652d68d3d02d47d2b"}, - {file = "matplotlib-3.7.3-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:12b4f6795efea037ce2d41e7c417ad8bd02d5719c6ad4a8450a0708f4a1cfb89"}, - {file = "matplotlib-3.7.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78b2136cc6c5415b78977e0e8c608647d597204b05b1d9089ccf513c7d913733"}, - {file = "matplotlib-3.7.3-cp310-cp310-win32.whl", hash = "sha256:122dcbf9be0086e2a95d9e5e0632dbf3bd5b65eaa68c369363310a6c87753059"}, - {file = "matplotlib-3.7.3-cp310-cp310-win_amd64.whl", hash = "sha256:4aab27d9e33293389e3c1d7c881d414a72bdfda0fedc3a6bf46c6fa88d9b8015"}, - {file = "matplotlib-3.7.3-cp311-cp311-macosx_10_12_universal2.whl", hash = "sha256:d5adc743de91e8e0b13df60deb1b1c285b8effea3d66223afceb14b63c9b05de"}, - {file = "matplotlib-3.7.3-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:55de4cf7cd0071b8ebf203981b53ab64f988a0a1f897a2dff300a1124e8bcd8b"}, - {file = "matplotlib-3.7.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ac03377fd908aaee2312d0b11735753e907adb6f4d1d102de5e2425249693f6c"}, - {file = "matplotlib-3.7.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:755bafc10a46918ce9a39980009b54b02dd249594e5adf52f9c56acfddb5d0b7"}, - {file = "matplotlib-3.7.3-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1a6094c6f8e8d18db631754df4fe9a34dec3caf074f6869a7db09f18f9b1d6b2"}, - {file = "matplotlib-3.7.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:272dba2f1b107790ed78ebf5385b8d14b27ad9e90419de340364b49fe549a993"}, - {file = "matplotlib-3.7.3-cp311-cp311-win32.whl", hash = "sha256:591c123bed1cb4b9996fb60b41a6d89c2ec4943244540776c5f1283fb6960a53"}, - {file = "matplotlib-3.7.3-cp311-cp311-win_amd64.whl", hash = "sha256:3bf3a178c6504694cee8b88b353df0051583f2f6f8faa146f67115c27c856881"}, - {file = "matplotlib-3.7.3-cp312-cp312-macosx_10_12_universal2.whl", hash = "sha256:edf54cac8ee3603f3093616b40a931e8c063969756a4d78a86e82c2fea9659f7"}, - {file = "matplotlib-3.7.3-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:91e36a85ea639a1ba9f91427041eac064b04829945fe331a92617b6cb21d27e5"}, - {file = "matplotlib-3.7.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:caf5eaaf7c68f8d7df269dfbcaf46f48a70ff482bfcebdcc97519671023f2a7d"}, - {file = "matplotlib-3.7.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:74bf57f505efea376097e948b7cdd87191a7ce8180616390aef496639edf601f"}, - {file = "matplotlib-3.7.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ee152a88a0da527840a426535514b6ed8ac4240eb856b1da92cf48124320e346"}, - {file = "matplotlib-3.7.3-cp312-cp312-win_amd64.whl", hash = "sha256:67a410a9c9e07cbc83581eeea144bbe298870bf0ac0ee2f2e10a015ab7efee19"}, - {file = "matplotlib-3.7.3-cp38-cp38-macosx_10_12_universal2.whl", hash = "sha256:259999c05285cb993d7f2a419cea547863fa215379eda81f7254c9e932963729"}, - {file = "matplotlib-3.7.3-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:3f4e7fd5a6157e1d018ce2166ec8e531a481dd4a36f035b5c23edfe05a25419a"}, - {file = "matplotlib-3.7.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:faa3d12d8811d08d14080a8b7b9caea9a457dc495350166b56df0db4b9909ef5"}, - {file = "matplotlib-3.7.3-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:336e88900c11441e458da01c8414fc57e04e17f9d3bb94958a76faa2652bcf6b"}, - {file = "matplotlib-3.7.3-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:12f4c0dd8aa280d796c8772ea8265a14f11a04319baa3a16daa5556065e8baea"}, - {file = "matplotlib-3.7.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1990955b11e7918d256cf3b956b10997f405b7917a3f1c7d8e69c1d15c7b1930"}, - {file = "matplotlib-3.7.3-cp38-cp38-win32.whl", hash = "sha256:e78707b751260b42b721507ad7aa60fe4026d7f51c74cca6b9cd8b123ebb633a"}, - {file = "matplotlib-3.7.3-cp38-cp38-win_amd64.whl", hash = "sha256:e594ee43c59ea39ca5c6244667cac9d017a3527febc31f5532ad9135cf7469ec"}, - {file = "matplotlib-3.7.3-cp39-cp39-macosx_10_12_universal2.whl", hash = "sha256:6eaa1cf0e94c936a26b78f6d756c5fbc12e0a58c8a68b7248a2a31456ce4e234"}, - {file = "matplotlib-3.7.3-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:0a97af9d22e8ebedc9f00b043d9bbd29a375e9e10b656982012dded44c10fd77"}, - {file = "matplotlib-3.7.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1f9c6c16597af660433ab330b59ee2934b832ee1fabcaf5cbde7b2add840f31e"}, - {file = "matplotlib-3.7.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a7240259b4b9cbc62381f6378cff4d57af539162a18e832c1e48042fabc40b6b"}, - {file = "matplotlib-3.7.3-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:747c6191d2e88ae854809e69aa358dbf852ff1a5738401b85c1cc9012309897a"}, - {file = "matplotlib-3.7.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec726b08a5275d827aa91bb951e68234a4423adb91cf65bc0fcdc0f2777663f7"}, - {file = "matplotlib-3.7.3-cp39-cp39-win32.whl", hash = "sha256:40e3b9b450c6534f07278310c4e34caff41c2a42377e4b9d47b0f8d3ac1083a2"}, - {file = "matplotlib-3.7.3-cp39-cp39-win_amd64.whl", hash = "sha256:dfc118642903a23e309b1da32886bb39a4314147d013e820c86b5fb4cb2e36d0"}, - {file = "matplotlib-3.7.3-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:165c8082bf8fc0360c24aa4724a22eaadbfd8c28bf1ccf7e94d685cad48261e4"}, - {file = "matplotlib-3.7.3-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ebd8470cc2a3594746ff0513aecbfa2c55ff6f58e6cef2efb1a54eb87c88ffa2"}, - {file = "matplotlib-3.7.3-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7153453669c9672b52095119fd21dd032d19225d48413a2871519b17db4b0fde"}, - {file = "matplotlib-3.7.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:498a08267dc69dd8f24c4b5d7423fa584d7ce0027ba71f7881df05fc09b89bb7"}, - {file = "matplotlib-3.7.3-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:d48999c4b19b5a0c058c9cd828ff6fc7748390679f6cf9a2ad653a3e802c87d3"}, - {file = "matplotlib-3.7.3-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:22d65d18b4ee8070a5fea5761d59293f1f9e2fac37ec9ce090463b0e629432fd"}, - {file = "matplotlib-3.7.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c40cde976c36693cc0767e27cf5f443f91c23520060bd9496678364adfafe9c"}, - {file = "matplotlib-3.7.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:39018a2b17592448fbfdf4b8352955e6c3905359939791d4ff429296494d1a0c"}, - {file = "matplotlib-3.7.3.tar.gz", hash = "sha256:f09b3dd6bdeb588de91f853bbb2d6f0ff8ab693485b0c49035eaa510cb4f142e"}, + {file = "matplotlib-3.7.4-cp310-cp310-macosx_10_12_universal2.whl", hash = "sha256:b71079239bd866bf56df023e5146de159cb0c7294e508830901f4d79e2d89385"}, + {file = "matplotlib-3.7.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:bf91a42f6274a64cb41189120b620c02e574535ff6671fa836cade7701b06fbd"}, + {file = "matplotlib-3.7.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f757e8b42841d6add0cb69b42497667f0d25a404dcd50bd923ec9904e38414c4"}, + {file = "matplotlib-3.7.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4dfee00aa4bd291e08bb9461831c26ce0da85ca9781bb8794f2025c6e925281"}, + {file = "matplotlib-3.7.4-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3640f33632beb3993b698b1be9d1c262b742761d6101f3c27b87b2185d25c875"}, + {file = "matplotlib-3.7.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ff539c4a17ecdf076ed808ee271ffae4a30dcb7e157b99ccae2c837262c07db6"}, + {file = "matplotlib-3.7.4-cp310-cp310-win32.whl", hash = "sha256:24b8f28af3e766195c09b780b15aa9f6710192b415ae7866b9c03dee7ec86370"}, + {file = "matplotlib-3.7.4-cp310-cp310-win_amd64.whl", hash = "sha256:3fa193286712c3b6c3cfa5fe8a6bb563f8c52cc750006c782296e0807ce5e799"}, + {file = "matplotlib-3.7.4-cp311-cp311-macosx_10_12_universal2.whl", hash = "sha256:b167f54cb4654b210c9624ec7b54e2b3b8de68c93a14668937e7e53df60770ec"}, + {file = "matplotlib-3.7.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:7dfe6821f1944cb35603ff22e21510941bbcce7ccf96095beffaac890d39ce77"}, + {file = "matplotlib-3.7.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3c557d9165320dff3c5f2bb99bfa0b6813d3e626423ff71c40d6bc23b83c3339"}, + {file = "matplotlib-3.7.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:08372696b3bb45c563472a552a705bfa0942f0a8ffe084db8a4e8f9153fbdf9d"}, + {file = "matplotlib-3.7.4-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:81e1a7ac818000e8ac3ca696c3fdc501bc2d3adc89005e7b4e22ee5e9d51de98"}, + {file = "matplotlib-3.7.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:390920a3949906bc4b0216198d378f2a640c36c622e3584dd0c79a7c59ae9f50"}, + {file = "matplotlib-3.7.4-cp311-cp311-win32.whl", hash = "sha256:62e094d8da26294634da9e7f1856beee3978752b1b530c8e1763d2faed60cc10"}, + {file = "matplotlib-3.7.4-cp311-cp311-win_amd64.whl", hash = "sha256:f8fc2df756105784e650605e024d36dc2d048d68e5c1b26df97ee25d1bd41f9f"}, + {file = "matplotlib-3.7.4-cp312-cp312-macosx_10_12_universal2.whl", hash = "sha256:568574756127791903604e315c11aef9f255151e4cfe20ec603a70f9dda8e259"}, + {file = "matplotlib-3.7.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:7d479aac338195e2199a8cfc03c4f2f55914e6a120177edae79e0340a6406457"}, + {file = "matplotlib-3.7.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:32183d4be84189a4c52b4b8861434d427d9118db2cec32986f98ed6c02dcfbb6"}, + {file = "matplotlib-3.7.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0037d066cca1f4bda626c507cddeb6f7da8283bc6a214da2db13ff2162933c52"}, + {file = "matplotlib-3.7.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:44856632ebce88abd8efdc0a0dceec600418dcac06b72ae77af0019d260aa243"}, + {file = "matplotlib-3.7.4-cp312-cp312-win_amd64.whl", hash = "sha256:632fc938c22117d4241411191cfb88ac264a4c0a9ac702244641ddf30f0d739c"}, + {file = "matplotlib-3.7.4-cp38-cp38-macosx_10_12_universal2.whl", hash = "sha256:ce163be048613b9d1962273708cc97e09ca05d37312e670d166cf332b80bbaff"}, + {file = "matplotlib-3.7.4-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:e680f49bb8052ba3b2698e370155d2b4afb49f9af1cc611a26579d5981e2852a"}, + {file = "matplotlib-3.7.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:0604880e4327114054199108b7390f987f4f40ee5ce728985836889e11a780ba"}, + {file = "matplotlib-3.7.4-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:1e6abcde6fc52475f9d6a12b9f1792aee171ce7818ef6df5d61cb0b82816e6e8"}, + {file = "matplotlib-3.7.4-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f59a70e2ec3212033ef6633ed07682da03f5249379722512a3a2a26a7d9a738e"}, + {file = "matplotlib-3.7.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7a9981b2a2dd9da06eca4ab5855d09b54b8ce7377c3e0e3957767b83219d652d"}, + {file = "matplotlib-3.7.4-cp38-cp38-win32.whl", hash = "sha256:83859ac26839660ecd164ee8311272074250b915ac300f9b2eccc84410f8953b"}, + {file = "matplotlib-3.7.4-cp38-cp38-win_amd64.whl", hash = "sha256:7a7709796ac59fe8debde68272388be6ed449c8971362eb5b60d280eac8dadde"}, + {file = "matplotlib-3.7.4-cp39-cp39-macosx_10_12_universal2.whl", hash = "sha256:b1d70bc1ea1bf110bec64f4578de3e14947909a8887df4c1fd44492eca487955"}, + {file = "matplotlib-3.7.4-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:c83f49e795a5de6c168876eea723f5b88355202f9603c55977f5356213aa8280"}, + {file = "matplotlib-3.7.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5c9133f230945fe10652eb33e43642e933896194ef6a4f8d5e79bb722bdb2000"}, + {file = "matplotlib-3.7.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:798ff59022eeb276380ce9a73ba35d13c3d1499ab9b73d194fd07f1b0a41c304"}, + {file = "matplotlib-3.7.4-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1707b20b25e90538c2ce8d4409e30f0ef1df4017cc65ad0439633492a973635b"}, + {file = "matplotlib-3.7.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8e6227ca8492baeef873cdd8e169a318efb5c3a25ce94e69727e7f964995b0b1"}, + {file = "matplotlib-3.7.4-cp39-cp39-win32.whl", hash = "sha256:5661c8639aded7d1bbf781373a359011cb1dd09199dee49043e9e68dd16f07ba"}, + {file = "matplotlib-3.7.4-cp39-cp39-win_amd64.whl", hash = "sha256:55eec941a4743f0bd3e5b8ee180e36b7ea8e62f867bf2613937c9f01b9ac06a2"}, + {file = "matplotlib-3.7.4-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:ab16868714e5cc90ec8f7ff5d83d23bcd6559224d8e9cb5227c9f58748889fe8"}, + {file = "matplotlib-3.7.4-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0c698b33f9a3f0b127a8e614c8fb4087563bb3caa9c9d95298722fa2400cdd3f"}, + {file = "matplotlib-3.7.4-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:be3493bbcb4d255cb71de1f9050ac71682fce21a56089eadbcc8e21784cb12ee"}, + {file = "matplotlib-3.7.4-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:f8c725d1dd2901b2e7ec6cd64165e00da2978cc23d4143cb9ef745bec88e6b04"}, + {file = "matplotlib-3.7.4-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:286332f8f45f8ffde2d2119b9fdd42153dccd5025fa9f451b4a3b5c086e26da5"}, + {file = "matplotlib-3.7.4-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:116ef0b43aa00ff69260b4cce39c571e4b8c6f893795b708303fa27d9b9d7548"}, + {file = "matplotlib-3.7.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c90590d4b46458677d80bc3218f3f1ac11fc122baa9134e0cb5b3e8fc3714052"}, + {file = "matplotlib-3.7.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:de7c07069687be64fd9d119da3122ba13a8d399eccd3f844815f0dc78a870b2c"}, + {file = "matplotlib-3.7.4.tar.gz", hash = "sha256:7cd4fef8187d1dd0d9dcfdbaa06ac326d396fb8c71c647129f0bf56835d77026"}, ] [package.dependencies] @@ -1729,7 +1732,6 @@ packaging = ">=20.0" pillow = ">=6.2.0" pyparsing = ">=2.3.1" python-dateutil = ">=2.7" -setuptools_scm = ">=7" [[package]] name = "matplotlib-inline" @@ -1791,13 +1793,13 @@ test = ["flaky", "ipykernel (>=6.19.3)", "ipython", "ipywidgets", "nbconvert (>= [[package]] name = "nbconvert" -version = "7.11.0" +version = "7.14.2" description = "Converting Jupyter Notebooks" optional = false python-versions = ">=3.8" files = [ - {file = "nbconvert-7.11.0-py3-none-any.whl", hash = "sha256:d1d417b7f34a4e38887f8da5bdfd12372adf3b80f995d57556cb0972c68909fe"}, - {file = "nbconvert-7.11.0.tar.gz", hash = "sha256:abedc01cf543177ffde0bfc2a69726d5a478f6af10a332fc1bf29fcb4f0cf000"}, + {file = "nbconvert-7.14.2-py3-none-any.whl", hash = "sha256:db28590cef90f7faf2ebbc71acd402cbecf13d29176df728c0a9025a49345ea1"}, + {file = "nbconvert-7.14.2.tar.gz", hash = "sha256:a7f8808fd4e082431673ac538400218dd45efd076fbeb07cc6e5aa5a3a4e949e"}, ] [package.dependencies] @@ -1824,7 +1826,7 @@ docs = ["ipykernel", "ipython", "myst-parser", "nbsphinx (>=0.2.12)", "pydata-sp qtpdf = ["nbconvert[qtpng]"] qtpng = ["pyqtwebengine (>=5.15)"] serve = ["tornado (>=6.1)"] -test = ["flaky", "ipykernel", "ipywidgets (>=7)", "pytest"] +test = ["flaky", "ipykernel", "ipywidgets (>=7.5)", "pytest"] webpdf = ["playwright"] [[package]] @@ -1948,24 +1950,24 @@ tests = ["pytest"] [[package]] name = "nest-asyncio" -version = "1.5.8" +version = "1.5.9" description = "Patch asyncio to allow nested event loops" optional = false python-versions = ">=3.5" files = [ - {file = "nest_asyncio-1.5.8-py3-none-any.whl", hash = "sha256:accda7a339a70599cb08f9dd09a67e0c2ef8d8d6f4c07f96ab203f2ae254e48d"}, - {file = "nest_asyncio-1.5.8.tar.gz", hash = "sha256:25aa2ca0d2a5b5531956b9e273b45cf664cae2b145101d73b86b199978d48fdb"}, + {file = "nest_asyncio-1.5.9-py3-none-any.whl", hash = "sha256:61ec07ef052e72e3de22045b81b2cc7d71fceb04c568ba0b2e4b2f9f5231bec2"}, + {file = "nest_asyncio-1.5.9.tar.gz", hash = "sha256:d1e1144e9c6e3e6392e0fcf5211cb1c8374b5648a98f1ebe48e5336006b41907"}, ] [[package]] name = "notebook" -version = "7.0.6" +version = "7.0.7" description = "Jupyter Notebook - A web-based notebook environment for interactive computing" optional = false python-versions = ">=3.8" files = [ - {file = "notebook-7.0.6-py3-none-any.whl", hash = "sha256:0fe8f67102fea3744fedf652e4c15339390902ca70c5a31c4f547fa23da697cc"}, - {file = "notebook-7.0.6.tar.gz", hash = "sha256:ec6113b06529019f7f287819af06c97a2baf7a95ac21a8f6e32192898e9f9a58"}, + {file = "notebook-7.0.7-py3-none-any.whl", hash = "sha256:289b606d7e173f75a18beb1406ef411b43f97f7a9c55ba03efa3622905a62346"}, + {file = "notebook-7.0.7.tar.gz", hash = "sha256:3bcff00c17b3ac142ef5f436d50637d936b274cfa0b41f6ac0175363de9b4e09"}, ] [package.dependencies] @@ -2125,13 +2127,13 @@ xml = ["lxml (>=4.6.3)"] [[package]] name = "pandocfilters" -version = "1.5.0" +version = "1.5.1" description = "Utilities for writing pandoc filters in python" 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"}, + {file = "pandocfilters-1.5.1-py2.py3-none-any.whl", hash = "sha256:93be382804a9cdb0a7267585f157e5d1731bbe5545a85b268d6f5fe6232de2bc"}, + {file = "pandocfilters-1.5.1.tar.gz", hash = "sha256:002b4a555ee4ebc03f8b66307e287fa492e4a77b4ea14d3f934328297bb4939e"}, ] [[package]] @@ -2151,13 +2153,13 @@ testing = ["docopt", "pytest (<6.0.0)"] [[package]] name = "pexpect" -version = "4.8.0" +version = "4.9.0" description = "Pexpect allows easy control of interactive console applications." 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"}, + {file = "pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523"}, + {file = "pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f"}, ] [package.dependencies] @@ -2176,70 +2178,88 @@ files = [ [[package]] name = "pillow" -version = "10.1.0" +version = "10.2.0" description = "Python Imaging Library (Fork)" optional = false python-versions = ">=3.8" files = [ - {file = "Pillow-10.1.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:1ab05f3db77e98f93964697c8efc49c7954b08dd61cff526b7f2531a22410106"}, - {file = "Pillow-10.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6932a7652464746fcb484f7fc3618e6503d2066d853f68a4bd97193a3996e273"}, - {file = "Pillow-10.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5f63b5a68daedc54c7c3464508d8c12075e56dcfbd42f8c1bf40169061ae666"}, - {file = "Pillow-10.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0949b55eb607898e28eaccb525ab104b2d86542a85c74baf3a6dc24002edec2"}, - {file = "Pillow-10.1.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:ae88931f93214777c7a3aa0a8f92a683f83ecde27f65a45f95f22d289a69e593"}, - {file = "Pillow-10.1.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:b0eb01ca85b2361b09480784a7931fc648ed8b7836f01fb9241141b968feb1db"}, - {file = "Pillow-10.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d27b5997bdd2eb9fb199982bb7eb6164db0426904020dc38c10203187ae2ff2f"}, - {file = "Pillow-10.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7df5608bc38bd37ef585ae9c38c9cd46d7c81498f086915b0f97255ea60c2818"}, - {file = "Pillow-10.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:41f67248d92a5e0a2076d3517d8d4b1e41a97e2df10eb8f93106c89107f38b57"}, - {file = "Pillow-10.1.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:1fb29c07478e6c06a46b867e43b0bcdb241b44cc52be9bc25ce5944eed4648e7"}, - {file = "Pillow-10.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2cdc65a46e74514ce742c2013cd4a2d12e8553e3a2563c64879f7c7e4d28bce7"}, - {file = "Pillow-10.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50d08cd0a2ecd2a8657bd3d82c71efd5a58edb04d9308185d66c3a5a5bed9610"}, - {file = "Pillow-10.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:062a1610e3bc258bff2328ec43f34244fcec972ee0717200cb1425214fe5b839"}, - {file = "Pillow-10.1.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:61f1a9d247317fa08a308daaa8ee7b3f760ab1809ca2da14ecc88ae4257d6172"}, - {file = "Pillow-10.1.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:a646e48de237d860c36e0db37ecaecaa3619e6f3e9d5319e527ccbc8151df061"}, - {file = "Pillow-10.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:47e5bf85b80abc03be7455c95b6d6e4896a62f6541c1f2ce77a7d2bb832af262"}, - {file = "Pillow-10.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a92386125e9ee90381c3369f57a2a50fa9e6aa8b1cf1d9c4b200d41a7dd8e992"}, - {file = "Pillow-10.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:0f7c276c05a9767e877a0b4c5050c8bee6a6d960d7f0c11ebda6b99746068c2a"}, - {file = "Pillow-10.1.0-cp312-cp312-macosx_10_10_x86_64.whl", hash = "sha256:a89b8312d51715b510a4fe9fc13686283f376cfd5abca8cd1c65e4c76e21081b"}, - {file = "Pillow-10.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:00f438bb841382b15d7deb9a05cc946ee0f2c352653c7aa659e75e592f6fa17d"}, - {file = "Pillow-10.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3d929a19f5469b3f4df33a3df2983db070ebb2088a1e145e18facbc28cae5b27"}, - {file = "Pillow-10.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a92109192b360634a4489c0c756364c0c3a2992906752165ecb50544c251312"}, - {file = "Pillow-10.1.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:0248f86b3ea061e67817c47ecbe82c23f9dd5d5226200eb9090b3873d3ca32de"}, - {file = "Pillow-10.1.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:9882a7451c680c12f232a422730f986a1fcd808da0fd428f08b671237237d651"}, - {file = "Pillow-10.1.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:1c3ac5423c8c1da5928aa12c6e258921956757d976405e9467c5f39d1d577a4b"}, - {file = "Pillow-10.1.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:806abdd8249ba3953c33742506fe414880bad78ac25cc9a9b1c6ae97bedd573f"}, - {file = "Pillow-10.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:eaed6977fa73408b7b8a24e8b14e59e1668cfc0f4c40193ea7ced8e210adf996"}, - {file = "Pillow-10.1.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:fe1e26e1ffc38be097f0ba1d0d07fcade2bcfd1d023cda5b29935ae8052bd793"}, - {file = "Pillow-10.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7a7e3daa202beb61821c06d2517428e8e7c1aab08943e92ec9e5755c2fc9ba5e"}, - {file = "Pillow-10.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:24fadc71218ad2b8ffe437b54876c9382b4a29e030a05a9879f615091f42ffc2"}, - {file = "Pillow-10.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa1d323703cfdac2036af05191b969b910d8f115cf53093125e4058f62012c9a"}, - {file = "Pillow-10.1.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:912e3812a1dbbc834da2b32299b124b5ddcb664ed354916fd1ed6f193f0e2d01"}, - {file = "Pillow-10.1.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:7dbaa3c7de82ef37e7708521be41db5565004258ca76945ad74a8e998c30af8d"}, - {file = "Pillow-10.1.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9d7bc666bd8c5a4225e7ac71f2f9d12466ec555e89092728ea0f5c0c2422ea80"}, - {file = "Pillow-10.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:baada14941c83079bf84c037e2d8b7506ce201e92e3d2fa0d1303507a8538212"}, - {file = "Pillow-10.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:2ef6721c97894a7aa77723740a09547197533146fba8355e86d6d9a4a1056b14"}, - {file = "Pillow-10.1.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:0a026c188be3b443916179f5d04548092e253beb0c3e2ee0a4e2cdad72f66099"}, - {file = "Pillow-10.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:04f6f6149f266a100374ca3cc368b67fb27c4af9f1cc8cb6306d849dcdf12616"}, - {file = "Pillow-10.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb40c011447712d2e19cc261c82655f75f32cb724788df315ed992a4d65696bb"}, - {file = "Pillow-10.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a8413794b4ad9719346cd9306118450b7b00d9a15846451549314a58ac42219"}, - {file = "Pillow-10.1.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:c9aeea7b63edb7884b031a35305629a7593272b54f429a9869a4f63a1bf04c34"}, - {file = "Pillow-10.1.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:b4005fee46ed9be0b8fb42be0c20e79411533d1fd58edabebc0dd24626882cfd"}, - {file = "Pillow-10.1.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4d0152565c6aa6ebbfb1e5d8624140a440f2b99bf7afaafbdbf6430426497f28"}, - {file = "Pillow-10.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d921bc90b1defa55c9917ca6b6b71430e4286fc9e44c55ead78ca1a9f9eba5f2"}, - {file = "Pillow-10.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:cfe96560c6ce2f4c07d6647af2d0f3c54cc33289894ebd88cfbb3bcd5391e256"}, - {file = "Pillow-10.1.0-pp310-pypy310_pp73-macosx_10_10_x86_64.whl", hash = "sha256:937bdc5a7f5343d1c97dc98149a0be7eb9704e937fe3dc7140e229ae4fc572a7"}, - {file = "Pillow-10.1.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1c25762197144e211efb5f4e8ad656f36c8d214d390585d1d21281f46d556ba"}, - {file = "Pillow-10.1.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:afc8eef765d948543a4775f00b7b8c079b3321d6b675dde0d02afa2ee23000b4"}, - {file = "Pillow-10.1.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:883f216eac8712b83a63f41b76ddfb7b2afab1b74abbb413c5df6680f071a6b9"}, - {file = "Pillow-10.1.0-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:b920e4d028f6442bea9a75b7491c063f0b9a3972520731ed26c83e254302eb1e"}, - {file = "Pillow-10.1.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c41d960babf951e01a49c9746f92c5a7e0d939d1652d7ba30f6b3090f27e412"}, - {file = "Pillow-10.1.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:1fafabe50a6977ac70dfe829b2d5735fd54e190ab55259ec8aea4aaea412fa0b"}, - {file = "Pillow-10.1.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:3b834f4b16173e5b92ab6566f0473bfb09f939ba14b23b8da1f54fa63e4b623f"}, - {file = "Pillow-10.1.0.tar.gz", hash = "sha256:e6bf8de6c36ed96c86ea3b6e1d5273c53f46ef518a062464cd7ef5dd2cf92e38"}, + {file = "pillow-10.2.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:7823bdd049099efa16e4246bdf15e5a13dbb18a51b68fa06d6c1d4d8b99a796e"}, + {file = "pillow-10.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:83b2021f2ade7d1ed556bc50a399127d7fb245e725aa0113ebd05cfe88aaf588"}, + {file = "pillow-10.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6fad5ff2f13d69b7e74ce5b4ecd12cc0ec530fcee76356cac6742785ff71c452"}, + {file = "pillow-10.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da2b52b37dad6d9ec64e653637a096905b258d2fc2b984c41ae7d08b938a67e4"}, + {file = "pillow-10.2.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:47c0995fc4e7f79b5cfcab1fc437ff2890b770440f7696a3ba065ee0fd496563"}, + {file = "pillow-10.2.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:322bdf3c9b556e9ffb18f93462e5f749d3444ce081290352c6070d014c93feb2"}, + {file = "pillow-10.2.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:51f1a1bffc50e2e9492e87d8e09a17c5eea8409cda8d3f277eb6edc82813c17c"}, + {file = "pillow-10.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:69ffdd6120a4737710a9eee73e1d2e37db89b620f702754b8f6e62594471dee0"}, + {file = "pillow-10.2.0-cp310-cp310-win32.whl", hash = "sha256:c6dafac9e0f2b3c78df97e79af707cdc5ef8e88208d686a4847bab8266870023"}, + {file = "pillow-10.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:aebb6044806f2e16ecc07b2a2637ee1ef67a11840a66752751714a0d924adf72"}, + {file = "pillow-10.2.0-cp310-cp310-win_arm64.whl", hash = "sha256:7049e301399273a0136ff39b84c3678e314f2158f50f517bc50285fb5ec847ad"}, + {file = "pillow-10.2.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:35bb52c37f256f662abdfa49d2dfa6ce5d93281d323a9af377a120e89a9eafb5"}, + {file = "pillow-10.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9c23f307202661071d94b5e384e1e1dc7dfb972a28a2310e4ee16103e66ddb67"}, + {file = "pillow-10.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:773efe0603db30c281521a7c0214cad7836c03b8ccff897beae9b47c0b657d61"}, + {file = "pillow-10.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11fa2e5984b949b0dd6d7a94d967743d87c577ff0b83392f17cb3990d0d2fd6e"}, + {file = "pillow-10.2.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:716d30ed977be8b37d3ef185fecb9e5a1d62d110dfbdcd1e2a122ab46fddb03f"}, + {file = "pillow-10.2.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:a086c2af425c5f62a65e12fbf385f7c9fcb8f107d0849dba5839461a129cf311"}, + {file = "pillow-10.2.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c8de2789052ed501dd829e9cae8d3dcce7acb4777ea4a479c14521c942d395b1"}, + {file = "pillow-10.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:609448742444d9290fd687940ac0b57fb35e6fd92bdb65386e08e99af60bf757"}, + {file = "pillow-10.2.0-cp311-cp311-win32.whl", hash = "sha256:823ef7a27cf86df6597fa0671066c1b596f69eba53efa3d1e1cb8b30f3533068"}, + {file = "pillow-10.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:1da3b2703afd040cf65ec97efea81cfba59cdbed9c11d8efc5ab09df9509fc56"}, + {file = "pillow-10.2.0-cp311-cp311-win_arm64.whl", hash = "sha256:edca80cbfb2b68d7b56930b84a0e45ae1694aeba0541f798e908a49d66b837f1"}, + {file = "pillow-10.2.0-cp312-cp312-macosx_10_10_x86_64.whl", hash = "sha256:1b5e1b74d1bd1b78bc3477528919414874748dd363e6272efd5abf7654e68bef"}, + {file = "pillow-10.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0eae2073305f451d8ecacb5474997c08569fb4eb4ac231ffa4ad7d342fdc25ac"}, + {file = "pillow-10.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b7c2286c23cd350b80d2fc9d424fc797575fb16f854b831d16fd47ceec078f2c"}, + {file = "pillow-10.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e23412b5c41e58cec602f1135c57dfcf15482013ce6e5f093a86db69646a5aa"}, + {file = "pillow-10.2.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:52a50aa3fb3acb9cf7213573ef55d31d6eca37f5709c69e6858fe3bc04a5c2a2"}, + {file = "pillow-10.2.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:127cee571038f252a552760076407f9cff79761c3d436a12af6000cd182a9d04"}, + {file = "pillow-10.2.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:8d12251f02d69d8310b046e82572ed486685c38f02176bd08baf216746eb947f"}, + {file = "pillow-10.2.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:54f1852cd531aa981bc0965b7d609f5f6cc8ce8c41b1139f6ed6b3c54ab82bfb"}, + {file = "pillow-10.2.0-cp312-cp312-win32.whl", hash = "sha256:257d8788df5ca62c980314053197f4d46eefedf4e6175bc9412f14412ec4ea2f"}, + {file = "pillow-10.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:154e939c5f0053a383de4fd3d3da48d9427a7e985f58af8e94d0b3c9fcfcf4f9"}, + {file = "pillow-10.2.0-cp312-cp312-win_arm64.whl", hash = "sha256:f379abd2f1e3dddb2b61bc67977a6b5a0a3f7485538bcc6f39ec76163891ee48"}, + {file = "pillow-10.2.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:8373c6c251f7ef8bda6675dd6d2b3a0fcc31edf1201266b5cf608b62a37407f9"}, + {file = "pillow-10.2.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:870ea1ada0899fd0b79643990809323b389d4d1d46c192f97342eeb6ee0b8483"}, + {file = "pillow-10.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b4b6b1e20608493548b1f32bce8cca185bf0480983890403d3b8753e44077129"}, + {file = "pillow-10.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3031709084b6e7852d00479fd1d310b07d0ba82765f973b543c8af5061cf990e"}, + {file = "pillow-10.2.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:3ff074fc97dd4e80543a3e91f69d58889baf2002b6be64347ea8cf5533188213"}, + {file = "pillow-10.2.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:cb4c38abeef13c61d6916f264d4845fab99d7b711be96c326b84df9e3e0ff62d"}, + {file = "pillow-10.2.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b1b3020d90c2d8e1dae29cf3ce54f8094f7938460fb5ce8bc5c01450b01fbaf6"}, + {file = "pillow-10.2.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:170aeb00224ab3dc54230c797f8404507240dd868cf52066f66a41b33169bdbe"}, + {file = "pillow-10.2.0-cp38-cp38-win32.whl", hash = "sha256:c4225f5220f46b2fde568c74fca27ae9771536c2e29d7c04f4fb62c83275ac4e"}, + {file = "pillow-10.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:0689b5a8c5288bc0504d9fcee48f61a6a586b9b98514d7d29b840143d6734f39"}, + {file = "pillow-10.2.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:b792a349405fbc0163190fde0dc7b3fef3c9268292586cf5645598b48e63dc67"}, + {file = "pillow-10.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c570f24be1e468e3f0ce7ef56a89a60f0e05b30a3669a459e419c6eac2c35364"}, + {file = "pillow-10.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8ecd059fdaf60c1963c58ceb8997b32e9dc1b911f5da5307aab614f1ce5c2fb"}, + {file = "pillow-10.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c365fd1703040de1ec284b176d6af5abe21b427cb3a5ff68e0759e1e313a5e7e"}, + {file = "pillow-10.2.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:70c61d4c475835a19b3a5aa42492409878bbca7438554a1f89d20d58a7c75c01"}, + {file = "pillow-10.2.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:b6f491cdf80ae540738859d9766783e3b3c8e5bd37f5dfa0b76abdecc5081f13"}, + {file = "pillow-10.2.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9d189550615b4948f45252d7f005e53c2040cea1af5b60d6f79491a6e147eef7"}, + {file = "pillow-10.2.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:49d9ba1ed0ef3e061088cd1e7538a0759aab559e2e0a80a36f9fd9d8c0c21591"}, + {file = "pillow-10.2.0-cp39-cp39-win32.whl", hash = "sha256:babf5acfede515f176833ed6028754cbcd0d206f7f614ea3447d67c33be12516"}, + {file = "pillow-10.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:0304004f8067386b477d20a518b50f3fa658a28d44e4116970abfcd94fac34a8"}, + {file = "pillow-10.2.0-cp39-cp39-win_arm64.whl", hash = "sha256:0fb3e7fc88a14eacd303e90481ad983fd5b69c761e9e6ef94c983f91025da869"}, + {file = "pillow-10.2.0-pp310-pypy310_pp73-macosx_10_10_x86_64.whl", hash = "sha256:322209c642aabdd6207517e9739c704dc9f9db943015535783239022002f054a"}, + {file = "pillow-10.2.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3eedd52442c0a5ff4f887fab0c1c0bb164d8635b32c894bc1faf4c618dd89df2"}, + {file = "pillow-10.2.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cb28c753fd5eb3dd859b4ee95de66cc62af91bcff5db5f2571d32a520baf1f04"}, + {file = "pillow-10.2.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:33870dc4653c5017bf4c8873e5488d8f8d5f8935e2f1fb9a2208c47cdd66efd2"}, + {file = "pillow-10.2.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:3c31822339516fb3c82d03f30e22b1d038da87ef27b6a78c9549888f8ceda39a"}, + {file = "pillow-10.2.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:a2b56ba36e05f973d450582fb015594aaa78834fefe8dfb8fcd79b93e64ba4c6"}, + {file = "pillow-10.2.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:d8e6aeb9201e655354b3ad049cb77d19813ad4ece0df1249d3c793de3774f8c7"}, + {file = "pillow-10.2.0-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:2247178effb34a77c11c0e8ac355c7a741ceca0a732b27bf11e747bbc950722f"}, + {file = "pillow-10.2.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:15587643b9e5eb26c48e49a7b33659790d28f190fc514a322d55da2fb5c2950e"}, + {file = "pillow-10.2.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753cd8f2086b2b80180d9b3010dd4ed147efc167c90d3bf593fe2af21265e5a5"}, + {file = "pillow-10.2.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:7c8f97e8e7a9009bcacbe3766a36175056c12f9a44e6e6f2d5caad06dcfbf03b"}, + {file = "pillow-10.2.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:d1b35bcd6c5543b9cb547dee3150c93008f8dd0f1fef78fc0cd2b141c5baf58a"}, + {file = "pillow-10.2.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:fe4c15f6c9285dc54ce6553a3ce908ed37c8f3825b5a51a15c91442bb955b868"}, + {file = "pillow-10.2.0.tar.gz", hash = "sha256:e87f0b2c78157e12d7686b27d63c070fd65d994e8ddae6f328e0dcf4a0cd007e"}, ] [package.extras] docs = ["furo", "olefile", "sphinx (>=2.4)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinx-removed-in", "sphinxext-opengraph"] +fpx = ["olefile"] +mic = ["olefile"] tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"] +typing = ["typing-extensions"] +xmp = ["defusedxml"] [[package]] name = "pkgutil-resolve-name" @@ -2254,13 +2274,13 @@ files = [ [[package]] name = "platformdirs" -version = "4.0.0" +version = "4.1.0" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "platformdirs-4.0.0-py3-none-any.whl", hash = "sha256:118c954d7e949b35437270383a3f2531e99dd93cf7ce4dc8340d3356d30f173b"}, - {file = "platformdirs-4.0.0.tar.gz", hash = "sha256:cb633b2bcf10c51af60beb0ab06d2f1d69064b43abf4c185ca6b28865f3f9731"}, + {file = "platformdirs-4.1.0-py3-none-any.whl", hash = "sha256:11c8f37bcca40db96d8144522d925583bdb7a31f7b0e37e3ed4318400a8e2380"}, + {file = "platformdirs-4.1.0.tar.gz", hash = "sha256:906d548203468492d432bcb294d4bc2fff751bf84971fbb2c10918cc206ee420"}, ] [package.extras] @@ -2313,13 +2333,13 @@ six = ">=1.5.2" [[package]] name = "prometheus-client" -version = "0.18.0" +version = "0.19.0" description = "Python client for the Prometheus monitoring system." optional = false python-versions = ">=3.8" files = [ - {file = "prometheus_client-0.18.0-py3-none-any.whl", hash = "sha256:8de3ae2755f890826f4b6479e5571d4f74ac17a81345fe69a6778fdb92579184"}, - {file = "prometheus_client-0.18.0.tar.gz", hash = "sha256:35f7a8c22139e2bb7ca5a698e92d38145bc8dc74c1c0bf56f25cca886a764e17"}, + {file = "prometheus_client-0.19.0-py3-none-any.whl", hash = "sha256:c88b1e6ecf6b41cd8fb5731c7ae919bf66df6ec6fafa555cd6c0e16ca169ae92"}, + {file = "prometheus_client-0.19.0.tar.gz", hash = "sha256:4585b0d1223148c27a225b10dbec5ae9bc4c81a99a3fa80774fa6209935324e1"}, ] [package.extras] @@ -2327,13 +2347,13 @@ twisted = ["twisted"] [[package]] name = "prompt-toolkit" -version = "3.0.41" +version = "3.0.43" description = "Library for building powerful interactive command lines in Python" optional = false python-versions = ">=3.7.0" files = [ - {file = "prompt_toolkit-3.0.41-py3-none-any.whl", hash = "sha256:f36fe301fafb7470e86aaf90f036eef600a3210be4decf461a5b1ca8403d3cb2"}, - {file = "prompt_toolkit-3.0.41.tar.gz", hash = "sha256:941367d97fc815548822aa26c2a269fdc4eb21e9ec05fc5d447cf09bad5d75f0"}, + {file = "prompt_toolkit-3.0.43-py3-none-any.whl", hash = "sha256:a11a29cb3bf0a28a387fe5122cdb649816a957cd9261dcedf8c9f1fef33eacf6"}, + {file = "prompt_toolkit-3.0.43.tar.gz", hash = "sha256:3527b7af26106cbc65a040bcc84839a3566ec1b051bb0bfe953631e704b0ff7d"}, ] [package.dependencies] @@ -2341,27 +2361,27 @@ wcwidth = "*" [[package]] name = "psutil" -version = "5.9.6" +version = "5.9.8" description = "Cross-platform lib for process and system monitoring in Python." optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" files = [ - {file = "psutil-5.9.6-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:fb8a697f11b0f5994550555fcfe3e69799e5b060c8ecf9e2f75c69302cc35c0d"}, - {file = "psutil-5.9.6-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:91ecd2d9c00db9817a4b4192107cf6954addb5d9d67a969a4f436dbc9200f88c"}, - {file = "psutil-5.9.6-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:10e8c17b4f898d64b121149afb136c53ea8b68c7531155147867b7b1ac9e7e28"}, - {file = "psutil-5.9.6-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:18cd22c5db486f33998f37e2bb054cc62fd06646995285e02a51b1e08da97017"}, - {file = "psutil-5.9.6-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:ca2780f5e038379e520281e4c032dddd086906ddff9ef0d1b9dcf00710e5071c"}, - {file = "psutil-5.9.6-cp27-none-win32.whl", hash = "sha256:70cb3beb98bc3fd5ac9ac617a327af7e7f826373ee64c80efd4eb2856e5051e9"}, - {file = "psutil-5.9.6-cp27-none-win_amd64.whl", hash = "sha256:51dc3d54607c73148f63732c727856f5febec1c7c336f8f41fcbd6315cce76ac"}, - {file = "psutil-5.9.6-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:c69596f9fc2f8acd574a12d5f8b7b1ba3765a641ea5d60fb4736bf3c08a8214a"}, - {file = "psutil-5.9.6-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:92e0cc43c524834af53e9d3369245e6cc3b130e78e26100d1f63cdb0abeb3d3c"}, - {file = "psutil-5.9.6-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:748c9dd2583ed86347ed65d0035f45fa8c851e8d90354c122ab72319b5f366f4"}, - {file = "psutil-5.9.6-cp36-cp36m-win32.whl", hash = "sha256:3ebf2158c16cc69db777e3c7decb3c0f43a7af94a60d72e87b2823aebac3d602"}, - {file = "psutil-5.9.6-cp36-cp36m-win_amd64.whl", hash = "sha256:ff18b8d1a784b810df0b0fff3bcb50ab941c3b8e2c8de5726f9c71c601c611aa"}, - {file = "psutil-5.9.6-cp37-abi3-win32.whl", hash = "sha256:a6f01f03bf1843280f4ad16f4bde26b817847b4c1a0db59bf6419807bc5ce05c"}, - {file = "psutil-5.9.6-cp37-abi3-win_amd64.whl", hash = "sha256:6e5fb8dc711a514da83098bc5234264e551ad980cec5f85dabf4d38ed6f15e9a"}, - {file = "psutil-5.9.6-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:daecbcbd29b289aac14ece28eca6a3e60aa361754cf6da3dfb20d4d32b6c7f57"}, - {file = "psutil-5.9.6.tar.gz", hash = "sha256:e4b92ddcd7dd4cdd3f900180ea1e104932c7bce234fb88976e2a3b296441225a"}, + {file = "psutil-5.9.8-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:26bd09967ae00920df88e0352a91cff1a78f8d69b3ecabbfe733610c0af486c8"}, + {file = "psutil-5.9.8-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:05806de88103b25903dff19bb6692bd2e714ccf9e668d050d144012055cbca73"}, + {file = "psutil-5.9.8-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:611052c4bc70432ec770d5d54f64206aa7203a101ec273a0cd82418c86503bb7"}, + {file = "psutil-5.9.8-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:50187900d73c1381ba1454cf40308c2bf6f34268518b3f36a9b663ca87e65e36"}, + {file = "psutil-5.9.8-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:02615ed8c5ea222323408ceba16c60e99c3f91639b07da6373fb7e6539abc56d"}, + {file = "psutil-5.9.8-cp27-none-win32.whl", hash = "sha256:36f435891adb138ed3c9e58c6af3e2e6ca9ac2f365efe1f9cfef2794e6c93b4e"}, + {file = "psutil-5.9.8-cp27-none-win_amd64.whl", hash = "sha256:bd1184ceb3f87651a67b2708d4c3338e9b10c5df903f2e3776b62303b26cb631"}, + {file = "psutil-5.9.8-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:aee678c8720623dc456fa20659af736241f575d79429a0e5e9cf88ae0605cc81"}, + {file = "psutil-5.9.8-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8cb6403ce6d8e047495a701dc7c5bd788add903f8986d523e3e20b98b733e421"}, + {file = "psutil-5.9.8-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d06016f7f8625a1825ba3732081d77c94589dca78b7a3fc072194851e88461a4"}, + {file = "psutil-5.9.8-cp36-cp36m-win32.whl", hash = "sha256:7d79560ad97af658a0f6adfef8b834b53f64746d45b403f225b85c5c2c140eee"}, + {file = "psutil-5.9.8-cp36-cp36m-win_amd64.whl", hash = "sha256:27cc40c3493bb10de1be4b3f07cae4c010ce715290a5be22b98493509c6299e2"}, + {file = "psutil-5.9.8-cp37-abi3-win32.whl", hash = "sha256:bc56c2a1b0d15aa3eaa5a60c9f3f8e3e565303b465dbf57a1b730e7a2b9844e0"}, + {file = "psutil-5.9.8-cp37-abi3-win_amd64.whl", hash = "sha256:8db4c1b57507eef143a15a6884ca10f7c73876cdf5d51e713151c1236a0e68cf"}, + {file = "psutil-5.9.8-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:d16bbddf0693323b8c6123dd804100241da461e41d6e332fb0ba6058f630f8c8"}, + {file = "psutil-5.9.8.tar.gz", hash = "sha256:6be126e3225486dff286a8fb9a06246a5253f4c7c53b475ea5f5ac934e64194c"}, ] [package.extras] @@ -2405,17 +2425,18 @@ files = [ [[package]] name = "pygments" -version = "2.16.1" +version = "2.17.2" description = "Pygments is a syntax highlighting package written in Python." optional = false python-versions = ">=3.7" files = [ - {file = "Pygments-2.16.1-py3-none-any.whl", hash = "sha256:13fc09fa63bc8d8671a6d247e1eb303c4b343eaee81d861f3404db2935653692"}, - {file = "Pygments-2.16.1.tar.gz", hash = "sha256:1daff0494820c69bc8941e407aa20f577374ee88364ee10a98fdbe0aece96e29"}, + {file = "pygments-2.17.2-py3-none-any.whl", hash = "sha256:b27c2826c47d0f3219f29554824c30c5e8945175d888647acd804ddd04af846c"}, + {file = "pygments-2.17.2.tar.gz", hash = "sha256:da46cec9fd2de5be3a8a784f434e4c4ab670b4ff54d605c4c2717e9d49c4c367"}, ] [package.extras] plugins = ["importlib-metadata"] +windows-terminal = ["colorama (>=0.4.6)"] [[package]] name = "pylint" @@ -2484,13 +2505,13 @@ diagrams = ["jinja2", "railroad-diagrams"] [[package]] name = "pytest" -version = "7.4.3" +version = "7.4.4" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.7" files = [ - {file = "pytest-7.4.3-py3-none-any.whl", hash = "sha256:0d009c083ea859a71b76adf7c1d502e4bc170b80a8ef002da5806527b9591fac"}, - {file = "pytest-7.4.3.tar.gz", hash = "sha256:d989d136982de4e3b29dabcc838ad581c64e8ed52c11fbe86ddebd9da0818cd5"}, + {file = "pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8"}, + {file = "pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280"}, ] [package.dependencies] @@ -2635,6 +2656,7 @@ files = [ {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"}, {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, @@ -2671,104 +2693,104 @@ files = [ [[package]] name = "pyzmq" -version = "25.1.1" +version = "25.1.2" description = "Python bindings for 0MQ" optional = false python-versions = ">=3.6" files = [ - {file = "pyzmq-25.1.1-cp310-cp310-macosx_10_15_universal2.whl", hash = "sha256:381469297409c5adf9a0e884c5eb5186ed33137badcbbb0560b86e910a2f1e76"}, - {file = "pyzmq-25.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:955215ed0604dac5b01907424dfa28b40f2b2292d6493445dd34d0dfa72586a8"}, - {file = "pyzmq-25.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:985bbb1316192b98f32e25e7b9958088431d853ac63aca1d2c236f40afb17c83"}, - {file = "pyzmq-25.1.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:afea96f64efa98df4da6958bae37f1cbea7932c35878b185e5982821bc883369"}, - {file = "pyzmq-25.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76705c9325d72a81155bb6ab48d4312e0032bf045fb0754889133200f7a0d849"}, - {file = "pyzmq-25.1.1-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:77a41c26205d2353a4c94d02be51d6cbdf63c06fbc1295ea57dad7e2d3381b71"}, - {file = "pyzmq-25.1.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:12720a53e61c3b99d87262294e2b375c915fea93c31fc2336898c26d7aed34cd"}, - {file = "pyzmq-25.1.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:57459b68e5cd85b0be8184382cefd91959cafe79ae019e6b1ae6e2ba8a12cda7"}, - {file = "pyzmq-25.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:292fe3fc5ad4a75bc8df0dfaee7d0babe8b1f4ceb596437213821f761b4589f9"}, - {file = "pyzmq-25.1.1-cp310-cp310-win32.whl", hash = "sha256:35b5ab8c28978fbbb86ea54958cd89f5176ce747c1fb3d87356cf698048a7790"}, - {file = "pyzmq-25.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:11baebdd5fc5b475d484195e49bae2dc64b94a5208f7c89954e9e354fc609d8f"}, - {file = "pyzmq-25.1.1-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:d20a0ddb3e989e8807d83225a27e5c2eb2260eaa851532086e9e0fa0d5287d83"}, - {file = "pyzmq-25.1.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e1c1be77bc5fb77d923850f82e55a928f8638f64a61f00ff18a67c7404faf008"}, - {file = "pyzmq-25.1.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d89528b4943d27029a2818f847c10c2cecc79fa9590f3cb1860459a5be7933eb"}, - {file = "pyzmq-25.1.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:90f26dc6d5f241ba358bef79be9ce06de58d477ca8485e3291675436d3827cf8"}, - {file = "pyzmq-25.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c2b92812bd214018e50b6380ea3ac0c8bb01ac07fcc14c5f86a5bb25e74026e9"}, - {file = "pyzmq-25.1.1-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:2f957ce63d13c28730f7fd6b72333814221c84ca2421298f66e5143f81c9f91f"}, - {file = "pyzmq-25.1.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:047a640f5c9c6ade7b1cc6680a0e28c9dd5a0825135acbd3569cc96ea00b2505"}, - {file = "pyzmq-25.1.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:7f7e58effd14b641c5e4dec8c7dab02fb67a13df90329e61c869b9cc607ef752"}, - {file = "pyzmq-25.1.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c2910967e6ab16bf6fbeb1f771c89a7050947221ae12a5b0b60f3bca2ee19bca"}, - {file = "pyzmq-25.1.1-cp311-cp311-win32.whl", hash = "sha256:76c1c8efb3ca3a1818b837aea423ff8a07bbf7aafe9f2f6582b61a0458b1a329"}, - {file = "pyzmq-25.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:44e58a0554b21fc662f2712814a746635ed668d0fbc98b7cb9d74cb798d202e6"}, - {file = "pyzmq-25.1.1-cp312-cp312-macosx_10_15_universal2.whl", hash = "sha256:e1ffa1c924e8c72778b9ccd386a7067cddf626884fd8277f503c48bb5f51c762"}, - {file = "pyzmq-25.1.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:1af379b33ef33757224da93e9da62e6471cf4a66d10078cf32bae8127d3d0d4a"}, - {file = "pyzmq-25.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cff084c6933680d1f8b2f3b4ff5bbb88538a4aac00d199ac13f49d0698727ecb"}, - {file = "pyzmq-25.1.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e2400a94f7dd9cb20cd012951a0cbf8249e3d554c63a9c0cdfd5cbb6c01d2dec"}, - {file = "pyzmq-25.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2d81f1ddae3858b8299d1da72dd7d19dd36aab654c19671aa8a7e7fb02f6638a"}, - {file = "pyzmq-25.1.1-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:255ca2b219f9e5a3a9ef3081512e1358bd4760ce77828e1028b818ff5610b87b"}, - {file = "pyzmq-25.1.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:a882ac0a351288dd18ecae3326b8a49d10c61a68b01419f3a0b9a306190baf69"}, - {file = "pyzmq-25.1.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:724c292bb26365659fc434e9567b3f1adbdb5e8d640c936ed901f49e03e5d32e"}, - {file = "pyzmq-25.1.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ca1ed0bb2d850aa8471387882247c68f1e62a4af0ce9c8a1dbe0d2bf69e41fb"}, - {file = "pyzmq-25.1.1-cp312-cp312-win32.whl", hash = "sha256:b3451108ab861040754fa5208bca4a5496c65875710f76789a9ad27c801a0075"}, - {file = "pyzmq-25.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:eadbefd5e92ef8a345f0525b5cfd01cf4e4cc651a2cffb8f23c0dd184975d787"}, - {file = "pyzmq-25.1.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:db0b2af416ba735c6304c47f75d348f498b92952f5e3e8bff449336d2728795d"}, - {file = "pyzmq-25.1.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7c133e93b405eb0d36fa430c94185bdd13c36204a8635470cccc200723c13bb"}, - {file = "pyzmq-25.1.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:273bc3959bcbff3f48606b28229b4721716598d76b5aaea2b4a9d0ab454ec062"}, - {file = "pyzmq-25.1.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:cbc8df5c6a88ba5ae385d8930da02201165408dde8d8322072e3e5ddd4f68e22"}, - {file = "pyzmq-25.1.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:18d43df3f2302d836f2a56f17e5663e398416e9dd74b205b179065e61f1a6edf"}, - {file = "pyzmq-25.1.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:73461eed88a88c866656e08f89299720a38cb4e9d34ae6bf5df6f71102570f2e"}, - {file = "pyzmq-25.1.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:34c850ce7976d19ebe7b9d4b9bb8c9dfc7aac336c0958e2651b88cbd46682123"}, - {file = "pyzmq-25.1.1-cp36-cp36m-win32.whl", hash = "sha256:d2045d6d9439a0078f2a34b57c7b18c4a6aef0bee37f22e4ec9f32456c852c71"}, - {file = "pyzmq-25.1.1-cp36-cp36m-win_amd64.whl", hash = "sha256:458dea649f2f02a0b244ae6aef8dc29325a2810aa26b07af8374dc2a9faf57e3"}, - {file = "pyzmq-25.1.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:7cff25c5b315e63b07a36f0c2bab32c58eafbe57d0dce61b614ef4c76058c115"}, - {file = "pyzmq-25.1.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b1579413ae492b05de5a6174574f8c44c2b9b122a42015c5292afa4be2507f28"}, - {file = "pyzmq-25.1.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3d0a409d3b28607cc427aa5c30a6f1e4452cc44e311f843e05edb28ab5e36da0"}, - {file = "pyzmq-25.1.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:21eb4e609a154a57c520e3d5bfa0d97e49b6872ea057b7c85257b11e78068222"}, - {file = "pyzmq-25.1.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:034239843541ef7a1aee0c7b2cb7f6aafffb005ede965ae9cbd49d5ff4ff73cf"}, - {file = "pyzmq-25.1.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f8115e303280ba09f3898194791a153862cbf9eef722ad8f7f741987ee2a97c7"}, - {file = "pyzmq-25.1.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:1a5d26fe8f32f137e784f768143728438877d69a586ddeaad898558dc971a5ae"}, - {file = "pyzmq-25.1.1-cp37-cp37m-win32.whl", hash = "sha256:f32260e556a983bc5c7ed588d04c942c9a8f9c2e99213fec11a031e316874c7e"}, - {file = "pyzmq-25.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:abf34e43c531bbb510ae7e8f5b2b1f2a8ab93219510e2b287a944432fad135f3"}, - {file = "pyzmq-25.1.1-cp38-cp38-macosx_10_15_universal2.whl", hash = "sha256:87e34f31ca8f168c56d6fbf99692cc8d3b445abb5bfd08c229ae992d7547a92a"}, - {file = "pyzmq-25.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c9c6c9b2c2f80747a98f34ef491c4d7b1a8d4853937bb1492774992a120f475d"}, - {file = "pyzmq-25.1.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:5619f3f5a4db5dbb572b095ea3cb5cc035335159d9da950830c9c4db2fbb6995"}, - {file = "pyzmq-25.1.1-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:5a34d2395073ef862b4032343cf0c32a712f3ab49d7ec4f42c9661e0294d106f"}, - {file = "pyzmq-25.1.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25f0e6b78220aba09815cd1f3a32b9c7cb3e02cb846d1cfc526b6595f6046618"}, - {file = "pyzmq-25.1.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:3669cf8ee3520c2f13b2e0351c41fea919852b220988d2049249db10046a7afb"}, - {file = "pyzmq-25.1.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:2d163a18819277e49911f7461567bda923461c50b19d169a062536fffe7cd9d2"}, - {file = "pyzmq-25.1.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:df27ffddff4190667d40de7beba4a950b5ce78fe28a7dcc41d6f8a700a80a3c0"}, - {file = "pyzmq-25.1.1-cp38-cp38-win32.whl", hash = "sha256:a382372898a07479bd34bda781008e4a954ed8750f17891e794521c3e21c2e1c"}, - {file = "pyzmq-25.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:52533489f28d62eb1258a965f2aba28a82aa747202c8fa5a1c7a43b5db0e85c1"}, - {file = "pyzmq-25.1.1-cp39-cp39-macosx_10_15_universal2.whl", hash = "sha256:03b3f49b57264909aacd0741892f2aecf2f51fb053e7d8ac6767f6c700832f45"}, - {file = "pyzmq-25.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:330f9e188d0d89080cde66dc7470f57d1926ff2fb5576227f14d5be7ab30b9fa"}, - {file = "pyzmq-25.1.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:2ca57a5be0389f2a65e6d3bb2962a971688cbdd30b4c0bd188c99e39c234f414"}, - {file = "pyzmq-25.1.1-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:d457aed310f2670f59cc5b57dcfced452aeeed77f9da2b9763616bd57e4dbaae"}, - {file = "pyzmq-25.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c56d748ea50215abef7030c72b60dd723ed5b5c7e65e7bc2504e77843631c1a6"}, - {file = "pyzmq-25.1.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:8f03d3f0d01cb5a018debeb412441996a517b11c5c17ab2001aa0597c6d6882c"}, - {file = "pyzmq-25.1.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:820c4a08195a681252f46926de10e29b6bbf3e17b30037bd4250d72dd3ddaab8"}, - {file = "pyzmq-25.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:17ef5f01d25b67ca8f98120d5fa1d21efe9611604e8eb03a5147360f517dd1e2"}, - {file = "pyzmq-25.1.1-cp39-cp39-win32.whl", hash = "sha256:04ccbed567171579ec2cebb9c8a3e30801723c575601f9a990ab25bcac6b51e2"}, - {file = "pyzmq-25.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:e61f091c3ba0c3578411ef505992d356a812fb200643eab27f4f70eed34a29ef"}, - {file = "pyzmq-25.1.1-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ade6d25bb29c4555d718ac6d1443a7386595528c33d6b133b258f65f963bb0f6"}, - {file = "pyzmq-25.1.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e0c95ddd4f6e9fca4e9e3afaa4f9df8552f0ba5d1004e89ef0a68e1f1f9807c7"}, - {file = "pyzmq-25.1.1-pp310-pypy310_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:48e466162a24daf86f6b5ca72444d2bf39a5e58da5f96370078be67c67adc978"}, - {file = "pyzmq-25.1.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:abc719161780932c4e11aaebb203be3d6acc6b38d2f26c0f523b5b59d2fc1996"}, - {file = "pyzmq-25.1.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:1ccf825981640b8c34ae54231b7ed00271822ea1c6d8ba1090ebd4943759abf5"}, - {file = "pyzmq-25.1.1-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:c2f20ce161ebdb0091a10c9ca0372e023ce24980d0e1f810f519da6f79c60800"}, - {file = "pyzmq-25.1.1-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:deee9ca4727f53464daf089536e68b13e6104e84a37820a88b0a057b97bba2d2"}, - {file = "pyzmq-25.1.1-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:aa8d6cdc8b8aa19ceb319aaa2b660cdaccc533ec477eeb1309e2a291eaacc43a"}, - {file = "pyzmq-25.1.1-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:019e59ef5c5256a2c7378f2fb8560fc2a9ff1d315755204295b2eab96b254d0a"}, - {file = "pyzmq-25.1.1-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:b9af3757495c1ee3b5c4e945c1df7be95562277c6e5bccc20a39aec50f826cd0"}, - {file = "pyzmq-25.1.1-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:548d6482dc8aadbe7e79d1b5806585c8120bafa1ef841167bc9090522b610fa6"}, - {file = "pyzmq-25.1.1-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:057e824b2aae50accc0f9a0570998adc021b372478a921506fddd6c02e60308e"}, - {file = "pyzmq-25.1.1-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2243700cc5548cff20963f0ca92d3e5e436394375ab8a354bbea2b12911b20b0"}, - {file = "pyzmq-25.1.1-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79986f3b4af059777111409ee517da24a529bdbd46da578b33f25580adcff728"}, - {file = "pyzmq-25.1.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:11d58723d44d6ed4dd677c5615b2ffb19d5c426636345567d6af82be4dff8a55"}, - {file = "pyzmq-25.1.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:49d238cf4b69652257db66d0c623cd3e09b5d2e9576b56bc067a396133a00d4a"}, - {file = "pyzmq-25.1.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fedbdc753827cf014c01dbbee9c3be17e5a208dcd1bf8641ce2cd29580d1f0d4"}, - {file = "pyzmq-25.1.1-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bc16ac425cc927d0a57d242589f87ee093884ea4804c05a13834d07c20db203c"}, - {file = "pyzmq-25.1.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11c1d2aed9079c6b0c9550a7257a836b4a637feb334904610f06d70eb44c56d2"}, - {file = "pyzmq-25.1.1-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:e8a701123029cc240cea61dd2d16ad57cab4691804143ce80ecd9286b464d180"}, - {file = "pyzmq-25.1.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:61706a6b6c24bdece85ff177fec393545a3191eeda35b07aaa1458a027ad1304"}, - {file = "pyzmq-25.1.1.tar.gz", hash = "sha256:259c22485b71abacdfa8bf79720cd7bcf4b9d128b30ea554f01ae71fdbfdaa23"}, + {file = "pyzmq-25.1.2-cp310-cp310-macosx_10_15_universal2.whl", hash = "sha256:e624c789359f1a16f83f35e2c705d07663ff2b4d4479bad35621178d8f0f6ea4"}, + {file = "pyzmq-25.1.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:49151b0efece79f6a79d41a461d78535356136ee70084a1c22532fc6383f4ad0"}, + {file = "pyzmq-25.1.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d9a5f194cf730f2b24d6af1f833c14c10f41023da46a7f736f48b6d35061e76e"}, + {file = "pyzmq-25.1.2-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:faf79a302f834d9e8304fafdc11d0d042266667ac45209afa57e5efc998e3872"}, + {file = "pyzmq-25.1.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f51a7b4ead28d3fca8dda53216314a553b0f7a91ee8fc46a72b402a78c3e43d"}, + {file = "pyzmq-25.1.2-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:0ddd6d71d4ef17ba5a87becf7ddf01b371eaba553c603477679ae817a8d84d75"}, + {file = "pyzmq-25.1.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:246747b88917e4867e2367b005fc8eefbb4a54b7db363d6c92f89d69abfff4b6"}, + {file = "pyzmq-25.1.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:00c48ae2fd81e2a50c3485de1b9d5c7c57cd85dc8ec55683eac16846e57ac979"}, + {file = "pyzmq-25.1.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:5a68d491fc20762b630e5db2191dd07ff89834086740f70e978bb2ef2668be08"}, + {file = "pyzmq-25.1.2-cp310-cp310-win32.whl", hash = "sha256:09dfe949e83087da88c4a76767df04b22304a682d6154de2c572625c62ad6886"}, + {file = "pyzmq-25.1.2-cp310-cp310-win_amd64.whl", hash = "sha256:fa99973d2ed20417744fca0073390ad65ce225b546febb0580358e36aa90dba6"}, + {file = "pyzmq-25.1.2-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:82544e0e2d0c1811482d37eef297020a040c32e0687c1f6fc23a75b75db8062c"}, + {file = "pyzmq-25.1.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:01171fc48542348cd1a360a4b6c3e7d8f46cdcf53a8d40f84db6707a6768acc1"}, + {file = "pyzmq-25.1.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bc69c96735ab501419c432110016329bf0dea8898ce16fab97c6d9106dc0b348"}, + {file = "pyzmq-25.1.2-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3e124e6b1dd3dfbeb695435dff0e383256655bb18082e094a8dd1f6293114642"}, + {file = "pyzmq-25.1.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7598d2ba821caa37a0f9d54c25164a4fa351ce019d64d0b44b45540950458840"}, + {file = "pyzmq-25.1.2-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:d1299d7e964c13607efd148ca1f07dcbf27c3ab9e125d1d0ae1d580a1682399d"}, + {file = "pyzmq-25.1.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:4e6f689880d5ad87918430957297c975203a082d9a036cc426648fcbedae769b"}, + {file = "pyzmq-25.1.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:cc69949484171cc961e6ecd4a8911b9ce7a0d1f738fcae717177c231bf77437b"}, + {file = "pyzmq-25.1.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9880078f683466b7f567b8624bfc16cad65077be046b6e8abb53bed4eeb82dd3"}, + {file = "pyzmq-25.1.2-cp311-cp311-win32.whl", hash = "sha256:4e5837af3e5aaa99a091302df5ee001149baff06ad22b722d34e30df5f0d9097"}, + {file = "pyzmq-25.1.2-cp311-cp311-win_amd64.whl", hash = "sha256:25c2dbb97d38b5ac9fd15586e048ec5eb1e38f3d47fe7d92167b0c77bb3584e9"}, + {file = "pyzmq-25.1.2-cp312-cp312-macosx_10_15_universal2.whl", hash = "sha256:11e70516688190e9c2db14fcf93c04192b02d457b582a1f6190b154691b4c93a"}, + {file = "pyzmq-25.1.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:313c3794d650d1fccaaab2df942af9f2c01d6217c846177cfcbc693c7410839e"}, + {file = "pyzmq-25.1.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1b3cbba2f47062b85fe0ef9de5b987612140a9ba3a9c6d2543c6dec9f7c2ab27"}, + {file = "pyzmq-25.1.2-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fc31baa0c32a2ca660784d5af3b9487e13b61b3032cb01a115fce6588e1bed30"}, + {file = "pyzmq-25.1.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:02c9087b109070c5ab0b383079fa1b5f797f8d43e9a66c07a4b8b8bdecfd88ee"}, + {file = "pyzmq-25.1.2-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:f8429b17cbb746c3e043cb986328da023657e79d5ed258b711c06a70c2ea7537"}, + {file = "pyzmq-25.1.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:5074adeacede5f810b7ef39607ee59d94e948b4fd954495bdb072f8c54558181"}, + {file = "pyzmq-25.1.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:7ae8f354b895cbd85212da245f1a5ad8159e7840e37d78b476bb4f4c3f32a9fe"}, + {file = "pyzmq-25.1.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:b264bf2cc96b5bc43ce0e852be995e400376bd87ceb363822e2cb1964fcdc737"}, + {file = "pyzmq-25.1.2-cp312-cp312-win32.whl", hash = "sha256:02bbc1a87b76e04fd780b45e7f695471ae6de747769e540da909173d50ff8e2d"}, + {file = "pyzmq-25.1.2-cp312-cp312-win_amd64.whl", hash = "sha256:ced111c2e81506abd1dc142e6cd7b68dd53747b3b7ae5edbea4578c5eeff96b7"}, + {file = "pyzmq-25.1.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:7b6d09a8962a91151f0976008eb7b29b433a560fde056ec7a3db9ec8f1075438"}, + {file = "pyzmq-25.1.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:967668420f36878a3c9ecb5ab33c9d0ff8d054f9c0233d995a6d25b0e95e1b6b"}, + {file = "pyzmq-25.1.2-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5edac3f57c7ddaacdb4d40f6ef2f9e299471fc38d112f4bc6d60ab9365445fb0"}, + {file = "pyzmq-25.1.2-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:0dabfb10ef897f3b7e101cacba1437bd3a5032ee667b7ead32bbcdd1a8422fe7"}, + {file = "pyzmq-25.1.2-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:2c6441e0398c2baacfe5ba30c937d274cfc2dc5b55e82e3749e333aabffde561"}, + {file = "pyzmq-25.1.2-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:16b726c1f6c2e7625706549f9dbe9b06004dfbec30dbed4bf50cbdfc73e5b32a"}, + {file = "pyzmq-25.1.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:a86c2dd76ef71a773e70551a07318b8e52379f58dafa7ae1e0a4be78efd1ff16"}, + {file = "pyzmq-25.1.2-cp36-cp36m-win32.whl", hash = "sha256:359f7f74b5d3c65dae137f33eb2bcfa7ad9ebefd1cab85c935f063f1dbb245cc"}, + {file = "pyzmq-25.1.2-cp36-cp36m-win_amd64.whl", hash = "sha256:55875492f820d0eb3417b51d96fea549cde77893ae3790fd25491c5754ea2f68"}, + {file = "pyzmq-25.1.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b8c8a419dfb02e91b453615c69568442e897aaf77561ee0064d789705ff37a92"}, + {file = "pyzmq-25.1.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8807c87fa893527ae8a524c15fc505d9950d5e856f03dae5921b5e9aa3b8783b"}, + {file = "pyzmq-25.1.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5e319ed7d6b8f5fad9b76daa0a68497bc6f129858ad956331a5835785761e003"}, + {file = "pyzmq-25.1.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:3c53687dde4d9d473c587ae80cc328e5b102b517447456184b485587ebd18b62"}, + {file = "pyzmq-25.1.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:9add2e5b33d2cd765ad96d5eb734a5e795a0755f7fc49aa04f76d7ddda73fd70"}, + {file = "pyzmq-25.1.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:e690145a8c0c273c28d3b89d6fb32c45e0d9605b2293c10e650265bf5c11cfec"}, + {file = "pyzmq-25.1.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:00a06faa7165634f0cac1abb27e54d7a0b3b44eb9994530b8ec73cf52e15353b"}, + {file = "pyzmq-25.1.2-cp37-cp37m-win32.whl", hash = "sha256:0f97bc2f1f13cb16905a5f3e1fbdf100e712d841482b2237484360f8bc4cb3d7"}, + {file = "pyzmq-25.1.2-cp37-cp37m-win_amd64.whl", hash = "sha256:6cc0020b74b2e410287e5942e1e10886ff81ac77789eb20bec13f7ae681f0fdd"}, + {file = "pyzmq-25.1.2-cp38-cp38-macosx_10_15_universal2.whl", hash = "sha256:bef02cfcbded83473bdd86dd8d3729cd82b2e569b75844fb4ea08fee3c26ae41"}, + {file = "pyzmq-25.1.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e10a4b5a4b1192d74853cc71a5e9fd022594573926c2a3a4802020360aa719d8"}, + {file = "pyzmq-25.1.2-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:8c5f80e578427d4695adac6fdf4370c14a2feafdc8cb35549c219b90652536ae"}, + {file = "pyzmq-25.1.2-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:5dde6751e857910c1339890f3524de74007958557593b9e7e8c5f01cd919f8a7"}, + {file = "pyzmq-25.1.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ea1608dd169da230a0ad602d5b1ebd39807ac96cae1845c3ceed39af08a5c6df"}, + {file = "pyzmq-25.1.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:0f513130c4c361201da9bc69df25a086487250e16b5571ead521b31ff6b02220"}, + {file = "pyzmq-25.1.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:019744b99da30330798bb37df33549d59d380c78e516e3bab9c9b84f87a9592f"}, + {file = "pyzmq-25.1.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2e2713ef44be5d52dd8b8e2023d706bf66cb22072e97fc71b168e01d25192755"}, + {file = "pyzmq-25.1.2-cp38-cp38-win32.whl", hash = "sha256:07cd61a20a535524906595e09344505a9bd46f1da7a07e504b315d41cd42eb07"}, + {file = "pyzmq-25.1.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb7e49a17fb8c77d3119d41a4523e432eb0c6932187c37deb6fbb00cc3028088"}, + {file = "pyzmq-25.1.2-cp39-cp39-macosx_10_15_universal2.whl", hash = "sha256:94504ff66f278ab4b7e03e4cba7e7e400cb73bfa9d3d71f58d8972a8dc67e7a6"}, + {file = "pyzmq-25.1.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6dd0d50bbf9dca1d0bdea219ae6b40f713a3fb477c06ca3714f208fd69e16fd8"}, + {file = "pyzmq-25.1.2-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:004ff469d21e86f0ef0369717351073e0e577428e514c47c8480770d5e24a565"}, + {file = "pyzmq-25.1.2-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c0b5ca88a8928147b7b1e2dfa09f3b6c256bc1135a1338536cbc9ea13d3b7add"}, + {file = "pyzmq-25.1.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c9a79f1d2495b167119d02be7448bfba57fad2a4207c4f68abc0bab4b92925b"}, + {file = "pyzmq-25.1.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:518efd91c3d8ac9f9b4f7dd0e2b7b8bf1a4fe82a308009016b07eaa48681af82"}, + {file = "pyzmq-25.1.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:1ec23bd7b3a893ae676d0e54ad47d18064e6c5ae1fadc2f195143fb27373f7f6"}, + {file = "pyzmq-25.1.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:db36c27baed588a5a8346b971477b718fdc66cf5b80cbfbd914b4d6d355e44e2"}, + {file = "pyzmq-25.1.2-cp39-cp39-win32.whl", hash = "sha256:39b1067f13aba39d794a24761e385e2eddc26295826530a8c7b6c6c341584289"}, + {file = "pyzmq-25.1.2-cp39-cp39-win_amd64.whl", hash = "sha256:8e9f3fabc445d0ce320ea2c59a75fe3ea591fdbdeebec5db6de530dd4b09412e"}, + {file = "pyzmq-25.1.2-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a8c1d566344aee826b74e472e16edae0a02e2a044f14f7c24e123002dcff1c05"}, + {file = "pyzmq-25.1.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:759cfd391a0996345ba94b6a5110fca9c557ad4166d86a6e81ea526c376a01e8"}, + {file = "pyzmq-25.1.2-pp310-pypy310_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7c61e346ac34b74028ede1c6b4bcecf649d69b707b3ff9dc0fab453821b04d1e"}, + {file = "pyzmq-25.1.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4cb8fc1f8d69b411b8ec0b5f1ffbcaf14c1db95b6bccea21d83610987435f1a4"}, + {file = "pyzmq-25.1.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:3c00c9b7d1ca8165c610437ca0c92e7b5607b2f9076f4eb4b095c85d6e680a1d"}, + {file = "pyzmq-25.1.2-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:df0c7a16ebb94452d2909b9a7b3337940e9a87a824c4fc1c7c36bb4404cb0cde"}, + {file = "pyzmq-25.1.2-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:45999e7f7ed5c390f2e87ece7f6c56bf979fb213550229e711e45ecc7d42ccb8"}, + {file = "pyzmq-25.1.2-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ac170e9e048b40c605358667aca3d94e98f604a18c44bdb4c102e67070f3ac9b"}, + {file = "pyzmq-25.1.2-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1b604734bec94f05f81b360a272fc824334267426ae9905ff32dc2be433ab96"}, + {file = "pyzmq-25.1.2-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:a793ac733e3d895d96f865f1806f160696422554e46d30105807fdc9841b9f7d"}, + {file = "pyzmq-25.1.2-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:0806175f2ae5ad4b835ecd87f5f85583316b69f17e97786f7443baaf54b9bb98"}, + {file = "pyzmq-25.1.2-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:ef12e259e7bc317c7597d4f6ef59b97b913e162d83b421dd0db3d6410f17a244"}, + {file = "pyzmq-25.1.2-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ea253b368eb41116011add00f8d5726762320b1bda892f744c91997b65754d73"}, + {file = "pyzmq-25.1.2-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1b9b1f2ad6498445a941d9a4fee096d387fee436e45cc660e72e768d3d8ee611"}, + {file = "pyzmq-25.1.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:8b14c75979ce932c53b79976a395cb2a8cd3aaf14aef75e8c2cb55a330b9b49d"}, + {file = "pyzmq-25.1.2-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:889370d5174a741a62566c003ee8ddba4b04c3f09a97b8000092b7ca83ec9c49"}, + {file = "pyzmq-25.1.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9a18fff090441a40ffda8a7f4f18f03dc56ae73f148f1832e109f9bffa85df15"}, + {file = "pyzmq-25.1.2-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:99a6b36f95c98839ad98f8c553d8507644c880cf1e0a57fe5e3a3f3969040882"}, + {file = "pyzmq-25.1.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4345c9a27f4310afbb9c01750e9461ff33d6fb74cd2456b107525bbeebcb5be3"}, + {file = "pyzmq-25.1.2-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:3516e0b6224cf6e43e341d56da15fd33bdc37fa0c06af4f029f7d7dfceceabbc"}, + {file = "pyzmq-25.1.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:146b9b1f29ead41255387fb07be56dc29639262c0f7344f570eecdcd8d683314"}, + {file = "pyzmq-25.1.2.tar.gz", hash = "sha256:93f1aa311e8bb912e34f004cf186407a4e90eec4f0ecc0efd26056bf7eda0226"}, ] [package.dependencies] @@ -2818,13 +2840,13 @@ test = ["pytest (>=6,!=7.0.0,!=7.0.1)", "pytest-cov (>=3.0.0)", "pytest-qt"] [[package]] name = "referencing" -version = "0.31.0" +version = "0.32.1" description = "JSON Referencing + Python" optional = false python-versions = ">=3.8" files = [ - {file = "referencing-0.31.0-py3-none-any.whl", hash = "sha256:381b11e53dd93babb55696c71cf42aef2d36b8a150c49bf0bc301e36d536c882"}, - {file = "referencing-0.31.0.tar.gz", hash = "sha256:cc28f2c88fbe7b961a7817a0abc034c09a1e36358f82fedb4ffdf29a25398863"}, + {file = "referencing-0.32.1-py3-none-any.whl", hash = "sha256:7e4dc12271d8e15612bfe35792f5ea1c40970dadf8624602e33db2758f7ee554"}, + {file = "referencing-0.32.1.tar.gz", hash = "sha256:3c57da0513e9563eb7e203ebe9bb3a1b509b042016433bd1e45a2853466c3dd3"}, ] [package.dependencies] @@ -2879,110 +2901,110 @@ files = [ [[package]] name = "rpds-py" -version = "0.13.0" +version = "0.17.1" description = "Python bindings to Rust's persistent data structures (rpds)" optional = false python-versions = ">=3.8" files = [ - {file = "rpds_py-0.13.0-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:1758197cc8d7ff383c07405f188253535b4aa7fa745cbc54d221ae84b18e0702"}, - {file = "rpds_py-0.13.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:715df74cbcef4387d623c917f295352127f4b3e0388038d68fa577b4e4c6e540"}, - {file = "rpds_py-0.13.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c8a9cec0f49df9bac252d92f138c0d7708d98828e21fd57db78087d8f50b5656"}, - {file = "rpds_py-0.13.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5c2545bba02f68abdf398ef4990dc77592cc1e5d29438b35b3a3ca34d171fb4b"}, - {file = "rpds_py-0.13.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:95375c44ffb9ea2bc25d67fb66e726ea266ff1572df50b9556fe28a5f3519cd7"}, - {file = "rpds_py-0.13.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:54e513df45a8a9419e7952ffd26ac9a5b7b1df97fe72530421794b0de29f9d72"}, - {file = "rpds_py-0.13.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a25f514a53927b6b4bd04a9a6a13b55209df54f548660eeed673336c0c946d14"}, - {file = "rpds_py-0.13.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c1a920fa679ec2758411d66bf68840b0a21317b9954ab0e973742d723bb67709"}, - {file = "rpds_py-0.13.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f9339d1404b87e6d8cb35e485945753be57a99ab9bb389f42629215b2f6bda0f"}, - {file = "rpds_py-0.13.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:c99f9dda2c959f7bb69a7125e192c74fcafb7a534a95ccf49313ae3a04807804"}, - {file = "rpds_py-0.13.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:bad6758df5f1042b35683bd1811d5432ac1b17700a5a2a51fdc293f7df5f7827"}, - {file = "rpds_py-0.13.0-cp310-none-win32.whl", hash = "sha256:2a29ec68fa9655ce9501bc6ae074b166e8b45c2dfcd2d71d90d1a61758ed8c73"}, - {file = "rpds_py-0.13.0-cp310-none-win_amd64.whl", hash = "sha256:244be953f13f148b0071d67a610f89cd72eb5013a147e517d6ca3f3f3b7e0380"}, - {file = "rpds_py-0.13.0-cp311-cp311-macosx_10_7_x86_64.whl", hash = "sha256:240279ca0b2afd6d4710afce1c94bf9e75fc161290bf62c0feba64d64780d80b"}, - {file = "rpds_py-0.13.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:25c9727da2dabc93664a18eda7a70feedf478f0c4c8294e4cdba7f60a479a246"}, - {file = "rpds_py-0.13.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:981e46e1e5064f95460381bff4353783b4b5ce351c930e5b507ebe0278c61dac"}, - {file = "rpds_py-0.13.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6052bb47ea583646b8ff562acacb9a2ec5ec847267049cbae3919671929e94c6"}, - {file = "rpds_py-0.13.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:87f591ff8cc834fa01ca5899ab5edcd7ee590492a9cdcf43424ac142e731ce3e"}, - {file = "rpds_py-0.13.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:62772259b3381e2aabf274c74fd1e1ac03b0524de0a6593900684becfa8cfe4b"}, - {file = "rpds_py-0.13.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b4de9d20fe68c16b4d97f551a09920745add0c86430262230528b83c2ed2fe90"}, - {file = "rpds_py-0.13.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b70a54fb628c1d6400e351674a31ba63d2912b8c5b707f99b408674a5d8b69ab"}, - {file = "rpds_py-0.13.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2063ab9cd1be7ef6b5ed0f408e2bdf32c060b6f40c097a468f32864731302636"}, - {file = "rpds_py-0.13.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:84f7f3f18d29a1c645729634003d21d84028bd9c2fd78eba9d028998f46fa5aa"}, - {file = "rpds_py-0.13.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f7c7ddc8d1a64623068da5a15e28001fbd0f0aff754aae7a75a4be5042191638"}, - {file = "rpds_py-0.13.0-cp311-none-win32.whl", hash = "sha256:8a33d2b6340261191bb59adb5a453fa6c7d99de85552bd4e8196411f0509c9bf"}, - {file = "rpds_py-0.13.0-cp311-none-win_amd64.whl", hash = "sha256:8b9c1dd90461940315981499df62a627571c4f0992e8bafc5396d33916224cac"}, - {file = "rpds_py-0.13.0-cp312-cp312-macosx_10_7_x86_64.whl", hash = "sha256:15a2d542de5cbfc6abddc4846d9412b59f8ee9c8dfa0b9c92a29321297c91745"}, - {file = "rpds_py-0.13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8dd69e01b29ff45a0062cad5c480d8aa9301c3ef09da471f86337a78eb2d3405"}, - {file = "rpds_py-0.13.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:efdd02971a02f98492a72b25484f1f6125fb9f2166e48cc4c9bfa563349c851b"}, - {file = "rpds_py-0.13.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:91ca9aaee7ccdfa66d800b5c4ec634fefca947721bab52d6ad2f6350969a3771"}, - {file = "rpds_py-0.13.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:afcec1f5b09d0db70aeb2d90528a9164acb61841a3124e28f6ac0137f4c36cb4"}, - {file = "rpds_py-0.13.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5c6824673f66c47f7ee759c21e973bfce3ceaf2c25cb940cb45b41105dc914e8"}, - {file = "rpds_py-0.13.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50b6d80925dfeb573fc5e38582fb9517c6912dc462cc858a11c8177b0837127a"}, - {file = "rpds_py-0.13.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3a1a38512925829784b5dc38591c757b80cfce115c72c594dc59567dab62b9c4"}, - {file = "rpds_py-0.13.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:977c6123c359dcc70ce3161b781ab70b0d342de2666944b776617e01a0a7822a"}, - {file = "rpds_py-0.13.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:c472409037e05ed87b99430f97a6b82130328bb977502813547e8ee6a3392502"}, - {file = "rpds_py-0.13.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:28bb22019f4a783ea06a6b81437d5996551869e8a722ee8720b744f7684d97f4"}, - {file = "rpds_py-0.13.0-cp312-none-win32.whl", hash = "sha256:46be9c0685cce2ea02151aa8308f2c1b78581be41a5dd239448a941a210ef5dd"}, - {file = "rpds_py-0.13.0-cp312-none-win_amd64.whl", hash = "sha256:3c5b9ad4d3e05dfcf8629f0d534f92610e9805dbce2fcb9b3c801ddb886431d5"}, - {file = "rpds_py-0.13.0-cp38-cp38-macosx_10_7_x86_64.whl", hash = "sha256:66eb5aa36e857f768c598d2082fafb733eaf53e06e1169c6b4de65636e04ffd0"}, - {file = "rpds_py-0.13.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c9f4c2b7d989426e9fe9b720211172cf10eb5f7aa16c63de2e5dc61457abcf35"}, - {file = "rpds_py-0.13.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c1e37dfffe8959a492b7b331995f291847a41a035b4aad82d6060f38e8378a2b"}, - {file = "rpds_py-0.13.0-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8220321f2dccd9d66f72639185247cb7bbdd90753bf0b6bfca0fa31dba8af23c"}, - {file = "rpds_py-0.13.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e8f1d466a9747213d3cf7e1afec849cc51edb70d5b4ae9a82eca0f172bfbb6d0"}, - {file = "rpds_py-0.13.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c4c4b4ff3de834ec5c1c690e5a18233ca78547d003eb83664668ccf09ef1398"}, - {file = "rpds_py-0.13.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:525d19ef0a999229ef0f0a7687ab2c9a00d1b6a47a005006f4d8c4b8975fdcec"}, - {file = "rpds_py-0.13.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0982b59d014efb84a57128e7e69399fb29ad8f2da5b0a5bcbfd12e211c00492e"}, - {file = "rpds_py-0.13.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:f714dd5b705f1c394d1b361d96486c4981055c434a7eafb1a3147ac75e34a3de"}, - {file = "rpds_py-0.13.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:766b573a964389ef0d91a26bb31e1b59dbc5d06eff7707f3dfcec23d93080ba3"}, - {file = "rpds_py-0.13.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:2ed65ad3fc5065d13e31e90794e0b52e405b63ae4fab1080caeaadc10a3439c5"}, - {file = "rpds_py-0.13.0-cp38-none-win32.whl", hash = "sha256:9645f7fe10a68b2396d238250b4b264c2632d2eb6ce2cb90aa0fe08adee194be"}, - {file = "rpds_py-0.13.0-cp38-none-win_amd64.whl", hash = "sha256:42d0ad129c102856a364ccc7d356faec017af86b3543a8539795f22b6cabad11"}, - {file = "rpds_py-0.13.0-cp39-cp39-macosx_10_7_x86_64.whl", hash = "sha256:95c11647fac2a3515ea2614a79e14b7c75025724ad54c91c7db4a6ea5c25ef19"}, - {file = "rpds_py-0.13.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9435bf4832555c4f769c6be9401664357be33d5f5d8dc58f5c20fb8d21e2c45d"}, - {file = "rpds_py-0.13.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54b1d671a74395344239ee3adbcd8c496525f6a2b2e54c40fec69620a31a8dcb"}, - {file = "rpds_py-0.13.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:13c8061115f1468de6ffdfb1d31b446e1bd814f1ff6e556862169aacb9fbbc5d"}, - {file = "rpds_py-0.13.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a78861123b002725633871a2096c3a4313224aab3d11b953dced87cfba702418"}, - {file = "rpds_py-0.13.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:97c1be5a018cdad54fa7e5f7d36b9ab45ef941a1d185987f18bdab0a42344012"}, - {file = "rpds_py-0.13.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e33b17915c8e4fb2ea8b91bb4c46cba92242c63dd38b87e869ead5ba217e2970"}, - {file = "rpds_py-0.13.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:153b6d8cf7ae4b9ffd09de6abeda661e351e3e06eaafd18a8c104ea00099b131"}, - {file = "rpds_py-0.13.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:da2852201e8e00c86be82c43d6893e6c380ef648ae53f337ffd1eaa35e3dfb8a"}, - {file = "rpds_py-0.13.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:a2383f400691fd7bd63347d4d75eb2fd525de9d901799a33a4e896c9885609f8"}, - {file = "rpds_py-0.13.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:d5bf560634ea6e9a59ceb2181a6cd6195a03f48cef9a400eb15e197e18f14548"}, - {file = "rpds_py-0.13.0-cp39-none-win32.whl", hash = "sha256:fdaef49055cc0c701fb17b9b34a38ef375e5cdb230b3722d4a12baf9b7cbc6d3"}, - {file = "rpds_py-0.13.0-cp39-none-win_amd64.whl", hash = "sha256:26660c74a20fe249fad75ca00bbfcf60e57c3fdbde92971c88a20e07fea1de64"}, - {file = "rpds_py-0.13.0-pp310-pypy310_pp73-macosx_10_7_x86_64.whl", hash = "sha256:28324f2f0247d407daabf7ff357ad9f36126075c92a0cf5319396d96ff4e1248"}, - {file = "rpds_py-0.13.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:b431c2c0ff1ea56048a2b066d99d0c2d151ae7625b20be159b7e699f3e80390b"}, - {file = "rpds_py-0.13.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7472bd60a8293217444bdc6a46e516feb8d168da44d5f3fccea0336e88e3b79a"}, - {file = "rpds_py-0.13.0-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:169063f346b8fd84f47d986c9c48e6094eb38b839c1287e7cb886b8a2b32195d"}, - {file = "rpds_py-0.13.0-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eef7ee7c70f8b8698be468d54f9f5e01804f3a1dd5657e8a96363dbd52b9b5ec"}, - {file = "rpds_py-0.13.0-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:762013dd59df12380c5444f61ccbf9ae1297027cabbd7aa25891f724ebf8c8f7"}, - {file = "rpds_py-0.13.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:152570689a27ae0be1d5f50b21dad38d450b9227d0974f23bd400400ea087e88"}, - {file = "rpds_py-0.13.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d70a93a40e55da117c511ddc514642bc7d59a95a99137168a5f3f2f876b47962"}, - {file = "rpds_py-0.13.0-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:e6c6fed07d13b9e0fb689356c40c81f1aa92e3c9d91d8fd5816a0348ccd999f7"}, - {file = "rpds_py-0.13.0-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:cdded3cf9e36840b09ccef714d5fa74a03f4eb6cf81e694226ed9cb5e6f90de0"}, - {file = "rpds_py-0.13.0-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:e1f40faf406c52c7ae7d208b9140377c06397248978ccb03fbfbb30a0571e359"}, - {file = "rpds_py-0.13.0-pp38-pypy38_pp73-macosx_10_7_x86_64.whl", hash = "sha256:c10326e30c97a95b7e1d75e5200ef0b9827aa0f861e331e43b15dfdfd63e669b"}, - {file = "rpds_py-0.13.0-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:afde37e3763c602d0385bce5c12f262e7b1dd2a0f323e239fa9d7b2d4d5d8509"}, - {file = "rpds_py-0.13.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4084ab6840bd4d79eff3b5f497add847a7db31ce5a0c2d440c90b2d2b7011857"}, - {file = "rpds_py-0.13.0-pp38-pypy38_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1c9c9cb48ab77ebfa47db25b753f594d4f44959cfe43b713439ca6e3c9329671"}, - {file = "rpds_py-0.13.0-pp38-pypy38_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:533d728ea5ad5253af3395102723ca8a77b62de47b2295155650c9a88fcdeec8"}, - {file = "rpds_py-0.13.0-pp38-pypy38_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f22cab655b41033d430f20266bf563b35038a7f01c9a099b0ccfd30a7fb9247"}, - {file = "rpds_py-0.13.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b9a0507342c37132813449393e6e6f351bbff376031cfff1ee6e616402ac7908"}, - {file = "rpds_py-0.13.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4eb1faf8e2ee9a2de3cb3ae4c8c355914cdc85f2cd7f27edf76444c9550ce1e7"}, - {file = "rpds_py-0.13.0-pp38-pypy38_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:a61a152d61e3ae26e0bbba7b2f568f6f25ca0abdeb6553eca7e7c45b59d9b1a9"}, - {file = "rpds_py-0.13.0-pp38-pypy38_pp73-musllinux_1_2_i686.whl", hash = "sha256:e499bf2200eb74774a6f85a7465e3bc5273fa8ef0055590d97a88c1e7ea02eea"}, - {file = "rpds_py-0.13.0-pp38-pypy38_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:1e5becd0de924616ca9a12abeb6458568d1dc8fe5c670d5cdb738402a8a8429d"}, - {file = "rpds_py-0.13.0-pp39-pypy39_pp73-macosx_10_7_x86_64.whl", hash = "sha256:70cfe098d915f566eeebcb683f49f9404d2f948432891b6e075354336eda9dfb"}, - {file = "rpds_py-0.13.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:2e73511e88368f93c24efe7c9a20b319eaa828bc7431f8a17713efb9e31a39fa"}, - {file = "rpds_py-0.13.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c07cb9bcccd08f9bc2fd05bf586479df4272ea5a6a70fbcb59b018ed48a5a84d"}, - {file = "rpds_py-0.13.0-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8c4e84016ba225e09df20fed8befe8c68d14fbeff6078f4a0ff907ae2095e17e"}, - {file = "rpds_py-0.13.0-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6ad465e5a70580ca9c1944f43a9a71bca3a7b74554347fc96ca0479eca8981f9"}, - {file = "rpds_py-0.13.0-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:189aebd44a07fa7b7966cf78b85bde8335b0b6c3b1c4ef5589f8c03176830107"}, - {file = "rpds_py-0.13.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f50ca0460f1f7a89ab9b8355d83ac993d5998ad4218e76654ecf8afe648d8aa"}, - {file = "rpds_py-0.13.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f6c225011467021879c0482316e42d8a28852fc29f0c15d2a435ff457cadccd4"}, - {file = "rpds_py-0.13.0-pp39-pypy39_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:1e63b32b856c0f08a56b76967d61b6ad811d8d330a8aebb9d21afadd82a296f6"}, - {file = "rpds_py-0.13.0-pp39-pypy39_pp73-musllinux_1_2_i686.whl", hash = "sha256:7e5fbe9800f09c56967fda88c4d9272955e781699a66102bd098f22511a3f260"}, - {file = "rpds_py-0.13.0-pp39-pypy39_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:fea99967d4a978ce95dd52310bcb4a943b77c61725393bca631b0908047d6e2f"}, - {file = "rpds_py-0.13.0.tar.gz", hash = "sha256:35cc91cbb0b775705e0feb3362490b8418c408e9e3c3b9cb3b02f6e495f03ee7"}, + {file = "rpds_py-0.17.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:4128980a14ed805e1b91a7ed551250282a8ddf8201a4e9f8f5b7e6225f54170d"}, + {file = "rpds_py-0.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ff1dcb8e8bc2261a088821b2595ef031c91d499a0c1b031c152d43fe0a6ecec8"}, + {file = "rpds_py-0.17.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d65e6b4f1443048eb7e833c2accb4fa7ee67cc7d54f31b4f0555b474758bee55"}, + {file = "rpds_py-0.17.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a71169d505af63bb4d20d23a8fbd4c6ce272e7bce6cc31f617152aa784436f29"}, + {file = "rpds_py-0.17.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:436474f17733c7dca0fbf096d36ae65277e8645039df12a0fa52445ca494729d"}, + {file = "rpds_py-0.17.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:10162fe3f5f47c37ebf6d8ff5a2368508fe22007e3077bf25b9c7d803454d921"}, + {file = "rpds_py-0.17.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:720215373a280f78a1814becb1312d4e4d1077b1202a56d2b0815e95ccb99ce9"}, + {file = "rpds_py-0.17.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:70fcc6c2906cfa5c6a552ba7ae2ce64b6c32f437d8f3f8eea49925b278a61453"}, + {file = "rpds_py-0.17.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:91e5a8200e65aaac342a791272c564dffcf1281abd635d304d6c4e6b495f29dc"}, + {file = "rpds_py-0.17.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:99f567dae93e10be2daaa896e07513dd4bf9c2ecf0576e0533ac36ba3b1d5394"}, + {file = "rpds_py-0.17.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:24e4900a6643f87058a27320f81336d527ccfe503984528edde4bb660c8c8d59"}, + {file = "rpds_py-0.17.1-cp310-none-win32.whl", hash = "sha256:0bfb09bf41fe7c51413f563373e5f537eaa653d7adc4830399d4e9bdc199959d"}, + {file = "rpds_py-0.17.1-cp310-none-win_amd64.whl", hash = "sha256:20de7b7179e2031a04042e85dc463a93a82bc177eeba5ddd13ff746325558aa6"}, + {file = "rpds_py-0.17.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:65dcf105c1943cba45d19207ef51b8bc46d232a381e94dd38719d52d3980015b"}, + {file = "rpds_py-0.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:01f58a7306b64e0a4fe042047dd2b7d411ee82e54240284bab63e325762c1147"}, + {file = "rpds_py-0.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:071bc28c589b86bc6351a339114fb7a029f5cddbaca34103aa573eba7b482382"}, + {file = "rpds_py-0.17.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ae35e8e6801c5ab071b992cb2da958eee76340e6926ec693b5ff7d6381441745"}, + {file = "rpds_py-0.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:149c5cd24f729e3567b56e1795f74577aa3126c14c11e457bec1b1c90d212e38"}, + {file = "rpds_py-0.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e796051f2070f47230c745d0a77a91088fbee2cc0502e9b796b9c6471983718c"}, + {file = "rpds_py-0.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:60e820ee1004327609b28db8307acc27f5f2e9a0b185b2064c5f23e815f248f8"}, + {file = "rpds_py-0.17.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1957a2ab607f9added64478a6982742eb29f109d89d065fa44e01691a20fc20a"}, + {file = "rpds_py-0.17.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8587fd64c2a91c33cdc39d0cebdaf30e79491cc029a37fcd458ba863f8815383"}, + {file = "rpds_py-0.17.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4dc889a9d8a34758d0fcc9ac86adb97bab3fb7f0c4d29794357eb147536483fd"}, + {file = "rpds_py-0.17.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:2953937f83820376b5979318840f3ee47477d94c17b940fe31d9458d79ae7eea"}, + {file = "rpds_py-0.17.1-cp311-none-win32.whl", hash = "sha256:1bfcad3109c1e5ba3cbe2f421614e70439f72897515a96c462ea657261b96518"}, + {file = "rpds_py-0.17.1-cp311-none-win_amd64.whl", hash = "sha256:99da0a4686ada4ed0f778120a0ea8d066de1a0a92ab0d13ae68492a437db78bf"}, + {file = "rpds_py-0.17.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:1dc29db3900cb1bb40353772417800f29c3d078dbc8024fd64655a04ee3c4bdf"}, + {file = "rpds_py-0.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:82ada4a8ed9e82e443fcef87e22a3eed3654dd3adf6e3b3a0deb70f03e86142a"}, + {file = "rpds_py-0.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d36b2b59e8cc6e576f8f7b671e32f2ff43153f0ad6d0201250a7c07f25d570e"}, + {file = "rpds_py-0.17.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3677fcca7fb728c86a78660c7fb1b07b69b281964673f486ae72860e13f512ad"}, + {file = "rpds_py-0.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:516fb8c77805159e97a689e2f1c80655c7658f5af601c34ffdb916605598cda2"}, + {file = "rpds_py-0.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:df3b6f45ba4515632c5064e35ca7f31d51d13d1479673185ba8f9fefbbed58b9"}, + {file = "rpds_py-0.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a967dd6afda7715d911c25a6ba1517975acd8d1092b2f326718725461a3d33f9"}, + {file = "rpds_py-0.17.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:dbbb95e6fc91ea3102505d111b327004d1c4ce98d56a4a02e82cd451f9f57140"}, + {file = "rpds_py-0.17.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:02866e060219514940342a1f84303a1ef7a1dad0ac311792fbbe19b521b489d2"}, + {file = "rpds_py-0.17.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:2528ff96d09f12e638695f3a2e0c609c7b84c6df7c5ae9bfeb9252b6fa686253"}, + {file = "rpds_py-0.17.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:bd345a13ce06e94c753dab52f8e71e5252aec1e4f8022d24d56decd31e1b9b23"}, + {file = "rpds_py-0.17.1-cp312-none-win32.whl", hash = "sha256:2a792b2e1d3038daa83fa474d559acfd6dc1e3650ee93b2662ddc17dbff20ad1"}, + {file = "rpds_py-0.17.1-cp312-none-win_amd64.whl", hash = "sha256:292f7344a3301802e7c25c53792fae7d1593cb0e50964e7bcdcc5cf533d634e3"}, + {file = "rpds_py-0.17.1-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:8ffe53e1d8ef2520ebcf0c9fec15bb721da59e8ef283b6ff3079613b1e30513d"}, + {file = "rpds_py-0.17.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4341bd7579611cf50e7b20bb8c2e23512a3dc79de987a1f411cb458ab670eb90"}, + {file = "rpds_py-0.17.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f4eb548daf4836e3b2c662033bfbfc551db58d30fd8fe660314f86bf8510b93"}, + {file = "rpds_py-0.17.1-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b686f25377f9c006acbac63f61614416a6317133ab7fafe5de5f7dc8a06d42eb"}, + {file = "rpds_py-0.17.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4e21b76075c01d65d0f0f34302b5a7457d95721d5e0667aea65e5bb3ab415c25"}, + {file = "rpds_py-0.17.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b86b21b348f7e5485fae740d845c65a880f5d1eda1e063bc59bef92d1f7d0c55"}, + {file = "rpds_py-0.17.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f175e95a197f6a4059b50757a3dca33b32b61691bdbd22c29e8a8d21d3914cae"}, + {file = "rpds_py-0.17.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1701fc54460ae2e5efc1dd6350eafd7a760f516df8dbe51d4a1c79d69472fbd4"}, + {file = "rpds_py-0.17.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:9051e3d2af8f55b42061603e29e744724cb5f65b128a491446cc029b3e2ea896"}, + {file = "rpds_py-0.17.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:7450dbd659fed6dd41d1a7d47ed767e893ba402af8ae664c157c255ec6067fde"}, + {file = "rpds_py-0.17.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:5a024fa96d541fd7edaa0e9d904601c6445e95a729a2900c5aec6555fe921ed6"}, + {file = "rpds_py-0.17.1-cp38-none-win32.whl", hash = "sha256:da1ead63368c04a9bded7904757dfcae01eba0e0f9bc41d3d7f57ebf1c04015a"}, + {file = "rpds_py-0.17.1-cp38-none-win_amd64.whl", hash = "sha256:841320e1841bb53fada91c9725e766bb25009cfd4144e92298db296fb6c894fb"}, + {file = "rpds_py-0.17.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:f6c43b6f97209e370124baf2bf40bb1e8edc25311a158867eb1c3a5d449ebc7a"}, + {file = "rpds_py-0.17.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5e7d63ec01fe7c76c2dbb7e972fece45acbb8836e72682bde138e7e039906e2c"}, + {file = "rpds_py-0.17.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:81038ff87a4e04c22e1d81f947c6ac46f122e0c80460b9006e6517c4d842a6ec"}, + {file = "rpds_py-0.17.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:810685321f4a304b2b55577c915bece4c4a06dfe38f6e62d9cc1d6ca8ee86b99"}, + {file = "rpds_py-0.17.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:25f071737dae674ca8937a73d0f43f5a52e92c2d178330b4c0bb6ab05586ffa6"}, + {file = "rpds_py-0.17.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aa5bfb13f1e89151ade0eb812f7b0d7a4d643406caaad65ce1cbabe0a66d695f"}, + {file = "rpds_py-0.17.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dfe07308b311a8293a0d5ef4e61411c5c20f682db6b5e73de6c7c8824272c256"}, + {file = "rpds_py-0.17.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a000133a90eea274a6f28adc3084643263b1e7c1a5a66eb0a0a7a36aa757ed74"}, + {file = "rpds_py-0.17.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:5d0e8a6434a3fbf77d11448c9c25b2f25244226cfbec1a5159947cac5b8c5fa4"}, + {file = "rpds_py-0.17.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:efa767c220d94aa4ac3a6dd3aeb986e9f229eaf5bce92d8b1b3018d06bed3772"}, + {file = "rpds_py-0.17.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:dbc56680ecf585a384fbd93cd42bc82668b77cb525343170a2d86dafaed2a84b"}, + {file = "rpds_py-0.17.1-cp39-none-win32.whl", hash = "sha256:270987bc22e7e5a962b1094953ae901395e8c1e1e83ad016c5cfcfff75a15a3f"}, + {file = "rpds_py-0.17.1-cp39-none-win_amd64.whl", hash = "sha256:2a7b2f2f56a16a6d62e55354dd329d929560442bd92e87397b7a9586a32e3e76"}, + {file = "rpds_py-0.17.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:a3264e3e858de4fc601741498215835ff324ff2482fd4e4af61b46512dd7fc83"}, + {file = "rpds_py-0.17.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:f2f3b28b40fddcb6c1f1f6c88c6f3769cd933fa493ceb79da45968a21dccc920"}, + {file = "rpds_py-0.17.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9584f8f52010295a4a417221861df9bea4c72d9632562b6e59b3c7b87a1522b7"}, + {file = "rpds_py-0.17.1-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c64602e8be701c6cfe42064b71c84ce62ce66ddc6422c15463fd8127db3d8066"}, + {file = "rpds_py-0.17.1-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:060f412230d5f19fc8c8b75f315931b408d8ebf56aec33ef4168d1b9e54200b1"}, + {file = "rpds_py-0.17.1-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b9412abdf0ba70faa6e2ee6c0cc62a8defb772e78860cef419865917d86c7342"}, + {file = "rpds_py-0.17.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9737bdaa0ad33d34c0efc718741abaafce62fadae72c8b251df9b0c823c63b22"}, + {file = "rpds_py-0.17.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9f0e4dc0f17dcea4ab9d13ac5c666b6b5337042b4d8f27e01b70fae41dd65c57"}, + {file = "rpds_py-0.17.1-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:1db228102ab9d1ff4c64148c96320d0be7044fa28bd865a9ce628ce98da5973d"}, + {file = "rpds_py-0.17.1-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:d8bbd8e56f3ba25a7d0cf980fc42b34028848a53a0e36c9918550e0280b9d0b6"}, + {file = "rpds_py-0.17.1-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:be22ae34d68544df293152b7e50895ba70d2a833ad9566932d750d3625918b82"}, + {file = "rpds_py-0.17.1-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:bf046179d011e6114daf12a534d874958b039342b347348a78b7cdf0dd9d6041"}, + {file = "rpds_py-0.17.1-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:1a746a6d49665058a5896000e8d9d2f1a6acba8a03b389c1e4c06e11e0b7f40d"}, + {file = "rpds_py-0.17.1-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f0b8bf5b8db49d8fd40f54772a1dcf262e8be0ad2ab0206b5a2ec109c176c0a4"}, + {file = "rpds_py-0.17.1-pp38-pypy38_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f7f4cb1f173385e8a39c29510dd11a78bf44e360fb75610594973f5ea141028b"}, + {file = "rpds_py-0.17.1-pp38-pypy38_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7fbd70cb8b54fe745301921b0816c08b6d917593429dfc437fd024b5ba713c58"}, + {file = "rpds_py-0.17.1-pp38-pypy38_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9bdf1303df671179eaf2cb41e8515a07fc78d9d00f111eadbe3e14262f59c3d0"}, + {file = "rpds_py-0.17.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fad059a4bd14c45776600d223ec194e77db6c20255578bb5bcdd7c18fd169361"}, + {file = "rpds_py-0.17.1-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3664d126d3388a887db44c2e293f87d500c4184ec43d5d14d2d2babdb4c64cad"}, + {file = "rpds_py-0.17.1-pp38-pypy38_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:698ea95a60c8b16b58be9d854c9f993c639f5c214cf9ba782eca53a8789d6b19"}, + {file = "rpds_py-0.17.1-pp38-pypy38_pp73-musllinux_1_2_i686.whl", hash = "sha256:c3d2010656999b63e628a3c694f23020322b4178c450dc478558a2b6ef3cb9bb"}, + {file = "rpds_py-0.17.1-pp38-pypy38_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:938eab7323a736533f015e6069a7d53ef2dcc841e4e533b782c2bfb9fb12d84b"}, + {file = "rpds_py-0.17.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:1e626b365293a2142a62b9a614e1f8e331b28f3ca57b9f05ebbf4cf2a0f0bdc5"}, + {file = "rpds_py-0.17.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:380e0df2e9d5d5d339803cfc6d183a5442ad7ab3c63c2a0982e8c824566c5ccc"}, + {file = "rpds_py-0.17.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b760a56e080a826c2e5af09002c1a037382ed21d03134eb6294812dda268c811"}, + {file = "rpds_py-0.17.1-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5576ee2f3a309d2bb403ec292d5958ce03953b0e57a11d224c1f134feaf8c40f"}, + {file = "rpds_py-0.17.1-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1f3c3461ebb4c4f1bbc70b15d20b565759f97a5aaf13af811fcefc892e9197ba"}, + {file = "rpds_py-0.17.1-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:637b802f3f069a64436d432117a7e58fab414b4e27a7e81049817ae94de45d8d"}, + {file = "rpds_py-0.17.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ffee088ea9b593cc6160518ba9bd319b5475e5f3e578e4552d63818773c6f56a"}, + {file = "rpds_py-0.17.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3ac732390d529d8469b831949c78085b034bff67f584559340008d0f6041a049"}, + {file = "rpds_py-0.17.1-pp39-pypy39_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:93432e747fb07fa567ad9cc7aaadd6e29710e515aabf939dfbed8046041346c6"}, + {file = "rpds_py-0.17.1-pp39-pypy39_pp73-musllinux_1_2_i686.whl", hash = "sha256:7b7d9ca34542099b4e185b3c2a2b2eda2e318a7dbde0b0d83357a6d4421b5296"}, + {file = "rpds_py-0.17.1-pp39-pypy39_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:0387ce69ba06e43df54e43968090f3626e231e4bc9150e4c3246947567695f68"}, + {file = "rpds_py-0.17.1.tar.gz", hash = "sha256:0210b2668f24c078307260bf88bdac9d6f1093635df5123789bfee4d8d7fc8e7"}, ] [[package]] @@ -3039,44 +3061,6 @@ nativelib = ["pyobjc-framework-Cocoa", "pywin32"] objc = ["pyobjc-framework-Cocoa"] win32 = ["pywin32"] -[[package]] -name = "setuptools" -version = "68.2.2" -description = "Easily download, build, install, upgrade, and uninstall Python packages" -optional = false -python-versions = ">=3.8" -files = [ - {file = "setuptools-68.2.2-py3-none-any.whl", hash = "sha256:b454a35605876da60632df1a60f736524eb73cc47bbc9f3f1ef1b644de74fd2a"}, - {file = "setuptools-68.2.2.tar.gz", hash = "sha256:4ac1475276d2f1c48684874089fefcd83bd7162ddaafb81fac866ba0db282a87"}, -] - -[package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] -testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] -testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.1)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] - -[[package]] -name = "setuptools-scm" -version = "8.0.4" -description = "the blessed package to manage your versions by scm tags" -optional = false -python-versions = ">=3.8" -files = [ - {file = "setuptools-scm-8.0.4.tar.gz", hash = "sha256:b5f43ff6800669595193fd09891564ee9d1d7dcb196cab4b2506d53a2e1c95c7"}, - {file = "setuptools_scm-8.0.4-py3-none-any.whl", hash = "sha256:b47844cd2a84b83b3187a5782c71128c28b4c94cad8bfb871da2784a5cb54c4f"}, -] - -[package.dependencies] -packaging = ">=20" -setuptools = "*" -tomli = {version = ">=1", markers = "python_version < \"3.11\""} -typing-extensions = "*" - -[package.extras] -docs = ["entangled-cli[rich]", "mkdocs", "mkdocs-entangled-plugin", "mkdocs-material", "mkdocstrings[python]", "pygments"] -rich = ["rich"] -test = ["build", "pytest", "rich", "wheel"] - [[package]] name = "six" version = "1.16.0" @@ -3407,22 +3391,22 @@ files = [ [[package]] name = "tornado" -version = "6.3.3" +version = "6.4" description = "Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed." optional = false python-versions = ">= 3.8" files = [ - {file = "tornado-6.3.3-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:502fba735c84450974fec147340016ad928d29f1e91f49be168c0a4c18181e1d"}, - {file = "tornado-6.3.3-cp38-abi3-macosx_10_9_x86_64.whl", hash = "sha256:805d507b1f588320c26f7f097108eb4023bbaa984d63176d1652e184ba24270a"}, - {file = "tornado-6.3.3-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1bd19ca6c16882e4d37368e0152f99c099bad93e0950ce55e71daed74045908f"}, - {file = "tornado-6.3.3-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7ac51f42808cca9b3613f51ffe2a965c8525cb1b00b7b2d56828b8045354f76a"}, - {file = "tornado-6.3.3-cp38-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:71a8db65160a3c55d61839b7302a9a400074c9c753040455494e2af74e2501f2"}, - {file = "tornado-6.3.3-cp38-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:ceb917a50cd35882b57600709dd5421a418c29ddc852da8bcdab1f0db33406b0"}, - {file = "tornado-6.3.3-cp38-abi3-musllinux_1_1_i686.whl", hash = "sha256:7d01abc57ea0dbb51ddfed477dfe22719d376119844e33c661d873bf9c0e4a16"}, - {file = "tornado-6.3.3-cp38-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:9dc4444c0defcd3929d5c1eb5706cbe1b116e762ff3e0deca8b715d14bf6ec17"}, - {file = "tornado-6.3.3-cp38-abi3-win32.whl", hash = "sha256:65ceca9500383fbdf33a98c0087cb975b2ef3bfb874cb35b8de8740cf7f41bd3"}, - {file = "tornado-6.3.3-cp38-abi3-win_amd64.whl", hash = "sha256:22d3c2fa10b5793da13c807e6fc38ff49a4f6e1e3868b0a6f4164768bb8e20f5"}, - {file = "tornado-6.3.3.tar.gz", hash = "sha256:e7d8db41c0181c80d76c982aacc442c0783a2c54d6400fe028954201a2e032fe"}, + {file = "tornado-6.4-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:02ccefc7d8211e5a7f9e8bc3f9e5b0ad6262ba2fbb683a6443ecc804e5224ce0"}, + {file = "tornado-6.4-cp38-abi3-macosx_10_9_x86_64.whl", hash = "sha256:27787de946a9cffd63ce5814c33f734c627a87072ec7eed71f7fc4417bb16263"}, + {file = "tornado-6.4-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f7894c581ecdcf91666a0912f18ce5e757213999e183ebfc2c3fdbf4d5bd764e"}, + {file = "tornado-6.4-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e43bc2e5370a6a8e413e1e1cd0c91bedc5bd62a74a532371042a18ef19e10579"}, + {file = "tornado-6.4-cp38-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f0251554cdd50b4b44362f73ad5ba7126fc5b2c2895cc62b14a1c2d7ea32f212"}, + {file = "tornado-6.4-cp38-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:fd03192e287fbd0899dd8f81c6fb9cbbc69194d2074b38f384cb6fa72b80e9c2"}, + {file = "tornado-6.4-cp38-abi3-musllinux_1_1_i686.whl", hash = "sha256:88b84956273fbd73420e6d4b8d5ccbe913c65d31351b4c004ae362eba06e1f78"}, + {file = "tornado-6.4-cp38-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:71ddfc23a0e03ef2df1c1397d859868d158c8276a0603b96cf86892bff58149f"}, + {file = "tornado-6.4-cp38-abi3-win32.whl", hash = "sha256:6f8a6c77900f5ae93d8b4ae1196472d0ccc2775cc1dfdc9e7727889145c45052"}, + {file = "tornado-6.4-cp38-abi3-win_amd64.whl", hash = "sha256:10aeaa8006333433da48dec9fe417877f8bcc21f48dda8d661ae79da357b2a63"}, + {file = "tornado-6.4.tar.gz", hash = "sha256:72291fa6e6bc84e626589f1c29d90a5a6d593ef5ae68052ee2ef000dfd273dee"}, ] [[package]] @@ -3447,50 +3431,50 @@ telegram = ["requests"] [[package]] name = "traitlets" -version = "5.13.0" +version = "5.14.1" description = "Traitlets Python configuration system" optional = false python-versions = ">=3.8" files = [ - {file = "traitlets-5.13.0-py3-none-any.whl", hash = "sha256:baf991e61542da48fe8aef8b779a9ea0aa38d8a54166ee250d5af5ecf4486619"}, - {file = "traitlets-5.13.0.tar.gz", hash = "sha256:9b232b9430c8f57288c1024b34a8f0251ddcc47268927367a0dd3eeaca40deb5"}, + {file = "traitlets-5.14.1-py3-none-any.whl", hash = "sha256:2e5a030e6eff91737c643231bfcf04a65b0132078dad75e4936700b213652e74"}, + {file = "traitlets-5.14.1.tar.gz", hash = "sha256:8585105b371a04b8316a43d5ce29c098575c2e477850b62b848b964f1444527e"}, ] [package.extras] docs = ["myst-parser", "pydata-sphinx-theme", "sphinx"] -test = ["argcomplete (>=3.0.3)", "mypy (>=1.6.0)", "pre-commit", "pytest (>=7.0,<7.5)", "pytest-mock", "pytest-mypy-testing"] +test = ["argcomplete (>=3.0.3)", "mypy (>=1.7.0)", "pre-commit", "pytest (>=7.0,<7.5)", "pytest-mock", "pytest-mypy-testing"] [[package]] name = "types-python-dateutil" -version = "2.8.19.14" +version = "2.8.19.20240106" description = "Typing stubs for python-dateutil" optional = false -python-versions = "*" +python-versions = ">=3.8" files = [ - {file = "types-python-dateutil-2.8.19.14.tar.gz", hash = "sha256:1f4f10ac98bb8b16ade9dbee3518d9ace017821d94b057a425b069f834737f4b"}, - {file = "types_python_dateutil-2.8.19.14-py3-none-any.whl", hash = "sha256:f977b8de27787639986b4e28963263fd0e5158942b3ecef91b9335c130cb1ce9"}, + {file = "types-python-dateutil-2.8.19.20240106.tar.gz", hash = "sha256:1f8db221c3b98e6ca02ea83a58371b22c374f42ae5bbdf186db9c9a76581459f"}, + {file = "types_python_dateutil-2.8.19.20240106-py3-none-any.whl", hash = "sha256:efbbdc54590d0f16152fa103c9879c7d4a00e82078f6e2cf01769042165acaa2"}, ] [[package]] name = "typing-extensions" -version = "4.8.0" +version = "4.9.0" description = "Backported and Experimental Type Hints for Python 3.8+" optional = false python-versions = ">=3.8" files = [ - {file = "typing_extensions-4.8.0-py3-none-any.whl", hash = "sha256:8f92fc8806f9a6b641eaa5318da32b44d401efaac0f6678c9bc448ba3605faa0"}, - {file = "typing_extensions-4.8.0.tar.gz", hash = "sha256:df8e4339e9cb77357558cbdbceca33c303714cf861d1eef15e1070055ae8b7ef"}, + {file = "typing_extensions-4.9.0-py3-none-any.whl", hash = "sha256:af72aea155e91adfc61c3ae9e0e342dbc0cba726d6cba4b6c72c1f34e47291cd"}, + {file = "typing_extensions-4.9.0.tar.gz", hash = "sha256:23478f88c37f27d76ac8aee6c905017a143b0b1b886c3c9f66bc2fd94f9f5783"}, ] [[package]] name = "tzdata" -version = "2023.3" +version = "2023.4" description = "Provider of IANA time zone data" optional = false python-versions = ">=2" files = [ - {file = "tzdata-2023.3-py2.py3-none-any.whl", hash = "sha256:7e65763eef3120314099b6939b5546db7adce1e7d6f2e179e3df563c70511eda"}, - {file = "tzdata-2023.3.tar.gz", hash = "sha256:11ef1e08e54acb0d4f95bdb1be05da659673de4acbd21bf9c69e94cc5e907a3a"}, + {file = "tzdata-2023.4-py2.py3-none-any.whl", hash = "sha256:aa3ace4329eeacda5b7beb7ea08ece826c28d761cda36e747cfbf97996d39bf3"}, + {file = "tzdata-2023.4.tar.gz", hash = "sha256:dd54c94f294765522c77399649b4fefd95522479a664a0cec87f41bebc6148c9"}, ] [[package]] @@ -3540,13 +3524,13 @@ zstd = ["zstandard (>=0.18.0)"] [[package]] name = "wcwidth" -version = "0.2.10" +version = "0.2.13" description = "Measures the displayed width of unicode strings in a terminal" optional = false python-versions = "*" files = [ - {file = "wcwidth-0.2.10-py2.py3-none-any.whl", hash = "sha256:aec5179002dd0f0d40c456026e74a729661c9d468e1ed64405e3a6c2176ca36f"}, - {file = "wcwidth-0.2.10.tar.gz", hash = "sha256:390c7454101092a6a5e43baad8f83de615463af459201709556b6e4b1c861f97"}, + {file = "wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859"}, + {file = "wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5"}, ] [[package]] @@ -3577,13 +3561,13 @@ files = [ [[package]] name = "websocket-client" -version = "1.6.4" +version = "1.7.0" description = "WebSocket client for Python with low level API options" optional = false python-versions = ">=3.8" files = [ - {file = "websocket-client-1.6.4.tar.gz", hash = "sha256:b3324019b3c28572086c4a319f91d1dcd44e6e11cd340232978c684a7650d0df"}, - {file = "websocket_client-1.6.4-py3-none-any.whl", hash = "sha256:084072e0a7f5f347ef2ac3d8698a5e0b4ffbfcab607628cadabc650fc9a83a24"}, + {file = "websocket-client-1.7.0.tar.gz", hash = "sha256:10e511ea3a8c744631d3bd77e61eb17ed09304c413ad42cf6ddfa4c7787e8fe6"}, + {file = "websocket_client-1.7.0-py3-none-any.whl", hash = "sha256:f4c3d22fec12a2461427a29957ff07d35098ee2d976d3ba244e688b8b4057588"}, ] [package.extras] diff --git a/pyproject.toml b/pyproject.toml index c14171d0..4e77c9ed 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "gnss-lib-py" -version = "0.2.3" +version = "1.0.0" description = "Modular Python tool for parsing, analyzing, and visualizing Global Navigation Satellite Systems (GNSS) data and state estimates" authors = ["Derek Knowles ", "Ashwin Kanhere ", diff --git a/requirements.txt b/requirements.txt index 9efc502d..255d7c89 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,114 +1,112 @@ -anyio==4.0.0 ; python_version >= "3.8" and python_version < "3.12" +anyio==4.2.0 ; python_version >= "3.8" and python_version < "3.12" appnope==0.1.3 ; python_version >= "3.8" and python_version < "3.12" and (platform_system == "Darwin" or sys_platform == "darwin") argon2-cffi-bindings==21.2.0 ; python_version >= "3.8" and python_version < "3.12" argon2-cffi==23.1.0 ; python_version >= "3.8" and python_version < "3.12" arrow==1.3.0 ; python_version >= "3.8" and python_version < "3.12" asttokens==2.4.1 ; python_version >= "3.8" and python_version < "3.12" async-lru==2.0.4 ; python_version >= "3.8" and python_version < "3.12" -attrs==23.1.0 ; python_version >= "3.8" and python_version < "3.12" -babel==2.13.1 ; python_version >= "3.8" and python_version < "3.12" +attrs==23.2.0 ; python_version >= "3.8" and python_version < "3.12" +babel==2.14.0 ; python_version >= "3.8" and python_version < "3.12" backcall==0.2.0 ; python_version >= "3.8" and python_version < "3.12" -beautifulsoup4==4.12.2 ; python_version >= "3.8" and python_version < "3.12" +beautifulsoup4==4.12.3 ; python_version >= "3.8" and python_version < "3.12" bleach==6.1.0 ; python_version >= "3.8" and python_version < "3.12" -certifi==2023.7.22 ; python_version >= "3.8" and python_version < "3.12" +certifi==2023.11.17 ; python_version >= "3.8" and python_version < "3.12" cffi==1.16.0 ; python_version >= "3.8" and python_version < "3.12" charset-normalizer==3.3.2 ; python_version >= "3.8" and python_version < "3.12" colorama==0.4.6 ; python_version >= "3.8" and python_version < "3.12" and sys_platform == "win32" -comm==0.2.0 ; python_version >= "3.8" and python_version < "3.12" +comm==0.2.1 ; python_version >= "3.8" and python_version < "3.12" contourpy==1.1.1 ; python_version >= "3.8" and python_version < "3.12" cycler==0.12.1 ; python_version >= "3.8" and python_version < "3.12" debugpy==1.8.0 ; python_version >= "3.8" and python_version < "3.12" decorator==5.1.1 ; python_version >= "3.8" and python_version < "3.12" defusedxml==0.7.1 ; python_version >= "3.8" and python_version < "3.12" -exceptiongroup==1.1.3 ; python_version >= "3.8" and python_version < "3.11" +exceptiongroup==1.2.0 ; python_version >= "3.8" and python_version < "3.11" executing==2.0.1 ; python_version >= "3.8" and python_version < "3.12" -fastjsonschema==2.19.0 ; python_version >= "3.8" and python_version < "3.12" -fonttools==4.44.3 ; python_version >= "3.8" and python_version < "3.12" +fastjsonschema==2.19.1 ; python_version >= "3.8" and python_version < "3.12" +fonttools==4.47.2 ; python_version >= "3.8" and python_version < "3.12" fqdn==1.5.1 ; python_version >= "3.8" and python_version < "3.12" georinex==1.16.1 ; python_version >= "3.8" and python_version < "3.12" hatanaka==2.8.1 ; python_version >= "3.8" and python_version < "3.12" -idna==3.4 ; python_version >= "3.8" and python_version < "3.12" -importlib-metadata==6.8.0 ; python_version >= "3.8" and python_version < "3.10" +idna==3.6 ; python_version >= "3.8" and python_version < "3.12" +importlib-metadata==7.0.1 ; python_version >= "3.8" and python_version < "3.10" importlib-resources==6.1.1 ; python_version >= "3.8" and python_version < "3.12" iniconfig==2.0.0 ; python_version >= "3.8" and python_version < "3.12" -ipykernel==6.26.0 ; python_version >= "3.8" and python_version < "3.12" +ipykernel==6.29.0 ; python_version >= "3.8" and python_version < "3.12" ipython==8.12.3 ; python_version >= "3.8" and python_version < "3.12" ipywidgets==8.1.1 ; python_version >= "3.8" and python_version < "3.12" isoduration==20.11.0 ; python_version >= "3.8" and python_version < "3.12" jedi==0.19.1 ; python_version >= "3.8" and python_version < "3.12" -jinja2==3.1.2 ; python_version >= "3.8" and python_version < "3.12" +jinja2==3.1.3 ; python_version >= "3.8" and python_version < "3.12" json5==0.9.14 ; python_version >= "3.8" and python_version < "3.12" jsonpointer==2.4 ; python_version >= "3.8" and python_version < "3.12" -jsonschema-specifications==2023.11.1 ; python_version >= "3.8" and python_version < "3.12" -jsonschema==4.20.0 ; python_version >= "3.8" and python_version < "3.12" -jsonschema[format-nongpl]==4.20.0 ; python_version >= "3.8" and python_version < "3.12" +jsonschema-specifications==2023.12.1 ; python_version >= "3.8" and python_version < "3.12" +jsonschema==4.21.1 ; python_version >= "3.8" and python_version < "3.12" +jsonschema[format-nongpl]==4.21.1 ; python_version >= "3.8" and python_version < "3.12" jupyter-client==8.6.0 ; python_version >= "3.8" and python_version < "3.12" jupyter-console==6.6.3 ; python_version >= "3.8" and python_version < "3.12" -jupyter-core==5.5.0 ; python_version >= "3.8" and python_version < "3.12" +jupyter-core==5.7.1 ; python_version >= "3.8" and python_version < "3.12" jupyter-events==0.9.0 ; python_version >= "3.8" and python_version < "3.12" -jupyter-lsp==2.2.0 ; python_version >= "3.8" and python_version < "3.12" -jupyter-server-terminals==0.4.4 ; python_version >= "3.8" and python_version < "3.12" -jupyter-server==2.10.1 ; python_version >= "3.8" and python_version < "3.12" +jupyter-lsp==2.2.2 ; python_version >= "3.8" and python_version < "3.12" +jupyter-server-terminals==0.5.1 ; python_version >= "3.8" and python_version < "3.12" +jupyter-server==2.12.5 ; python_version >= "3.8" and python_version < "3.12" jupyter==1.0.0 ; python_version >= "3.8" and python_version < "3.12" -jupyterlab-pygments==0.2.2 ; python_version >= "3.8" and python_version < "3.12" -jupyterlab-server==2.25.1 ; python_version >= "3.8" and python_version < "3.12" +jupyterlab-pygments==0.3.0 ; python_version >= "3.8" and python_version < "3.12" +jupyterlab-server==2.25.2 ; python_version >= "3.8" and python_version < "3.12" jupyterlab-widgets==3.0.9 ; python_version >= "3.8" and python_version < "3.12" -jupyterlab==4.0.8 ; python_version >= "3.8" and python_version < "3.12" +jupyterlab==4.0.11 ; python_version >= "3.8" and python_version < "3.12" kaleido==0.2.1 ; python_version >= "3.8" and python_version < "3.12" kiwisolver==1.4.5 ; python_version >= "3.8" and python_version < "3.12" markupsafe==2.1.3 ; python_version >= "3.8" and python_version < "3.12" matplotlib-inline==0.1.6 ; python_version >= "3.8" and python_version < "3.12" -matplotlib==3.7.3 ; python_version >= "3.8" and python_version < "3.12" +matplotlib==3.7.4 ; python_version >= "3.8" and python_version < "3.12" mistune==3.0.2 ; python_version >= "3.8" and python_version < "3.12" nbclient==0.9.0 ; python_version >= "3.8" and python_version < "3.12" -nbconvert==7.11.0 ; python_version >= "3.8" and python_version < "3.12" +nbconvert==7.14.2 ; python_version >= "3.8" and python_version < "3.12" nbformat==5.9.2 ; python_version >= "3.8" and python_version < "3.12" ncompress==1.0.1 ; python_version >= "3.8" and python_version < "3.12" -nest-asyncio==1.5.8 ; python_version >= "3.8" and python_version < "3.12" +nest-asyncio==1.5.9 ; python_version >= "3.8" and python_version < "3.12" notebook-shim==0.2.3 ; python_version >= "3.8" and python_version < "3.12" -notebook==7.0.6 ; python_version >= "3.8" and python_version < "3.12" +notebook==7.0.7 ; python_version >= "3.8" and python_version < "3.12" numpy==1.24.4 ; python_version >= "3.8" and python_version < "3.12" overrides==7.4.0 ; python_version >= "3.8" and python_version < "3.12" packaging==23.2 ; python_version >= "3.8" and python_version < "3.12" pandas==2.0.3 ; python_version >= "3.8" and python_version < "3.12" -pandocfilters==1.5.0 ; python_version >= "3.8" and python_version < "3.12" +pandocfilters==1.5.1 ; python_version >= "3.8" and python_version < "3.12" parso==0.8.3 ; python_version >= "3.8" and python_version < "3.12" -pexpect==4.8.0 ; python_version >= "3.8" and python_version < "3.12" and sys_platform != "win32" +pexpect==4.9.0 ; python_version >= "3.8" and python_version < "3.12" and sys_platform != "win32" pickleshare==0.7.5 ; python_version >= "3.8" and python_version < "3.12" -pillow==10.1.0 ; python_version >= "3.8" and python_version < "3.12" +pillow==10.2.0 ; python_version >= "3.8" and python_version < "3.12" pkgutil-resolve-name==1.3.10 ; python_version >= "3.8" and python_version < "3.9" -platformdirs==4.0.0 ; python_version >= "3.8" and python_version < "3.12" +platformdirs==4.1.0 ; python_version >= "3.8" and python_version < "3.12" plotly==5.18.0 ; python_version >= "3.8" and python_version < "3.12" pluggy==1.3.0 ; python_version >= "3.8" and python_version < "3.12" -prometheus-client==0.18.0 ; python_version >= "3.8" and python_version < "3.12" -prompt-toolkit==3.0.41 ; python_version >= "3.8" and python_version < "3.12" -psutil==5.9.6 ; python_version >= "3.8" and python_version < "3.12" +prometheus-client==0.19.0 ; python_version >= "3.8" and python_version < "3.12" +prompt-toolkit==3.0.43 ; python_version >= "3.8" and python_version < "3.12" +psutil==5.9.8 ; python_version >= "3.8" and python_version < "3.12" ptyprocess==0.7.0 ; python_version >= "3.8" and python_version < "3.12" and (os_name != "nt" or sys_platform != "win32") pure-eval==0.2.2 ; python_version >= "3.8" and python_version < "3.12" pycparser==2.21 ; python_version >= "3.8" and python_version < "3.12" -pygments==2.16.1 ; python_version >= "3.8" and python_version < "3.12" +pygments==2.17.2 ; python_version >= "3.8" and python_version < "3.12" pynmea2==1.19.0 ; python_version >= "3.8" and python_version < "3.12" pyparsing==3.1.1 ; python_version >= "3.8" and python_version < "3.12" pytest-lazy-fixture==0.6.3 ; python_version >= "3.8" and python_version < "3.12" -pytest==7.4.3 ; python_version >= "3.8" and python_version < "3.12" +pytest==7.4.4 ; python_version >= "3.8" and python_version < "3.12" python-dateutil==2.8.2 ; python_version >= "3.8" and python_version < "3.12" python-json-logger==2.0.7 ; python_version >= "3.8" and python_version < "3.12" pytz==2023.3.post1 ; python_version >= "3.8" and python_version < "3.12" pywin32==306 ; sys_platform == "win32" and platform_python_implementation != "PyPy" and python_version >= "3.8" and python_version < "3.12" pywinpty==2.0.12 ; python_version >= "3.8" and python_version < "3.12" and os_name == "nt" pyyaml==6.0.1 ; python_version >= "3.8" and python_version < "3.12" -pyzmq==25.1.1 ; python_version >= "3.8" and python_version < "3.12" +pyzmq==25.1.2 ; python_version >= "3.8" and python_version < "3.12" qtconsole==5.5.1 ; python_version >= "3.8" and python_version < "3.12" qtpy==2.4.1 ; python_version >= "3.8" and python_version < "3.12" -referencing==0.31.0 ; python_version >= "3.8" and python_version < "3.12" +referencing==0.32.1 ; python_version >= "3.8" and python_version < "3.12" requests==2.31.0 ; python_version >= "3.8" and python_version < "3.12" rfc3339-validator==0.1.4 ; python_version >= "3.8" and python_version < "3.12" rfc3986-validator==0.1.1 ; python_version >= "3.8" and python_version < "3.12" -rpds-py==0.13.0 ; python_version >= "3.8" and python_version < "3.12" +rpds-py==0.17.1 ; python_version >= "3.8" and python_version < "3.12" scipy==1.10.1 ; python_version >= "3.8" and python_version < "3.12" send2trash==1.8.2 ; python_version >= "3.8" and python_version < "3.12" -setuptools-scm==8.0.4 ; python_version >= "3.8" and python_version < "3.12" -setuptools==68.2.2 ; python_version >= "3.8" and python_version < "3.12" six==1.16.0 ; python_version >= "3.8" and python_version < "3.12" sniffio==1.3.0 ; python_version >= "3.8" and python_version < "3.12" soupsieve==2.5 ; python_version >= "3.8" and python_version < "3.12" @@ -117,18 +115,18 @@ tenacity==8.2.3 ; python_version >= "3.8" and python_version < "3.12" terminado==0.18.0 ; python_version >= "3.8" and python_version < "3.12" tinycss2==1.2.1 ; python_version >= "3.8" and python_version < "3.12" tomli==2.0.1 ; python_version >= "3.8" and python_version < "3.11" -tornado==6.3.3 ; python_version >= "3.8" and python_version < "3.12" -traitlets==5.13.0 ; python_version >= "3.8" and python_version < "3.12" -types-python-dateutil==2.8.19.14 ; python_version >= "3.8" and python_version < "3.12" -typing-extensions==4.8.0 ; python_version >= "3.8" and python_version < "3.12" -tzdata==2023.3 ; python_version >= "3.8" and python_version < "3.12" +tornado==6.4 ; python_version >= "3.8" and python_version < "3.12" +traitlets==5.14.1 ; python_version >= "3.8" and python_version < "3.12" +types-python-dateutil==2.8.19.20240106 ; python_version >= "3.8" and python_version < "3.12" +typing-extensions==4.9.0 ; python_version >= "3.8" and python_version < "3.11" +tzdata==2023.4 ; python_version >= "3.8" and python_version < "3.12" unlzw3==0.2.2 ; python_version >= "3.8" and python_version < "3.12" uri-template==1.3.0 ; python_version >= "3.8" and python_version < "3.12" urllib3==2.1.0 ; python_version >= "3.8" and python_version < "3.12" -wcwidth==0.2.10 ; python_version >= "3.8" and python_version < "3.12" +wcwidth==0.2.13 ; python_version >= "3.8" and python_version < "3.12" webcolors==1.13 ; python_version >= "3.8" and python_version < "3.12" webencodings==0.5.1 ; python_version >= "3.8" and python_version < "3.12" -websocket-client==1.6.4 ; python_version >= "3.8" and python_version < "3.12" +websocket-client==1.7.0 ; python_version >= "3.8" and python_version < "3.12" widgetsnbextension==4.0.9 ; python_version >= "3.8" and python_version < "3.12" xarray==2023.1.0 ; python_version >= "3.8" and python_version < "3.12" zipp==3.17.0 ; python_version >= "3.8" and python_version < "3.10" diff --git a/tests/algorithms/test_fde.py b/tests/algorithms/test_fde.py index 009e74a5..7281250e 100644 --- a/tests/algorithms/test_fde.py +++ b/tests/algorithms/test_fde.py @@ -10,7 +10,7 @@ import pytest import numpy as np -from gnss_lib_py.parsers.navdata import NavData +from gnss_lib_py.navdata.navdata import NavData from gnss_lib_py.parsers.google_decimeter import AndroidDerived2022 from gnss_lib_py.algorithms.fde import solve_fde, evaluate_fde diff --git a/tests/algorithms/test_gnss_filters.py b/tests/algorithms/test_gnss_filters.py index f8cc2b9b..38a8c1b5 100644 --- a/tests/algorithms/test_gnss_filters.py +++ b/tests/algorithms/test_gnss_filters.py @@ -11,7 +11,8 @@ import numpy as np from numpy.random import default_rng -from gnss_lib_py.parsers.navdata import NavData +from gnss_lib_py.navdata.navdata import NavData +from gnss_lib_py.navdata.operations import loop_time from gnss_lib_py.parsers.google_decimeter import AndroidDerived2021 from gnss_lib_py.algorithms.gnss_filters import GNSSEKF, solve_gnss_ekf @@ -190,7 +191,7 @@ def test_solve_gnss_ekf(derived): assert "alt_rx_ekf_m" in state_estimate.rows # should have the same length as the number of unique timesteps - assert len(state_estimate) == sum(1 for _ in derived.loop_time("gps_millis")) + assert len(state_estimate) == sum(1 for _ in loop_time(derived,"gps_millis")) # len(np.unique(derived["gps_millis",:])) diff --git a/tests/algorithms/test_residuals.py b/tests/algorithms/test_residuals.py index eb8f4ade..5e40c6bb 100644 --- a/tests/algorithms/test_residuals.py +++ b/tests/algorithms/test_residuals.py @@ -12,7 +12,7 @@ from gnss_lib_py.algorithms.snapshot import solve_wls from gnss_lib_py.parsers.google_decimeter import AndroidDerived2021 -from gnss_lib_py.parsers.navdata import NavData +from gnss_lib_py.navdata.navdata import NavData from gnss_lib_py.algorithms.residuals import solve_residuals @pytest.fixture(name="root_path") diff --git a/tests/algorithms/test_snapshot.py b/tests/algorithms/test_snapshot.py index 534f05e3..c2911809 100644 --- a/tests/algorithms/test_snapshot.py +++ b/tests/algorithms/test_snapshot.py @@ -12,9 +12,9 @@ import numpy as np from gnss_lib_py.parsers.google_decimeter import AndroidDerived2021, AndroidDerived2022 -from gnss_lib_py.parsers.navdata import NavData +from gnss_lib_py.navdata.navdata import NavData from gnss_lib_py.algorithms.snapshot import wls, solve_wls - +from gnss_lib_py.navdata.operations import loop_time # Defining test fixtures TEST_REPEAT_COUNT = 10 @@ -381,7 +381,7 @@ def test_solve_wls(derived): assert "alt_rx_wls_m" in state_estimate.rows # should have the same length as the number of unique timesteps - assert len(state_estimate) == sum(1 for _ in derived.loop_time("gps_millis")) + assert len(state_estimate) == sum(1 for _ in loop_time(derived,"gps_millis")) # len(np.unique(derived["gps_millis",:])) @@ -521,12 +521,12 @@ def test_solve_wls_bias_only(derived_2022): # Solve with receiver positions given ecef_rows = ['x_rx_m', 'y_rx_m', 'z_rx_m'] wls_rows = ['x_rx_wls_m','y_rx_wls_m','z_rx_wls_m'] - time_length = sum(1 for _ in derived_2022.loop_time("gps_millis")) + time_length = sum(1 for _ in loop_time(derived_2022,"gps_millis")) input_position = NavData() for row in ecef_rows: input_position[row] = np.zeros(time_length) col = 0 - for _, _, measure_frame in derived_2022.loop_time('gps_millis'): + for _, _, measure_frame in loop_time(derived_2022,'gps_millis'): for row in ecef_rows: input_position[row, col] = measure_frame[row, 0] col += 1 @@ -628,7 +628,7 @@ def test_rotation_of_earth_fix(derived_2022): """ google_wls = derived_2022[['x_rx_m', 'y_rx_m', 'z_rx_m']] google_wls = np.empty([3, len(np.unique(np.round(derived_2022['gps_millis'], decimals=-2)))]) - for idx, (_, _, frame) in enumerate(derived_2022.loop_time('gps_millis')): + for idx, (_, _, frame) in enumerate(loop_time(derived_2022,'gps_millis')): google_wls[:, idx] = frame[['x_rx_m', 'y_rx_m', 'z_rx_m'], 0] derived_2022['wls_weights'] = 1/derived_2022['raw_pr_sigma_m'] state_with_rotn = solve_wls(derived_2022, weight_type='wls_weights', diff --git a/tests/navdata/conftest.py b/tests/navdata/conftest.py new file mode 100644 index 00000000..8afdcdb1 --- /dev/null +++ b/tests/navdata/conftest.py @@ -0,0 +1,206 @@ +"""Fixtures for NavData class. + +""" + +__authors__ = "A. Kanhere, D. Knowles" +__date__ = "30 Apr 2022" + +import os + +import pytest +import numpy as np +import pandas as pd + +from gnss_lib_py.navdata.navdata import NavData + +def fixture_csv_path(csv_filepath): + """Location of measurements for unit test + + Returns + ------- + root_path : string + Folder location containing measurements + """ + root_path = os.path.dirname( + os.path.dirname( + os.path.dirname( + os.path.realpath(__file__)))) + root_path = os.path.join(root_path, 'data/unit_test/navdata') + + csv_path = os.path.join(root_path, csv_filepath) + + return csv_path + +@pytest.fixture(name="csv_simple") +def fixture_csv_simple(): + """csv with simple format. + + """ + return fixture_csv_path("navdata_test_simple.csv") + +@pytest.fixture(name="csv_headless") +def fixture_csv_headless(): + """csv without column names. + + """ + return fixture_csv_path("navdata_test_headless.csv") + +@pytest.fixture(name="csv_missing") +def fixture_csv_missing(): + """csv with missing entries. + + """ + return fixture_csv_path("navdata_test_missing.csv") + +@pytest.fixture(name="csv_mixed") +def fixture_csv_mixed(): + """csv with mixed data types. + + """ + return fixture_csv_path("navdata_test_mixed.csv") + +@pytest.fixture(name="csv_inf") +def fixture_csv_inf(): + """csv with infinity values in numeric columns. + + """ + return fixture_csv_path("navdata_test_inf.csv") + +@pytest.fixture(name="csv_nan") +def fixture_csv_nan(): + """csv with NaN values in columns. + + """ + return fixture_csv_path("navdata_test_nan.csv") + +@pytest.fixture(name="csv_int_first") +def fixture_csv_int_first(): + """csv where first column are integers. + + """ + return fixture_csv_path("navdata_test_int_first.csv") + +@pytest.fixture(name="csv_only_header") +def fixture_csv_only_header(): + """csv where there's no data, only columns. + + """ + return fixture_csv_path("navdata_only_header.csv") + +@pytest.fixture(name="csv_dtypes") +def fixture_csv_dtypes(): + """csv made up of different data types. + + """ + return fixture_csv_path("navdata_test_dtypes.csv") + +def load_test_dataframe(csv_filepath, header="infer"): + """Create dataframe test fixture. + + """ + + data = pd.read_csv(csv_filepath, header=header) + + return data + +@pytest.fixture(name='df_simple') +def fixture_df_simple(csv_simple): + """df with simple format. + + """ + return load_test_dataframe(csv_simple) + +@pytest.fixture(name='df_headless') +def fixture_df_headless(csv_headless): + """df without column names. + + """ + return load_test_dataframe(csv_headless,None) + +@pytest.fixture(name='df_missing') +def fixture_df_missing(csv_missing): + """df with missing entries. + + """ + return load_test_dataframe(csv_missing) + +@pytest.fixture(name='df_mixed') +def fixture_df_mixed(csv_mixed): + """df with mixed data types. + + """ + return load_test_dataframe(csv_mixed) + +@pytest.fixture(name='df_inf') +def fixture_df_inf(csv_inf): + """df with infinity values in numeric columns. + + """ + return load_test_dataframe(csv_inf) + +@pytest.fixture(name='df_nan') +def fixture_df_nan(csv_nan): + """df with NaN values in columns. + + """ + return load_test_dataframe(csv_nan) + +@pytest.fixture(name='df_int_first') +def fixture_df_int_first(csv_int_first): + """df where first column are integers. + + """ + return load_test_dataframe(csv_int_first) + +@pytest.fixture(name='df_only_header') +def fixture_df_only_header(csv_only_header): + """df where only headers given and no data. + + """ + return load_test_dataframe(csv_only_header) + +@pytest.fixture(name="data") +def load_test_navdata(df_simple): + """Creates a NavData instance from df_simple. + + """ + return NavData(pandas_df=df_simple) + +@pytest.fixture(name="numpy_array") +def create_numpy_array(): + """Create np.ndarray test fixture. + """ + test_array = np.array([[1,2,3,4,5,6], + [0.5,0.6,0.7,0.8,-0.001,-0.3], + [-3.0,-1.2,-100.,-2.7,-30.,-5], + [-543,-234,-986,-123,843,1000], + ]) + return test_array + +@pytest.fixture(name='add_array') +def fixture_add_array(): + """Array added as additional timesteps to NavData from np.ndarray + + Returns + ------- + add_array : np.ndarray + Array that will be added to NavData + """ + add_array = np.hstack((10*np.ones([4,1]), 11*np.ones([4,1]))) + return add_array + +@pytest.fixture(name='add_df') +def fixture_add_dataframe(): + """Pandas DataFrame to be added as additional timesteps to NavData + + Returns + ------- + add_df : pd.DataFrame + Dataframe that will be added to NavData + """ + add_data = {'names': np.asarray(['beta', 'alpha'], dtype=object), + 'integers': np.asarray([-2, 45], dtype=np.int64), + 'floats': np.asarray([1.4, 1.5869]), + 'strings': np.asarray(['glonass', 'beidou'], dtype=object)} + add_df = pd.DataFrame(data=add_data) + return add_df diff --git a/tests/parsers/test_navdata.py b/tests/navdata/test_navdata.py similarity index 66% rename from tests/parsers/test_navdata.py rename to tests/navdata/test_navdata.py index d5b53198..18906070 100644 --- a/tests/parsers/test_navdata.py +++ b/tests/navdata/test_navdata.py @@ -5,8 +5,6 @@ __authors__ = "A. Kanhere, D. Knowles" __date__ = "30 Apr 2022" - -import os import pathlib import itertools @@ -15,171 +13,7 @@ import pandas as pd from pytest_lazyfixture import lazy_fixture -from gnss_lib_py.parsers.navdata import NavData - -def fixture_csv_path(csv_filepath): - """Location of measurements for unit test - - Returns - ------- - root_path : string - Folder location containing measurements - """ - root_path = os.path.dirname( - os.path.dirname( - os.path.dirname( - os.path.realpath(__file__)))) - root_path = os.path.join(root_path, 'data/unit_test/navdata') - - csv_path = os.path.join(root_path, csv_filepath) - - return csv_path - -@pytest.fixture(name="csv_simple") -def fixture_csv_simple(): - """csv with simple format. - - """ - return fixture_csv_path("navdata_test_simple.csv") - -@pytest.fixture(name="csv_headless") -def fixture_csv_headless(): - """csv without column names. - - """ - return fixture_csv_path("navdata_test_headless.csv") - -@pytest.fixture(name="csv_missing") -def fixture_csv_missing(): - """csv with missing entries. - - """ - return fixture_csv_path("navdata_test_missing.csv") - -@pytest.fixture(name="csv_mixed") -def fixture_csv_mixed(): - """csv with mixed data types. - - """ - return fixture_csv_path("navdata_test_mixed.csv") - -@pytest.fixture(name="csv_inf") -def fixture_csv_inf(): - """csv with infinity values in numeric columns. - - """ - return fixture_csv_path("navdata_test_inf.csv") - -@pytest.fixture(name="csv_nan") -def fixture_csv_nan(): - """csv with NaN values in columns. - - """ - return fixture_csv_path("navdata_test_nan.csv") - -@pytest.fixture(name="csv_int_first") -def fixture_csv_int_first(): - """csv where first column are integers. - - """ - return fixture_csv_path("navdata_test_int_first.csv") - -@pytest.fixture(name="csv_only_header") -def fixture_csv_only_header(): - """csv where there's no data, only columns. - - """ - return fixture_csv_path("navdata_only_header.csv") - -@pytest.fixture(name="csv_dtypes") -def fixture_csv_dtypes(): - """csv made up of different data types. - - """ - return fixture_csv_path("navdata_test_dtypes.csv") - -def load_test_dataframe(csv_filepath, header="infer"): - """Create dataframe test fixture. - - """ - - data = pd.read_csv(csv_filepath, header=header) - - return data - -@pytest.fixture(name='df_simple') -def fixture_df_simple(csv_simple): - """df with simple format. - - """ - return load_test_dataframe(csv_simple) - -@pytest.fixture(name='df_headless') -def fixture_df_headless(csv_headless): - """df without column names. - - """ - return load_test_dataframe(csv_headless,None) - -@pytest.fixture(name='df_missing') -def fixture_df_missing(csv_missing): - """df with missing entries. - - """ - return load_test_dataframe(csv_missing) - -@pytest.fixture(name='df_mixed') -def fixture_df_mixed(csv_mixed): - """df with mixed data types. - - """ - return load_test_dataframe(csv_mixed) - -@pytest.fixture(name='df_inf') -def fixture_df_inf(csv_inf): - """df with infinity values in numeric columns. - - """ - return load_test_dataframe(csv_inf) - -@pytest.fixture(name='df_nan') -def fixture_df_nan(csv_nan): - """df with NaN values in columns. - - """ - return load_test_dataframe(csv_nan) - -@pytest.fixture(name='df_int_first') -def fixture_df_int_first(csv_int_first): - """df where first column are integers. - - """ - return load_test_dataframe(csv_int_first) - -@pytest.fixture(name='df_only_header') -def fixture_df_only_header(csv_only_header): - """df where only headers given and no data. - - """ - return load_test_dataframe(csv_only_header) - -@pytest.fixture(name="data") -def load_test_navdata(df_simple): - """Creates a NavData instance from df_simple. - - """ - return NavData(pandas_df=df_simple) - -@pytest.fixture(name="numpy_array") -def create_numpy_array(): - """Create np.ndarray test fixture. - """ - test_array = np.array([[1,2,3,4,5,6], - [0.5,0.6,0.7,0.8,-0.001,-0.3], - [-3.0,-1.2,-100.,-2.7,-30.,-5], - [-543,-234,-986,-123,843,1000], - ]) - return test_array +from gnss_lib_py.navdata.navdata import NavData def test_init_blank(): """Test initializing blank NavData class @@ -341,36 +175,6 @@ def test_init_np(numpy_array): with pytest.raises(TypeError): data = NavData(numpy_array=pd.DataFrame([0])) -def test_init_only_header(csv_only_header, csv_simple): - """Test initializing NavData class with csv with only header - - Parameters - ---------- - csv_only_header : string - Path to csv file containing headers, but no data - csv_simple : string - Path to csv file headers and data - - """ - - # should work when csv is passed - csv_data = NavData(csv_path=csv_only_header) - assert csv_data.shape == (4,0) - # test adding new data to empty NavData with column names - csv_data.concat(NavData(csv_path=csv_simple),axis=1,inplace=True) - assert csv_data.shape == (4,6) - pd.testing.assert_frame_equal(csv_data.pandas_df().sort_index(axis=1), - pd.read_csv(csv_simple).sort_index(axis=1), - check_dtype=False, check_names=True) - - # should work when DataFrame is passed - pd_data = NavData(pandas_df=pd.read_csv(csv_only_header)) - assert pd_data.shape == (4,0) - # test adding new data to empty NavData with column names - pd_data.concat(NavData(pandas_df=pd.read_csv(csv_simple)),axis=1, - inplace=True) - assert pd_data.shape == (4,6) - @pytest.mark.parametrize('pandas_df', [ lazy_fixture("df_simple"), @@ -928,7 +732,7 @@ def test_get_item(data, index, exp_value): Parameters ---------- - data : gnss_lib_py.parsers.navdata.NavData + data : gnss_lib_py.navdata.navdata.NavData Data to test getting values from index : slice/str/int/tuple Index to query data at @@ -1059,7 +863,7 @@ def test_set_get_item(data, index, new_value, exp_value): Parameters ---------- - data : gnss_lib_py.parsers.navdata.NavData + data : gnss_lib_py.navdata.navdata.NavData NavData instance for testing index : slice/str/int/tuple Index to query data at @@ -1076,7 +880,7 @@ def test_multi_set(data,new_string): Parameters ---------- - data : gnss_lib_py.parsers.navdata.NavData + data : gnss_lib_py.navdata.navdata.NavData NavData instance for testing new_string : np.ndarray String of length 6 to test string assignment @@ -1129,7 +933,7 @@ def test_set_changing_type(data,new_string): Parameters ---------- - data : gnss_lib_py.parsers.navdata.NavData + data : gnss_lib_py.navdata.navdata.NavData NavData instance for testing new_string : np.ndarray String of length 6 to test string assignment @@ -1178,7 +982,7 @@ def test_multi_set_changing_type(data,new_string): Parameters ---------- - data : gnss_lib_py.parsers.navdata.NavData + data : gnss_lib_py.navdata.navdata.NavData NavData instance for testing new_string : np.ndarray String of length 6 to test string assignment @@ -1214,248 +1018,6 @@ def test_wrong_init_set(row_idx): with pytest.raises(KeyError): empty_data[row_idx] = np.zeros([1, 6]) -@pytest.fixture(name='add_array') -def fixture_add_array(): - """Array added as additional timesteps to NavData from np.ndarray - - Returns - ------- - add_array : np.ndarray - Array that will be added to NavData - """ - add_array = np.hstack((10*np.ones([4,1]), 11*np.ones([4,1]))) - return add_array - - -@pytest.fixture(name='add_df') -def fixture_add_dataframe(): - """Pandas DataFrame to be added as additional timesteps to NavData - - Returns - ------- - add_df : pd.DataFrame - Dataframe that will be added to NavData - """ - add_data = {'names': np.asarray(['beta', 'alpha'], dtype=object), - 'integers': np.asarray([-2, 45], dtype=np.int64), - 'floats': np.asarray([1.4, 1.5869]), - 'strings': np.asarray(['glonass', 'beidou'], dtype=object)} - add_df = pd.DataFrame(data=add_data) - return add_df - - -def test_add_numpy(numpy_array, add_array): - """Test addition of a numpy array to NavData - - Parameters - ---------- - numpy_array : np.ndarray - Array with which NavData instance is initialized - add_array : np.ndarray - Array to add to NavData - """ - data = NavData(numpy_array=numpy_array) - data.concat(NavData(numpy_array=add_array),axis=1,inplace=True) - new_col_num = np.shape(add_array)[1] - np.testing.assert_array_equal(data[:, -new_col_num:], add_array) - - -def test_add_numpy_1d(): - """Test addition of a 1D numpy array to NavData with single row - """ - data = NavData(numpy_array=np.zeros([1,6])) - data.concat(NavData(numpy_array=np.ones(8)),axis=1, inplace=True) - np.testing.assert_array_equal(data[0, :], np.hstack((np.zeros(6), - np.ones(8)))) - - # test adding to empty NavData - data_empty = NavData() - data_empty.concat(NavData(numpy_array=np.ones((8,8))),axis=1, - inplace=True) - np.testing.assert_array_equal(data_empty[:,:],np.ones((8,8))) - -def test_add_csv(df_simple, csv_simple): - """Test adding a csv. - - """ - # Create and add to NavData - data = NavData(csv_path=csv_simple) - data.concat(NavData(csv_path=csv_simple),axis=1,inplace=True) - data_df = data.pandas_df() - # Set up dataframe for comparison - df_types = {'names': object, 'integers': np.int64, - 'floats': np.float64, 'strings': object} - expected_df = pd.concat((df_simple,df_simple)).reset_index(drop=True) - expected_df = expected_df.astype(df_types) - pd.testing.assert_frame_equal(data_df.sort_index(axis=1), - expected_df.sort_index(axis=1), - check_index_type=False) - - # test adding to empty NavData - data_empty = NavData() - data_empty.concat(NavData(csv_path=csv_simple),axis=1,inplace=True) - pd.testing.assert_frame_equal(data_empty.pandas_df().sort_index(axis=1), - df_simple.astype(df_types).sort_index(axis=1), - check_index_type=False) - -def test_add_pandas_df(df_simple, add_df): - """Test addition of a pd.DataFrame to NavData - - Parameters - ---------- - df_simple : pd.DataFrame - pd.DataFrame to initialize NavData with - add_df : pd.DataFrame - pd.DataFrame to add to NavData - """ - data = NavData(pandas_df=df_simple) - data.concat(NavData(pandas_df=add_df),axis=1,inplace=True) - new_df = data.pandas_df() - add_row_num = add_df.shape[0] - subset_df = new_df.iloc[-add_row_num:, :].reset_index(drop=True) - pd.testing.assert_frame_equal(subset_df.sort_index(axis=1), - add_df.sort_index(axis=1), - check_index_type=False) - - # test adding to empty NavData - data_empty = NavData() - data_empty.concat(NavData(pandas_df=add_df),axis=1,inplace=True) - pd.testing.assert_frame_equal(add_df.sort_index(axis=1), - data_empty.pandas_df().sort_index(axis=1), - check_index_type=False) - -def test_concat(df_simple): - """Test concat functionaltiy. - - Parameters - ---------- - df_simple : pd.DataFrame - Simple pd.DataFrame with which to initialize NavData. - - """ - - navdata_1 = NavData(pandas_df=df_simple) - navdata_2 = navdata_1.copy() - navdata_2.rename(mapper={"floats": "decimals", "names": "words"}, - inplace = True) - - # add new columns - navdata = navdata_1.concat(navdata_1) - assert navdata.shape == (4,12) - pandas_equiv = pd.concat((df_simple,df_simple),axis=0) - pandas_equiv.reset_index(drop=True, inplace=True) - pd.testing.assert_frame_equal(pandas_equiv.sort_index(axis=1), - navdata.pandas_df().sort_index(axis=1), - check_index_type=False, - check_dtype=False) - - # add new rows - navdata = navdata_1.concat(navdata_1,axis=0) - assert navdata.shape == (8,6) - mapper = {"names":"names_0", - "floats":"floats_0", - "integers":"integers_0", - "strings":"strings_0"} - df_simple_2 = df_simple.rename(mapper,axis=1) - pandas_equiv = pd.concat((df_simple,df_simple_2),axis=1) - pandas_equiv.reset_index(drop=True, inplace=True) - pd.testing.assert_frame_equal(pandas_equiv.sort_index(axis=1), - navdata.pandas_df().sort_index(axis=1), - check_index_type=False, - check_dtype=False) - - # concatenate empty NavData - navdata = NavData().concat(navdata_1,axis=1) - pd.testing.assert_frame_equal(df_simple.sort_index(axis=1), - navdata.pandas_df().sort_index(axis=1), - check_index_type=False, - check_dtype=False) - navdata = navdata_1.concat(NavData(),axis=1) - pd.testing.assert_frame_equal(df_simple.sort_index(axis=1), - navdata.pandas_df().sort_index(axis=1), - check_index_type=False, - check_dtype=False) - - # test multiple rows with the same name - navdata_long = navdata_1.copy() - for count in range(13): - navdata_long.concat(navdata_1,axis=0,inplace=True) - for word in ["names","integers","floats","strings"]: - assert word + "_" + str(count) in navdata_long.rows - - # add semi new columns - navdata = navdata_1.concat(navdata_2) - assert navdata.shape == (6,12) - assert np.all(np.isnan(navdata["floats"][-6:])) - assert np.all(navdata["names"][-6:] == np.array([np.nan]).astype(str)[0]) - assert np.all(np.isnan(navdata["decimals"][:6])) - assert np.all(navdata["words"][:6] == np.array([np.nan]).astype(str)[0]) - - # add semi new columns in opposite order - navdata = navdata_2.concat(navdata_1) - assert navdata.shape == (6,12) - assert np.all(np.isnan(navdata["floats"][:6])) - assert np.all(navdata["names"][:6] == np.array([np.nan]).astype(str)[0]) - assert np.all(np.isnan(navdata["decimals"][-6:])) - assert np.all(navdata["words"][-6:] == np.array([np.nan]).astype(str)[0]) - - # add as new rows - navdata = navdata_1.concat(navdata_2,axis=0) - assert navdata.shape == (8,6) - mapper = {"names":"words", - "floats":"decimals", - "integers":"integers_0", - "strings":"strings_0"} - df_simple_2 = df_simple.rename(mapper,axis=1) - pandas_equiv = pd.concat((df_simple,df_simple_2),axis=1) - pandas_equiv.reset_index(drop=True, inplace=True) - pd.testing.assert_frame_equal(pandas_equiv.sort_index(axis=1), - navdata.pandas_df().sort_index(axis=1), - check_index_type=False, - check_dtype=False) - - navdata_a = NavData(pandas_df=pd.DataFrame({'a':[0],'b':[1],'c':[2], - 'd':[3],'e':[4],'f':[5], - })) - navdata_b = navdata_a.concat(navdata_a.copy(),axis=0) - assert navdata_b.shape == (12,1) - navdata_b = navdata_a.concat(navdata_a.copy(),axis=1) - assert navdata_b.shape == (6,2) - -def test_concat_fails(df_simple): - """Test when concat should fail. - - Parameters - ---------- - df_simple : pd.DataFrame - Simple pd.DataFrame with which to initialize NavData. - - """ - - navdata_1 = NavData(pandas_df=df_simple) - - with pytest.raises(TypeError) as excinfo: - navdata_1.concat(np.array([])) - assert "concat" in str(excinfo.value) - assert "NavData" in str(excinfo.value) - - navdata_2 = navdata_1.remove(cols=[0]) - - with pytest.raises(RuntimeError) as excinfo: - navdata_1.concat(navdata_2,axis=0) - assert "same length" in str(excinfo.value) - assert "concat" in str(excinfo.value) - - with pytest.raises(RuntimeError) as excinfo: - navdata_1.concat(NavData(),axis=0) - assert "same length" in str(excinfo.value) - assert "concat" in str(excinfo.value) - - with pytest.raises(RuntimeError) as excinfo: - NavData().concat(navdata_1,axis=0) - assert "same length" in str(excinfo.value) - assert "concat" in str(excinfo.value) - @pytest.mark.parametrize("rows", [None, ['names', 'integers', 'floats', 'strings'], @@ -1477,7 +1039,7 @@ def test_copy_navdata(data, df_simple, rows, cols): Parameters ---------- - data : gnss_lib_py.parsers.navdata.NavData + data : gnss_lib_py.navdata.navdata.NavData Instance of NavData df_simple : pd.DataFrame Dataframe that is sliced to compare copies against @@ -1525,7 +1087,7 @@ def test_remove_navdata(data, df_simple, rows, cols): Parameters ---------- - data : gnss_lib_py.parsers.navdata.NavData + data : gnss_lib_py.navdata.navdata.NavData Instance of NavData df_simple : pd.DataFrame Dataframe that is sliced to compare copies against @@ -1610,7 +1172,7 @@ def test_remove_inplace(data, df_simple, rows, cols): Parameters ---------- - data : gnss_lib_py.parsers.navdata.NavData + data : gnss_lib_py.navdata.navdata.NavData Instance of NavData df_simple : pd.DataFrame Dataframe that is sliced to compare copies against @@ -1796,49 +1358,6 @@ def test_where_errors(csv_simple): with pytest.raises(ValueError): _ = data.where("names", 0.342, condition="eq") -def test_time_looping(csv_simple): - """Testing implementation to loop over times - - Parameters - ---------- - csv_simple : str - path to csv file used to create NavData - """ - data = NavData(csv_path=csv_simple) - data['times'] = np.hstack((np.zeros([1, 2]), - 1.0001*np.ones([1, 1]), - 1.0003*np.ones([1,1]), - 1.50004*np.ones([1, 1]), - 1.49999*np.ones([1,1]))) - compare_df = data.pandas_df() - count = 0 - # Testing when loop_time finds overlapping times - for time, delta_t, measure in data.loop_time('times', delta_t_decimals=2): - if count == 0: - np.testing.assert_almost_equal(delta_t, 0) - np.testing.assert_almost_equal(time, 0) - row_num = [0,1] - elif count == 1: - np.testing.assert_almost_equal(delta_t, 1) - np.testing.assert_almost_equal(time, 1) - row_num = [2,3] - elif count == 2: - np.testing.assert_almost_equal(delta_t, 0.5) - np.testing.assert_almost_equal(time, 1.5) - row_num = [4,5] - small_df = measure.pandas_df().reset_index(drop=True) - expected_df = compare_df.iloc[row_num, :].reset_index(drop=True) - pd.testing.assert_frame_equal(small_df, expected_df, - check_index_type=False) - count += 1 - - # Testing for when loop_time finds only unique times - count = 0 - expected_times = [0., 1.0001, 1.0003, 1.49999, 1.50004] - for time, _, measure in data.loop_time('times', delta_t_decimals=5): - np.testing.assert_almost_equal(time, expected_times[count]) - count += 1 - def test_col_looping(csv_simple): """Testing implementation to loop over columns in NavData @@ -1919,7 +1438,7 @@ def test_in_rows_single(data): Parameters ---------- - data : gnss_lib_py.parsers.navdata.NavData + data : gnss_lib_py.navdata.navdata.NavData Instance of NavData """ @@ -1987,7 +1506,7 @@ def test_in_rows_multi(data): Parameters ---------- - data : gnss_lib_py.parsers.navdata.NavData + data : gnss_lib_py.navdata.navdata.NavData Instance of NavData """ @@ -2073,173 +1592,6 @@ def test_large_int(): np.testing.assert_array_equal(navdata["numbers"], test_list) -def test_find_wildcard_indexes(data): - """Tests find_wildcard_indexes - - """ - - all_matching = data.rename({"names" : "x_alpha_m", - "integers" : "x_beta_m", - "floats" : "x_gamma_m", - "strings" : "x_zeta_m"}) - expected = ["x_alpha_m","x_beta_m","x_gamma_m","x_zeta_m"] - - indexes = all_matching.find_wildcard_indexes("x_*_m") - assert indexes["x_*_m"] == expected - expect_pass_allows = [None,12,4] - for max_allow in expect_pass_allows: - indexes = all_matching.find_wildcard_indexes("x_*_m",max_allow) - assert indexes["x_*_m"] == expected - - expect_fail_allows = [0,-1,3,2,1] - for max_allow in expect_fail_allows: - with pytest.raises(KeyError) as excinfo: - all_matching.find_wildcard_indexes("x_*_m",max_allow) - assert "More than " + str(max_allow) in str(excinfo.value) - assert "x_*_m" in str(excinfo.value) - - multi = data.rename({"names" : "x_alpha_m", - "integers" : "x_beta_m", - "floats" : "y_alpha_deg", - "strings" : "x_zeta_deg"}) - expected = {"x_*_m" : ["x_alpha_m","x_beta_m"], - "y_*_deg" : ["y_alpha_deg"]} - - expect_pass_allows = [None,2,4] - for max_allow in expect_pass_allows: - indexes = multi.find_wildcard_indexes(["x_*_m","y_*_deg"], - max_allow) - assert indexes == expected - - expect_pass_allows = [None,2,4] - for max_allow in expect_pass_allows: - indexes = multi.find_wildcard_indexes(tuple(["x_*_m","y_*_deg"]), - max_allow) - assert indexes == expected - - expect_pass_allows = [None,2,4] - for max_allow in expect_pass_allows: - indexes = multi.find_wildcard_indexes(set(["x_*_m","y_*_deg"]), - max_allow) - assert indexes == expected - - expect_pass_allows = [None,2,4] - for max_allow in expect_pass_allows: - indexes = multi.find_wildcard_indexes(np.array(["x_*_m", - "y_*_deg"]), - max_allow) - assert indexes == expected - - expect_fail_allows = [0,-1,1] - for max_allow in expect_fail_allows: - with pytest.raises(KeyError) as excinfo: - multi.find_wildcard_indexes(["x_*_m","y_*_deg"],max_allow) - assert "More than " + str(max_allow) in str(excinfo.value) - assert "x_*_m" in str(excinfo.value) - - with pytest.raises(KeyError) as excinfo: - multi.find_wildcard_indexes(["z_*_m"]) - assert "Missing " in str(excinfo.value) - assert "z_*_m" in str(excinfo.value) - - with pytest.raises(TypeError) as excinfo: - multi.find_wildcard_indexes(1.0) - assert "find_wildcard_indexes " in str(excinfo.value) - assert "array-like" in str(excinfo.value) - - with pytest.raises(TypeError) as excinfo: - multi.find_wildcard_indexes([1.0]) - assert "wildcards must be strings" in str(excinfo.value) - - with pytest.raises(RuntimeError) as excinfo: - multi.find_wildcard_indexes("x_*_*") - assert "One wildcard" in str(excinfo.value) - - incorrect_max_allow = [3.,"hi",[]] - for max_allow in incorrect_max_allow: - with pytest.raises(TypeError) as excinfo: - multi.find_wildcard_indexes("x_*_m",max_allow) - assert "max_allow" in str(excinfo.value) - -def test_find_wildcard_excludes(data): - """Tests find_wildcard_indexes - - """ - all_matching = data.rename({"names" : "x_alpha_m", - "integers" : "x_beta_m", - "floats" : "x_gamma_m", - "strings" : "x_zeta_m"}) - - # no exclusion - indexes = all_matching.find_wildcard_indexes("x_*_m",excludes=None) - assert indexes["x_*_m"] == ["x_alpha_m","x_beta_m", - "x_gamma_m","x_zeta_m"] - indexes = all_matching.find_wildcard_indexes("x_*_m",excludes=[None]) - assert indexes["x_*_m"] == ["x_alpha_m","x_beta_m", - "x_gamma_m","x_zeta_m"] - - # single exclusion - indexes = all_matching.find_wildcard_indexes("x_*_m",excludes="x_beta_m") - assert indexes["x_*_m"] == ["x_alpha_m","x_gamma_m","x_zeta_m"] - - # two exclusion - indexes = all_matching.find_wildcard_indexes("x_*_m", - excludes=[["x_beta_m","x_zeta_m"]]) - assert indexes["x_*_m"] == ["x_alpha_m","x_gamma_m"] - - # all excluded - with pytest.raises(KeyError) as excinfo: - all_matching.find_wildcard_indexes("x_*_m",excludes=["x_*_m"]) - assert "Missing " in str(excinfo.value) - assert "x_*_m" in str(excinfo.value) - - - multi = data.rename({"names" : "x_alpha_m", - "integers" : "x_beta_m", - "floats" : "y_alpha_deg", - "strings" : "y_beta_deg"}) - - # no exclusion - indexes = multi.find_wildcard_indexes(["x_*_m","y_*_deg"], - excludes=None) - assert indexes["x_*_m"] == ["x_alpha_m","x_beta_m"] - assert indexes["y_*_deg"] == ["y_alpha_deg","y_beta_deg"] - indexes = multi.find_wildcard_indexes(["x_*_m","y_*_deg"], - excludes=[None,None]) - assert indexes["x_*_m"] == ["x_alpha_m","x_beta_m"] - assert indexes["y_*_deg"] == ["y_alpha_deg","y_beta_deg"] - - # single exclusion - indexes = multi.find_wildcard_indexes(["x_*_m","y_*_deg"], - excludes=["x_alpha*",None]) - assert indexes["x_*_m"] == ["x_beta_m"] - assert indexes["y_*_deg"] == ["y_alpha_deg","y_beta_deg"] - - # double exclusion - indexes = multi.find_wildcard_indexes(["x_*_m","y_*_deg"], - excludes=["x_alpha*","y_beta*"]) - assert indexes["x_*_m"] == ["x_beta_m"] - assert indexes["y_*_deg"] == ["y_alpha_deg"] - - # must match length - with pytest.raises(TypeError) as excinfo: - multi.find_wildcard_indexes(["x_*_m","y_*_deg"], - excludes=[None]) - assert "match length" in str(excinfo.value) - - # must match length - with pytest.raises(TypeError) as excinfo: - multi.find_wildcard_indexes(["x_*_m","y_*_deg"], - excludes={"a":"dictionary"}) - assert "array-like" in str(excinfo.value) - # must match length - with pytest.raises(TypeError) as excinfo: - multi.find_wildcard_indexes(["x_*_m","y_*_deg"], - excludes=[None,{"a":"dictionary"}]) - assert "array-like" in str(excinfo.value) - - - @pytest.mark.parametrize('csv_path', [ lazy_fixture("csv_dtypes"), @@ -2305,83 +1657,6 @@ def test_dtypes_changing(csv_dtypes): assert data_changed.orig_dtypes["datetime"] == np.float64 assert data_changed.orig_dtypes["string"] == np.float64 -def test_interpolate(): - """Test inerpolate nan function. - - """ - - data = NavData() - data["ints"] = [1,2] - data["floats"] = [1.,2.] - new_data = data.interpolate("ints","floats") - new_data["ints"] = np.array([1,2]) - new_data["floats"] = np.array([1.,2.]) - - data = NavData() - data["UnixTimeMillis"] = [0,1,2,3,4,5,6,7,8,9,10] - data["LatitudeDegrees"] = [0., np.nan, np.nan, np.nan, np.nan, 50., - np.nan, np.nan, np.nan, np.nan, 55.] - data["LongitudeDegrees"] = [-10., np.nan, np.nan, np.nan, np.nan, - np.nan, np.nan, np.nan, np.nan, np.nan, 0.] - - new_data = data.interpolate("UnixTimeMillis",["LatitudeDegrees", - "LongitudeDegrees"]) - - np.testing.assert_array_equal(new_data["UnixTimeMillis"], - np.array([0,1,2,3,4,5,6,7,8,9,10])) - np.testing.assert_array_equal(new_data["LatitudeDegrees"], - np.array([0.,10.,20.,30.,40.,50., - 51.,52.,53.,54.,55.])) - np.testing.assert_array_equal(new_data["LongitudeDegrees"], - np.array([-10.,-9.,-8.,-7.,-6.,-5., - -4.,-3.,-2.,-1.,0.])) - - data = NavData() - data["UnixTimeMillis"] = [1,4,5,7,10] - data["LatitudeDegrees"] = [1., np.nan, np.nan, np.nan, 10.] - data["LongitudeDegrees"] = [-11., np.nan, np.nan, np.nan, -20.] - - new_data = data.interpolate("UnixTimeMillis",["LatitudeDegrees", - "LongitudeDegrees"]) - - np.testing.assert_array_equal(new_data["UnixTimeMillis"], - np.array([1,4,5,7,10])) - np.testing.assert_array_equal(new_data["LatitudeDegrees"], - np.array([1.,4.,5.,7.,10.])) - np.testing.assert_array_equal(new_data["LongitudeDegrees"], - np.array([-11.,-14.,-15.,-17.,-20.])) - - new_data = data.copy() - - new_data.interpolate("UnixTimeMillis",["LatitudeDegrees", - "LongitudeDegrees"], - inplace=True) - - np.testing.assert_array_equal(new_data["UnixTimeMillis"], - np.array([1,4,5,7,10])) - np.testing.assert_array_equal(new_data["LatitudeDegrees"], - np.array([1.,4.,5.,7.,10.])) - np.testing.assert_array_equal(new_data["LongitudeDegrees"], - np.array([-11.,-14.,-15.,-17.,-20.])) - -def test_interpolate_fails(): - """Test when inerpolate nan function should fail. - - """ - - data = NavData() - data["ints"] = [0,1,2,3,4] - data["floats"] = [0.,1.,2.,np.nan,4.] - - with pytest.raises(TypeError) as excinfo: - data.interpolate(1,"floats") - assert "x_row" in str(excinfo.value) - - with pytest.raises(TypeError) as excinfo: - data.interpolate("ints",1) - assert "y_rows" in str(excinfo.value) - - def test_keep_cols_where(data, df_simple): """Test keep columns with where. @@ -2405,47 +1680,3 @@ def test_keep_cols_where(data, df_simple): df_simple_subset = df_simple_subset.reset_index(drop=True) pd.testing.assert_frame_equal(data_subset.pandas_df(), df_simple_subset, check_dtype=False) - -def test_sort(data, df_simple): - """Test sorting function across simple dataframe. - - """ - - df_sorted_int = df_simple.sort_values('integers').reset_index(drop=True) - df_sorted_float = df_simple.sort_values('floats').reset_index(drop=True) - data_sorted_int = data.sort('integers').pandas_df() - data_sorted_float = data.sort('floats').pandas_df() - float_ind = np.argsort(data['floats']) - data_sorted_ind = data.sort(ind=float_ind).pandas_df() - pd.testing.assert_frame_equal(data_sorted_int, df_sorted_int) - pd.testing.assert_frame_equal(df_sorted_float, data_sorted_float) - pd.testing.assert_frame_equal(df_sorted_float, data_sorted_ind) - # test strings as well: - df_sorted_names = df_simple.sort_values('names').reset_index(drop=True) - data_sorted_names = data.sort('names').pandas_df() - pd.testing.assert_frame_equal(df_sorted_names, data_sorted_names) - - df_sorted_strings = df_simple.sort_values('strings').reset_index(drop=True) - data_sorted_strings = data.sort('strings').pandas_df() - pd.testing.assert_frame_equal(df_sorted_strings, data_sorted_strings) - - # Test usecase when descending order is given - df_sorted_int_des = df_simple.sort_values('integers', ascending=False).reset_index(drop=True) - data_sorted_int_des = data.sort('integers', ascending=False).pandas_df() - pd.testing.assert_frame_equal(df_sorted_int_des, data_sorted_int_des) - - # test inplace - data_sorted_int_des = data.copy() - data_sorted_int_des.sort('integers', ascending=False, inplace=True) - data_sorted_int_des = data_sorted_int_des.pandas_df() - pd.testing.assert_frame_equal(df_sorted_int_des, data_sorted_int_des) - - # Test sorting for only one column - unsort_navdata_single_col = NavData() - unsort_navdata_single_col['name'] = np.asarray(['NAVLab'], dtype=object) - unsort_navdata_single_col['number'] = 1 - unsort_navdata_single_col['weight'] = 100 - sorted_single_col = unsort_navdata_single_col.sort() - pd.testing.assert_frame_equal(sorted_single_col.pandas_df(), - unsort_navdata_single_col.pandas_df()) - diff --git a/tests/navdata/test_operations.py b/tests/navdata/test_operations.py new file mode 100644 index 00000000..8f67b499 --- /dev/null +++ b/tests/navdata/test_operations.py @@ -0,0 +1,580 @@ +"""Tests for NavData class. + +""" + +__authors__ = "A. Kanhere, D. Knowles" +__date__ = "30 Apr 2022" + +import pytest +import numpy as np +import pandas as pd + +import gnss_lib_py.navdata.operations as op +from gnss_lib_py.navdata.navdata import NavData + +def test_add_numpy(numpy_array, add_array): + """Test addition of a numpy array to NavData + + Parameters + ---------- + numpy_array : np.ndarray + Array with which NavData instance is initialized + add_array : np.ndarray + Array to add to NavData + """ + + data = NavData(numpy_array=numpy_array) + data = op.concat(data,NavData(numpy_array=add_array),axis=1) + new_col_num = np.shape(add_array)[1] + np.testing.assert_array_equal(data[:, -new_col_num:], add_array) + +def test_add_numpy_1d(): + """Test addition of a 1D numpy array to NavData with single row + """ + data = NavData(numpy_array=np.zeros([1,6])) + data = op.concat(data,NavData(numpy_array=np.ones(8)),axis=1) + np.testing.assert_array_equal(data[0, :], np.hstack((np.zeros(6), + np.ones(8)))) + + # test adding to empty NavData + data_empty = NavData() + data_empty = op.concat(data_empty,NavData(numpy_array=np.ones((8,8))),axis=1) + np.testing.assert_array_equal(data_empty[:,:],np.ones((8,8))) + +def test_add_csv(df_simple, csv_simple): + """Test adding a csv. + + """ + # Create and add to NavData + data = NavData(csv_path=csv_simple) + data = op.concat(data,NavData(csv_path=csv_simple),axis=1) + data_df = data.pandas_df() + # Set up dataframe for comparison + df_types = {'names': object, 'integers': np.int64, + 'floats': np.float64, 'strings': object} + expected_df = pd.concat((df_simple,df_simple)).reset_index(drop=True) + expected_df = expected_df.astype(df_types) + pd.testing.assert_frame_equal(data_df.sort_index(axis=1), + expected_df.sort_index(axis=1), + check_index_type=False) + + # test adding to empty NavData + data_empty = NavData() + data_empty = op.concat(data_empty,NavData(csv_path=csv_simple),axis=1) + pd.testing.assert_frame_equal(data_empty.pandas_df().sort_index(axis=1), + df_simple.astype(df_types).sort_index(axis=1), + check_index_type=False) + +def test_add_pandas_df(df_simple, add_df): + """Test addition of a pd.DataFrame to NavData + + Parameters + ---------- + df_simple : pd.DataFrame + pd.DataFrame to initialize NavData with + add_df : pd.DataFrame + pd.DataFrame to add to NavData + """ + data = NavData(pandas_df=df_simple) + data = op.concat(data,NavData(pandas_df=add_df),axis=1) + new_df = data.pandas_df() + add_row_num = add_df.shape[0] + subset_df = new_df.iloc[-add_row_num:, :].reset_index(drop=True) + pd.testing.assert_frame_equal(subset_df.sort_index(axis=1), + add_df.sort_index(axis=1), + check_index_type=False) + + # test adding to empty NavData + data_empty = NavData() + data_empty = op.concat(data_empty,NavData(pandas_df=add_df),axis=1) + pd.testing.assert_frame_equal(add_df.sort_index(axis=1), + data_empty.pandas_df().sort_index(axis=1), + check_index_type=False) + +def test_concat(df_simple): + """Test concat functionaltiy. + + Parameters + ---------- + df_simple : pd.DataFrame + Simple pd.DataFrame with which to initialize NavData. + + """ + + navdata_1 = NavData(pandas_df=df_simple) + navdata_2 = navdata_1.copy() + navdata_2.rename(mapper={"floats": "decimals", "names": "words"}, + inplace = True) + + # add new columns + navdata = op.concat(navdata_1,navdata_1.copy()) + assert navdata.shape == (4,12) + pandas_equiv = pd.concat((df_simple,df_simple),axis=0) + pandas_equiv.reset_index(drop=True, inplace=True) + pd.testing.assert_frame_equal(pandas_equiv.sort_index(axis=1), + navdata.pandas_df().sort_index(axis=1), + check_index_type=False, + check_dtype=False) + + # add new rows + navdata = op.concat(navdata_1,navdata_1.copy(),axis=0) + assert navdata.shape == (8,6) + mapper = {"names":"names_0", + "floats":"floats_0", + "integers":"integers_0", + "strings":"strings_0"} + df_simple_2 = df_simple.rename(mapper,axis=1) + pandas_equiv = pd.concat((df_simple,df_simple_2),axis=1) + pandas_equiv.reset_index(drop=True, inplace=True) + pd.testing.assert_frame_equal(pandas_equiv.sort_index(axis=1), + navdata.pandas_df().sort_index(axis=1), + check_index_type=False, + check_dtype=False) + + # concatenate empty NavData + navdata = op.concat(NavData(),navdata_1,axis=1) + pd.testing.assert_frame_equal(df_simple.sort_index(axis=1), + navdata.pandas_df().sort_index(axis=1), + check_index_type=False, + check_dtype=False) + navdata = op.concat(navdata_1,NavData(),axis=1) + pd.testing.assert_frame_equal(df_simple.sort_index(axis=1), + navdata.pandas_df().sort_index(axis=1), + check_index_type=False, + check_dtype=False) + + # test multiple rows with the same name + navdata_long = navdata_1.copy() + for count in range(13): + navdata_long = op.concat(navdata_long,navdata_1,axis=0) + for word in ["names","integers","floats","strings"]: + assert word + "_" + str(count) in navdata_long.rows + + # add semi new columns + navdata = op.concat(navdata_1,navdata_2) + assert navdata.shape == (6,12) + assert np.all(np.isnan(navdata["floats"][-6:])) + assert np.all(navdata["names"][-6:] == np.array([np.nan]).astype(str)[0]) + assert np.all(np.isnan(navdata["decimals"][:6])) + assert np.all(navdata["words"][:6] == np.array([np.nan]).astype(str)[0]) + + # add semi new columns in opposite order + navdata = op.concat(navdata_2,navdata_1) + assert navdata.shape == (6,12) + assert np.all(np.isnan(navdata["floats"][:6])) + assert np.all(navdata["names"][:6] == np.array([np.nan]).astype(str)[0]) + assert np.all(np.isnan(navdata["decimals"][-6:])) + assert np.all(navdata["words"][-6:] == np.array([np.nan]).astype(str)[0]) + + # add as new rows + navdata = op.concat(navdata_1,navdata_2,axis=0) + assert navdata.shape == (8,6) + mapper = {"names":"words", + "floats":"decimals", + "integers":"integers_0", + "strings":"strings_0"} + df_simple_2 = df_simple.rename(mapper,axis=1) + pandas_equiv = pd.concat((df_simple,df_simple_2),axis=1) + pandas_equiv.reset_index(drop=True, inplace=True) + pd.testing.assert_frame_equal(pandas_equiv.sort_index(axis=1), + navdata.pandas_df().sort_index(axis=1), + check_index_type=False, + check_dtype=False) + + navdata_a = NavData(pandas_df=pd.DataFrame({'a':[0],'b':[1],'c':[2], + 'd':[3],'e':[4],'f':[5], + })) + navdata_b = op.concat(navdata_a,navdata_a.copy(),axis=0) + assert navdata_b.shape == (12,1) + navdata_b = op.concat(navdata_a,navdata_a.copy(),axis=1) + assert navdata_b.shape == (6,2) + +def test_concat_fails(df_simple): + """Test when concat should fail. + + Parameters + ---------- + df_simple : pd.DataFrame + Simple pd.DataFrame with which to initialize NavData. + + """ + + navdata_1 = NavData(pandas_df=df_simple) + + with pytest.raises(TypeError) as excinfo: + op.concat(navdata_1,np.array([])) + assert "concat" in str(excinfo.value) + assert "NavData" in str(excinfo.value) + + navdata_2 = navdata_1.remove(cols=[0]) + + with pytest.raises(RuntimeError) as excinfo: + op.concat(navdata_1,navdata_2,axis=0) + assert "same length" in str(excinfo.value) + assert "concat" in str(excinfo.value) + + with pytest.raises(RuntimeError) as excinfo: + op.concat(navdata_1,NavData(),axis=0) + assert "same length" in str(excinfo.value) + assert "concat" in str(excinfo.value) + + with pytest.raises(RuntimeError) as excinfo: + op.concat(NavData(),navdata_1,axis=0) + assert "same length" in str(excinfo.value) + assert "concat" in str(excinfo.value) + +def test_init_only_header(csv_only_header, csv_simple): + """Test initializing NavData class with csv with only header + + Parameters + ---------- + csv_only_header : string + Path to csv file containing headers, but no data + csv_simple : string + Path to csv file headers and data + + """ + + # should work when csv is passed + csv_data = NavData(csv_path=csv_only_header) + assert csv_data.shape == (4,0) + # test adding new data to empty NavData with column names + csv_data = op.concat(csv_data,NavData(csv_path=csv_simple),axis=1) + assert csv_data.shape == (4,6) + pd.testing.assert_frame_equal(csv_data.pandas_df().sort_index(axis=1), + pd.read_csv(csv_simple).sort_index(axis=1), + check_dtype=False, check_names=True) + + # should work when DataFrame is passed + pd_data = NavData(pandas_df=pd.read_csv(csv_only_header)) + assert pd_data.shape == (4,0) + # test adding new data to empty NavData with column names + pd_data = op.concat(pd_data,NavData(pandas_df=pd.read_csv(csv_simple)),axis=1) + assert pd_data.shape == (4,6) + +def test_time_looping(csv_simple): + """Testing implementation to loop over times + + Parameters + ---------- + csv_simple : str + path to csv file used to create NavData + """ + data = NavData(csv_path=csv_simple) + data['times'] = np.hstack((np.zeros([1, 2]), + 1.0001*np.ones([1, 1]), + 1.0003*np.ones([1,1]), + 1.50004*np.ones([1, 1]), + 1.49999*np.ones([1,1]))) + compare_df = data.pandas_df() + count = 0 + # Testing when loop_time finds overlapping times + for time, delta_t, measure in op.loop_time(data,'times', delta_t_decimals=2): + if count == 0: + np.testing.assert_almost_equal(delta_t, 0) + np.testing.assert_almost_equal(time, 0) + row_num = [0,1] + elif count == 1: + np.testing.assert_almost_equal(delta_t, 1) + np.testing.assert_almost_equal(time, 1) + row_num = [2,3] + elif count == 2: + np.testing.assert_almost_equal(delta_t, 0.5) + np.testing.assert_almost_equal(time, 1.5) + row_num = [4,5] + small_df = measure.pandas_df().reset_index(drop=True) + expected_df = compare_df.iloc[row_num, :].reset_index(drop=True) + pd.testing.assert_frame_equal(small_df, expected_df, + check_index_type=False) + count += 1 + + # Testing for when loop_time finds only unique times + count = 0 + expected_times = [0., 1.0001, 1.0003, 1.49999, 1.50004] + for time, _, measure in op.loop_time(data,'times', delta_t_decimals=5): + np.testing.assert_almost_equal(time, expected_times[count]) + count += 1 + +def test_sort(data, df_simple): + """Test sorting function across simple dataframe. + + """ + + df_sorted_int = df_simple.sort_values('integers').reset_index(drop=True) + df_sorted_float = df_simple.sort_values('floats').reset_index(drop=True) + data_sorted_int = op.sort(data,'integers').pandas_df() + data_sorted_float = op.sort(data,'floats').pandas_df() + float_ind = np.argsort(data['floats']) + data_sorted_ind = op.sort(data,ind=float_ind).pandas_df() + pd.testing.assert_frame_equal(data_sorted_int, df_sorted_int) + pd.testing.assert_frame_equal(df_sorted_float, data_sorted_float) + pd.testing.assert_frame_equal(df_sorted_float, data_sorted_ind) + # test strings as well: + df_sorted_names = df_simple.sort_values('names').reset_index(drop=True) + data_sorted_names = op.sort(data,'names').pandas_df() + pd.testing.assert_frame_equal(df_sorted_names, data_sorted_names) + + df_sorted_strings = df_simple.sort_values('strings').reset_index(drop=True) + data_sorted_strings = op.sort(data,'strings').pandas_df() + pd.testing.assert_frame_equal(df_sorted_strings, data_sorted_strings) + + # Test usecase when descending order is given + df_sorted_int_des = df_simple.sort_values('integers', ascending=False).reset_index(drop=True) + data_sorted_int_des = op.sort(data,'integers', ascending=False).pandas_df() + pd.testing.assert_frame_equal(df_sorted_int_des, data_sorted_int_des) + + # test inplace + data_sorted_int_des = data.copy() + op.sort(data_sorted_int_des,'integers', ascending=False, inplace=True) + data_sorted_int_des = data_sorted_int_des.pandas_df() + pd.testing.assert_frame_equal(df_sorted_int_des, data_sorted_int_des) + + # Test sorting for only one column + unsort_navdata_single_col = NavData() + unsort_navdata_single_col['name'] = np.asarray(['NAVLab'], dtype=object) + unsort_navdata_single_col['number'] = 1 + unsort_navdata_single_col['weight'] = 100 + sorted_single_col = op.sort(unsort_navdata_single_col) + pd.testing.assert_frame_equal(sorted_single_col.pandas_df(), + unsort_navdata_single_col.pandas_df()) + +def test_find_wildcard_indexes(data): + """Tests find_wildcard_indexes + + """ + + all_matching = data.rename({"names" : "x_alpha_m", + "integers" : "x_beta_m", + "floats" : "x_gamma_m", + "strings" : "x_zeta_m"}) + expected = ["x_alpha_m","x_beta_m","x_gamma_m","x_zeta_m"] + + indexes = op.find_wildcard_indexes(all_matching,"x_*_m") + assert indexes["x_*_m"] == expected + expect_pass_allows = [None,12,4] + for max_allow in expect_pass_allows: + indexes = op.find_wildcard_indexes(all_matching,"x_*_m",max_allow) + assert indexes["x_*_m"] == expected + + expect_fail_allows = [0,-1,3,2,1] + for max_allow in expect_fail_allows: + with pytest.raises(KeyError) as excinfo: + op.find_wildcard_indexes(all_matching,"x_*_m",max_allow) + assert "More than " + str(max_allow) in str(excinfo.value) + assert "x_*_m" in str(excinfo.value) + + multi = data.rename({"names" : "x_alpha_m", + "integers" : "x_beta_m", + "floats" : "y_alpha_deg", + "strings" : "x_zeta_deg"}) + expected = {"x_*_m" : ["x_alpha_m","x_beta_m"], + "y_*_deg" : ["y_alpha_deg"]} + + expect_pass_allows = [None,2,4] + for max_allow in expect_pass_allows: + indexes = op.find_wildcard_indexes(multi,["x_*_m","y_*_deg"], + max_allow) + assert indexes == expected + + expect_pass_allows = [None,2,4] + for max_allow in expect_pass_allows: + indexes = op.find_wildcard_indexes(multi,tuple(["x_*_m","y_*_deg"]), + max_allow) + assert indexes == expected + + expect_pass_allows = [None,2,4] + for max_allow in expect_pass_allows: + indexes = op.find_wildcard_indexes(multi,set(["x_*_m","y_*_deg"]), + max_allow) + assert indexes == expected + + expect_pass_allows = [None,2,4] + for max_allow in expect_pass_allows: + indexes = op.find_wildcard_indexes(multi,np.array(["x_*_m", + "y_*_deg"]), + max_allow) + assert indexes == expected + + expect_fail_allows = [0,-1,1] + for max_allow in expect_fail_allows: + with pytest.raises(KeyError) as excinfo: + op.find_wildcard_indexes(multi,["x_*_m","y_*_deg"],max_allow) + assert "More than " + str(max_allow) in str(excinfo.value) + assert "x_*_m" in str(excinfo.value) + + with pytest.raises(KeyError) as excinfo: + op.find_wildcard_indexes(multi,["z_*_m"]) + assert "Missing " in str(excinfo.value) + assert "z_*_m" in str(excinfo.value) + + with pytest.raises(TypeError) as excinfo: + op.find_wildcard_indexes(multi,1.0) + assert "find_wildcard_indexes " in str(excinfo.value) + assert "array-like" in str(excinfo.value) + + with pytest.raises(TypeError) as excinfo: + op.find_wildcard_indexes(multi,[1.0]) + assert "wildcards must be strings" in str(excinfo.value) + + with pytest.raises(RuntimeError) as excinfo: + op.find_wildcard_indexes(multi,"x_*_*") + assert "One wildcard" in str(excinfo.value) + + incorrect_max_allow = [3.,"hi",[]] + for max_allow in incorrect_max_allow: + with pytest.raises(TypeError) as excinfo: + op.find_wildcard_indexes(multi,"x_*_m",max_allow) + assert "max_allow" in str(excinfo.value) + +def test_find_wildcard_excludes(data): + """Tests find_wildcard_indexes + + """ + all_matching = data.rename({"names" : "x_alpha_m", + "integers" : "x_beta_m", + "floats" : "x_gamma_m", + "strings" : "x_zeta_m"}) + + # no exclusion + indexes = op.find_wildcard_indexes(all_matching,"x_*_m",excludes=None) + assert indexes["x_*_m"] == ["x_alpha_m","x_beta_m", + "x_gamma_m","x_zeta_m"] + indexes = op.find_wildcard_indexes(all_matching,"x_*_m",excludes=[None]) + assert indexes["x_*_m"] == ["x_alpha_m","x_beta_m", + "x_gamma_m","x_zeta_m"] + + # single exclusion + indexes = op.find_wildcard_indexes(all_matching,"x_*_m",excludes="x_beta_m") + assert indexes["x_*_m"] == ["x_alpha_m","x_gamma_m","x_zeta_m"] + + # two exclusion + indexes = op.find_wildcard_indexes(all_matching,"x_*_m", + excludes=[["x_beta_m","x_zeta_m"]]) + assert indexes["x_*_m"] == ["x_alpha_m","x_gamma_m"] + + # all excluded + with pytest.raises(KeyError) as excinfo: + op.find_wildcard_indexes(all_matching,"x_*_m",excludes=["x_*_m"]) + assert "Missing " in str(excinfo.value) + assert "x_*_m" in str(excinfo.value) + + + multi = data.rename({"names" : "x_alpha_m", + "integers" : "x_beta_m", + "floats" : "y_alpha_deg", + "strings" : "y_beta_deg"}) + + # no exclusion + indexes = op.find_wildcard_indexes(multi,["x_*_m","y_*_deg"], + excludes=None) + assert indexes["x_*_m"] == ["x_alpha_m","x_beta_m"] + assert indexes["y_*_deg"] == ["y_alpha_deg","y_beta_deg"] + indexes = op.find_wildcard_indexes(multi,["x_*_m","y_*_deg"], + excludes=[None,None]) + assert indexes["x_*_m"] == ["x_alpha_m","x_beta_m"] + assert indexes["y_*_deg"] == ["y_alpha_deg","y_beta_deg"] + + # single exclusion + indexes = op.find_wildcard_indexes(multi,["x_*_m","y_*_deg"], + excludes=["x_alpha*",None]) + assert indexes["x_*_m"] == ["x_beta_m"] + assert indexes["y_*_deg"] == ["y_alpha_deg","y_beta_deg"] + + # double exclusion + indexes = op.find_wildcard_indexes(multi,["x_*_m","y_*_deg"], + excludes=["x_alpha*","y_beta*"]) + assert indexes["x_*_m"] == ["x_beta_m"] + assert indexes["y_*_deg"] == ["y_alpha_deg"] + + # must match length + with pytest.raises(TypeError) as excinfo: + op.find_wildcard_indexes(multi,["x_*_m","y_*_deg"], + excludes=[None]) + assert "match length" in str(excinfo.value) + + # must match length + with pytest.raises(TypeError) as excinfo: + op.find_wildcard_indexes(multi,["x_*_m","y_*_deg"], + excludes={"a":"dictionary"}) + assert "array-like" in str(excinfo.value) + # must match length + with pytest.raises(TypeError) as excinfo: + op.find_wildcard_indexes(multi,["x_*_m","y_*_deg"], + excludes=[None,{"a":"dictionary"}]) + assert "array-like" in str(excinfo.value) + +def test_interpolate(): + """Test inerpolate nan function. + + """ + + data = NavData() + data["ints"] = [1,2] + data["floats"] = [1.,2.] + new_data = op.interpolate(data,"ints","floats") + new_data["ints"] = np.array([1,2]) + new_data["floats"] = np.array([1.,2.]) + + data = NavData() + data["UnixTimeMillis"] = [0,1,2,3,4,5,6,7,8,9,10] + data["LatitudeDegrees"] = [0., np.nan, np.nan, np.nan, np.nan, 50., + np.nan, np.nan, np.nan, np.nan, 55.] + data["LongitudeDegrees"] = [-10., np.nan, np.nan, np.nan, np.nan, + np.nan, np.nan, np.nan, np.nan, np.nan, 0.] + + new_data = op.interpolate(data,"UnixTimeMillis",["LatitudeDegrees", + "LongitudeDegrees"]) + + np.testing.assert_array_equal(new_data["UnixTimeMillis"], + np.array([0,1,2,3,4,5,6,7,8,9,10])) + np.testing.assert_array_equal(new_data["LatitudeDegrees"], + np.array([0.,10.,20.,30.,40.,50., + 51.,52.,53.,54.,55.])) + np.testing.assert_array_equal(new_data["LongitudeDegrees"], + np.array([-10.,-9.,-8.,-7.,-6.,-5., + -4.,-3.,-2.,-1.,0.])) + + data = NavData() + data["UnixTimeMillis"] = [1,4,5,7,10] + data["LatitudeDegrees"] = [1., np.nan, np.nan, np.nan, 10.] + data["LongitudeDegrees"] = [-11., np.nan, np.nan, np.nan, -20.] + + new_data = op.interpolate(data,"UnixTimeMillis",["LatitudeDegrees", + "LongitudeDegrees"]) + + np.testing.assert_array_equal(new_data["UnixTimeMillis"], + np.array([1,4,5,7,10])) + np.testing.assert_array_equal(new_data["LatitudeDegrees"], + np.array([1.,4.,5.,7.,10.])) + np.testing.assert_array_equal(new_data["LongitudeDegrees"], + np.array([-11.,-14.,-15.,-17.,-20.])) + + new_data = data.copy() + + op.interpolate(new_data,"UnixTimeMillis",["LatitudeDegrees", + "LongitudeDegrees"], + inplace=True) + + np.testing.assert_array_equal(new_data["UnixTimeMillis"], + np.array([1,4,5,7,10])) + np.testing.assert_array_equal(new_data["LatitudeDegrees"], + np.array([1.,4.,5.,7.,10.])) + np.testing.assert_array_equal(new_data["LongitudeDegrees"], + np.array([-11.,-14.,-15.,-17.,-20.])) + +def test_interpolate_fails(): + """Test when inerpolate nan function should fail. + + """ + + data = NavData() + data["ints"] = [0,1,2,3,4] + data["floats"] = [0.,1.,2.,np.nan,4.] + + with pytest.raises(TypeError) as excinfo: + op.interpolate(data,1,"floats") + assert "x_row" in str(excinfo.value) + + with pytest.raises(TypeError) as excinfo: + op.interpolate(data,"ints",1) + assert "y_rows" in str(excinfo.value) diff --git a/tests/parsers/test_android.py b/tests/parsers/test_android.py index 85ac4f79..d9b4e370 100644 --- a/tests/parsers/test_android.py +++ b/tests/parsers/test_android.py @@ -16,7 +16,7 @@ from gnss_lib_py.parsers import android from gnss_lib_py.parsers.google_decimeter import AndroidDerived2023 -from gnss_lib_py.parsers.navdata import NavData +from gnss_lib_py.navdata.navdata import NavData # pylint: disable=protected-access diff --git a/tests/parsers/test_google_decimeter.py b/tests/parsers/test_google_decimeter.py index c7233fe6..225aa826 100644 --- a/tests/parsers/test_google_decimeter.py +++ b/tests/parsers/test_google_decimeter.py @@ -12,9 +12,10 @@ import pandas as pd from gnss_lib_py.parsers import google_decimeter -from gnss_lib_py.parsers.navdata import NavData +from gnss_lib_py.navdata.navdata import NavData from gnss_lib_py.algorithms.snapshot import solve_wls from gnss_lib_py.algorithms.gnss_filters import solve_gnss_ekf +from gnss_lib_py.navdata.operations import loop_time, concat # pylint: disable=protected-access @@ -310,8 +311,8 @@ def test_android_concat(derived, pd_df): # extract and combine gps and glonass data gps_data = derived.where("gnss_id","gps") glonass_data = derived.where("gnss_id","glonass") - gps_glonass_navdata = gps_data.concat(glonass_data) - glonass_gps_navdata = glonass_data.concat(gps_data) + gps_glonass_navdata = concat(gps_data,glonass_data) + glonass_gps_navdata = concat(glonass_data,gps_data) # combine using pandas gps_df = pd_df[pd_df["constellationType"]==1] @@ -361,7 +362,7 @@ def test_timestep_parsing(derived_path_xl): pd_svid_groups.pop(0) navdata_svid_groups = [] - for _, _, group in derived_xl.loop_time("gps_millis"): + for _, _, group in loop_time(derived_xl,"gps_millis"): navdata_svid_groups.append(group["sv_id"].astype(int).tolist()) assert len(pd_svid_groups) == len(navdata_svid_groups) @@ -641,7 +642,7 @@ def test_solve_kaggle_baseline(derived_2022): Returns ------- - state_estimate : gnss_lib_py.parsers.navdata.NavData + state_estimate : gnss_lib_py.navdata.navdata.NavData Baseline state estimate. """ @@ -663,7 +664,7 @@ def test_prepare_kaggle_submission(state_estimate): Parameters ---------- - state_estimate : gnss_lib_py.parsers.navdata.NavData + state_estimate : gnss_lib_py.navdata.navdata.NavData Baseline state estimate. """ diff --git a/tests/parsers/test_rinex_nav.py b/tests/parsers/test_rinex_nav.py index 93c61b9b..353f0b22 100644 --- a/tests/parsers/test_rinex_nav.py +++ b/tests/parsers/test_rinex_nav.py @@ -11,7 +11,7 @@ import pytest import numpy as np -from gnss_lib_py.parsers.navdata import NavData +from gnss_lib_py.navdata.navdata import NavData from gnss_lib_py.parsers.rinex_nav import RinexNav, get_time_cropped_rinex from gnss_lib_py.utils.time_conversions import gps_millis_to_tow from gnss_lib_py.utils.time_conversions import datetime_to_gps_millis @@ -120,7 +120,7 @@ def test_get_time_cropped_rinex(ephem_path, ephem_time, satellites): ephem = get_time_cropped_rinex(ephem_time, satellites, ephem_path, verbose=True) - # Test that ephem is of type gnss_lib_py.parsers.navdata.NavData + # Test that ephem is of type gnss_lib_py.navdata.navdata.NavData assert isinstance(ephem, NavData) # check that there's one row per satellite diff --git a/tests/parsers/test_rinex_obs.py b/tests/parsers/test_rinex_obs.py index 67746690..92ed5da2 100644 --- a/tests/parsers/test_rinex_obs.py +++ b/tests/parsers/test_rinex_obs.py @@ -12,6 +12,7 @@ from pytest_lazyfixture import lazy_fixture from gnss_lib_py.parsers.rinex_obs import RinexObs +from gnss_lib_py.navdata.operations import loop_time @pytest.fixture(name="root_path") def fixture_root_path(): @@ -127,7 +128,7 @@ def test_rinex_obs_3_load_single(rinex_single_values, single_exp_values): """ count = 0 - for _, _, rinex_frame in rinex_single_values.loop_time('gps_millis'): + for _, _, rinex_frame in loop_time(rinex_single_values,'gps_millis'): #For each time case, check that the expected values are correct for case in single_exp_values: @@ -151,7 +152,7 @@ def test_rinex_obs_3_load_mixed(rinex_mixed_values, mixed_exp_values): List of indices and values to compare against. """ count = 0 - for _, _, rinex_frame in rinex_mixed_values.loop_time('gps_millis'): + for _, _, rinex_frame in loop_time(rinex_mixed_values,'gps_millis'): #For each time case, check that the expected values are correct for case in mixed_exp_values: if case[0] == count: @@ -212,7 +213,7 @@ def test_rinex_obs_3_complete_load(rinex_navdata, time_steps, sats_per_time): assert len(np.unique(rinex_navdata['gps_millis'])) == time_steps, \ "Measurements for all times were not loaded." count = 0 - for _, _, rinex_frame in rinex_navdata.loop_time('gps_millis'): + for _, _, rinex_frame in loop_time(rinex_navdata,'gps_millis'): assert len(rinex_frame) == sats_per_time[count], \ "Measurements for all recorded satellites were not loaded." count += 1 @@ -231,7 +232,7 @@ def test_rinex_obs_3_fails(rinex_mixed_values): Instance of RinexObs class with data loaded from file with measurements received in both, single and double bands. """ - for count , (_, _, rinex_frame) in enumerate(rinex_mixed_values.loop_time('gps_millis')): + for count , (_, _, rinex_frame) in enumerate(loop_time(rinex_mixed_values,'gps_millis')): # SV wasn't received sv_not_rx = rinex_frame.where('gnss_sv_id', 'G01', 'eq') assert len(sv_not_rx) == 0 diff --git a/tests/parsers/test_smartloc.py b/tests/parsers/test_smartloc.py index 5c8058b9..51ca444e 100644 --- a/tests/parsers/test_smartloc.py +++ b/tests/parsers/test_smartloc.py @@ -11,7 +11,7 @@ import numpy as np import pandas as pd -from gnss_lib_py.parsers.navdata import NavData +from gnss_lib_py.navdata.navdata import NavData from gnss_lib_py.utils.coordinates import wrap_0_to_2pi from gnss_lib_py.parsers.smartloc import SmartLocRaw, remove_nlos, \ calculate_gt_ecef, calculate_gt_vel diff --git a/tests/utils/conftest.py b/tests/utils/conftest.py index d6b0fb9b..bce9c141 100644 --- a/tests/utils/conftest.py +++ b/tests/utils/conftest.py @@ -11,10 +11,10 @@ import pytest import numpy as np -from gnss_lib_py.utils.time_conversions import gps_millis_to_datetime -from gnss_lib_py.parsers.navdata import NavData +from gnss_lib_py.navdata.navdata import NavData from gnss_lib_py.parsers.google_decimeter import AndroidDerived2022, AndroidGroundTruth2022 from gnss_lib_py.parsers.rinex_nav import get_time_cropped_rinex +from gnss_lib_py.navdata.operations import loop_time, concat def pytest_collection_modifyitems(items): """Run ephemeris download tests after all other tests. @@ -221,11 +221,11 @@ def fixture_derived_gps_l1_reversed(android_gps_l1): instance. """ android_gps_l1_reversed = NavData() - for _, _, measure_frame in android_gps_l1.loop_time('gps_millis', delta_t_decimals=-2): + for _, _, measure_frame in loop_time(android_gps_l1,'gps_millis', delta_t_decimals=-2): if len(android_gps_l1_reversed)==0: android_gps_l1_reversed = measure_frame else: - android_gps_l1_reversed.concat(measure_frame, inplace=True) + android_gps_l1_reversed = concat(android_gps_l1_reversed,measure_frame) return android_gps_l1_reversed @@ -241,7 +241,7 @@ def fixture_android_state(android_derived): Returns ------- - android_state_estimate : gnss_lib_py.parsers.navdata.NavData + android_state_estimate : gnss_lib_py.navdata.navdata.NavData Instance of `NavData` containing `gps_millis` and Rx position estimates from Android Derived. """ @@ -317,7 +317,7 @@ def fixture_all_gps_ephem(ephemeris_path, start_time, all_gps_sats): Returns ------- - ephem : gnss_lib_py.parsers.navdata.NavData + ephem : gnss_lib_py.navdata.navdata.NavData NavData instance containing ephemeris parameters for all GPS satellites at the start time for measurement reception. """ @@ -334,7 +334,7 @@ def fixture_gps_measurement_frames(all_gps_ephem, android_gps_l1): Parameters ---------- - all_gps_ephem : gnss_lib_py.parsers.navdata.NavData + all_gps_ephem : gnss_lib_py.navdata.navdata.NavData NavData instance containing ephemeris parameters for all GPS satellites at the start time for measurement reception. android_gps_l1 : gnss_lib_py.parsers.google_decimeter.AndroidDerived2022 @@ -348,7 +348,7 @@ def fixture_gps_measurement_frames(all_gps_ephem, android_gps_l1): received Android measurements and SV states. The lists are indexed by discrete time indices. """ - android_frames = android_gps_l1.loop_time('gps_millis', delta_t_decimals=-2) + android_frames = loop_time(android_gps_l1,'gps_millis', delta_t_decimals=-2) ephems = [] frames = [] sv_states = [] diff --git a/tests/utils/test_coordinates.py b/tests/utils/test_coordinates.py index 6ccf2151..9100a0b6 100644 --- a/tests/utils/test_coordinates.py +++ b/tests/utils/test_coordinates.py @@ -17,7 +17,7 @@ from gnss_lib_py.utils.coordinates import geodetic_to_ecef from gnss_lib_py.utils.coordinates import ecef_to_geodetic, LocalCoord from gnss_lib_py.utils.coordinates import wrap_0_to_2pi - +from gnss_lib_py.navdata.operations import loop_time @pytest.fixture(name="local_ecef") def fixture_local_ecef(): @@ -372,7 +372,7 @@ def test_android_ecef_to_el_az(navdata): """ - for _, _, navdata_subset in navdata.loop_time("gps_millis"): + for _, _, navdata_subset in loop_time(navdata,"gps_millis"): pos_sv_m = navdata_subset[["x_sv_m","y_sv_m","z_sv_m"]] pos_rx_m = navdata_subset[["x_rx_m","y_rx_m","z_rx_m"],0].reshape(-1,1) diff --git a/tests/utils/test_gnss_models.py b/tests/utils/test_gnss_models.py index c0d483a0..d52a9610 100644 --- a/tests/utils/test_gnss_models.py +++ b/tests/utils/test_gnss_models.py @@ -13,7 +13,7 @@ from pytest_lazyfixture import lazy_fixture from gnss_lib_py.algorithms.snapshot import solve_wls -from gnss_lib_py.parsers.navdata import NavData +from gnss_lib_py.navdata.navdata import NavData import gnss_lib_py.utils.gnss_models as gnss_models from gnss_lib_py.utils.sv_models import _extract_pos_vel_arr from gnss_lib_py.utils.coordinates import LocalCoord @@ -51,7 +51,7 @@ def calculate_state(android_gt, idx): Returns ------- - state : gnss_lib_py.parsers.navdata.NavData + state : gnss_lib_py.navdata.navdata.NavData NavData containing state information for one time instance. """ @@ -234,10 +234,10 @@ def test_add_measures_wrapper(android_measurements, android_state, Parameters ---------- - android_measurements : gnss_lib_py.parsers.navdata.NavData + android_measurements : gnss_lib_py.navdata.navdata.NavData NavData instance containing L1 measurements for received GPS measurements. - android_state : gnss_lib_py.parsers.navdata.NavData + android_state : gnss_lib_py.navdata.navdata.NavData Instance of `NavData` containing `gps_millis` and Rx position estimates from Android Derived. ephemeris_path : string diff --git a/tests/utils/test_sv_models.py b/tests/utils/test_sv_models.py index 78bb5c49..778ac214 100644 --- a/tests/utils/test_sv_models.py +++ b/tests/utils/test_sv_models.py @@ -15,10 +15,11 @@ from gnss_lib_py.parsers.clk import Clk from gnss_lib_py.parsers.sp3 import Sp3 -from gnss_lib_py.parsers.navdata import NavData +from gnss_lib_py.navdata.navdata import NavData import gnss_lib_py.utils.sv_models as sv_models import gnss_lib_py.utils.time_conversions as tc from gnss_lib_py.parsers.google_decimeter import AndroidDerived2021 +from gnss_lib_py.navdata.operations import loop_time # pylint: disable=protected-access # Number of time to run meausurement simulation code @@ -53,7 +54,7 @@ def fixture_dummy_pos_vel(scaling_value): Returns ------- - dummy_posvel = gnss_lib_py.parsers.navdata.NavData + dummy_posvel = gnss_lib_py.navdata.navdata.NavData NavData example containing position and velocity. """ dummy_posvel = NavData() @@ -94,7 +95,7 @@ def test_posvel_extract(dummy_pos_vel, scaling_value): Parameters ---------- - dummy_pos_vel : gnss_lib_py.parsers.navdata.NavData + dummy_pos_vel : gnss_lib_py.navdata.navdata.NavData NavData example containing position and velocity. scaling_value : np.ndarray Linear range for 6 instances of positions and velocities. @@ -115,7 +116,7 @@ def test_del_xyz_range(dummy_pos_vel, scaling_value): Parameters ---------- - dummy_pos_vel : gnss_lib_py.parsers.navdata.NavData + dummy_pos_vel : gnss_lib_py.navdata.navdata.NavData NavData example containing position and velocity. scaling_value : np.ndarray Linear range for 6 instances of positions and velocities. @@ -145,7 +146,7 @@ def test_sv_state_model(gps_measurement_frames, android_gt): Dictionary containing NavData instances of ephemeris parameters for received satellites, received Android measurements and SV states, all corresponding to the same received time frame. - android_gt : gnss_lib_py.parsers.navdata.NavData + android_gt : gnss_lib_py.navdata.navdata.NavData Ground truth for received measurements. """ android_frames = gps_measurement_frames['android_frames'] @@ -178,14 +179,14 @@ def test_visible_ephem(all_gps_ephem, gps_measurement_frames, android_gt): Parameters ---------- - all_gps_ephem : gnss_lib_py.parsers.navdata.NavData + all_gps_ephem : gnss_lib_py.navdata.navdata.NavData Ephemeris parameters for all satellites at the time when measurements were received. gps_measurement_frames : Dict Dictionary containing NavData instances of ephemeris parameters for received satellites, received Android measurements and SV states, all corresponding to the same received time frame. - android_gt : gnss_lib_py.parsers.navdata.NavData + android_gt : gnss_lib_py.navdata.navdata.NavData Ground truth for received measurements. """ android_frames = gps_measurement_frames['android_frames'] @@ -214,7 +215,7 @@ def test_visible_sv_posvel(gps_measurement_frames, android_gt): Dictionary containing NavData instances of ephemeris parameters for received satellites, received Android measurements and SV states, all corresponding to the same received time frame. - android_gt : gnss_lib_py.parsers.navdata.NavData + android_gt : gnss_lib_py.navdata.navdata.NavData Ground truth for received measurements. """ android_frames = gps_measurement_frames['android_frames'] @@ -238,7 +239,7 @@ def test_add_sv_state_wrapper(android_measurements, ephemeris_path, error_tol_de Parameters ---------- - android_measurements : gnss_lib_py.parsers.navdata.NavData + android_measurements : gnss_lib_py.navdata.navdata.NavData NavData instance containing L1 measurements for received GPS measurements. ephemeris_path : string @@ -292,7 +293,7 @@ def test_filter_ephemeris_none(android_gps_l1, ephemeris_path): Parameters ---------- - android_gps_l1 : gnss_lib_py.parsers.navdata.NavData + android_gps_l1 : gnss_lib_py.navdata.navdata.NavData NavData instance containing L1 measurements for received GPS measurements. ephemeris_path : string @@ -312,7 +313,7 @@ def test_add_visible_svs_for_trajectory(android_gps_l1, ephemeris_path, Parameters ---------- - android_gps_l1 : gnss_lib_py.parsers.navdata.NavData + android_gps_l1 : gnss_lib_py.navdata.navdata.NavData NavData instance containing L1 measurements for received GPS measurements. ephemeris_path : string @@ -339,7 +340,7 @@ def test_add_visible_svs_for_trajectory(android_gps_l1, ephemeris_path, # subset of those considered visible true_rows = ['x_sv_m', 'y_sv_m', 'z_sv_m', 'vx_sv_mps', 'vy_sv_mps', 'vz_sv_mps'] - for milli, _, measure_frame in android_gps_l1.loop_time("gps_millis", + for milli, _, measure_frame in loop_time(android_gps_l1,"gps_millis", delta_t_decimals=-2): se_frame = sv_posvel_traj.where("gps_millis", milli) se_svs = set(np.unique(se_frame['sv_id'])) diff --git a/tests/utils/test_time_conversions.py b/tests/utils/test_time_conversions.py index cccc9ef2..d0ea735f 100644 --- a/tests/utils/test_time_conversions.py +++ b/tests/utils/test_time_conversions.py @@ -11,7 +11,7 @@ import numpy as np -from gnss_lib_py.parsers.navdata import NavData +from gnss_lib_py.navdata.navdata import NavData import gnss_lib_py.utils.time_conversions as tc from gnss_lib_py.utils.constants import GPS_EPOCH_0 diff --git a/tests/utils/test_visualizations.py b/tests/utils/test_visualizations.py deleted file mode 100644 index 0f4098d8..00000000 --- a/tests/utils/test_visualizations.py +++ /dev/null @@ -1,590 +0,0 @@ -"""Tests for visualizations. - -""" - -__authors__ = "D. Knowles" -__date__ = "22 Jun 2022" - -import os -import random - -import pytest -import numpy as np -import plotly.graph_objects as go -from pytest_lazyfixture import lazy_fixture -import matplotlib as mpl -import matplotlib.pyplot as plt - -import gnss_lib_py.utils.visualizations as viz -from gnss_lib_py.algorithms.snapshot import solve_wls -from gnss_lib_py.parsers.navdata import NavData -from gnss_lib_py.parsers.google_decimeter import AndroidDerived2021 -from gnss_lib_py.parsers.google_decimeter import AndroidDerived2022 -from gnss_lib_py.parsers.google_decimeter import AndroidGroundTruth2021 -from gnss_lib_py.utils.coordinates import geodetic_to_ecef - -# pylint: disable=protected-access - -@pytest.fixture(name="root_path") -def fixture_root_path(): - """Location of measurements for unit test - - Returns - ------- - root_path : string - Folder location containing measurements - """ - root_path = os.path.dirname( - os.path.dirname( - os.path.dirname( - os.path.realpath(__file__)))) - root_path = os.path.join(root_path, 'data/unit_test/') - return root_path - -@pytest.fixture(name="derived_path") -def fixture_derived_path(root_path): - """Filepath of Android Derived measurements - - Returns - ------- - derived_path : string - Location for the unit_test Android derived measurements - - Notes - ----- - Test data is a subset of the Android Raw Measurement Dataset [1]_, - particularly the train/2020-05-14-US-MTV-1/Pixel4 trace. The dataset - was retrieved from - https://www.kaggle.com/c/google-smartphone-decimeter-challenge/data - - References - ---------- - .. [1] Fu, Guoyu Michael, Mohammed Khider, and Frank van Diggelen. - "Android Raw GNSS Measurement Datasets for Precise Positioning." - Proceedings of the 33rd International Technical Meeting of the - Satellite Division of The Institute of Navigation (ION GNSS+ - 2020). 2020. - """ - derived_path = os.path.join(root_path, 'google_decimeter_2021', - 'Pixel4_derived.csv') - return derived_path - -@pytest.fixture(name="derived_path_xl") -def fixture_derived_path_xl(root_path): - """Filepath of Android Derived measurements - - Parameters - ---------- - root_path : string - Path of testing dataset root path - - Returns - ------- - derived_path : string - Location for the unit_test Android derived measurements - - Notes - ----- - Test data is a subset of the Android Raw Measurement Dataset [6]_, - particularly the train/2020-05-14-US-MTV-1/Pixel4XL trace. The - dataset was retrieved from - https://www.kaggle.com/c/google-smartphone-decimeter-challenge/data - - References - ---------- - .. [6] Fu, Guoyu Michael, Mohammed Khider, and Frank van Diggelen. - "Android Raw GNSS Measurement Datasets for Precise Positioning." - Proceedings of the 33rd International Technical Meeting of the - Satellite Division of The Institute of Navigation (ION GNSS+ - 2020). 2020. - """ - derived_path = os.path.join(root_path, 'google_decimeter_2021', - 'Pixel4XL_derived.csv') - return derived_path - -@pytest.fixture(name="root_path_2022") -def fixture_root_path_2022(): - """Location of measurements for unit test - - Returns - ------- - root_path : string - Folder location containing measurements - """ - root_path = os.path.dirname( - os.path.dirname( - os.path.dirname( - os.path.realpath(__file__)))) - root_path = os.path.join(root_path, 'data/unit_test/google_decimeter_2022') - return root_path - - -@pytest.fixture(name="derived_2022_path") -def fixture_derived_2022_path(root_path_2022): - """Filepath of Android Derived measurements - - Returns - ------- - derived_path : string - Location for the unit_test Android derived 2022 measurements - - Notes - ----- - Test data is a subset of the Android Raw Measurement Dataset [4]_, - from the 2022 Decimeter Challenge. Particularly, the - train/2021-04-29-MTV-2/SamsungGalaxyS20Ultra trace. The dataset - was retrieved from - https://www.kaggle.com/competitions/smartphone-decimeter-2022/data - - References - ---------- - .. [4] Fu, Guoyu Michael, Mohammed Khider, and Frank van Diggelen. - "Android Raw GNSS Measurement Datasets for Precise Positioning." - Proceedings of the 33rd International Technical Meeting of the - Satellite Division of The Institute of Navigation (ION GNSS+ - 2020). 2020. - """ - derived_path = os.path.join(root_path_2022, 'device_gnss.csv') - return derived_path - -@pytest.fixture(name="derived") -def fixture_load_derived(derived_path): - """Load instance of AndroidDerived2021 - - Parameters - ---------- - derived_path : pytest.fixture - String with location of Android derived measurement file - - Returns - ------- - derived : AndroidDerived2021 - Instance of AndroidDerived2021 for testing - """ - derived = AndroidDerived2021(derived_path) - return derived - -@pytest.fixture(name="derived_xl") -def fixture_load_derived_xl(derived_path_xl): - """Load instance of AndroidDerived2021 - - Parameters - ---------- - derived_path : pytest.fixture - String with location of Android derived measurement file - - Returns - ------- - derived : AndroidDerived2021 - Instance of AndroidDerived2021 for testing - """ - derived = AndroidDerived2021(derived_path_xl, - remove_timing_outliers=False) - return derived - -@pytest.fixture(name="derived_2022") -def fixture_load_derived_2022(derived_2022_path): - """Load instance of AndroidDerived2022 - - Parameters - ---------- - derived_path : pytest.fixture - String with location of Android derived measurement file - - Returns - ------- - derived : AndroidDerived2022 - Instance of AndroidDerived2022 for testing - """ - derived = AndroidDerived2022(derived_2022_path) - return derived - - -@pytest.fixture(name="gtruth") -def fixture_load_gtruth(root_path): - """Load instance of AndroidGroundTruth2021 - - Parameters - ---------- - root_path : string - Path of testing dataset root path - - Returns - ------- - gtruth : AndroidGroundTruth2021 - Instance of AndroidGroundTruth2021 for testing - """ - gtruth = AndroidGroundTruth2021(os.path.join(root_path, - 'google_decimeter_2021', - 'Pixel4_ground_truth.csv')) - return gtruth - -@pytest.fixture(name="state_estimate") -def fixture_solve_wls(derived): - """Fixture of WLS state estimate. - - Parameters - ---------- - derived : AndroidDerived2021 - Instance of AndroidDerived2021 for testing. - - Returns - ------- - state_estimate : gnss_lib_py.parsers.navdata.NavData - Estimated receiver position in ECEF frame in meters and the - estimated receiver clock bias also in meters as an instance of - the NavData class with shape (4 x # unique timesteps) and - the following rows: x_rx_m, y_rx_m, z_rx_m, b_rx_m. - - """ - state_estimate = solve_wls(derived) - return state_estimate - -def test_plot_metrics(derived): - """Test for plotting metrics. - - Parameters - ---------- - derived : AndroidDerived2021 - Instance of AndroidDerived2021 for testing. - - """ - test_rows = [ - "raw_pr_m", - ] - - for row in derived.rows: - if not derived.is_str(row): - if row in test_rows: - fig = plt.figure() - for groupby in ["gnss_id",None]: - fig = viz.plot_metric(derived, row, - groupby = groupby, - save=False) - viz.close_figures(fig) - else: - # string rows should cause a KeyError - with pytest.raises(KeyError) as excinfo: - fig = viz.plot_metric(derived, row, save=False) - viz.close_figures(fig) - assert "non-numeric row" in str(excinfo.value) - - with pytest.raises(TypeError) as excinfo: - viz.plot_metric(derived, "raw_pr_m", save=True, prefix=1) - assert "Prefix" in str(excinfo.value) - - for row in derived.rows: - if not derived.is_str(row): - if row in test_rows: - for groupby in ["gnss_id",None]: - fig = viz.plot_metric(derived, "raw_pr_m", row, - groupby=groupby, save=False) - viz.close_figures(fig) - else: - # string rows should cause a KeyError - with pytest.raises(KeyError) as excinfo: - fig = viz.plot_metric(derived, "raw_pr_m", row, save=False) - viz.close_figures(fig) - with pytest.raises(KeyError) as excinfo: - fig = viz.plot_metric(derived, row, "raw_pr_m", save=False) - viz.close_figures(fig) - assert "non-numeric row" in str(excinfo.value) - - viz.close_figures() - - # test repeating figure and average y - fig = plt.figure() - fig = viz.plot_metric(derived, "gps_millis", "raw_pr_m", - fig = fig, - groupby = "gnss_id", - save=False) - fig = viz.plot_metric(derived, "gps_millis", "raw_pr_m", - fig = fig, - groupby = "gnss_id", - avg_y = True, - linestyle="dotted", - save=False, - ) - viz.close_figures(fig) - - with pytest.raises(TypeError) as excinfo: - viz.plot_metric(derived, "raw_pr_m", save=True, prefix=1) - assert "Prefix" in str(excinfo.value) - - with pytest.raises(ValueError) as excinfo: - viz.plot_metric(derived, 'raw_pr_m', row, row, save=False) - - with pytest.raises(TypeError) as excinfo: - viz.plot_metric("derived", 'raw_pr_m', save=False) - assert "NavData" in str(excinfo.value) - -def test_plot_metrics_by_constellation(derived): - """Test for plotting metrics by constellation. - - Parameters - ---------- - derived : AndroidDerived2021 - Instance of AndroidDerived2021 for testing. - - """ - - test_rows = [ - "raw_pr_m", - ] - - for row in derived.rows: - if not derived.is_str(row): - if row in test_rows: - for prefix in ["","test"]: - fig = viz.plot_metric_by_constellation(derived, row, - prefix=prefix,save=False) - viz.close_figures() - else: - # string rows should cause a KeyError - with pytest.raises(KeyError) as excinfo: - fig = viz.plot_metric_by_constellation(derived, row, - save=False) - viz.close_figures(fig) - assert "non-numeric row" in str(excinfo.value) - - with pytest.raises(TypeError) as excinfo: - viz.plot_metric_by_constellation(derived, "raw_pr_m", save=True, - prefix=1) - assert "Prefix" in str(excinfo.value) - - with pytest.raises(TypeError) as excinfo: - viz.plot_metric_by_constellation("derived", "raw_pr_m", save=True) - assert "NavData" in str(excinfo.value) - - derived_no_gnss_id = derived.remove(rows="gnss_id") - with pytest.raises(KeyError) as excinfo: - viz.plot_metric_by_constellation(derived_no_gnss_id, "raw_pr_m", - save=False) - assert "gnss_id" in str(excinfo.value) - - for optional_row in ["sv_id","signal_type",["sv_id","signal_type"]]: - derived_partial = derived.remove(rows=optional_row) - figs = viz.plot_metric_by_constellation(derived_partial, - "raw_pr_m", save=False) - viz.close_figures(figs) - -@pytest.mark.parametrize('navdata',[ - # lazy_fixture('derived_2022'), - lazy_fixture('derived'), - ]) -def test_plot_skyplot(navdata, state_estimate): - """Test for plotting skyplot. - - Parameters - ---------- - navdata : AndroidDerived - Instance of AndroidDerived for testing. - state_estimate : gnss_lib_py.parsers.navdata.NavData - Estimated receiver position in ECEF frame in meters and the - estimated receiver clock bias also in meters as an instance of - the NavData class with shape (4 x # unique timesteps) and - the following rows: x_rx_m, y_rx_m, z_rx_m, b_rx_m. - - """ - - if isinstance(navdata, AndroidDerived2022): - state_estimate = navdata.copy(rows=["gps_millis","x_rx_m","y_rx_m","z_rx_m"]) - - sv_nan = np.unique(navdata["sv_id"])[0] - for col_idx, col in enumerate(navdata): - if col["sv_id"] == sv_nan: - navdata["x_sv_m",col_idx] = np.nan - - # don't save figures - fig = viz.plot_skyplot(navdata.copy(), state_estimate, save=False) - viz.close_figures(fig) - - with pytest.raises(TypeError) as excinfo: - viz.plot_skyplot(navdata.copy(), state_estimate, save=True, prefix=1) - assert "Prefix" in str(excinfo.value) - - with pytest.raises(TypeError) as excinfo: - viz.plot_skyplot("derived", "raw_pr_m", save=True) - assert "NavData" in str(excinfo.value) - - for row in ["x_sv_m","y_sv_m","z_sv_m","gps_millis"]: - derived_removed = navdata.remove(rows=row) - with pytest.raises(KeyError) as excinfo: - viz.plot_skyplot(derived_removed, state_estimate, save=False) - assert row in str(excinfo.value) - - for row in ["x_rx_m","y_rx_m","z_rx_m"]: - row_idx = state_estimate.find_wildcard_indexes(row[:4]+'*'+row[4:])[row[:4]+'*'+row[4:]][0] - state_removed = state_estimate.remove(rows=row_idx) - with pytest.raises(KeyError) as excinfo: - viz.plot_skyplot(navdata, state_removed, save=False) - assert row[:4]+'*'+row[4:] in str(excinfo.value) - assert "Missing" in str(excinfo.value) - - for row in ["x_rx_m","y_rx_m","z_rx_m"]: - state_double = state_estimate.copy() - row_idx = state_estimate.find_wildcard_indexes(row[:4]+'*'+row[4:])[row[:4]+'*'+row[4:]][0] - state_double[row_idx.replace("rx_","rx_gt_")] = state_double[row_idx] - with pytest.raises(KeyError) as excinfo: - viz.plot_skyplot(navdata, state_double, save=False) - assert row[:4]+'*'+row[4:] in str(excinfo.value) - assert "More than 1" in str(excinfo.value) - -def test_skyplot_trim(root_path): - """Test trimming separate time instances for same SV. - - Parameters - ---------- - root_path : string - Folder location containing unit test data - - """ - - sp3_path = os.path.join(root_path,"vis","sp3_g05.csv") - sp3 = NavData(csv_path=sp3_path) - - lat, lon, alt = -77.87386688990695, -34.62755517700375, 0. - x_rx_m, y_rx_m, z_rx_m = geodetic_to_ecef(np.array([[lat,lon,alt]]))[0] - receiver_state = NavData() - receiver_state["gps_millis"] = 0. - receiver_state["x_rx_m"] = x_rx_m - receiver_state["y_rx_m"] = y_rx_m - receiver_state["z_rx_m"] = z_rx_m - - fig = viz.plot_skyplot(sp3,receiver_state) - # verify that two line segments were removed. Should be 57 not 59 - # after trimming the two separated ones. - for child in fig.get_children(): - if isinstance(child,mpl.projections.polar.PolarAxes): - for grandchild in child.get_children(): - if isinstance(grandchild,mpl.collections.LineCollection): - assert len(grandchild.get_array()) == 57 - viz.close_figures() - - fig = viz.plot_skyplot(sp3,receiver_state,trim_options={"az" : 95.}) - # verify that only one line segment was removed. Should be 58 not 59 - # after trimming the one larger than 95 degrees in azimuth separated ones. - for child in fig.get_children(): - if isinstance(child,mpl.projections.polar.PolarAxes): - for grandchild in child.get_children(): - if isinstance(grandchild,mpl.collections.LineCollection): - assert len(grandchild.get_array()) == 58 - viz.close_figures() - - fig = viz.plot_skyplot(sp3,receiver_state,trim_options={"gps_millis" : 3.5E6}) - # verify that only one line segment was removed. Should be 58 not 59 - # after trimming the one larger than 95 degrees in azimuth separated ones. - for child in fig.get_children(): - if isinstance(child,mpl.projections.polar.PolarAxes): - for grandchild in child.get_children(): - if isinstance(grandchild,mpl.collections.LineCollection): - assert len(grandchild.get_array()) == 57 - viz.close_figures() - - - with pytest.raises(TypeError) as excinfo: - viz.plot_skyplot(sp3, receiver_state, step=20.1) - assert "step" in str(excinfo.value) - - -def test_get_label(): - """Test for getting nice labels. - - """ - - assert viz._get_label({"signal_type" : "l1"}) == "L1" - assert viz._get_label({"signal_type" : "g1"}) == "G1" - assert viz._get_label({"signal_type" : "b1i"}) == "B1i" - # shouldn't do lowercase 'i' trick if not signal_type - assert viz._get_label({"random" : "BDS_B1I"}) == "BDS B1I" - - assert viz._get_label({"gnss_id" : "beidou"}) == "BeiDou" - assert viz._get_label({"gnss_id" : "gps"}) == "GPS" - assert viz._get_label({"gnss_id" : "galileo"}) == "Galileo" - - assert viz._get_label({"gnss_id" : "galileo", - "signal_type" : "b1i"}) == "Galileo B1i" - - assert viz._get_label({"row" : "x_rx_m"}) == "X RX [m]" - assert viz._get_label({"row" : "lat_rx_deg"}) == "LAT RX [deg]" - assert viz._get_label({"row" : "vx_sv_mps"}) == "VX SV [m/s]" - - with pytest.raises(TypeError) as excinfo: - viz._get_label(["should","fail"]) - assert "dictionary" in str(excinfo.value) - -def test_sort_gnss_ids(): - """Test sorting GNSS IDs. - - """ - - unsorted_ids = ["galileo","beta","beidou","irnss","gps","unknown","glonass", - "alpha","qzss","sbas"] - sorted_ids = ["gps","glonass","galileo","beidou","qzss","irnss","sbas", - "unknown", "alpha", "beta"] - - assert viz._sort_gnss_ids(unsorted_ids) == sorted_ids - assert viz._sort_gnss_ids(np.array(unsorted_ids)) == sorted_ids - assert viz._sort_gnss_ids(set(unsorted_ids)) == sorted_ids - assert viz._sort_gnss_ids(tuple(unsorted_ids)) == sorted_ids - - for _ in range(100): - random.shuffle(unsorted_ids) - assert viz._sort_gnss_ids(unsorted_ids) == sorted_ids - assert viz._sort_gnss_ids(np.array(unsorted_ids)) == sorted_ids - assert viz._sort_gnss_ids(set(unsorted_ids)) == sorted_ids - assert viz._sort_gnss_ids(tuple(unsorted_ids)) == sorted_ids - -def test_close_figures_fail(): - """Test expected fail conditions. - - """ - - viz.close_figures([]) - - with pytest.raises(TypeError) as excinfo: - viz.close_figures(0.) - assert "figure" in str(excinfo.value) - - -def test_plot_map(gtruth, state_estimate): - """Test for plotting map. - - Parameters - ---------- - gtruth : AndroidGroundTruth2021 - Instance of AndroidGroundTruth2021 for testing - state_estimate : gnss_lib_py.parsers.navdata.NavData - Estimated receiver position in ECEF frame in meters and the - estimated receiver clock bias also in meters as an instance of - the NavData class with shape (4 x # unique timesteps) and - the following rows: x_rx_m, y_rx_m, z_rx_m, b_rx_m. - - """ - - fig = viz.plot_map(gtruth, state_estimate, save=False) - assert isinstance(fig, go.Figure) - - figs = viz.plot_map(gtruth, state_estimate,sections=3, save=False) - for fig in figs: - assert isinstance(fig, go.Figure) - - with pytest.raises(TypeError) as excinfo: - viz.plot_map([], state_estimate, save=False) - assert "NavData" in str(excinfo.value) - assert "Input" in str(excinfo.value) - - for row in ["lat_rx_wls_deg","lon_rx_wls_deg"]: - state_removed = state_estimate.remove(rows=row) - with pytest.raises(KeyError) as excinfo: - viz.plot_map(gtruth, state_removed, save=False) - assert row.replace("rx_wls","*") in str(excinfo.value) - assert "Missing" in str(excinfo.value) - - for row in ["lat_rx_wls_deg","lon_rx_wls_deg"]: - state_double = state_estimate.copy() - state_double[row.replace("rx","2")] = state_double[row] - with pytest.raises(KeyError) as excinfo: - viz.plot_map(gtruth, state_double, save=False) - assert row.replace("rx_wls","*") in str(excinfo.value) - assert "More than 1" in str(excinfo.value) diff --git a/tests/visualizations/conftest.py b/tests/visualizations/conftest.py new file mode 100644 index 00000000..5c959ea9 --- /dev/null +++ b/tests/visualizations/conftest.py @@ -0,0 +1,230 @@ +"""Tests for visualizations. + +""" + +__authors__ = "D. Knowles" +__date__ = "22 Jun 2022" + +import os + +import pytest + +from gnss_lib_py.algorithms.snapshot import solve_wls +from gnss_lib_py.parsers.google_decimeter import AndroidDerived2021 +from gnss_lib_py.parsers.google_decimeter import AndroidDerived2022 +from gnss_lib_py.parsers.google_decimeter import AndroidGroundTruth2021 + +@pytest.fixture(name="root_path") +def fixture_root_path(): + """Location of measurements for unit test + + Returns + ------- + root_path : string + Folder location containing measurements + """ + root_path = os.path.dirname( + os.path.dirname( + os.path.dirname( + os.path.realpath(__file__)))) + root_path = os.path.join(root_path, 'data/unit_test/') + return root_path + +@pytest.fixture(name="derived_path") +def fixture_derived_path(root_path): + """Filepath of Android Derived measurements + + Returns + ------- + derived_path : string + Location for the unit_test Android derived measurements + + Notes + ----- + Test data is a subset of the Android Raw Measurement Dataset [1]_, + particularly the train/2020-05-14-US-MTV-1/Pixel4 trace. The dataset + was retrieved from + https://www.kaggle.com/c/google-smartphone-decimeter-challenge/data + + References + ---------- + .. [1] Fu, Guoyu Michael, Mohammed Khider, and Frank van Diggelen. + "Android Raw GNSS Measurement Datasets for Precise Positioning." + Proceedings of the 33rd International Technical Meeting of the + Satellite Division of The Institute of Navigation (ION GNSS+ + 2020). 2020. + """ + derived_path = os.path.join(root_path, 'google_decimeter_2021', + 'Pixel4_derived.csv') + return derived_path + +@pytest.fixture(name="derived_path_xl") +def fixture_derived_path_xl(root_path): + """Filepath of Android Derived measurements + + Parameters + ---------- + root_path : string + Path of testing dataset root path + + Returns + ------- + derived_path : string + Location for the unit_test Android derived measurements + + Notes + ----- + Test data is a subset of the Android Raw Measurement Dataset [6]_, + particularly the train/2020-05-14-US-MTV-1/Pixel4XL trace. The + dataset was retrieved from + https://www.kaggle.com/c/google-smartphone-decimeter-challenge/data + + References + ---------- + .. [6] Fu, Guoyu Michael, Mohammed Khider, and Frank van Diggelen. + "Android Raw GNSS Measurement Datasets for Precise Positioning." + Proceedings of the 33rd International Technical Meeting of the + Satellite Division of The Institute of Navigation (ION GNSS+ + 2020). 2020. + """ + derived_path = os.path.join(root_path, 'google_decimeter_2021', + 'Pixel4XL_derived.csv') + return derived_path + +@pytest.fixture(name="root_path_2022") +def fixture_root_path_2022(): + """Location of measurements for unit test + + Returns + ------- + root_path : string + Folder location containing measurements + """ + root_path = os.path.dirname( + os.path.dirname( + os.path.dirname( + os.path.realpath(__file__)))) + root_path = os.path.join(root_path, 'data/unit_test/google_decimeter_2022') + return root_path + + +@pytest.fixture(name="derived_2022_path") +def fixture_derived_2022_path(root_path_2022): + """Filepath of Android Derived measurements + + Returns + ------- + derived_path : string + Location for the unit_test Android derived 2022 measurements + + Notes + ----- + Test data is a subset of the Android Raw Measurement Dataset [4]_, + from the 2022 Decimeter Challenge. Particularly, the + train/2021-04-29-MTV-2/SamsungGalaxyS20Ultra trace. The dataset + was retrieved from + https://www.kaggle.com/competitions/smartphone-decimeter-2022/data + + References + ---------- + .. [4] Fu, Guoyu Michael, Mohammed Khider, and Frank van Diggelen. + "Android Raw GNSS Measurement Datasets for Precise Positioning." + Proceedings of the 33rd International Technical Meeting of the + Satellite Division of The Institute of Navigation (ION GNSS+ + 2020). 2020. + """ + derived_path = os.path.join(root_path_2022, 'device_gnss.csv') + return derived_path + +@pytest.fixture(name="derived") +def fixture_load_derived(derived_path): + """Load instance of AndroidDerived2021 + + Parameters + ---------- + derived_path : pytest.fixture + String with location of Android derived measurement file + + Returns + ------- + derived : AndroidDerived2021 + Instance of AndroidDerived2021 for testing + """ + derived = AndroidDerived2021(derived_path) + return derived + +@pytest.fixture(name="derived_xl") +def fixture_load_derived_xl(derived_path_xl): + """Load instance of AndroidDerived2021 + + Parameters + ---------- + derived_path : pytest.fixture + String with location of Android derived measurement file + + Returns + ------- + derived : AndroidDerived2021 + Instance of AndroidDerived2021 for testing + """ + derived = AndroidDerived2021(derived_path_xl, + remove_timing_outliers=False) + return derived + +@pytest.fixture(name="derived_2022") +def fixture_load_derived_2022(derived_2022_path): + """Load instance of AndroidDerived2022 + + Parameters + ---------- + derived_path : pytest.fixture + String with location of Android derived measurement file + + Returns + ------- + derived : AndroidDerived2022 + Instance of AndroidDerived2022 for testing + """ + derived = AndroidDerived2022(derived_2022_path) + return derived + + +@pytest.fixture(name="gtruth") +def fixture_load_gtruth(root_path): + """Load instance of AndroidGroundTruth2021 + + Parameters + ---------- + root_path : string + Path of testing dataset root path + + Returns + ------- + gtruth : AndroidGroundTruth2021 + Instance of AndroidGroundTruth2021 for testing + """ + gtruth = AndroidGroundTruth2021(os.path.join(root_path, + 'google_decimeter_2021', + 'Pixel4_ground_truth.csv')) + return gtruth + +@pytest.fixture(name="state_estimate") +def fixture_solve_wls(derived): + """Fixture of WLS state estimate. + + Parameters + ---------- + derived : AndroidDerived2021 + Instance of AndroidDerived2021 for testing. + + Returns + ------- + state_estimate : gnss_lib_py.navdata.navdata.NavData + Estimated receiver position in ECEF frame in meters and the + estimated receiver clock bias also in meters as an instance of + the NavData class with shape (4 x # unique timesteps) and + the following rows: x_rx_m, y_rx_m, z_rx_m, b_rx_m. + + """ + state_estimate = solve_wls(derived) + return state_estimate diff --git a/tests/visualizations/test_plot_map.py b/tests/visualizations/test_plot_map.py new file mode 100644 index 00000000..c0874268 --- /dev/null +++ b/tests/visualizations/test_plot_map.py @@ -0,0 +1,53 @@ +"""Tests for visualizations. + +""" + +__authors__ = "D. Knowles" +__date__ = "22 Jun 2022" + +import pytest + +import plotly.graph_objects as go +from gnss_lib_py.visualizations import plot_map + +def test_plot_map(gtruth, state_estimate): + """Test for plotting map. + + Parameters + ---------- + gtruth : AndroidGroundTruth2021 + Instance of AndroidGroundTruth2021 for testing + state_estimate : gnss_lib_py.navdata.navdata.NavData + Estimated receiver position in ECEF frame in meters and the + estimated receiver clock bias also in meters as an instance of + the NavData class with shape (4 x # unique timesteps) and + the following rows: x_rx_m, y_rx_m, z_rx_m, b_rx_m. + + """ + + fig = plot_map.plot_map(gtruth, state_estimate, save=False) + assert isinstance(fig, go.Figure) + + figs = plot_map.plot_map(gtruth, state_estimate,sections=3, save=False) + for fig in figs: + assert isinstance(fig, go.Figure) + + with pytest.raises(TypeError) as excinfo: + plot_map.plot_map([], state_estimate, save=False) + assert "NavData" in str(excinfo.value) + assert "Input" in str(excinfo.value) + + for row in ["lat_rx_wls_deg","lon_rx_wls_deg"]: + state_removed = state_estimate.remove(rows=row) + with pytest.raises(KeyError) as excinfo: + plot_map.plot_map(gtruth, state_removed, save=False) + assert row.replace("rx_wls","*") in str(excinfo.value) + assert "Missing" in str(excinfo.value) + + for row in ["lat_rx_wls_deg","lon_rx_wls_deg"]: + state_double = state_estimate.copy() + state_double[row.replace("rx","2")] = state_double[row] + with pytest.raises(KeyError) as excinfo: + plot_map.plot_map(gtruth, state_double, save=False) + assert row.replace("rx_wls","*") in str(excinfo.value) + assert "More than 1" in str(excinfo.value) diff --git a/tests/visualizations/test_plot_metric.py b/tests/visualizations/test_plot_metric.py new file mode 100644 index 00000000..ff0b2572 --- /dev/null +++ b/tests/visualizations/test_plot_metric.py @@ -0,0 +1,140 @@ +"""Tests for visualizations. + +""" + +__authors__ = "D. Knowles" +__date__ = "22 Jun 2022" + +import pytest +import matplotlib.pyplot as plt + +from gnss_lib_py.visualizations import style +import gnss_lib_py.visualizations.plot_metric as metric + +def test_plot_metrics(derived): + """Test for plotting metrics. + + Parameters + ---------- + derived : AndroidDerived2021 + Instance of AndroidDerived2021 for testing. + + """ + test_rows = [ + "raw_pr_m", + ] + + for row in derived.rows: + if not derived.is_str(row): + if row in test_rows: + fig = plt.figure() + for groupby in ["gnss_id",None]: + fig = metric.plot_metric(derived, row, + groupby = groupby, + save=False) + style.close_figures(fig) + else: + # string rows should cause a KeyError + with pytest.raises(KeyError) as excinfo: + fig = metric.plot_metric(derived, row, save=False) + style.close_figures(fig) + assert "non-numeric row" in str(excinfo.value) + + with pytest.raises(TypeError) as excinfo: + metric.plot_metric(derived, "raw_pr_m", save=True, prefix=1) + assert "Prefix" in str(excinfo.value) + + for row in derived.rows: + if not derived.is_str(row): + if row in test_rows: + for groupby in ["gnss_id",None]: + fig = metric.plot_metric(derived, "raw_pr_m", row, + groupby=groupby, save=False) + style.close_figures(fig) + else: + # string rows should cause a KeyError + with pytest.raises(KeyError) as excinfo: + fig = metric.plot_metric(derived, "raw_pr_m", row, save=False) + style.close_figures(fig) + with pytest.raises(KeyError) as excinfo: + fig = metric.plot_metric(derived, row, "raw_pr_m", save=False) + style.close_figures(fig) + assert "non-numeric row" in str(excinfo.value) + + style.close_figures() + + # test repeating figure and average y + fig = plt.figure() + fig = metric.plot_metric(derived, "gps_millis", "raw_pr_m", + fig = fig, + groupby = "gnss_id", + save=False) + fig = metric.plot_metric(derived, "gps_millis", "raw_pr_m", + fig = fig, + groupby = "gnss_id", + avg_y = True, + linestyle="dotted", + save=False, + ) + style.close_figures(fig) + + with pytest.raises(TypeError) as excinfo: + metric.plot_metric(derived, "raw_pr_m", save=True, prefix=1) + assert "Prefix" in str(excinfo.value) + + with pytest.raises(ValueError) as excinfo: + metric.plot_metric(derived, 'raw_pr_m', row, row, save=False) + + with pytest.raises(TypeError) as excinfo: + metric.plot_metric("derived", 'raw_pr_m', save=False) + assert "NavData" in str(excinfo.value) + +def test_plot_metrics_by_constellation(derived): + """Test for plotting metrics by constellation. + + Parameters + ---------- + derived : AndroidDerived2021 + Instance of AndroidDerived2021 for testing. + + """ + + test_rows = [ + "raw_pr_m", + ] + + for row in derived.rows: + if not derived.is_str(row): + if row in test_rows: + for prefix in ["","test"]: + fig = metric.plot_metric_by_constellation(derived, row, + prefix=prefix,save=False) + style.close_figures() + else: + # string rows should cause a KeyError + with pytest.raises(KeyError) as excinfo: + fig = metric.plot_metric_by_constellation(derived, row, + save=False) + style.close_figures(fig) + assert "non-numeric row" in str(excinfo.value) + + with pytest.raises(TypeError) as excinfo: + metric.plot_metric_by_constellation(derived, "raw_pr_m", save=True, + prefix=1) + assert "Prefix" in str(excinfo.value) + + with pytest.raises(TypeError) as excinfo: + metric.plot_metric_by_constellation("derived", "raw_pr_m", save=True) + assert "NavData" in str(excinfo.value) + + derived_no_gnss_id = derived.remove(rows="gnss_id") + with pytest.raises(KeyError) as excinfo: + metric.plot_metric_by_constellation(derived_no_gnss_id, "raw_pr_m", + save=False) + assert "gnss_id" in str(excinfo.value) + + for optional_row in ["sv_id","signal_type",["sv_id","signal_type"]]: + derived_partial = derived.remove(rows=optional_row) + figs = metric.plot_metric_by_constellation(derived_partial, + "raw_pr_m", save=False) + style.close_figures(figs) diff --git a/tests/visualizations/test_plot_skyplot.py b/tests/visualizations/test_plot_skyplot.py new file mode 100644 index 00000000..1a93c6df --- /dev/null +++ b/tests/visualizations/test_plot_skyplot.py @@ -0,0 +1,138 @@ +"""Tests for visualizations. + +""" + +__authors__ = "D. Knowles" +__date__ = "22 Jun 2022" + +import os + +import pytest +import numpy as np +import matplotlib as mpl +from pytest_lazyfixture import lazy_fixture + +from gnss_lib_py.navdata.navdata import NavData +from gnss_lib_py.navdata.operations import find_wildcard_indexes +from gnss_lib_py.visualizations import style +from gnss_lib_py.visualizations import plot_skyplot +from gnss_lib_py.utils.coordinates import geodetic_to_ecef +from gnss_lib_py.parsers.google_decimeter import AndroidDerived2022 + +@pytest.mark.parametrize('navdata',[ + # lazy_fixture('derived_2022'), + lazy_fixture('derived'), + ]) +def test_plot_skyplot(navdata, state_estimate): + """Test for plotting skyplot. + + Parameters + ---------- + navdata : AndroidDerived + Instance of AndroidDerived for testing. + state_estimate : gnss_lib_py.navdata.navdata.NavData + Estimated receiver position in ECEF frame in meters and the + estimated receiver clock bias also in meters as an instance of + the NavData class with shape (4 x # unique timesteps) and + the following rows: x_rx_m, y_rx_m, z_rx_m, b_rx_m. + + """ + + if isinstance(navdata, AndroidDerived2022): + state_estimate = navdata.copy(rows=["gps_millis","x_rx_m","y_rx_m","z_rx_m"]) + + sv_nan = np.unique(navdata["sv_id"])[0] + for col_idx, col in enumerate(navdata): + if col["sv_id"] == sv_nan: + navdata["x_sv_m",col_idx] = np.nan + + # don't save figures + fig = plot_skyplot.plot_skyplot(navdata.copy(), state_estimate, save=False) + style.close_figures(fig) + + with pytest.raises(TypeError) as excinfo: + plot_skyplot.plot_skyplot(navdata.copy(), state_estimate, save=True, prefix=1) + assert "Prefix" in str(excinfo.value) + + with pytest.raises(TypeError) as excinfo: + plot_skyplot.plot_skyplot("derived", "raw_pr_m", save=True) + assert "NavData" in str(excinfo.value) + + for row in ["x_sv_m","y_sv_m","z_sv_m","gps_millis"]: + derived_removed = navdata.remove(rows=row) + with pytest.raises(KeyError) as excinfo: + plot_skyplot.plot_skyplot(derived_removed, state_estimate, save=False) + assert row in str(excinfo.value) + + for row in ["x_rx_m","y_rx_m","z_rx_m"]: + row_idx = find_wildcard_indexes(state_estimate,row[:4]+'*'+row[4:])[row[:4]+'*'+row[4:]][0] + state_removed = state_estimate.remove(rows=row_idx) + with pytest.raises(KeyError) as excinfo: + plot_skyplot.plot_skyplot(navdata, state_removed, save=False) + assert row[:4]+'*'+row[4:] in str(excinfo.value) + assert "Missing" in str(excinfo.value) + + for row in ["x_rx_m","y_rx_m","z_rx_m"]: + state_double = state_estimate.copy() + row_idx = find_wildcard_indexes(state_estimate,row[:4]+'*'+row[4:])[row[:4]+'*'+row[4:]][0] + state_double[row_idx.replace("rx_","rx_gt_")] = state_double[row_idx] + with pytest.raises(KeyError) as excinfo: + plot_skyplot.plot_skyplot(navdata, state_double, save=False) + assert row[:4]+'*'+row[4:] in str(excinfo.value) + assert "More than 1" in str(excinfo.value) + +def test_skyplot_trim(root_path): + """Test trimming separate time instances for same SV. + + Parameters + ---------- + root_path : string + Folder location containing unit test data + + """ + + sp3_path = os.path.join(root_path,"vis","sp3_g05.csv") + sp3 = NavData(csv_path=sp3_path) + + lat, lon, alt = -77.87386688990695, -34.62755517700375, 0. + x_rx_m, y_rx_m, z_rx_m = geodetic_to_ecef(np.array([[lat,lon,alt]]))[0] + receiver_state = NavData() + receiver_state["gps_millis"] = 0. + receiver_state["x_rx_m"] = x_rx_m + receiver_state["y_rx_m"] = y_rx_m + receiver_state["z_rx_m"] = z_rx_m + + fig = plot_skyplot.plot_skyplot(sp3,receiver_state) + # verify that two line segments were removed. Should be 57 not 59 + # after trimming the two separated ones. + for child in fig.get_children(): + if isinstance(child,mpl.projections.polar.PolarAxes): + for grandchild in child.get_children(): + if isinstance(grandchild,mpl.collections.LineCollection): + assert len(grandchild.get_array()) == 57 + style.close_figures() + + fig = plot_skyplot.plot_skyplot(sp3,receiver_state,trim_options={"az" : 95.}) + # verify that only one line segment was removed. Should be 58 not 59 + # after trimming the one larger than 95 degrees in azimuth separated ones. + for child in fig.get_children(): + if isinstance(child,mpl.projections.polar.PolarAxes): + for grandchild in child.get_children(): + if isinstance(grandchild,mpl.collections.LineCollection): + assert len(grandchild.get_array()) == 58 + style.close_figures() + + fig = plot_skyplot.plot_skyplot(sp3,receiver_state,trim_options={"gps_millis" : 3.5E6}) + # verify that only one line segment was removed. Should be 58 not 59 + # after trimming the one larger than 95 degrees in azimuth separated ones. + for child in fig.get_children(): + if isinstance(child,mpl.projections.polar.PolarAxes): + for grandchild in child.get_children(): + if isinstance(grandchild,mpl.collections.LineCollection): + assert len(grandchild.get_array()) == 57 + style.close_figures() + + + with pytest.raises(TypeError) as excinfo: + plot_skyplot.plot_skyplot(sp3, receiver_state, step=20.1) + assert "step" in str(excinfo.value) diff --git a/tests/visualizations/test_style.py b/tests/visualizations/test_style.py new file mode 100644 index 00000000..f2eca141 --- /dev/null +++ b/tests/visualizations/test_style.py @@ -0,0 +1,74 @@ +"""Tests for visualizations. + +""" + +__authors__ = "D. Knowles" +__date__ = "22 Jun 2022" + +import random + +import pytest +import numpy as np + +from gnss_lib_py.visualizations import style + +# pylint: disable=protected-access + +def testget_label(): + """Test for getting nice labels. + + """ + + assert style.get_label({"signal_type" : "l1"}) == "L1" + assert style.get_label({"signal_type" : "g1"}) == "G1" + assert style.get_label({"signal_type" : "b1i"}) == "B1i" + # shouldn't do lowercase 'i' trick if not signal_type + assert style.get_label({"random" : "BDS_B1I"}) == "BDS B1I" + + assert style.get_label({"gnss_id" : "beidou"}) == "BeiDou" + assert style.get_label({"gnss_id" : "gps"}) == "GPS" + assert style.get_label({"gnss_id" : "galileo"}) == "Galileo" + + assert style.get_label({"gnss_id" : "galileo", + "signal_type" : "b1i"}) == "Galileo B1i" + + assert style.get_label({"row" : "x_rx_m"}) == "X RX [m]" + assert style.get_label({"row" : "lat_rx_deg"}) == "LAT RX [deg]" + assert style.get_label({"row" : "vx_sv_mps"}) == "VX SV [m/s]" + + with pytest.raises(TypeError) as excinfo: + style.get_label(["should","fail"]) + assert "dictionary" in str(excinfo.value) + +def testsort_gnss_ids(): + """Test sorting GNSS IDs. + + """ + + unsorted_ids = ["galileo","beta","beidou","irnss","gps","unknown","glonass", + "alpha","qzss","sbas"] + sorted_ids = ["gps","glonass","galileo","beidou","qzss","irnss","sbas", + "unknown", "alpha", "beta"] + + assert style.sort_gnss_ids(unsorted_ids) == sorted_ids + assert style.sort_gnss_ids(np.array(unsorted_ids)) == sorted_ids + assert style.sort_gnss_ids(set(unsorted_ids)) == sorted_ids + assert style.sort_gnss_ids(tuple(unsorted_ids)) == sorted_ids + + for _ in range(100): + random.shuffle(unsorted_ids) + assert style.sort_gnss_ids(unsorted_ids) == sorted_ids + assert style.sort_gnss_ids(np.array(unsorted_ids)) == sorted_ids + assert style.sort_gnss_ids(set(unsorted_ids)) == sorted_ids + assert style.sort_gnss_ids(tuple(unsorted_ids)) == sorted_ids + +def test_close_figures_fail(): + """Test expected fail conditions. + + """ + + style.close_figures([]) + + with pytest.raises(TypeError) as excinfo: + style.close_figures(0.) + assert "figure" in str(excinfo.value)