From fd9140093d15c79c0f8c955301e6fb5b340b7c38 Mon Sep 17 00:00:00 2001 From: Brian Bjarke Jensen Date: Mon, 20 May 2024 19:19:29 +0200 Subject: [PATCH] moved webui and updated poetry packages --- model/src/dataloader.py | 2 +- model/src/models/visual_communication.py | 22 +- poetry.lock | 212 +++++++------- pyproject.toml | 25 +- {core => shared}/__init__.py | 0 {core => shared}/database/__init__.py | 0 {core => shared}/database/classes/__init__.py | 0 {core => shared}/database/classes/dataset.py | 0 .../database/classes/exceptions.py | 0 .../database/classes/visual_communication.py | 2 +- {core => shared}/database/utils/__init__.py | 0 {core => shared}/database/utils/connect.py | 0 .../database/utils/count_documents.py | 0 .../database/utils/get_dataset.py | 0 .../utils/get_visual_communication.py | 4 +- {core => shared}/database/utils/list_names.py | 0 .../database/utils/save_dataset.py | 4 +- .../database/utils/upsert_annotation.py | 2 +- .../database/utils/upsert_prediction.py | 2 +- .../utils/upsert_visual_communication.py | 2 +- {core => shared}/dto/__init__.py | 0 {core => shared}/dto/angle.py | 0 {core => shared}/dto/contact.py | 0 {core => shared}/dto/data_model.py | 0 {core => shared}/dto/distance.py | 0 {core => shared}/dto/framing.py | 0 {core => shared}/dto/information_value.py | 0 {core => shared}/dto/modality_color.py | 0 {core => shared}/dto/modality_depth.py | 0 {core => shared}/dto/modality_lighting.py | 0 {core => shared}/dto/model_data.py | 0 {core => shared}/dto/point_of_view.py | 0 {core => shared}/dto/salience.py | 0 {core => shared}/dto/visual_syntax.py | 0 shared/utils/__init__.py | 4 + shared/utils/check_env.py | 24 ++ shared/utils/setup_logging.py | 21 ++ tests/generate_random_prediction_test.py | 2 +- tests/get_visual_communication_test.py | 4 +- tests/image_download_test.py | 4 +- tests/image_upload_test.py | 4 +- tests/image_upload_to_server_test.py | 4 +- tests/model_outputs_from_annotation_test.py | 2 +- tests/prediction_upload_test.py | 6 +- tests/total_annotated_test.py | 4 +- tests/total_documents_test.py | 4 +- web_ui/Dockerfile | 5 +- web_ui/docker-compose.server.yml | 13 + web_ui/src/app/__init__.py | 2 +- web_ui/src/app/app.py | 251 ----------------- web_ui/src/app/init_app.py | 260 ++++++++++++++++++ web_ui/src/app/layout/labels.py | 22 +- web_ui/src/app/layout/stores.py | 4 +- web_ui/src/main.py | 54 +--- 54 files changed, 511 insertions(+), 460 deletions(-) rename {core => shared}/__init__.py (100%) rename {core => shared}/database/__init__.py (100%) rename {core => shared}/database/classes/__init__.py (100%) rename {core => shared}/database/classes/dataset.py (100%) rename {core => shared}/database/classes/exceptions.py (100%) rename {core => shared}/database/classes/visual_communication.py (98%) rename {core => shared}/database/utils/__init__.py (100%) rename {core => shared}/database/utils/connect.py (100%) rename {core => shared}/database/utils/count_documents.py (100%) rename {core => shared}/database/utils/get_dataset.py (100%) rename {core => shared}/database/utils/get_visual_communication.py (90%) rename {core => shared}/database/utils/list_names.py (100%) rename {core => shared}/database/utils/save_dataset.py (89%) rename {core => shared}/database/utils/upsert_annotation.py (94%) rename {core => shared}/database/utils/upsert_prediction.py (94%) rename {core => shared}/database/utils/upsert_visual_communication.py (91%) rename {core => shared}/dto/__init__.py (100%) rename {core => shared}/dto/angle.py (100%) rename {core => shared}/dto/contact.py (100%) rename {core => shared}/dto/data_model.py (100%) rename {core => shared}/dto/distance.py (100%) rename {core => shared}/dto/framing.py (100%) rename {core => shared}/dto/information_value.py (100%) rename {core => shared}/dto/modality_color.py (100%) rename {core => shared}/dto/modality_depth.py (100%) rename {core => shared}/dto/modality_lighting.py (100%) rename {core => shared}/dto/model_data.py (100%) rename {core => shared}/dto/point_of_view.py (100%) rename {core => shared}/dto/salience.py (100%) rename {core => shared}/dto/visual_syntax.py (100%) create mode 100644 shared/utils/__init__.py create mode 100644 shared/utils/check_env.py create mode 100644 shared/utils/setup_logging.py create mode 100644 web_ui/docker-compose.server.yml delete mode 100644 web_ui/src/app/app.py create mode 100644 web_ui/src/app/init_app.py diff --git a/model/src/dataloader.py b/model/src/dataloader.py index 77c433e..681e885 100644 --- a/model/src/dataloader.py +++ b/model/src/dataloader.py @@ -14,7 +14,7 @@ from torchvision.transforms.functional import pad from torchvision.transforms.functional import resize from torchvision.transforms.functional import rotate -from core.database import connect +from shared.database import connect # resnet18 original normalization values RESNET_NORMALIZE_MEAN = [0.485, 0.456, 0.406] diff --git a/model/src/models/visual_communication.py b/model/src/models/visual_communication.py index 8d666f5..70c9ae6 100644 --- a/model/src/models/visual_communication.py +++ b/model/src/models/visual_communication.py @@ -14,17 +14,17 @@ from .point_of_view import PointOfViewTail from .resnet18_head import ResNet18Head from .salience import SalienceTail from .visual_syntax import VisualSyntaxTail -from core.dto import AngleData -from core.dto import ContactData -from core.dto import DistanceData -from core.dto import FramingData -from core.dto import InformationValueData -from core.dto import ModalityColorData -from core.dto import ModalityDepthData -from core.dto import ModalityLightingData -from core.dto import PointOfViewData -from core.dto import SalienceData -from core.dto import VisualSyntaxData +from shared.dto import AngleData +from shared.dto import ContactData +from shared.dto import DistanceData +from shared.dto import FramingData +from shared.dto import InformationValueData +from shared.dto import ModalityColorData +from shared.dto import ModalityDepthData +from shared.dto import ModalityLightingData +from shared.dto import PointOfViewData +from shared.dto import SalienceData +from shared.dto import VisualSyntaxData class VisualCommunicationModel(nn.Module): diff --git a/poetry.lock b/poetry.lock index 44b9a9a..3dad6fc 100644 --- a/poetry.lock +++ b/poetry.lock @@ -32,13 +32,13 @@ tests-no-zope = ["attrs[tests-mypy]", "cloudpickle", "hypothesis", "pympler", "p [[package]] name = "blinker" -version = "1.8.1" +version = "1.8.2" description = "Fast, simple object-to-object and broadcast signaling" optional = false python-versions = ">=3.8" files = [ - {file = "blinker-1.8.1-py3-none-any.whl", hash = "sha256:5f1cdeff423b77c31b89de0565cd03e5275a03028f44b2b15f912632a58cced6"}, - {file = "blinker-1.8.1.tar.gz", hash = "sha256:da44ec748222dcd0105ef975eed946da197d5bdf8bafb6aa92f5bc89da63fa25"}, + {file = "blinker-1.8.2-py3-none-any.whl", hash = "sha256:1779309f71bf239144b9399d06ae925637cf6634cf6bd131104184531bf67c01"}, + {file = "blinker-1.8.2.tar.gz", hash = "sha256:8f77b09d3bf7c795e969e9486f39c2c5e9c39d4ee07424be2bc594ece9642d83"}, ] [[package]] @@ -242,13 +242,13 @@ files = [ [[package]] name = "dash" -version = "2.16.1" +version = "2.17.0" description = "A Python framework for building reactive web-apps. Developed by Plotly." optional = false python-versions = ">=3.8" files = [ - {file = "dash-2.16.1-py3-none-any.whl", hash = "sha256:8a9d2a618e415113c0b2a4d25d5dc4df5cb921f733b33dde75559db2316b1df1"}, - {file = "dash-2.16.1.tar.gz", hash = "sha256:b2871d6b8d4c9dfd0a64f89f22d001c93292910b41d92d9ff2bb424a28283976"}, + {file = "dash-2.17.0-py3-none-any.whl", hash = "sha256:2421569023b2cd46ea2d4b2c14fe72c71b7436527a3102219b2265fa361e7c67"}, + {file = "dash-2.17.0.tar.gz", hash = "sha256:d065cd88771e45d0485993be0d27565e08918cb7edd18e31ee1c5b41252fc2fa"}, ] [package.dependencies] @@ -267,7 +267,7 @@ Werkzeug = "<3.1" [package.extras] celery = ["celery[redis] (>=5.1.2)", "redis (>=3.5.3)"] -ci = ["black (==22.3.0)", "dash-dangerously-set-inner-html", "dash-flow-example (==0.0.5)", "flake8 (==7.0.0)", "flaky (==3.7.0)", "flask-talisman (==1.0.0)", "jupyterlab (<4.0.0)", "mimesis (<=11.1.0)", "mock (==4.0.3)", "numpy (<=1.26.3)", "openpyxl", "orjson (==3.9.12)", "pandas (>=1.4.0)", "pyarrow", "pylint (==3.0.3)", "pytest-mock", "pytest-rerunfailures", "pytest-sugar (==0.9.6)", "pyzmq (==25.1.2)", "xlrd (>=2.0.1)"] +ci = ["black (==22.3.0)", "dash-dangerously-set-inner-html", "dash-flow-example (==0.0.5)", "flake8 (==7.0.0)", "flaky (==3.8.1)", "flask-talisman (==1.0.0)", "jupyterlab (<4.0.0)", "mimesis (<=11.1.0)", "mock (==4.0.3)", "numpy (<=1.26.3)", "openpyxl", "orjson (==3.9.12)", "pandas (>=1.4.0)", "pyarrow", "pylint (==3.0.3)", "pytest-mock", "pytest-rerunfailures", "pytest-sugar (==0.9.6)", "pyzmq (==25.1.2)", "xlrd (>=2.0.1)"] compress = ["flask-compress"] dev = ["PyYAML (>=5.4.1)", "coloredlogs (>=15.0.1)", "fire (>=0.4.0)"] diskcache = ["diskcache (>=5.2.1)", "multiprocess (>=0.70.12)", "psutil (>=5.8.0)"] @@ -440,13 +440,13 @@ dotenv = ["python-dotenv"] [[package]] name = "fsspec" -version = "2024.3.1" +version = "2024.5.0" description = "File-system specification" optional = false python-versions = ">=3.8" files = [ - {file = "fsspec-2024.3.1-py3-none-any.whl", hash = "sha256:918d18d41bf73f0e2b261824baeb1b124bcf771767e3a26425cd7dec3332f512"}, - {file = "fsspec-2024.3.1.tar.gz", hash = "sha256:f39780e282d7d117ffb42bb96992f8a90795e4d0fb0f661a70ca39fe9c43ded9"}, + {file = "fsspec-2024.5.0-py3-none-any.whl", hash = "sha256:e0fdbc446d67e182f49a70b82cf7889028a63588fde6b222521f10937b2b670c"}, + {file = "fsspec-2024.5.0.tar.gz", hash = "sha256:1d021b0b0f933e3b3029ed808eb400c08ba101ca2de4b3483fbc9ca23fcee94a"}, ] [package.extras] @@ -454,7 +454,7 @@ abfs = ["adlfs"] adl = ["adlfs"] arrow = ["pyarrow (>=1)"] dask = ["dask", "distributed"] -devel = ["pytest", "pytest-cov"] +dev = ["pre-commit", "ruff"] dropbox = ["dropbox", "dropboxdrivefs", "requests"] full = ["adlfs", "aiohttp (!=4.0.0a0,!=4.0.0a1)", "dask", "distributed", "dropbox", "dropboxdrivefs", "fusepy", "gcsfs", "libarchive-c", "ocifs", "panel", "paramiko", "pyarrow (>=1)", "pygit2", "requests", "s3fs", "smbprotocol", "tqdm"] fuse = ["fusepy"] @@ -471,6 +471,9 @@ s3 = ["s3fs"] sftp = ["paramiko"] smb = ["smbprotocol"] ssh = ["paramiko"] +test = ["aiohttp (!=4.0.0a0,!=4.0.0a1)", "numpy", "pytest", "pytest-asyncio (!=0.22.0)", "pytest-benchmark", "pytest-cov", "pytest-mock", "pytest-recording", "pytest-rerunfailures", "requests"] +test-downstream = ["aiobotocore (>=2.5.4,<3.0.0)", "dask-expr", "dask[dataframe,test]", "moto[server] (>4,<5)", "pytest-timeout", "xarray"] +test-full = ["adlfs", "aiohttp (!=4.0.0a0,!=4.0.0a1)", "cloudpickle", "dask", "distributed", "dropbox", "dropboxdrivefs", "fastparquet", "fusepy", "gcsfs", "jinja2", "kerchunk", "libarchive-c", "lz4", "notebook", "numpy", "ocifs", "pandas", "panel", "paramiko", "pyarrow", "pyarrow (>=1)", "pyftpdlib", "pygit2", "pytest", "pytest-asyncio (!=0.22.0)", "pytest-benchmark", "pytest-cov", "pytest-mock", "pytest-recording", "pytest-rerunfailures", "python-snappy", "requests", "smbprotocol", "tqdm", "urllib3", "zarr", "zstandard"] tqdm = ["tqdm"] [[package]] @@ -547,13 +550,13 @@ files = [ [[package]] name = "jinja2" -version = "3.1.3" +version = "3.1.4" description = "A very fast and expressive template engine." optional = false python-versions = ">=3.7" files = [ - {file = "Jinja2-3.1.3-py3-none-any.whl", hash = "sha256:7d6d50dd97d52cbc355597bd845fabfbac3f551e1f99619e39a35ce8c370b5fa"}, - {file = "Jinja2-3.1.3.tar.gz", hash = "sha256:ac8bd6544d4bb2c9792bf3a159e80bba8fda7f07e81bc3aed565432d5925ba90"}, + {file = "jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d"}, + {file = "jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369"}, ] [package.dependencies] @@ -1113,13 +1116,13 @@ xmp = ["defusedxml"] [[package]] name = "plotly" -version = "5.21.0" +version = "5.22.0" description = "An open-source, interactive data visualization library for Python" optional = false python-versions = ">=3.8" files = [ - {file = "plotly-5.21.0-py3-none-any.whl", hash = "sha256:a33f41fd5922e45b2b253f795b200d14452eb625790bb72d0a72cf1328a6abbf"}, - {file = "plotly-5.21.0.tar.gz", hash = "sha256:69243f8c165d4be26c0df1c6f0b7b258e2dfeefe032763404ad7e7fb7d7c2073"}, + {file = "plotly-5.22.0-py3-none-any.whl", hash = "sha256:68fc1901f098daeb233cc3dd44ec9dc31fb3ca4f4e53189344199c43496ed006"}, + {file = "plotly-5.22.0.tar.gz", hash = "sha256:859fdadbd86b5770ae2466e542b761b247d1c6b49daed765b95bb8c7063e7469"}, ] [package.dependencies] @@ -1282,71 +1285,71 @@ files = [ [[package]] name = "pymongo" -version = "4.7.1" +version = "4.7.2" description = "Python driver for MongoDB " optional = false python-versions = ">=3.7" files = [ - {file = "pymongo-4.7.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f8822614975038e0cece47d12e7634a79c2ee590a0ae78ae64c37b9c6610a14c"}, - {file = "pymongo-4.7.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:57b5b485ef89270ed2e603814f43f0fdd9b8ba5d4039124d90878cdc2327000c"}, - {file = "pymongo-4.7.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9e99dac3c7c2cb498937cc1767361851099da38861e921113318c87d71e3d127"}, - {file = "pymongo-4.7.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:253ed8fd6e7f4b2a1caa89e6b287b9e04f42613319ee1e1240c2db2afe1637e7"}, - {file = "pymongo-4.7.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8cee62188127a126f59ea45d3981868a5e35343be4ef4ad8712eaf42be37a00b"}, - {file = "pymongo-4.7.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31ed8ba3da0366346264604b3a443f5a4232cab5ed45f520bead6184cf0851a1"}, - {file = "pymongo-4.7.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:887d043ecc0c7d5591925bbc7abb67caf21c94d6e6e5d442cb49eb5d9d8ee76b"}, - {file = "pymongo-4.7.1-cp310-cp310-win32.whl", hash = "sha256:bfd5c7e5bb87171a5296fa32205adb50b27704a612036ec4395c3cd316fc0e91"}, - {file = "pymongo-4.7.1-cp310-cp310-win_amd64.whl", hash = "sha256:5ae1aeeb405c29885266666dc7115792d647ed68cfdb6ed02e2e211d12f2e1c8"}, - {file = "pymongo-4.7.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e4a63ba6813a2168ebd35ea5369f6c33f7787525986cd77668b7956acc3d2a38"}, - {file = "pymongo-4.7.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:811a07bba9d35f1e34159ede632ac71dbc429b372a20004e32d6578af872db1a"}, - {file = "pymongo-4.7.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4d227555be35078b53f506f6b58bd0b0e8fd4513e89e6f29e83a97efab439250"}, - {file = "pymongo-4.7.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:daf35ab13b86aba7cc8c4b019882f1fa8d287a26f586ef5eaf60a5233d3eaa52"}, - {file = "pymongo-4.7.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aa354933a158e57494c98b592f46d5d24d1b109e6ba05a05179cde719d9f7fd3"}, - {file = "pymongo-4.7.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad360630c221aee7c0841a51851496a3ca6fdea87007098a982c1aa26e34083a"}, - {file = "pymongo-4.7.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5119c66af8c4197c8757b4b7d98c443e5b127c224ac92fb657dbe2b512ae2713"}, - {file = "pymongo-4.7.1-cp311-cp311-win32.whl", hash = "sha256:11f74dafde63ad2dc30c01f40b4c69d9af157f8ba5224b0c9d4de7158537266f"}, - {file = "pymongo-4.7.1-cp311-cp311-win_amd64.whl", hash = "sha256:ec94d29103317aa920dae59ed385de9604cb0ef840b5b7137b5eaa7a2042580a"}, - {file = "pymongo-4.7.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b8b95e2163b73d03a913efa89b0f7c5012be82efd4e9dbce8aa62010a75a277c"}, - {file = "pymongo-4.7.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fb1a884b1c6aeac5ffeb8ccb696fbc242a7ae1bba36f2328c01f76fab7221b94"}, - {file = "pymongo-4.7.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2ccc8dd4fe9aac18dde27c33a53271c6c90159b74c43fbdab1d33d5efc36c2f5"}, - {file = "pymongo-4.7.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7247c1dc7d8eed4e24eb1dd92c4c58ebf1e5159500015652552acfdebdeed256"}, - {file = "pymongo-4.7.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:45ac46f0d6bdc2baac34ced60aae27b2083170d77397330eff0ac5689ea29d38"}, - {file = "pymongo-4.7.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a848249d5b4763497add62f7dd7bd0ce1538129bf42f4cb132a76d24c61bf98d"}, - {file = "pymongo-4.7.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5ff6d56ca1f0cd3687a13ce90a32a8efb3cc3a53728e5ac160c4c30d10385a72"}, - {file = "pymongo-4.7.1-cp312-cp312-win32.whl", hash = "sha256:e175d74c52b6c8414a4b4504a2dd42b0202d101b2eb9508a34c137357683864e"}, - {file = "pymongo-4.7.1-cp312-cp312-win_amd64.whl", hash = "sha256:263c169302df636f9086b584994a51d0adfc8738fe27d7b8e2aacf46fd68b6cb"}, - {file = "pymongo-4.7.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:337d99f88d32a5f8056d6d2bc365ccf09d09583f3942882c50cf11b459e8fbc0"}, - {file = "pymongo-4.7.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:30a9d891631d7e847b24f551b1d89ff2033539e7cd8e9af29714b4d0db7abb06"}, - {file = "pymongo-4.7.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:73bf96ece4999b0bbab7169cb2b9c60918b434487009e48be4bd47eeb2aa7b14"}, - {file = "pymongo-4.7.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3ef32a7cfe748c0c72fdad9e51459de5e0c6b16c5288b39f863abfff23503847"}, - {file = "pymongo-4.7.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:24c8f1dd545360ec1b79007a3ba6573af565df6fde49f6dfc53813f3f475a751"}, - {file = "pymongo-4.7.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b897b60b2d55c26f3efea0effc11b655db68125c3731274bc3953375e9ccab73"}, - {file = "pymongo-4.7.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5a58b6cd7c423ba49db10d8445756062c931ad2246ba0da1e705bf22962fd9e9"}, - {file = "pymongo-4.7.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:ed6b3a0740efe98bb03ccf054578e9788ebcd06d021d548b8217ab2c82e45975"}, - {file = "pymongo-4.7.1-cp37-cp37m-win32.whl", hash = "sha256:85b8dd3756b73993b1e3ab6b1cba826b9e4987a094a5d5b6d37313776458cd94"}, - {file = "pymongo-4.7.1-cp37-cp37m-win_amd64.whl", hash = "sha256:297cdc87c4b4168782b571c8643540e9b0ad1d09266b43d2f5954f8632280835"}, - {file = "pymongo-4.7.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7b10603ba64af08f5af7eb9a69d6b24e3c69d91fdd48c54b95e284686c1c582d"}, - {file = "pymongo-4.7.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:64b69b9cd8a6d23881a80490d575e92918f9afca43096a7d6c1013d6b3e5c75c"}, - {file = "pymongo-4.7.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4c7e05454cdc5aa4702e03cad0df4205daccd6fd631bbbf0a85bbe598129a6cc"}, - {file = "pymongo-4.7.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9e0a30a022ac8a9164ee5a4b761e13dbb3d10a21845f7258011e3415151fb645"}, - {file = "pymongo-4.7.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:13fc201e073644acd77860d9e91ccfc27addf510563e07381cadc9a55ac3a894"}, - {file = "pymongo-4.7.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4dd998e9f0f7694032c1648c7f57fccaa78903df6329b8f8ae20cfa7c4ceca34"}, - {file = "pymongo-4.7.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:455f9d603ed0990a787773d5718e871300bddf585ce543baf129c9f5ca3adb02"}, - {file = "pymongo-4.7.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d804eaf19a65211cc2c8c5db75be685c3f31c64cdab639794f66f13f8e258ba6"}, - {file = "pymongo-4.7.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:a46c08ef0b273c415b1e8933f6739596be264ae700a4927f84e0b84e70fdf0eb"}, - {file = "pymongo-4.7.1-cp38-cp38-win32.whl", hash = "sha256:58989bcb94233233a71645236b972835d4f87a6bb1b7e818d38a7e6e6d4630de"}, - {file = "pymongo-4.7.1-cp38-cp38-win_amd64.whl", hash = "sha256:d63f38454a2e23c117d3ceab3b661568f2418536825787256ad24e5baaedfd27"}, - {file = "pymongo-4.7.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d50969de00d3522b2c394f7e59b843871e2be4b525af92066da7f3bd02799fdc"}, - {file = "pymongo-4.7.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f2a720e787c9b9b912db5bb4c3e7123ccff1352d6c3ac0cb2c7ee392cdc95c00"}, - {file = "pymongo-4.7.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c808098f2cdb87d4035144e536ba5fa7709d0420c17b68e6ace5da18c38ded5f"}, - {file = "pymongo-4.7.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d1829a7db720ff586aaf59c806e89e0a388548063aa844d21a570a231ad8ca87"}, - {file = "pymongo-4.7.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:615c7573d7a9c4837332a673fdc5a5f214b474dd52d846bcf4cc3d011550bee1"}, - {file = "pymongo-4.7.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e935712b17e7a42831022353bac91a346a792658a54e12bec907ec11695cc899"}, - {file = "pymongo-4.7.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dbc32217c81d87750401fa1c2bc9450e854b23e6e30243c82d3514b8e58f39e3"}, - {file = "pymongo-4.7.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5bc87db2e9563295c4e45602ab978a2fcbaba3ab89e745503b24f895cddeb755"}, - {file = "pymongo-4.7.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:65c45682d5ed8c6618bde22cd6716b47a197f4ef800a025213b28d13a59e5fca"}, - {file = "pymongo-4.7.1-cp39-cp39-win32.whl", hash = "sha256:67cbee427c263a4483e3249fef480788ccc16edb1a4fc330c4c6cb0cb9db94a8"}, - {file = "pymongo-4.7.1-cp39-cp39-win_amd64.whl", hash = "sha256:1bd1eef70c1eda838b26397ef75c9580d7a97fd94b6324971d7f3d2ad3552e9a"}, - {file = "pymongo-4.7.1.tar.gz", hash = "sha256:811c41c6227b7548afcb53e1b996c25262d837b5e5f519e2ddc2c7e59d8728a5"}, + {file = "pymongo-4.7.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:268d8578c0500012140c5460755ea405cbfe541ef47c81efa9d6744f0f99aeca"}, + {file = "pymongo-4.7.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:827611beb6c483260d520cfa6a49662d980dfa5368a04296f65fa39e78fccea7"}, + {file = "pymongo-4.7.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a754e366c404d19ff3f077ddeed64be31e0bb515e04f502bf11987f1baa55a16"}, + {file = "pymongo-4.7.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c44efab10d9a3db920530f7bcb26af8f408b7273d2f0214081d3891979726328"}, + {file = "pymongo-4.7.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:35b3f0c7d49724859d4df5f0445818d525824a6cd55074c42573d9b50764df67"}, + {file = "pymongo-4.7.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e37faf298a37ffb3e0809e77fbbb0a32b6a2d18a83c59cfc2a7b794ea1136b0"}, + {file = "pymongo-4.7.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d1bcd58669e56c08f1e72c5758868b5df169fe267501c949ee83c418e9df9155"}, + {file = "pymongo-4.7.2-cp310-cp310-win32.whl", hash = "sha256:c72d16fede22efe7cdd1f422e8da15760e9498024040429362886f946c10fe95"}, + {file = "pymongo-4.7.2-cp310-cp310-win_amd64.whl", hash = "sha256:12d1fef77d25640cb78893d07ff7d2fac4c4461d8eec45bd3b9ad491a1115d6e"}, + {file = "pymongo-4.7.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:fc5af24fcf5fc6f7f40d65446400d45dd12bea933d0299dc9e90c5b22197f1e9"}, + {file = "pymongo-4.7.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:730778b6f0964b164c187289f906bbc84cb0524df285b7a85aa355bbec43eb21"}, + {file = "pymongo-4.7.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:47a1a4832ef2f4346dcd1a10a36ade7367ad6905929ddb476459abb4fd1b98cb"}, + {file = "pymongo-4.7.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e6eab12c6385526d386543d6823b07187fefba028f0da216506e00f0e1855119"}, + {file = "pymongo-4.7.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:37e9ea81fa59ee9274457ed7d59b6c27f6f2a5fe8e26f184ecf58ea52a019cb8"}, + {file = "pymongo-4.7.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e9d9d2c0aae73aa4369bd373ac2ac59f02c46d4e56c4b6d6e250cfe85f76802"}, + {file = "pymongo-4.7.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cb6e00a79dff22c9a72212ad82021b54bdb3b85f38a85f4fc466bde581d7d17a"}, + {file = "pymongo-4.7.2-cp311-cp311-win32.whl", hash = "sha256:02efd1bb3397e24ef2af45923888b41a378ce00cb3a4259c5f4fc3c70497a22f"}, + {file = "pymongo-4.7.2-cp311-cp311-win_amd64.whl", hash = "sha256:87bb453ac3eb44db95cb6d5a616fbc906c1c00661eec7f55696253a6245beb8a"}, + {file = "pymongo-4.7.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:12c466e02133b7f8f4ff1045c6b5916215c5f7923bc83fd6e28e290cba18f9f6"}, + {file = "pymongo-4.7.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f91073049c43d14e66696970dd708d319b86ee57ef9af359294eee072abaac79"}, + {file = "pymongo-4.7.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:87032f818bf5052ab742812c715eff896621385c43f8f97cdd37d15b5d394e95"}, + {file = "pymongo-4.7.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6a87eef394039765679f75c6a47455a4030870341cb76eafc349c5944408c882"}, + {file = "pymongo-4.7.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d275596f840018858757561840767b39272ac96436fcb54f5cac6d245393fd97"}, + {file = "pymongo-4.7.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82102e353be13f1a6769660dd88115b1da382447672ba1c2662a0fbe3df1d861"}, + {file = "pymongo-4.7.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:194065c9d445017b3c82fb85f89aa2055464a080bde604010dc8eb932a6b3c95"}, + {file = "pymongo-4.7.2-cp312-cp312-win32.whl", hash = "sha256:db4380d1e69fdad1044a4b8f3bb105200542c49a0dde93452d938ff9db1d6d29"}, + {file = "pymongo-4.7.2-cp312-cp312-win_amd64.whl", hash = "sha256:fadc6e8db7707c861ebe25b13ad6aca19ea4d2c56bf04a26691f46c23dadf6e4"}, + {file = "pymongo-4.7.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2cb77d09bd012cb4b30636e7e38d00b5f9be5eb521c364bde66490c45ee6c4b4"}, + {file = "pymongo-4.7.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:56bf8b706946952acdea0fe478f8e44f1ed101c4b87f046859e6c3abe6c0a9f4"}, + {file = "pymongo-4.7.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bcf337d1b252405779d9c79978d6ca15eab3cdaa2f44c100a79221bddad97c8a"}, + {file = "pymongo-4.7.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4ffd1519edbe311df73c74ec338de7d294af535b2748191c866ea3a7c484cd15"}, + {file = "pymongo-4.7.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d4d59776f435564159196d971aa89422ead878174aff8fe18e06d9a0bc6d648c"}, + {file = "pymongo-4.7.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:347c49cf7f0ba49ea87c1a5a1984187ecc5516b7c753f31938bf7b37462824fd"}, + {file = "pymongo-4.7.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:84bc00200c3cbb6c98a2bb964c9e8284b641e4a33cf10c802390552575ee21de"}, + {file = "pymongo-4.7.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:fcaf8c911cb29316a02356f89dbc0e0dfcc6a712ace217b6b543805690d2aefd"}, + {file = "pymongo-4.7.2-cp37-cp37m-win32.whl", hash = "sha256:b48a5650ee5320d59f6d570bd99a8d5c58ac6f297a4e9090535f6561469ac32e"}, + {file = "pymongo-4.7.2-cp37-cp37m-win_amd64.whl", hash = "sha256:5239ef7e749f1326ea7564428bf861d5250aa39d7f26d612741b1b1273227062"}, + {file = "pymongo-4.7.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d2dcf608d35644e8d276d61bf40a93339d8d66a0e5f3e3f75b2c155a421a1b71"}, + {file = "pymongo-4.7.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:25eeb2c18ede63891cbd617943dd9e6b9cbccc54f276e0b2e693a0cc40f243c5"}, + {file = "pymongo-4.7.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9349f0bb17a31371d4cacb64b306e4ca90413a3ad1fffe73ac7cd495570d94b5"}, + {file = "pymongo-4.7.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ffd4d7cb2e6c6e100e2b39606d38a9ffc934e18593dc9bb326196afc7d93ce3d"}, + {file = "pymongo-4.7.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9a8bd37f5dabc86efceb8d8cbff5969256523d42d08088f098753dba15f3b37a"}, + {file = "pymongo-4.7.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c78f156edc59b905c80c9003e022e1a764c54fd40ac4fea05b0764f829790e2"}, + {file = "pymongo-4.7.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9d892fb91e81cccb83f507cdb2ea0aa026ec3ced7f12a1d60f6a5bf0f20f9c1f"}, + {file = "pymongo-4.7.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:87832d6076c2c82f42870157414fd876facbb6554d2faf271ffe7f8f30ce7bed"}, + {file = "pymongo-4.7.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:ce1a374ea0e49808e0380ffc64284c0ce0f12bd21042b4bef1af3eb7bdf49054"}, + {file = "pymongo-4.7.2-cp38-cp38-win32.whl", hash = "sha256:eb0642e5f0dd7e86bb358749cc278e70b911e617f519989d346f742dc9520dfb"}, + {file = "pymongo-4.7.2-cp38-cp38-win_amd64.whl", hash = "sha256:4bdb5ffe1cd3728c9479671a067ef44dacafc3743741d4dc700c377c4231356f"}, + {file = "pymongo-4.7.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:743552033c63f0afdb56b9189ab04b5c1dbffd7310cf7156ab98eebcecf24621"}, + {file = "pymongo-4.7.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5239776633f7578b81207e5646245415a5a95f6ae5ef5dff8e7c2357e6264bfc"}, + {file = "pymongo-4.7.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:727ad07952c155cd20045f2ce91143c7dc4fb01a5b4e8012905a89a7da554b0c"}, + {file = "pymongo-4.7.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9385654f01a90f73827af4db90c290a1519f7d9102ba43286e187b373e9a78e9"}, + {file = "pymongo-4.7.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0d833651f1ba938bb7501f13e326b96cfbb7d98867b2d545ca6d69c7664903e0"}, + {file = "pymongo-4.7.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cf17ea9cea14d59b0527403dd7106362917ced7c4ec936c4ba22bd36c912c8e0"}, + {file = "pymongo-4.7.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cecd2df037249d1c74f0af86fb5b766104a5012becac6ff63d85d1de53ba8b98"}, + {file = "pymongo-4.7.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:65b4c00dedbd333698b83cd2095a639a6f0d7c4e2a617988f6c65fb46711f028"}, + {file = "pymongo-4.7.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:d9b6cbc037108ff1a0a867e7670d8513c37f9bcd9ee3d2464411bfabf70ca002"}, + {file = "pymongo-4.7.2-cp39-cp39-win32.whl", hash = "sha256:cf28430ec1924af1bffed37b69a812339084697fd3f3e781074a0148e6475803"}, + {file = "pymongo-4.7.2-cp39-cp39-win_amd64.whl", hash = "sha256:e004527ea42a6b99a8b8d5b42b42762c3bdf80f88fbdb5c3a9d47f3808495b86"}, + {file = "pymongo-4.7.2.tar.gz", hash = "sha256:9024e1661c6e40acf468177bf90ce924d1bc681d2b244adda3ed7b2f4c4d17d7"}, ] [package.dependencies] @@ -1414,13 +1417,13 @@ files = [ [[package]] name = "requests" -version = "2.31.0" +version = "2.32.0" description = "Python HTTP for Humans." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"}, - {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"}, + {file = "requests-2.32.0-py3-none-any.whl", hash = "sha256:f2c3881dddb70d056c5bd7600a4fae312b2a300e39be6a118d30b90bd27262b5"}, + {file = "requests-2.32.0.tar.gz", hash = "sha256:fa5490319474c82ef1d2c9bc459d3652e3ae4ef4c4ebdd18a21145a47ca4b6b8"}, ] [package.dependencies] @@ -1464,13 +1467,13 @@ six = ">=1.7.0" [[package]] name = "selenium" -version = "4.20.0" +version = "4.21.0" description = "" optional = false python-versions = ">=3.8" files = [ - {file = "selenium-4.20.0-py3-none-any.whl", hash = "sha256:b1d0c33b38ca27d0499183e48e1dd09ff26973481f5d3ef2983073813ae6588d"}, - {file = "selenium-4.20.0.tar.gz", hash = "sha256:0bd564ee166980d419a8aaf4ac00289bc152afcf2eadca5efe8c8e36711853fd"}, + {file = "selenium-4.21.0-py3-none-any.whl", hash = "sha256:4770ffe5a5264e609de7dc914be6b89987512040d5a8efb2abb181330d097993"}, + {file = "selenium-4.21.0.tar.gz", hash = "sha256:650dbfa5159895ff00ad16e5ddb6ceecb86b90c7ed2012b3f041f64e6e4904fe"}, ] [package.dependencies] @@ -1545,17 +1548,18 @@ mpmath = ">=0.19" [[package]] name = "tenacity" -version = "8.2.3" +version = "8.3.0" description = "Retry code until it succeeds" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "tenacity-8.2.3-py3-none-any.whl", hash = "sha256:ce510e327a630c9e1beaf17d42e6ffacc88185044ad85cf74c0a8887c6a0f88c"}, - {file = "tenacity-8.2.3.tar.gz", hash = "sha256:5398ef0d78e63f40007c1fb4c0bff96e1911394d2fa8d194f77619c05ff6cc8a"}, + {file = "tenacity-8.3.0-py3-none-any.whl", hash = "sha256:3649f6443dbc0d9b01b9d8020a9c4ec7a1ff5f6f3c6c8a036ef371f573fe9185"}, + {file = "tenacity-8.3.0.tar.gz", hash = "sha256:953d4e6ad24357bceffbc9707bc74349aca9d245f68eb65419cf0c249a1949a2"}, ] [package.extras] -doc = ["reno", "sphinx", "tornado (>=4.5)"] +doc = ["reno", "sphinx"] +test = ["pytest", "tornado (>=4.5)", "typeguard"] [[package]] name = "torch" @@ -1658,13 +1662,13 @@ scipy = ["scipy"] [[package]] name = "trio" -version = "0.25.0" +version = "0.25.1" description = "A friendly Python library for async concurrency and I/O" optional = false python-versions = ">=3.8" files = [ - {file = "trio-0.25.0-py3-none-any.whl", hash = "sha256:e6458efe29cc543e557a91e614e2b51710eba2961669329ce9c862d50c6e8e81"}, - {file = "trio-0.25.0.tar.gz", hash = "sha256:9b41f5993ad2c0e5f62d0acca320ec657fdb6b2a2c22b8c7aed6caf154475c4e"}, + {file = "trio-0.25.1-py3-none-any.whl", hash = "sha256:e42617ba091e7b2e50c899052e83a3c403101841de925187f61e7b7eaebdf3fb"}, + {file = "trio-0.25.1.tar.gz", hash = "sha256:9f5314f014ea3af489e77b001861c535005c3858d38ec46b6b071ebfa339d7fb"}, ] [package.dependencies] @@ -1692,13 +1696,13 @@ wsproto = ">=0.14" [[package]] name = "types-pillow" -version = "10.2.0.20240423" +version = "10.2.0.20240520" description = "Typing stubs for Pillow" optional = false python-versions = ">=3.8" files = [ - {file = "types-Pillow-10.2.0.20240423.tar.gz", hash = "sha256:696e68b9b6a58548fc307a8669830469237c5b11809ddf978ac77fafa79251cd"}, - {file = "types_Pillow-10.2.0.20240423-py3-none-any.whl", hash = "sha256:bd12923093b96c91d523efcdb66967a307f1a843bcfaf2d5a529146c10a9ced3"}, + {file = "types-Pillow-10.2.0.20240520.tar.gz", hash = "sha256:130b979195465fa1e1676d8e81c9c7c30319e8e95b12fae945e8f0d525213107"}, + {file = "types_Pillow-10.2.0.20240520-py3-none-any.whl", hash = "sha256:33c36494b380e2a269bb742181bea5d9b00820367822dbd3760f07210a1da23d"}, ] [[package]] @@ -1761,13 +1765,13 @@ requests = "*" [[package]] name = "werkzeug" -version = "3.0.2" +version = "3.0.3" description = "The comprehensive WSGI web application library." optional = false python-versions = ">=3.8" files = [ - {file = "werkzeug-3.0.2-py3-none-any.whl", hash = "sha256:3aac3f5da756f93030740bc235d3e09449efcf65f2f55e3602e1d851b8f48795"}, - {file = "werkzeug-3.0.2.tar.gz", hash = "sha256:e39b645a6ac92822588e7b39a692e7828724ceae0b0d702ef96701f90e70128d"}, + {file = "werkzeug-3.0.3-py3-none-any.whl", hash = "sha256:fc9645dc43e03e4d630d23143a04a7f947a9a3b5727cd535fdfe155a17cc48c8"}, + {file = "werkzeug-3.0.3.tar.gz", hash = "sha256:097e5bfda9f0aba8da6b8545146def481d06aa7d3266e7448e2cccf67dd8bd18"}, ] [package.dependencies] @@ -1792,20 +1796,20 @@ h11 = ">=0.9.0,<1" [[package]] name = "zipp" -version = "3.18.1" +version = "3.18.2" description = "Backport of pathlib-compatible object wrapper for zip files" optional = false python-versions = ">=3.8" files = [ - {file = "zipp-3.18.1-py3-none-any.whl", hash = "sha256:206f5a15f2af3dbaee80769fb7dc6f249695e940acca08dfb2a4769fe61e538b"}, - {file = "zipp-3.18.1.tar.gz", hash = "sha256:2884ed22e7d8961de1c9a05142eb69a247f120291bc0206a00a7642f09b5b715"}, + {file = "zipp-3.18.2-py3-none-any.whl", hash = "sha256:dce197b859eb796242b0622af1b8beb0a722d52aa2f57133ead08edd5bf5374e"}, + {file = "zipp-3.18.2.tar.gz", hash = "sha256:6278d9ddbcfb1f1089a88fde84481528b07b0e10474e09dcfe53dad4069fa059"}, ] [package.extras] docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy", "pytest-ruff (>=0.2.1)"] +testing = ["big-O", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy", "pytest-ruff (>=0.2.1)"] [metadata] lock-version = "2.0" python-versions = "^3.12" -content-hash = "6c4bb66c774e93a08e4180f3148942edf91e2b5620ea1f6c3e4c30045bb643e5" +content-hash = "2db221a28d46b210a8fa0b78ad2fa3ac606a52052ad5e02b50d5b07db1e1c5b7" diff --git a/pyproject.toml b/pyproject.toml index bbbc443..74fc46c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,20 +5,11 @@ description = "" authors = ["Brian Bjarke Jensen "] readme = "README.md" packages = [ - { include = "core" }, + { include = "shared" }, ] [tool.poetry.dependencies] python = "^3.12" -gunicorn = "^21.2.0" -python-dotenv = "^1.0.1" -dash = "^2.15.0" -dash-bootstrap-components = "^1.5.0" -dash-mantine-components = "^0.12.1" -pydantic = "^2.6.1" -pillow = "^10.2.0" -pymongo = "^4.6.1" -dash-auth = "^2.2.0" [tool.poetry.group.test.dependencies] @@ -38,6 +29,20 @@ retry = "^0.9.2" torch = "^2.2.1" torchvision = "^0.17.1" + +[tool.poetry.group.shared.dependencies] +pymongo = "^4.7.2" +pydantic = "^2.6.1" +pillow = "^10.2.0" + + +[tool.poetry.group.web_ui.dependencies] +dash = "^2.17.0" +gunicorn = "^21.2.0" +dash-bootstrap-components = "^1.5.0" +dash-mantine-components = "^0.12.1" +dash-auth = "^2.2.0" + [build-system] requires = ["poetry-core"] build-backend = "poetry.core.masonry.api" diff --git a/core/__init__.py b/shared/__init__.py similarity index 100% rename from core/__init__.py rename to shared/__init__.py diff --git a/core/database/__init__.py b/shared/database/__init__.py similarity index 100% rename from core/database/__init__.py rename to shared/database/__init__.py diff --git a/core/database/classes/__init__.py b/shared/database/classes/__init__.py similarity index 100% rename from core/database/classes/__init__.py rename to shared/database/classes/__init__.py diff --git a/core/database/classes/dataset.py b/shared/database/classes/dataset.py similarity index 100% rename from core/database/classes/dataset.py rename to shared/database/classes/dataset.py diff --git a/core/database/classes/exceptions.py b/shared/database/classes/exceptions.py similarity index 100% rename from core/database/classes/exceptions.py rename to shared/database/classes/exceptions.py diff --git a/core/database/classes/visual_communication.py b/shared/database/classes/visual_communication.py similarity index 98% rename from core/database/classes/visual_communication.py rename to shared/database/classes/visual_communication.py index 78e9e61..f20a0ab 100755 --- a/core/database/classes/visual_communication.py +++ b/shared/database/classes/visual_communication.py @@ -12,7 +12,7 @@ from pydantic import BaseModel from pydantic import field_serializer from pydantic import field_validator -from core.dto import ModelData +from shared.dto import ModelData class VisualCommunication(BaseModel): diff --git a/core/database/utils/__init__.py b/shared/database/utils/__init__.py similarity index 100% rename from core/database/utils/__init__.py rename to shared/database/utils/__init__.py diff --git a/core/database/utils/connect.py b/shared/database/utils/connect.py similarity index 100% rename from core/database/utils/connect.py rename to shared/database/utils/connect.py diff --git a/core/database/utils/count_documents.py b/shared/database/utils/count_documents.py similarity index 100% rename from core/database/utils/count_documents.py rename to shared/database/utils/count_documents.py diff --git a/core/database/utils/get_dataset.py b/shared/database/utils/get_dataset.py similarity index 100% rename from core/database/utils/get_dataset.py rename to shared/database/utils/get_dataset.py diff --git a/core/database/utils/get_visual_communication.py b/shared/database/utils/get_visual_communication.py similarity index 90% rename from core/database/utils/get_visual_communication.py rename to shared/database/utils/get_visual_communication.py index 9b0b043..739d1df 100755 --- a/core/database/utils/get_visual_communication.py +++ b/shared/database/utils/get_visual_communication.py @@ -5,8 +5,8 @@ import logging from pymongo.collection import Collection -from core.database import NoDocumentFoundException -from core.database import VisualCommunication +from shared.database import NoDocumentFoundException +from shared.database import VisualCommunication def get_visual_communication( diff --git a/core/database/utils/list_names.py b/shared/database/utils/list_names.py similarity index 100% rename from core/database/utils/list_names.py rename to shared/database/utils/list_names.py diff --git a/core/database/utils/save_dataset.py b/shared/database/utils/save_dataset.py similarity index 89% rename from core/database/utils/save_dataset.py rename to shared/database/utils/save_dataset.py index afb2a64..f4605ec 100755 --- a/core/database/utils/save_dataset.py +++ b/shared/database/utils/save_dataset.py @@ -4,7 +4,7 @@ import logging from pymongo.collection import Collection -from core.database import Dataset +from shared.database import Dataset def save_dataset( @@ -21,7 +21,7 @@ def save_dataset( if __name__ == '__main__': from dotenv import load_dotenv load_dotenv('local.env') - from core.database import list_names, connect + from shared.database import list_names, connect # connect to database collection, db, client = connect() print(client.server_info()) diff --git a/core/database/utils/upsert_annotation.py b/shared/database/utils/upsert_annotation.py similarity index 94% rename from core/database/utils/upsert_annotation.py rename to shared/database/utils/upsert_annotation.py index 2124d2f..ea7c8ae 100755 --- a/core/database/utils/upsert_annotation.py +++ b/shared/database/utils/upsert_annotation.py @@ -4,7 +4,7 @@ import logging from pymongo.collection import Collection -from core.dto import ModelData +from shared.dto import ModelData def upsert_annotation( diff --git a/core/database/utils/upsert_prediction.py b/shared/database/utils/upsert_prediction.py similarity index 94% rename from core/database/utils/upsert_prediction.py rename to shared/database/utils/upsert_prediction.py index f47d75b..88388d3 100755 --- a/core/database/utils/upsert_prediction.py +++ b/shared/database/utils/upsert_prediction.py @@ -4,7 +4,7 @@ import logging from pymongo.collection import Collection -from core.dto import ModelData +from shared.dto import ModelData def upsert_prediction( diff --git a/core/database/utils/upsert_visual_communication.py b/shared/database/utils/upsert_visual_communication.py similarity index 91% rename from core/database/utils/upsert_visual_communication.py rename to shared/database/utils/upsert_visual_communication.py index 6744e01..30c2e7e 100755 --- a/core/database/utils/upsert_visual_communication.py +++ b/shared/database/utils/upsert_visual_communication.py @@ -2,7 +2,7 @@ from __future__ import annotations from pymongo.collection import Collection -from core.database import VisualCommunication +from shared.database import VisualCommunication def upsert_visual_communication( diff --git a/core/dto/__init__.py b/shared/dto/__init__.py similarity index 100% rename from core/dto/__init__.py rename to shared/dto/__init__.py diff --git a/core/dto/angle.py b/shared/dto/angle.py similarity index 100% rename from core/dto/angle.py rename to shared/dto/angle.py diff --git a/core/dto/contact.py b/shared/dto/contact.py similarity index 100% rename from core/dto/contact.py rename to shared/dto/contact.py diff --git a/core/dto/data_model.py b/shared/dto/data_model.py similarity index 100% rename from core/dto/data_model.py rename to shared/dto/data_model.py diff --git a/core/dto/distance.py b/shared/dto/distance.py similarity index 100% rename from core/dto/distance.py rename to shared/dto/distance.py diff --git a/core/dto/framing.py b/shared/dto/framing.py similarity index 100% rename from core/dto/framing.py rename to shared/dto/framing.py diff --git a/core/dto/information_value.py b/shared/dto/information_value.py similarity index 100% rename from core/dto/information_value.py rename to shared/dto/information_value.py diff --git a/core/dto/modality_color.py b/shared/dto/modality_color.py similarity index 100% rename from core/dto/modality_color.py rename to shared/dto/modality_color.py diff --git a/core/dto/modality_depth.py b/shared/dto/modality_depth.py similarity index 100% rename from core/dto/modality_depth.py rename to shared/dto/modality_depth.py diff --git a/core/dto/modality_lighting.py b/shared/dto/modality_lighting.py similarity index 100% rename from core/dto/modality_lighting.py rename to shared/dto/modality_lighting.py diff --git a/core/dto/model_data.py b/shared/dto/model_data.py similarity index 100% rename from core/dto/model_data.py rename to shared/dto/model_data.py diff --git a/core/dto/point_of_view.py b/shared/dto/point_of_view.py similarity index 100% rename from core/dto/point_of_view.py rename to shared/dto/point_of_view.py diff --git a/core/dto/salience.py b/shared/dto/salience.py similarity index 100% rename from core/dto/salience.py rename to shared/dto/salience.py diff --git a/core/dto/visual_syntax.py b/shared/dto/visual_syntax.py similarity index 100% rename from core/dto/visual_syntax.py rename to shared/dto/visual_syntax.py diff --git a/shared/utils/__init__.py b/shared/utils/__init__.py new file mode 100644 index 0000000..fb7c48b --- /dev/null +++ b/shared/utils/__init__.py @@ -0,0 +1,4 @@ +from __future__ import annotations + +from .check_env import check_env +from .setup_logging import setup_logging diff --git a/shared/utils/check_env.py b/shared/utils/check_env.py new file mode 100644 index 0000000..0fb3a2f --- /dev/null +++ b/shared/utils/check_env.py @@ -0,0 +1,24 @@ +"""Definition of check_env function.""" +from __future__ import annotations + +import os + + +def check_env() -> None: + """Check necessary environment variables are set.""" + necesasary_var_list = { + 'MONGO_HOST', + 'MONGO_DB', + 'MONGO_COLLECTION', + 'MONGO_USER', + 'MONGO_PASSWORD', + 'DASH_AUTH_USERNAME', + 'DASH_AUTH_PASSWORD', + } + for env_var in necesasary_var_list: + # ensure env var set + assert ( + env_var in os.environ + ), ( + f"environment variable not set: {env_var}" + ) diff --git a/shared/utils/setup_logging.py b/shared/utils/setup_logging.py new file mode 100644 index 0000000..aa4ca14 --- /dev/null +++ b/shared/utils/setup_logging.py @@ -0,0 +1,21 @@ +"""Definition of setup_logging function.""" +from __future__ import annotations + +import logging +import os + + +def setup_logging() -> None: + """Setup logging.""" + requested_log_level = os.getenv('LOG_LEVEL', default='info') + level = getattr(logging, requested_log_level.upper()) + fmt = ( + '%(asctime)s | ' + '%(levelname)s | ' + '%(filename)s | ' + '%(funcName)s | ' + '%(message)s' + ) + datefmt = '%Y-%m-%d %H:%M:%S' + logging.basicConfig(format=fmt, datefmt=datefmt, level=level) + logging.debug('finished') diff --git a/tests/generate_random_prediction_test.py b/tests/generate_random_prediction_test.py index c97e54c..2fccdd5 100644 --- a/tests/generate_random_prediction_test.py +++ b/tests/generate_random_prediction_test.py @@ -2,7 +2,7 @@ from __future__ import annotations from pathlib import Path -from core.database.classes import VisualCommunication +from shared.database.classes import VisualCommunication if __name__ == '__main__': diff --git a/tests/get_visual_communication_test.py b/tests/get_visual_communication_test.py index 6cd29cc..ed1ad5e 100644 --- a/tests/get_visual_communication_test.py +++ b/tests/get_visual_communication_test.py @@ -5,8 +5,8 @@ from pathlib import Path from dotenv import load_dotenv -from core.database import connect -from core.database import get_visual_communication +from shared.database import connect +from shared.database import get_visual_communication if __name__ == '__main__': # prepare env vars diff --git a/tests/image_download_test.py b/tests/image_download_test.py index 8f94244..7020013 100644 --- a/tests/image_download_test.py +++ b/tests/image_download_test.py @@ -5,8 +5,8 @@ from pathlib import Path from dotenv import load_dotenv -from core.database import connect -from core.database.classes import VisualCommunication +from shared.database import connect +from shared.database.classes import VisualCommunication if __name__ == '__main__': # prepare env vars diff --git a/tests/image_upload_test.py b/tests/image_upload_test.py index 4a99c71..916db40 100644 --- a/tests/image_upload_test.py +++ b/tests/image_upload_test.py @@ -6,8 +6,8 @@ from pathlib import Path from dotenv import load_dotenv from pymongo.errors import DuplicateKeyError -from core.database import connect -from core.database.classes import VisualCommunication +from shared.database import connect +from shared.database.classes import VisualCommunication if __name__ == '__main__': # get list of image paths diff --git a/tests/image_upload_to_server_test.py b/tests/image_upload_to_server_test.py index 5db75e2..9ecc79b 100644 --- a/tests/image_upload_to_server_test.py +++ b/tests/image_upload_to_server_test.py @@ -5,8 +5,8 @@ from pathlib import Path from dotenv import load_dotenv from pymongo.errors import DuplicateKeyError -from core.database import connect -from core.database import VisualCommunication +from shared.database import connect +from shared.database import VisualCommunication if __name__ == '__main__': # get list of image paths diff --git a/tests/model_outputs_from_annotation_test.py b/tests/model_outputs_from_annotation_test.py index fa4a96e..c8181d9 100644 --- a/tests/model_outputs_from_annotation_test.py +++ b/tests/model_outputs_from_annotation_test.py @@ -1,6 +1,6 @@ from __future__ import annotations -from core.dto import ModelData +from shared.dto import ModelData if __name__ == '__main__': diff --git a/tests/prediction_upload_test.py b/tests/prediction_upload_test.py index 7d8987b..bf667f3 100644 --- a/tests/prediction_upload_test.py +++ b/tests/prediction_upload_test.py @@ -6,9 +6,9 @@ from pathlib import Path from dotenv import load_dotenv -from core.database import connect -from core.database import upsert_prediction -from core.database.classes import VisualCommunication +from shared.database import connect +from shared.database import upsert_prediction +from shared.database.classes import VisualCommunication if __name__ == '__main__': # setup logging diff --git a/tests/total_annotated_test.py b/tests/total_annotated_test.py index 0fe3717..8c94f27 100644 --- a/tests/total_annotated_test.py +++ b/tests/total_annotated_test.py @@ -5,8 +5,8 @@ from pathlib import Path from dotenv import load_dotenv -from core.database import connect -from core.database import count_documents +from shared.database import connect +from shared.database import count_documents if __name__ == '__main__': # prepare env vars diff --git a/tests/total_documents_test.py b/tests/total_documents_test.py index d7955fb..0242940 100644 --- a/tests/total_documents_test.py +++ b/tests/total_documents_test.py @@ -5,8 +5,8 @@ from pathlib import Path from dotenv import load_dotenv -from core.database import connect -from core.database import count_documents +from shared.database import connect +from shared.database import count_documents if __name__ == '__main__': # prepare env vars diff --git a/web_ui/Dockerfile b/web_ui/Dockerfile index 336607e..d83830c 100644 --- a/web_ui/Dockerfile +++ b/web_ui/Dockerfile @@ -30,7 +30,8 @@ ENV PATH="${POETRY_HOME}/bin:$PATH" # install runtime dependencies WORKDIR ${APP_HOME} COPY poetry.lock pyproject.toml ./ -RUN --mount=type=cache,target=${POETRY_CACHE_DIR} poetry install --without model +RUN --mount=type=cache,target=${POETRY_CACHE_DIR} poetry install \ + --with shared,web_ui # final stage FROM python:3.12-slim-bookworm @@ -48,7 +49,7 @@ RUN mkdir -p /home/app && \ # add code while changing ownership WORKDIR $APP_HOME -COPY --chown=app:app ./core ./core +COPY --chown=app:app ./shared ./shared COPY --chown=app:app ./web_ui/src ./src # change to the app user diff --git a/web_ui/docker-compose.server.yml b/web_ui/docker-compose.server.yml new file mode 100644 index 0000000..ffd8784 --- /dev/null +++ b/web_ui/docker-compose.server.yml @@ -0,0 +1,13 @@ +services: + web: + image: visual_critical_discourse_analysis:test + container_name: visual_critical_discourse_analysis_alone + build: + context: ../ + dockerfile: ./web_ui/Dockerfile + ports: + - 8050:8050 + env_file: + - ../server.env + environment: + - ENV=TEST diff --git a/web_ui/src/app/__init__.py b/web_ui/src/app/__init__.py index 238bf79..7e45697 100644 --- a/web_ui/src/app/__init__.py +++ b/web_ui/src/app/__init__.py @@ -1,3 +1,3 @@ from __future__ import annotations -from .app import app +from .init_app import init_app diff --git a/web_ui/src/app/app.py b/web_ui/src/app/app.py deleted file mode 100644 index c1d5083..0000000 --- a/web_ui/src/app/app.py +++ /dev/null @@ -1,251 +0,0 @@ -from __future__ import annotations - -import logging -import os - -import dash_bootstrap_components as dbc -from dash import ALL -from dash import Dash -from dash import Input -from dash import Output -from dash import State -from dash_auth import BasicAuth -from pydantic import ValidationError - -from .layout import app_layout -from core.database import connect -from core.database import count_documents -from core.database import get_visual_communication -from core.database import NoDocumentFoundException -from core.database import upsert_annotation -from core.database import upsert_visual_communication -from core.database import VisualCommunication -from core.dto import ModelData - -# setup app -app = Dash(__name__, external_stylesheets=[dbc.themes.BOOTSTRAP]) -app.title = 'visual critical discourse analysis'.title() -app.layout = app_layout -server = app.server - -# setup authentication -AUTH_DICT = { - os.getenv('DASH_AUTH_USERNAME'): os.getenv('DASH_AUTH_PASSWORD'), -} -BasicAuth(app, AUTH_DICT) - -# connect to database -collection, db, client = connect() - - -# define callbacks -@app.callback( - Output('alert-element', 'is_open'), - Output('alert-element', 'children'), - Input('alert-message', 'data'), -) -def show_alert( - msg: str | None, -): - if msg is None or msg == '': - return False, '' - logging.info(f'updated alert message: {msg}') - return True, msg - - -@app.callback( - Output('success-element', 'is_open'), - Output('success-element', 'children'), - Input('success-message', 'data'), -) -def show_success( - msg: str | None, -): - if msg is None or msg == '': - return False, '' - logging.info(f"updated success message: {msg}") - return True, msg - - -@app.callback( - Output('progress-bar', 'value'), - Output('progress-bar', 'max'), - Output('progress-bar', 'label'), - Input('next-button', 'n_clicks'), - Input('upload-data', 'filename'), -) -def update_progress_bar( - n_clicks: int, - filename_list: list[str] | None, -): - logging.info('began updating progress bar') - global collection - # get values from database - num_total = count_documents( - collection=collection, - only_with_annotation=False, - ) - num_handled = count_documents( - collection=collection, - only_with_annotation=True, - ) - limit = int(num_total/20) - label_str = f"{num_handled}/{num_total}" if num_handled >= limit else '' - return num_handled, num_total, label_str - - -@app.callback( - Output('success-message', 'data', allow_duplicate=True), - Output('alert-message', 'data', allow_duplicate=True), - Input('upload-data', 'contents'), - State('upload-data', 'filename'), - prevent_initial_call=True, -) -def upload_images( - content_list: list[str] | None, - filename_list: list[str] | None, -): - logging.info('began uploading images') - global collection - # stop early if possible - if content_list is None or filename_list is None: - logging.warning('content_list is None -> nothing to upload') - return None, 'nothing selected to upload'.title() - # build list of visual communication - visual_communication_list = [] - for content, filename in zip(content_list, filename_list): - try: - image = VisualCommunication.decode_image(content) - vis_com = VisualCommunication( - name=filename, - image=image, - ) - except Exception as exc: - logging.warning( - ( - 'failed creating VisualCommunication object ' - f"from file {filename}" - ), - exc, - ) - else: - visual_communication_list.append(vis_com) - # upsert documents - success = upsert_visual_communication( - collection=collection, - visual_communication_list=visual_communication_list, - ) - if success: - logging.info(f"succesfully uploaded {len(filename_list)} images") - return 'successfully uploaded images'.title(), None - logging.warning('failed inserting images into database') - return None, 'failed uploading images'.title() - - -@app.callback( - Output('alert-message', 'data', allow_duplicate=True), - Output('vis-com-name', 'data'), - Output('image-container', 'src'), - Output({'type': 'annotation', 'index': ALL}, 'value'), - Input('next-button', 'n_clicks'), - State('vis-com-name', 'data'), - State('image-container', 'src'), - State({'type': 'annotation', 'index': ALL}, 'id'), - State({'type': 'annotation', 'index': ALL}, 'value'), - prevent_initial_call=True, -) -def cycle_visual_communication_data( - n_clicks: int, - vis_com_name: str, - image_src: str, - annotation_keys: list, - annotation_values: list, -): - logging.info('began cycling visual communication data') - global collection - # prepare default response - response = [ - '', - vis_com_name, - image_src, - annotation_values, - ] - # check if next-button clicked - if n_clicks == 0: - logging.info('stopping early: next-button has not yet been clicked') - return response - # check if visual communication name is set - if len(vis_com_name) > 0: - logging.info('saving annotations to database: %s', vis_com_name) - try: - # extract option keys - annotation_keys = [ - elem['index'] - for elem in annotation_keys - ] - # ensure all options are set - logging.info(annotation_keys) - for option, value in zip(annotation_keys, annotation_values): - if value is None: - raise ValueError(f"{option} is not set") - # prepare data to save - annotation_keys = [ - elem.replace(' ', '_') - for elem - in annotation_keys - ] - annotation_values = [ - elem.replace(' ', '_').lower() - for elem - in annotation_values - ] - annotation_map = { - key: value - for key, value - in zip(annotation_keys, annotation_values) - } - # instantiate ModelOutputs object - annotations = ModelData.from_annotations(**annotation_map) - # save data to database - upsert_annotation( - collection=collection, - vis_com_name=vis_com_name, - annotations=annotations, - ) - except (ValueError, ValidationError) as exc: - msg = f"failed saving annotation: {exc}" - logging.warning(msg) - response[0] = msg - return tuple(response) - # get new visual communication - logging.info('trying to get new visual communication') - try: - # get data - vis_com = get_visual_communication( - collection=collection, - with_annotation=False, - ) - # set variables - vis_com_name = vis_com.name - image_src = vis_com.webencoded_image() - if vis_com.prediction is not None: - # TODO: update to use optional predictions - pass - else: - # reset annotations - annotation_values = [None for elem in annotation_values] - except NoDocumentFoundException: - msg = 'no unannotated data in database' - logging.warning(msg) - response[0] = msg - return tuple(response) - else: - response[1] = vis_com_name - response[2] = image_src - response[3] = annotation_values - logging.info('finished getting visual communication: %s', vis_com_name) - return tuple(response) - - -if __name__ == '__main__': - app.run(debug=True) diff --git a/web_ui/src/app/init_app.py b/web_ui/src/app/init_app.py new file mode 100644 index 0000000..6d17e14 --- /dev/null +++ b/web_ui/src/app/init_app.py @@ -0,0 +1,260 @@ +"""Definition of init_app function.""" +from __future__ import annotations + +import logging +import os + +import dash_bootstrap_components as dbc +from dash import ALL +from dash import Dash +from dash import Input +from dash import Output +from dash import State +from dash_auth import BasicAuth +from pydantic import ValidationError +from pymongo.collection import Collection + +from .layout import app_layout +from shared.database import count_documents +from shared.database import get_visual_communication +from shared.database import NoDocumentFoundException +from shared.database import upsert_annotation +from shared.database import upsert_visual_communication +from shared.database import VisualCommunication +from shared.dto import ModelData + + +def init_app( + collection: Collection, +) -> Dash: + """Initialise web UI application.""" + # setup app + app = Dash( + name='visual_critical_discourse_analysis_web_ui', + external_stylesheets=[ + dbc.themes.BOOTSTRAP, + ], + ) + app.title = 'visual critical discourse analysis'.title() + app.layout = app_layout + + # setup authentication + auth_dict = { + os.getenv('DASH_AUTH_USERNAME'): os.getenv('DASH_AUTH_PASSWORD'), + } + BasicAuth(app, auth_dict) + + # define callback: show warning + @app.callback( + Output('warning-element', 'is_open'), + Output('warning-element', 'children'), + Input('warning-message', 'data'), + ) + def show_warning( + msg: str | None, + ) -> tuple[bool, str]: + """Show warning message in web ui.""" + if msg is None or msg == '': + return False, '' + logging.debug('updated warning message: %s', msg) + return True, msg + + # define callback: show info + @app.callback( + Output('info-element', 'is_open'), + Output('info-element', 'children'), + Input('info-message', 'data'), + ) + def show_info( + msg: str | None, + ) -> tuple[bool, str]: + """Show info message in web ui.""" + if msg is None or msg == '': + return False, '' + logging.debug('updated info message: %s', msg) + return True, msg + + # define callback: update progress bar + @app.callback( + Output('progress-bar', 'value'), + Output('progress-bar', 'max'), + Output('progress-bar', 'label'), + Input('next-button', 'n_clicks'), + Input('upload-data', 'filename'), + ) + def update_progress_bar( + n_clicks: int, + filename_list: list[str] | None, + ) -> tuple[int, int, str]: + """Update progress bar in web ui.""" + assert isinstance(n_clicks, int) + if filename_list is not None: + assert isinstance(filename_list, list) + # get values from database + num_total = count_documents( + collection=collection, + only_with_annotation=False, + ) + num_handled = count_documents( + collection=collection, + only_with_annotation=True, + ) + limit = int(num_total/20) + label_str = f"{ + num_handled + }/{num_total}" if num_handled >= limit else '' + return num_handled, num_total, label_str + + # define callback: upload image + @app.callback( + Output('info-message', 'data', allow_duplicate=True), + Output('warning-message', 'data', allow_duplicate=True), + Input('upload-data', 'contents'), + State('upload-data', 'filename'), + prevent_initial_call=True, + ) + def upload_images( + content_list: list[str] | None, + filename_list: list[str] | None, + ) -> tuple[str | None, str | None]: + """Upload image to database through web ui.""" + # stop early if possible + if content_list is None or filename_list is None: + logging.info('nothing to upload.') + return None, 'nothing to upload'.title() + # build list of visual communication + vis_com_list = [] + for content, filename in zip(content_list, filename_list): + try: + image = VisualCommunication.decode_image(content=content) + vis_com = VisualCommunication( + name=filename, + image=image, + ) + except Exception as exc: + logging.error('failed handling data from file: %s', filename) + logging.debug(exc) + continue + vis_com_list.append(vis_com) + # upsert documents + success = upsert_visual_communication( + collection=collection, + visual_communication_list=vis_com_list, + ) + if success: + logging.info('uploaded %s files to database', len(vis_com_list)) + return 'successfully uploaded images'.title(), None + logging.error('failed uploading images to database') + return None, 'failed uploading images'.title() + + # define callback: cycle visual communication data + @app.callback( + Output('warning-message', 'data', allow_duplicate=True), + Output('vis-com-name', 'data'), + Output('image-container', 'src'), + Output({'type': 'annotation', 'index': ALL}, 'value'), + Input('next-button', 'n_clicks'), + State('vis-com-name', 'data'), + State('image-container', 'src'), + State({'type': 'annotation', 'index': ALL}, 'id'), + State({'type': 'annotation', 'index': ALL}, 'value'), + prevent_initial_call=True, + ) + def cycle_vis_com_data( + n_clicks: int, + vis_com_name: str, + image_src: str, + annotation_keys: list, + annotation_values: list, + ) -> tuple: + """Cycle visual communcation data.""" + assert isinstance(n_clicks, int) + assert isinstance(vis_com_name, str) + assert isinstance(image_src, str) + assert isinstance(annotation_keys, list) + assert isinstance(annotation_values, list) + # prepare default response + response = [ + '', + vis_com_name, + image_src, + annotation_values, + ] + # stop early if possible + if n_clicks == 0: + return tuple(response) + # check if visual communication name is set + if len(vis_com_name) > 0: + logging.info('saving annotations to database: %s', vis_com_name) + try: + # extract option keys + annotation_keys = [ + elem['index'] + for elem in annotation_keys + ] + # ensure all options are set + logging.info(annotation_keys) + for option, value in zip(annotation_keys, annotation_values): + if value is None: + raise ValueError(f"{option} is not set") + # prepare data to save + annotation_keys = [ + elem.replace(' ', '_') + for elem + in annotation_keys + ] + annotation_values = [ + elem.replace(' ', '_').lower() + for elem + in annotation_values + ] + annotation_map = { + key: value + for key, value + in zip(annotation_keys, annotation_values) + } + # instantiate ModelOutputs object + annotations = ModelData.from_annotations(**annotation_map) + # save data to database + upsert_annotation( + collection=collection, + vis_com_name=vis_com_name, + annotations=annotations, + ) + except (ValueError, ValidationError) as exc: + msg = f"failed saving annotation: {exc}" + logging.warning(msg) + response[0] = msg + return tuple(response) + # get new visual communication + logging.info('trying to get new visual communication') + try: + # get data + vis_com = get_visual_communication( + collection=collection, + with_annotation=False, + ) + # set variables + vis_com_name = vis_com.name + image_src = vis_com.webencoded_image() + if vis_com.prediction is not None: + # TODO: update to use optional predictions + pass + else: + # reset annotations + annotation_values = [None for elem in annotation_values] + except NoDocumentFoundException: + msg = 'no unannotated data in database' + logging.warning(msg) + response[0] = msg + return tuple(response) + else: + response[1] = vis_com_name + response[2] = image_src + response[3] = annotation_values + logging.info( + 'finished getting visual communication: %s', vis_com_name, + ) + return tuple(response) + logging.info('initialised app') + return app diff --git a/web_ui/src/app/layout/labels.py b/web_ui/src/app/layout/labels.py index 510b70f..c87f4a0 100644 --- a/web_ui/src/app/layout/labels.py +++ b/web_ui/src/app/layout/labels.py @@ -4,17 +4,17 @@ import dash_mantine_components as dmc from dash import dcc from dash import html -from core.dto import AngleData -from core.dto import ContactData -from core.dto import DistanceData -from core.dto import FramingData -from core.dto import InformationValueData -from core.dto import ModalityColorData -from core.dto import ModalityDepthData -from core.dto import ModalityLightingData -from core.dto import PointOfViewData -from core.dto import SalienceData -from core.dto import VisualSyntaxData +from shared.dto import AngleData +from shared.dto import ContactData +from shared.dto import DistanceData +from shared.dto import FramingData +from shared.dto import InformationValueData +from shared.dto import ModalityColorData +from shared.dto import ModalityDepthData +from shared.dto import ModalityLightingData +from shared.dto import PointOfViewData +from shared.dto import SalienceData +from shared.dto import VisualSyntaxData def generate_visual_syntax_options_map(): diff --git a/web_ui/src/app/layout/stores.py b/web_ui/src/app/layout/stores.py index 11c59da..e80a43d 100644 --- a/web_ui/src/app/layout/stores.py +++ b/web_ui/src/app/layout/stores.py @@ -6,8 +6,8 @@ from dash import html stores_element = html.Div( children=[ - dcc.Store(id='alert-message', data=''), - dcc.Store(id='success-message', data=''), + dcc.Store(id='warning-message', data=''), + dcc.Store(id='info-message', data=''), dcc.Store(id='vis-com-name', data=''), ], ) diff --git a/web_ui/src/main.py b/web_ui/src/main.py index 5beed84..c22b45d 100644 --- a/web_ui/src/main.py +++ b/web_ui/src/main.py @@ -1,53 +1,23 @@ +"""Definition of web_ui main script.""" from __future__ import annotations -import logging import os -from pathlib import Path -from dotenv import load_dotenv - -from .app import app - -# prepare optional local setup -env_path = Path(__file__).parent.parent.parent / 'local.env' -load_dotenv(env_path) +from .app import init_app +from shared.database import connect +from shared.utils import check_env +from shared.utils import setup_logging # ensure env vars set -necesasary_var_list = { - 'MONGO_HOST', - 'MONGO_DB', - 'MONGO_COLLECTION', - 'MONGO_USER', - 'MONGO_PASSWORD', - 'DASH_AUTH_USERNAME', - 'DASH_AUTH_PASSWORD', -} -for env_var in necesasary_var_list: - # ensure env var set - assert ( - env_var in os.environ - ), ( - f"environment variable not set: {env_var}" - ) +check_env() # setup logging stream handler -FMT = ( - '%(asctime)s | ' - '%(levelname)s | ' - '%(filename)s | ' - '%(funcName)s | ' - '%(message)s' -) -DATEFMT = '%Y-%m-%d %H:%M:%S' -logging.basicConfig(format=FMT, datefmt=DATEFMT, level=logging.INFO) +setup_logging() -logging.info('initialized app') +# connect to database +collection, db, client = connect() + +# initialise application +app = init_app(collection=collection) server = app.server server.config.update(SECRET_KEY=os.urandom(24)) - -if __name__ == '__main__': - # prepare local env vars - os.environ['MONGO_HOST'] = 'localhost' - # run app - app.run(debug=True) - logging.info('started app')