Compare commits

..
354 Commits
Author SHA1 Message Date
Brian Bjarke Jensen 70492acafe Merge pull request '#67_unittests_for_DTOs' (#69) from #67_unittests_for_DTOs into main
Reviewed-on: #69
2025-04-16 00:35:07 +02:00
brian 06913064c3 added unittests for repositories DTO base class
Code Quality Pipeline / Check Code (pull_request) Successful in 2m44s
2025-04-15 22:24:02 +00:00
brian 7190b6c438 removed unused setting 2025-04-15 22:22:20 +00:00
Brian Bjarke Jensen 7ce7a368c7 Merge pull request 'object_based_docstore_approach' (#68) from object_based_docstore_approach into main
Reviewed-on: #68
2025-04-16 00:03:23 +02:00
brian a2b818bbcc updated ignored packages
Code Quality Pipeline / Check Code (pull_request) Successful in 3m20s
2025-04-15 21:56:45 +00:00
brian 08062ad87d removed unused packages 2025-04-15 21:56:33 +00:00
brian 7176dfdf90 added env vars back in
Code Quality Pipeline / Check Code (pull_request) Failing after 2m22s
2025-03-18 19:34:03 +00:00
brian 6399b88ade clarified comment 2025-03-18 19:33:50 +00:00
brian c5bcd09bba removed already defined environment variable 2025-03-18 19:30:12 +00:00
brian 7d8af1c582 changed returned class type 2025-03-18 19:28:12 +00:00
brian a379270add removed deprecated modules 2025-03-18 19:21:17 +00:00
brian 0b54ccaadb removed unused variable 2025-03-18 19:12:51 +00:00
brian ac8ca830e1 manually set needed environment variables in conftest
Code Quality Pipeline / Check Code (pull_request) Failing after 3m9s
2025-03-18 19:12:06 +00:00
brian 49d2c48d31 explicitly adding local env vars to file
Code Quality Pipeline / Check Code (pull_request) Failing after 2m41s
2025-03-18 15:34:39 +00:00
brian 280dbb579e added more explicit env var loading
Code Quality Pipeline / Check Code (pull_request) Failing after 3m45s
2025-03-17 22:46:52 +00:00
brian ab7e346939 tried loading github env file
Code Quality Pipeline / Check Code (pull_request) Failing after 3m9s
2025-03-17 22:32:06 +00:00
brian ad82ad4591 added explicit check for necessary env vars
Code Quality Pipeline / Check Code (pull_request) Failing after 3m7s
2025-03-17 22:19:14 +00:00
brian 400706e730 moved env var checks
Code Quality Pipeline / Check Code (pull_request) Failing after 3m21s
2025-03-17 22:09:48 +00:00
brian 096ffd56e9 added explicit env var checking
Code Quality Pipeline / Check Code (pull_request) Failing after 3m18s
2025-03-17 21:54:52 +00:00
brian 790204af6a updated logical check
Code Quality Pipeline / Check Code (pull_request) Failing after 3m18s
2025-03-17 21:48:43 +00:00
brian 14a4125688 updated to give full list of missing env vars
Code Quality Pipeline / Check Code (pull_request) Failing after 3m23s
2025-03-17 21:44:13 +00:00
brian 62519c371e removed explicitly setting env vars 2025-03-17 21:41:42 +00:00
brian 9c8089d091 tried explicit export statements
Code Quality Pipeline / Check Code (pull_request) Failing after 3m17s
2025-03-17 21:34:17 +00:00
brian 77a3219f39 fixed path variable
Code Quality Pipeline / Check Code (pull_request) Failing after 3m21s
2025-03-17 21:13:21 +00:00
brian 66370a33c2 tried to add if-statement to load_dotenv to see if this disturbs CI tests 2025-03-17 21:12:19 +00:00
brian 5870c3b0a3 added no chache-option to poetry 2025-03-17 21:11:52 +00:00
brian e9340f17c4 print out env vars for debugging
Code Quality Pipeline / Check Code (pull_request) Failing after 3m18s
2025-03-17 20:57:43 +00:00
brian 282fee0806 fixed spelling mistake 2025-03-17 20:57:30 +00:00
brian 33d30941a6 updated to run locally
Code Quality Pipeline / Check Code (pull_request) Failing after 3m23s
2025-03-17 18:06:46 +00:00
brian cb8d17cbc3 fixed poetry path
Code Quality Pipeline / Check Code (pull_request) Failing after 9m3s
2025-03-10 19:25:12 +00:00
brian df2c2b7e5f corrected spelling mistake
Code Quality Pipeline / Check Code (pull_request) Failing after 2m38s
2025-03-10 19:17:42 +00:00
brian 6756ae4871 updated github action versions
Code Quality Pipeline / Check Code (pull_request) Failing after 1m6s
2025-03-10 19:13:37 +00:00
brian fa30aae3a8 changed torch version
Code Quality Pipeline / Check Code (pull_request) Failing after 1m46s
2025-03-10 18:44:54 +00:00
brian 15f3944d27 corrected poetry version
Code Quality Pipeline / Check Code (pull_request) Failing after 1m47s
2025-03-10 18:38:43 +00:00
brian e6b73fe4fc corrected python version
Code Quality Pipeline / Check Code (pull_request) Failing after 1m47s
2025-03-08 21:47:11 +00:00
brian 1f72a2db8d added hook 'check-added-large-files' 2025-03-08 21:46:21 +00:00
brian a610bf4d89 downgraded python for torch compatibility 2025-03-08 21:29:06 +00:00
brian 9af3d9bc54 update pre-commit
Code Quality Pipeline / Check Code (pull_request) Failing after 1m45s
2025-03-08 21:19:18 +00:00
brian bbbeb403a8 Revert "updated packages"
Code Quality Pipeline / Check Code (pull_request) Failing after 57s
This reverts commit 62b6224de7.
2025-03-06 22:46:26 +00:00
brian 9be7ec9e2e redid lock file 2025-03-06 22:45:42 +00:00
brian a6c717a401 Revert "uninstalled testcontainers"
This reverts commit a18b4d304c.
2025-03-06 22:45:18 +00:00
brian 4db59d2801 added mongo endpoint 2025-03-06 22:44:06 +00:00
brian 3a0c1e9067 Revert "added env var for CI testing"
This reverts commit a2f892e35c.
2025-03-06 22:43:19 +00:00
brian 5a8350d0ad downgraded torch
Code Quality Pipeline / Check Code (pull_request) Failing after 1m49s
2025-03-06 22:30:04 +00:00
brian 62b6224de7 updated packages
Code Quality Pipeline / Check Code (pull_request) Failing after 1m53s
2025-03-06 22:16:50 +00:00
brian 68bfff87a7 poetry disappeared
Code Quality Pipeline / Check Code (pull_request) Failing after 40s
2025-03-06 22:11:11 +00:00
brian ac21c7a111 removed mypy overrides for testcontainers
Code Quality Pipeline / Check Code (pull_request) Failing after 37s
2025-03-06 22:03:04 +00:00
brian a18b4d304c uninstalled testcontainers 2025-03-06 22:02:32 +00:00
brian a2f892e35c added env var for CI testing 2025-03-06 22:02:21 +00:00
brian 4bcfbbed0a removed usage of testcontainers 2025-03-06 22:02:06 +00:00
brian 31be0d7aac added check for poetry installation
Code Quality Pipeline / Check Code (pull_request) Failing after 3m16s
2025-03-06 21:34:14 +00:00
brian d4c9738db4 next try
Code Quality Pipeline / Check Code (pull_request) Failing after 1m24s
2025-03-06 21:29:52 +00:00
brian 813daa0c90 next attempt
Code Quality Pipeline / Check Code (pull_request) Failing after 24s
2025-03-06 21:22:52 +00:00
brian 29bf41c7da next try
Code Quality Pipeline / Check Code (pull_request) Failing after 24s
2025-03-06 21:16:06 +00:00
brian d9bd5be46d again again
Code Quality Pipeline / Check Code (pull_request) Failing after 22s
2025-03-06 21:06:50 +00:00
brian c2a2c34967 again
Code Quality Pipeline / Check Code (pull_request) Failing after 23s
2025-03-06 21:04:24 +00:00
brian 9e66981d3e next attempt
Code Quality Pipeline / Check Code (pull_request) Failing after 9s
2025-03-06 21:02:17 +00:00
brian 6840ac72f1 try again with poetry
Code Quality Pipeline / Check Code (pull_request) Failing after 36s
2025-03-06 20:56:59 +00:00
brian ee304f1a75 yet another attempt at installing poetry
Code Quality Pipeline / Check Code (pull_request) Failing after 23s
2025-03-06 20:36:42 +00:00
brian 4827a3f7dd next attempt at installing poetry
Code Quality Pipeline / Check Code (pull_request) Failing after 39s
2025-03-06 20:26:56 +00:00
brian d92051f5c4 new attempt at installing poetry
Code Quality Pipeline / Check Code (pull_request) Failing after 23s
2025-03-06 20:23:16 +00:00
brian 10ce0d9124 updated to recommended CI install method
Code Quality Pipeline / Check Code (pull_request) Failing after 23s
2025-03-06 20:19:52 +00:00
brian 88a20c4b0b corrected type hints
Code Quality Pipeline / Check Code (pull_request) Failing after 1m48s
2025-02-28 22:54:53 +00:00
brian 090a1cd8bb added integration tests and repository pattern for image, model and visual communication DTOs
Code Quality Pipeline / Check Code (pull_request) Failing after 3m21s
2025-02-28 22:24:52 +00:00
brian ac6a305da8 fixed spelling 2025-01-07 17:50:17 +00:00
brian cbedc395bc corrected spelling 2025-01-07 17:48:10 +00:00
brian 0c762b55b2 corrected comment spelling 2025-01-06 17:32:33 +00:00
brian 99b5820ae6 renamed module 2025-01-06 17:15:48 +00:00
Brian Bjarke Jensen 8214bd2669 Merge pull request 'object_based_datastore' (#65) from object_based_datastore into main
Reviewed-on: http://192.168.1.2:3000/brian/visual_critical_discourse_analysis/pulls/65
2025-01-06 14:48:25 +01:00
brian aff0ae8fc6 fixed types
Code Quality Pipeline / Check Code (pull_request) Successful in 3m15s
2025-01-06 13:42:21 +00:00
brian 16ad2b80ee updated tests to match new interface
Code Quality Pipeline / Check Code (pull_request) Failing after 3m5s
2025-01-06 11:34:02 +00:00
brian 88698dd735 fixed relative import paths 2025-01-06 11:29:54 +00:00
brian f28bff7e47 implemented new interface 2025-01-06 11:29:31 +00:00
brian 9ee27b65a8 removed unused files 2025-01-06 11:27:26 +00:00
brian de4f984110 added minio-specific interface 2025-01-06 11:26:47 +00:00
brian 3b677e5e97 fixed bug in context manager definition 2025-01-06 11:26:23 +00:00
brian 226fb1a206 defined interface 2025-01-06 11:22:34 +00:00
brian 7a1e17c29f updated to raise proper error 2025-01-06 11:21:36 +00:00
brian f52720685e fixed relative import 2025-01-06 11:21:08 +00:00
Brian Bjarke Jensen a3da7b048d Merge pull request 'tests_for_datastore' (#63) from tests_for_datastore into main
Reviewed-on: #63
2024-12-21 17:31:12 +01:00
brian f61eb59afb installed coverage package and included coverage report in CI
Code Quality Pipeline / Check Code (pull_request) Successful in 2m35s
2024-12-21 16:27:48 +00:00
brian a430366bbe updated variable name for clarity
Code Quality Pipeline / Check Code (pull_request) Failing after 2m21s
2024-12-21 16:09:23 +00:00
brian 06d9753c46 added CI vars for tests
Code Quality Pipeline / Check Code (pull_request) Failing after 2m22s
2024-12-21 16:03:04 +00:00
brian 4e6499cee3 next attempt at using repo vars
Code Quality Pipeline / Check Code (pull_request) Failing after 2m59s
2024-12-21 15:55:31 +00:00
brian 7da3482366 updated to use env var from CI
Code Quality Pipeline / Check Code (pull_request) Failing after 2m49s
2024-12-20 23:46:23 +00:00
brian 7b3374d451 fixed tests running against server db
Code Quality Pipeline / Check Code (pull_request) Failing after 3m23s
2024-12-20 23:33:43 +00:00
brian b17c1745ab fixed bug referencing object in minio incorrectly 2024-12-20 23:31:33 +00:00
brian db4ce6b425 fixed bug referencing unset variable on exception 2024-12-20 23:15:55 +00:00
brian aa6a097e4f updated to use server db for testing 2024-12-20 22:40:53 +00:00
brian 150a213ae2 fixed bug when putting data 2024-12-20 22:39:53 +00:00
brian d860b71b1a attempt at showing the determined minio endpoint
Code Quality Pipeline / Check Code (pull_request) Failing after 2m48s
2024-12-19 22:18:58 +00:00
brian 1a3ee96932 added fix to allow running Minio container in CI
Code Quality Pipeline / Check Code (pull_request) Failing after 2m47s
2024-12-19 22:11:07 +00:00
brian 8e366f26bc updated port mapping
Code Quality Pipeline / Check Code (pull_request) Failing after 3m26s
2024-12-19 21:58:15 +00:00
brian 8a602264e6 mypy ignore testcontainers missing imports
Code Quality Pipeline / Check Code (pull_request) Failing after 3m14s
2024-12-19 16:01:52 +00:00
brian 99d5c88c8d fixed argument types 2024-12-19 16:01:29 +00:00
brian c9da7378a1 commented out more unused imports
Code Quality Pipeline / Check Code (pull_request) Failing after 2m11s
2024-12-19 15:41:28 +00:00
brian b2773ecf09 commented out unused imports
Code Quality Pipeline / Check Code (pull_request) Failing after 1m35s
2024-12-19 15:38:49 +00:00
brian 084a13426a commented out unused tests temporarily
Code Quality Pipeline / Check Code (pull_request) Failing after 2m11s
2024-12-19 15:34:11 +00:00
brian 499996329f added fixtures 2024-12-19 15:33:25 +00:00
brian ce08ee4ccd added function to compare images 2024-11-25 16:37:52 +00:00
brian cbbb044177 updated integration tests 2024-11-16 19:46:37 +00:00
brian c630498168 vulture ignore test folders 2024-11-16 19:46:18 +00:00
brian f9d23c5bd4 added type-specific unittests 2024-11-16 18:50:28 +00:00
brian 3fa79faa9e shortened variable name 2024-11-16 18:39:25 +00:00
brian 556e26f21d added unittests for base functions 2024-11-16 18:27:59 +00:00
brian 01ee4664ff implemented base functions 2024-11-16 18:18:13 +00:00
brian 169e0531d9 started making unittest 2024-11-08 22:32:27 +00:00
brian 3c46275015 updated input check 2024-11-08 22:29:33 +00:00
brian 0b86acd1a9 began adding tests 2024-11-08 22:29:03 +00:00
brian cddfced177 added unittest 2024-11-08 22:27:58 +00:00
brian 53d179663b added unittest 2024-11-08 21:05:20 +00:00
brian 6be837025b moved files 2024-11-08 21:03:53 +00:00
brian 2854f74db4 installed package for generating coverage report 2024-10-28 19:44:16 +00:00
brian 462f7c79b4 added step to print coverage report 2024-10-28 19:43:56 +00:00
brian 0c9537975c updated bucket name 2024-10-28 19:43:37 +00:00
brian 10bfac73bd extended tests 2024-10-25 17:19:47 +00:00
brian bdf3e73ba9 updated for more general use 2024-10-25 17:19:15 +00:00
brian 42400d6f32 updated for readability 2024-10-25 17:18:53 +00:00
brian 0676011f98 implemented new more general function 2024-10-25 17:10:03 +00:00
brian e189b7c1f7 updated for more general use 2024-10-25 17:09:43 +00:00
brian dc535167fa started making tests 2024-10-25 09:27:01 +00:00
brian b722ddcb83 installed new packages for testing 2024-10-25 09:26:10 +00:00
brian b755a81ef9 simplified code 2024-10-25 09:25:52 +00:00
brian ae18041d6e simplified code 2024-10-24 16:46:35 +00:00
brian d8e3d077e3 moved files into src folder 2024-10-24 16:40:26 +00:00
Brian Bjarke Jensen b54e2ed541 Merge pull request 'renamed module' (#61) from rename_module_datastore into main
Reviewed-on: #61
2024-10-24 18:23:23 +02:00
brian 30f37b6ca5 renamed module
Code Quality Pipeline / Check Code (pull_request) Successful in 2m40s
2024-10-24 16:19:52 +00:00
Brian Bjarke Jensen 860150905d Merge pull request 'rename_shared_module_database' (#57) from rename_shared_module_database into main
Reviewed-on: #57
2024-10-20 22:37:30 +02:00
brian 66c3f38dba added ignore for internal module
Code Quality Pipeline / Check Code (pull_request) Successful in 2m43s
2024-10-20 20:32:14 +00:00
brian 1a6cf69346 moved code 2024-10-20 20:21:16 +00:00
brian 6f06485776 updated imports 2024-10-20 20:18:20 +00:00
brian c7fd78dcf8 removed unused code 2024-10-20 20:18:14 +00:00
brian 7a8eb93c48 updated imports 2024-10-20 20:15:19 +00:00
brian 4ba68d056b moved definitions of data classes into module where used 2024-10-20 20:08:37 +00:00
brian ba7a7299c2 removed unused class 2024-10-20 20:05:47 +00:00
brian 6dba06704c moved definition of exception to separate folder 2024-10-20 20:04:48 +00:00
brian 20831c9bc3 renamed file 2024-10-20 20:02:36 +00:00
brian e2338ad710 renamed module 2024-10-20 20:00:13 +00:00
Brian Bjarke Jensen d1ee43e135 Merge pull request 'unittest_for_shared_utils' (#55) from unittest_for_shared_utils into main
Reviewed-on: #55
2024-10-20 21:43:05 +02:00
brian 909f8f220c added tests for setup_logging function
Code Quality Pipeline / Check Code (pull_request) Successful in 2m19s
2024-10-20 19:36:50 +00:00
brian 7fa88166ee fixed log not being set 2024-10-20 19:36:31 +00:00
Brian Bjarke Jensen 321defb7ff Merge pull request 'add_check_env_function' (#54) from add_check_env_function into main
Reviewed-on: #54
2024-10-20 20:57:20 +02:00
brian 00f1c07cfa updated filenames to avoid accidental running during test
Code Quality Pipeline / Check Code (pull_request) Successful in 2m12s
2024-10-20 18:52:00 +00:00
brian 312f525a46 fixed argument for check_env function
Code Quality Pipeline / Check Code (pull_request) Failing after 2m25s
2024-10-20 18:46:32 +00:00
brian 78363988f7 updated static variable name 2024-10-20 18:44:04 +00:00
brian b109ddb2b8 added step to run pytest
Code Quality Pipeline / Check Code (pull_request) Failing after 2m46s
2024-10-20 18:35:53 +00:00
brian 5b446a9f2f updated deprecated pydantic config 2024-10-20 18:34:06 +00:00
brian b462056e24 changed folder name to avoid accidental running during test 2024-10-20 18:30:59 +00:00
brian bdc0535ea4 added tests for check_env function 2024-10-20 18:30:12 +00:00
brian 5a1de4f2b2 added package pytest 2024-10-20 18:29:01 +00:00
brian 4e499da168 moved hardcoded values into main script 2024-10-20 18:28:23 +00:00
brian 8bfdec067b fixed import 2024-10-20 18:27:31 +00:00
Brian Bjarke Jensen 9d45f8a32b Merge pull request 'ci_pull_request_pipeline' (#51) from ci_pull_request_pipeline into main
Reviewed-on: http://192.168.1.2:3000/brian/visual_critical_discourse_analysis/pulls/51
2024-10-20 00:17:23 +02:00
brian 082ccf8382 updated to trigger on published release
Code Quality Pipeline / Check Code (pull_request) Successful in 3m2s
2024-10-19 22:16:34 +00:00
brian 8f10c16f3b added pull request ci script 2024-10-19 22:11:53 +00:00
Brian Bjarke Jensen 5a674603ca Merge pull request 'image_through_model' (#49) from image_through_model into main
Reviewed-on: http://192.168.1.2:3000/brian/visual_critical_discourse_analysis/pulls/49
2024-10-20 00:10:26 +02:00
brian ba0fc050fe fixed mypy types
CI Pipeline / Test (pull_request) Successful in 2m23s
CI Pipeline / Build and Publish (./Dockerfile.model, ${{ vars.docker_repo_url }}/${{ gitea.repository }}/model) (pull_request) Successful in 7m3s
CI Pipeline / Build and Publish (./Dockerfile.web_ui, ${{ vars.docker_repo_url }}/${{ gitea.repository }}/web_ui) (pull_request) Successful in 6m13s
2024-10-19 21:37:08 +00:00
brian 7fc4d20262 added types for checking 2024-10-19 20:11:49 +00:00
brian d6036a95a3 fixed deprecated import 2024-10-19 20:09:32 +00:00
brian 14ccaace95 mypy fixed types 2024-10-19 20:03:54 +00:00
brian 6d327bee22 fixed type bug 2024-10-19 19:55:58 +00:00
brian 270dc8e4d1 fixed type 2024-10-19 19:54:40 +00:00
brian e49a0c37a6 ensure env var set 2024-10-19 19:45:42 +00:00
brian a0726857a0 ensure env var set 2024-10-19 19:45:16 +00:00
brian bf44947024 added utility script
CI Pipeline / Test (pull_request) Failing after 2m31s
CI Pipeline / Build and Publish (./Dockerfile.model, ${{ vars.docker_repo_url }}/${{ gitea.repository }}/model) (pull_request) Has been skipped
CI Pipeline / Build and Publish (./Dockerfile.web_ui, ${{ vars.docker_repo_url }}/${{ gitea.repository }}/web_ui) (pull_request) Has been skipped
2024-10-19 19:15:41 +00:00
brian 4f22b9755c updated import 2024-10-19 19:15:20 +00:00
brian ae3ac714d4 added placeholder files for training and validation files 2024-10-19 19:14:56 +00:00
brian ad069269ac added settings for resnet18-based model 2024-10-19 19:14:34 +00:00
brian a54e7bbbb4 installed packages 2024-10-19 19:13:45 +00:00
brian 1f62054f36 installed packages 2024-10-19 19:13:35 +00:00
brian 59048a6862 added model training script 2024-10-19 19:12:21 +00:00
brian a4764b8fc5 added ignore for training run folder 2024-10-19 19:11:02 +00:00
brian bf4cc9c483 ensure non-empty input 2024-10-19 19:10:33 +00:00
brian f35cdefc21 removed deprecated dependency 2024-10-19 19:08:51 +00:00
Brian Bjarke Jensen 493da70ba4 updated to do prediction and print result 2024-07-28 23:22:40 +02:00
Brian Bjarke Jensen b415137e25 added method to instantiate from prediction dict 2024-07-28 23:19:51 +02:00
Brian Bjarke Jensen cead3d24c8 added from_tensor method 2024-07-28 23:13:15 +02:00
Brian Bjarke Jensen 0bfbb1920c fixed conversion of classes 2024-07-28 23:12:06 +02:00
Brian Bjarke Jensen 164102557e removed unused import 2024-07-28 23:10:15 +02:00
Brian Bjarke Jensen cf923a228d updated model name reference 2024-07-28 21:37:28 +02:00
Brian Bjarke Jensen 9f4a09a18c fixed definition of layers 2024-07-28 21:36:21 +02:00
Brian Bjarke Jensen 849b59e575 removed double connection to minio 2024-07-28 21:34:56 +02:00
Brian Bjarke Jensen 4a7e33b527 updated referenced model name 2024-07-28 21:32:16 +02:00
Brian Bjarke Jensen e5fad6d765 added function to generate and save new model 2024-07-28 21:31:48 +02:00
Brian Bjarke Jensen be6833ac6a added option to download weights 2024-07-28 21:25:24 +02:00
Brian Bjarke Jensen 6541a47668 added option to download resnet18 weights 2024-07-28 21:19:47 +02:00
Brian Bjarke Jensen 96ccdb5c90 moved file for clarity 2024-07-28 21:16:32 +02:00
Brian Bjarke Jensen 6e9c0a2626 fixed number of inputs 2024-07-28 21:13:51 +02:00
Brian Bjarke Jensen 396bf29f32 updated function name 2024-07-28 21:08:51 +02:00
Brian Bjarke Jensen 05774364fc renamed function for clarity 2024-07-28 21:08:27 +02:00
Brian Bjarke Jensen 74a9538feb renamed function for clarity 2024-07-28 21:05:15 +02:00
Brian Bjarke Jensen 9423c3378a fixed dockerfile syntax 2024-07-28 21:02:59 +02:00
Brian Bjarke Jensen e6fdbfe0b0 changed to not download pretrained weights for ResNet18 2024-07-28 21:01:19 +02:00
Brian Bjarke Jensen 6c354b23bd test run prediction on image 2024-07-27 21:41:29 +02:00
Brian Bjarke Jensen 3ad6c161b1 added new modules 2024-07-27 21:40:38 +02:00
Brian Bjarke Jensen a1ff6a6f33 moved for clarity 2024-07-27 21:40:17 +02:00
Brian Bjarke Jensen cf4f67d7a5 installed tqdm 2024-07-27 21:38:18 +02:00
Brian Bjarke Jensen 7c968db8ac renamed file for clarity 2024-07-27 21:18:39 +02:00
Brian Bjarke Jensen e656cd4e12 remove unneeded code for running in docker container 2024-07-27 21:08:47 +02:00
Brian Bjarke Jensen 8b1893b30a implemented load_model function 2024-07-24 09:13:28 +02:00
Brian Bjarke Jensen 96d868a40c added function to load model 2024-07-24 09:12:07 +02:00
Brian Bjarke Jensen e698d058ff removed debug log 2024-07-24 09:11:01 +02:00
Brian Bjarke Jensen 8aafbdae6c fixed hardcoded path 2024-07-24 09:10:37 +02:00
Brian Bjarke Jensen ec4162e34d removed newline char 2024-07-22 23:50:55 +02:00
Brian Bjarke Jensen 99b7067cfe added container resource limits 2024-07-22 23:48:01 +02:00
Brian Bjarke Jensen e62435cf22 added full model load script 2024-07-22 23:47:39 +02:00
Brian Bjarke Jensen 3560a61277 added model name loader 2024-07-22 23:47:11 +02:00
Brian Bjarke Jensen 7f0cbea937 added model name link file 2024-07-22 23:46:15 +02:00
Brian Bjarke Jensen 31156eea54 updated classes 2024-07-22 23:45:54 +02:00
Brian Bjarke Jensen ef11c33675 used buffered download 2024-07-22 23:45:19 +02:00
Brian Bjarke Jensen 89aecfdef7 updated to use new io functions 2024-07-14 11:16:35 +02:00
Brian Bjarke Jensen 68e04c7653 fixed bucket name checking bug 2024-07-14 11:16:13 +02:00
Brian Bjarke Jensen a12f8c46e8 added model io functions 2024-07-14 11:15:56 +02:00
Brian Bjarke Jensen ff88dc1719 refactor for readability 2024-07-14 11:13:23 +02:00
Brian Bjarke Jensen 6502ea043a rearranged imports 2024-07-14 10:47:04 +02:00
Brian Bjarke Jensen 9cd4f70194 refactor for readability and implement new get_image function 2024-07-14 00:10:22 +02:00
Brian Bjarke Jensen 2cf55efcb3 added checking if buckets exist 2024-07-13 23:45:57 +02:00
Brian Bjarke Jensen d55adf87ac added buckets exist check 2024-07-13 23:42:42 +02:00
Brian Bjarke Jensen a621525d9e updated to fit new workflow 2024-07-13 23:36:26 +02:00
Brian Bjarke Jensen ba1ba027f0 moved files 2024-07-13 23:35:15 +02:00
Brian Bjarke Jensen 24e1c44f5e fixed double reference 2024-07-13 23:34:57 +02:00
Brian Bjarke Jensen 94801efa6a added isort and flake8 black compatibility 2024-07-13 23:33:43 +02:00
Brian Bjarke Jensen 4f7e8150e9 added image specific io functions 2024-07-13 23:30:22 +02:00
Brian Bjarke Jensen e9b5250e95 script to move images to subfolder in minio 2024-07-13 23:29:28 +02:00
Brian Bjarke Jensen c1f38e3d77 moved functions to shared folder 2024-07-13 23:28:57 +02:00
Brian Bjarke Jensen 9a107c7bb7 added model instantiation test 2024-07-13 22:38:23 +02:00
Brian Bjarke Jensen 74d7307219 moved to shared folder 2024-07-13 22:36:45 +02:00
Brian Bjarke Jensen bdd0dfff10 updated to not get vulture error 2024-07-13 22:36:27 +02:00
Brian Bjarke Jensen e913bb28cc removed autopep 2024-07-13 22:34:30 +02:00
Brian Bjarke Jensen f9b845abd9 testing pre-commits 2024-07-13 22:31:22 +02:00
Brian Bjarke Jensen d46ad01e1d updated checks 2024-07-13 22:31:03 +02:00
Brian Bjarke Jensen 8d887b40b3 Merge pull request 'move_images_to_minio' (#44) from move_images_to_minio into main
Reviewed-on: #44
2024-06-19 14:57:32 +02:00
Brian Bjarke Jensen 2ee5ab2b9c added env var check
CI Pipeline / Test (pull_request) Successful in 2m7s
CI Pipeline / Build and Publish (./Dockerfile.model, ${{ vars.docker_repo_url }}/${{ gitea.repository }}/model) (pull_request) Successful in 16s
CI Pipeline / Build and Publish (./Dockerfile.web_ui, ${{ vars.docker_repo_url }}/${{ gitea.repository }}/web_ui) (pull_request) Successful in 15s
2024-06-16 00:06:28 +02:00
Brian Bjarke Jensen 962d028c07 added minio env vars
CI Pipeline / Test (pull_request) Successful in 2m15s
CI Pipeline / Build and Publish (./Dockerfile.model, ${{ vars.docker_repo_url }}/${{ gitea.repository }}/model) (pull_request) Successful in 17s
CI Pipeline / Build and Publish (./Dockerfile.web_ui, ${{ vars.docker_repo_url }}/${{ gitea.repository }}/web_ui) (pull_request) Successful in 15s
2024-06-15 23:59:39 +02:00
Brian Bjarke Jensen eaca23f27a updated to use new class
CI Pipeline / Test (pull_request) Successful in 2m7s
CI Pipeline / Build and Publish (./Dockerfile.model, ${{ vars.docker_repo_url }}/${{ gitea.repository }}/model) (pull_request) Successful in 6m26s
CI Pipeline / Build and Publish (./Dockerfile.web_ui, ${{ vars.docker_repo_url }}/${{ gitea.repository }}/web_ui) (pull_request) Successful in 5m40s
2024-06-15 23:18:27 +02:00
Brian Bjarke Jensen 0bd4008e72 updated to use new class 2024-06-15 23:17:28 +02:00
Brian Bjarke Jensen 123cab2500 updated to use new functions 2024-06-15 23:16:42 +02:00
Brian Bjarke Jensen d09cb9dd20 added delete function 2024-06-15 23:15:31 +02:00
Brian Bjarke Jensen e1075b3551 ignore outdated class reference 2024-06-15 23:14:55 +02:00
Brian Bjarke Jensen d537cdf11d updated to use new vis com class
CI Pipeline / Test (pull_request) Failing after 2m12s
CI Pipeline / Build and Publish (./Dockerfile.web_ui, ${{ vars.docker_repo_url }}/${{ gitea.repository }}/web_ui) (pull_request) Has been skipped
CI Pipeline / Build and Publish (./Dockerfile.model, ${{ vars.docker_repo_url }}/${{ gitea.repository }}/model) (pull_request) Has been skipped
2024-06-15 16:31:35 +02:00
Brian Bjarke Jensen b8061466ce updated to use new vis com class 2024-06-15 16:31:19 +02:00
Brian Bjarke Jensen 119282fee9 tested loading images from minio 2024-06-15 16:20:16 +02:00
Brian Bjarke Jensen 2e060f0776 updated to match images being stored in minio 2024-06-15 16:19:51 +02:00
Brian Bjarke Jensen 9d0648f3c1 transferred images from mongo to minio 2024-06-15 16:19:04 +02:00
Brian Bjarke Jensen b093154193 silenced noisy modules 2024-06-15 16:15:45 +02:00
Brian Bjarke Jensen f3aab68d5f added minio interface 2024-06-15 16:14:43 +02:00
Brian Bjarke Jensen 0ebf7e6ce1 updated logging formatting 2024-06-15 13:00:28 +02:00
Brian Bjarke Jensen 7229773371 Merge pull request 'add_pypi_proxy' (#42) from add_pypi_proxy into main
Reviewed-on: #42
2024-06-12 21:51:21 +02:00
Brian Bjarke Jensen ef985f6687 added no-root to poetry install
CI Pipeline / Test (pull_request) Successful in 3m27s
CI Pipeline / Build and Publish (./Dockerfile.model, ${{ vars.docker_repo_url }}/${{ gitea.repository }}/model) (pull_request) Successful in 7m0s
CI Pipeline / Build and Publish (./Dockerfile.web_ui, ${{ vars.docker_repo_url }}/${{ gitea.repository }}/web_ui) (pull_request) Successful in 5m29s
2024-06-05 22:31:29 +02:00
Brian Bjarke Jensen 599769a8f6 updated lock file
CI Pipeline / Test (pull_request) Successful in 4m28s
CI Pipeline / Build and Publish (./Dockerfile.model, ${{ vars.docker_repo_url }}/${{ gitea.repository }}/model) (pull_request) Successful in 5m39s
CI Pipeline / Build and Publish (./Dockerfile.web_ui, ${{ vars.docker_repo_url }}/${{ gitea.repository }}/web_ui) (pull_request) Successful in 5m55s
2024-06-05 22:14:46 +02:00
Brian Bjarke Jensen 70dca8f598 fixed link bug
CI Pipeline / Build and Publish (./Dockerfile.model, ${{ vars.docker_repo_url }}/${{ gitea.repository }}/model) (pull_request) Has been skipped
CI Pipeline / Test (pull_request) Failing after 31s
CI Pipeline / Build and Publish (./Dockerfile.web_ui, ${{ vars.docker_repo_url }}/${{ gitea.repository }}/web_ui) (pull_request) Has been skipped
2024-06-05 22:13:29 +02:00
Brian Bjarke Jensen 28d86e8762 fixed port bug
CI Pipeline / Test (pull_request) Failing after 32s
CI Pipeline / Build and Publish (./Dockerfile.model, ${{ vars.docker_repo_url }}/${{ gitea.repository }}/model) (pull_request) Has been skipped
CI Pipeline / Build and Publish (./Dockerfile.web_ui, ${{ vars.docker_repo_url }}/${{ gitea.repository }}/web_ui) (pull_request) Has been skipped
2024-06-05 22:10:15 +02:00
Brian Bjarke Jensen fb278b641c updated lock file
CI Pipeline / Test (pull_request) Failing after 33s
CI Pipeline / Build and Publish (./Dockerfile.model, ${{ vars.docker_repo_url }}/${{ gitea.repository }}/model) (pull_request) Has been skipped
CI Pipeline / Build and Publish (./Dockerfile.web_ui, ${{ vars.docker_repo_url }}/${{ gitea.repository }}/web_ui) (pull_request) Has been skipped
2024-06-05 22:06:51 +02:00
Brian Bjarke Jensen 3eb5f5e826 updated to use pypi proxy
CI Pipeline / Test (pull_request) Failing after 27s
CI Pipeline / Build and Publish (./Dockerfile.web_ui, ${{ vars.docker_repo_url }}/${{ gitea.repository }}/web_ui) (pull_request) Has been skipped
CI Pipeline / Build and Publish (./Dockerfile.model, ${{ vars.docker_repo_url }}/${{ gitea.repository }}/model) (pull_request) Has been skipped
2024-06-05 22:04:27 +02:00
Brian Bjarke Jensen 4a4ee95e31 added threadripper as pypi proxy 2024-06-05 22:04:19 +02:00
Brian Bjarke Jensen 4f8f32cf93 Merge pull request 'updated workflow' (#40) from add_test_workflow into main
Reviewed-on: http://192.168.1.2:3000/brian/visual_critical_discourse_analysis/pulls/40
2024-06-05 21:51:50 +02:00
Brian Bjarke Jensen 4e7f6cdee6 Merge pull request 'Delete .gitea/workflows/default.yaml' (#41) from delete_default_workflow into main
Reviewed-on: http://192.168.1.2:3000/brian/visual_critical_discourse_analysis/pulls/41
2024-06-05 21:46:25 +02:00
Brian Bjarke Jensen 6a36215d82 Delete .gitea/workflows/default.yaml
CI Pipeline / Test (pull_request) Successful in 4m15s
CI Pipeline / Build and Publish (./Dockerfile.web_ui, ${{ vars.docker_repo_url }}/${{ gitea.repository }}/web_ui) (pull_request) Successful in 14s
CI Pipeline / Build and Publish (./Dockerfile.model, ${{ vars.docker_repo_url }}/${{ gitea.repository }}/model) (pull_request) Successful in 14s
2024-06-05 21:29:08 +02:00
Brian Bjarke Jensen 8620d43e74 removed unnecessary workflow
CI Pipeline / Test (pull_request) Successful in 4m28s
CI Pipeline / Build and Publish (./Dockerfile.web_ui, ${{ vars.docker_repo_url }}/${{ gitea.repository }}/web_ui) (pull_request) Successful in 14s
2024-06-05 21:10:13 +02:00
Brian Bjarke Jensen d26eeaddd1 updated workflow
Code Quality Pipeline / Test (push) Successful in 7m5s
CI Pipeline / Test (pull_request) Successful in 4m36s
CI Pipeline / Build and Publish (./Dockerfile.web_ui, ${{ vars.docker_repo_url }}/${{ gitea.repository }}/web_ui) (pull_request) Successful in 15s
2024-06-05 21:08:59 +02:00
Brian Bjarke Jensen b9d883484f Merge pull request 'added workflow' (#38) from add_test_workflow into main
Code Quality Pipeline / Test (push) Successful in 4m27s
Reviewed-on: http://192.168.1.2:3000/brian/visual_critical_discourse_analysis/pulls/38
2024-06-04 22:44:13 +02:00
Brian Bjarke Jensen 833724912e added workflow
CI Pipeline / Build and Publish (./Dockerfile.web_ui, ${{ vars.docker_repo_url }}/${{ gitea.repository }}/web_ui) (pull_request) Successful in 15s
Code Quality Pipeline / Test (push) Has been cancelled
CI Pipeline / Test (pull_request) Successful in 4m17s
2024-06-04 21:39:44 +02:00
Brian Bjarke Jensen 717bb352e9 Merge pull request 'add_model_saving' (#37) from add_model_saving into main
Code Quality Pipeline / Test (push) Successful in 4m19s
Reviewed-on: http://192.168.1.2:3000/brian/visual_critical_discourse_analysis/pulls/37
2024-06-04 21:09:24 +02:00
Brian Bjarke Jensen cb8e907ecb updated mypy config
Code Quality Pipeline / Test (push) Successful in 4m21s
CI Pipeline / Test (pull_request) Successful in 4m27s
CI Pipeline / Build and Publish (./Dockerfile.model, ${{ vars.docker_repo_url }}/${{ gitea.repository }}/model) (pull_request) Successful in 15s
CI Pipeline / Build and Publish (./Dockerfile.web_ui, ${{ vars.docker_repo_url }}/${{ gitea.repository }}/web_ui) (pull_request) Successful in 13s
2024-06-04 20:38:45 +02:00
Brian Bjarke Jensen 084273f5bc updated mypy config
Code Quality Pipeline / Test (push) Has been cancelled
CI Pipeline / Test (pull_request) Successful in 5m33s
CI Pipeline / Build and Publish (./Dockerfile.model, ${{ vars.docker_repo_url }}/${{ gitea.repository }}/model) (pull_request) Successful in 7m28s
CI Pipeline / Build and Publish (./Dockerfile.web_ui, ${{ vars.docker_repo_url }}/${{ gitea.repository }}/web_ui) (pull_request) Successful in 7m31s
2024-06-04 20:36:41 +02:00
Brian Bjarke Jensen b98234267f renamed module 2024-06-04 20:35:08 +02:00
Brian Bjarke Jensen 11aed4497c updated flake8 config 2024-06-04 20:34:57 +02:00
Brian Bjarke Jensen 93af7a69be updated mypy script 2024-06-04 20:27:58 +02:00
Brian Bjarke Jensen 385eb79f0e added init files to avoid mypy errors 2024-06-04 20:27:44 +02:00
Brian Bjarke Jensen dcd01849ec added pandas type stubs 2024-06-04 20:16:39 +02:00
Brian Bjarke Jensen cf03275da0 installed package 2024-06-04 20:12:23 +02:00
Brian Bjarke Jensen cecdf546b8 fixed flake8 config 2024-06-04 20:08:19 +02:00
Brian Bjarke Jensen bd9c8136d1 installed packages 2024-06-04 20:08:01 +02:00
Brian Bjarke Jensen 675835a56b moved flake8 configuration 2024-06-04 20:05:41 +02:00
Brian Bjarke Jensen 68a542be04 updated CI
Code Quality Pipeline / Test (push) Failing after 3m27s
CI Pipeline / Test (pull_request) Failing after 3m39s
CI Pipeline / Build and Publish (./Dockerfile.model, ${{ vars.docker_repo_url }}/${{ gitea.repository }}/model) (pull_request) Has been skipped
CI Pipeline / Build and Publish (./Dockerfile.web_ui, ${{ vars.docker_repo_url }}/${{ gitea.repository }}/web_ui) (pull_request) Has been skipped
2024-05-21 19:16:59 +02:00
Brian Bjarke Jensen 2ab4cf3b71 updated docker files 2024-05-21 19:15:58 +02:00
Brian Bjarke Jensen 0fbdc3865c updated main script 2024-05-21 19:15:44 +02:00
Brian Bjarke Jensen d516578d4b added utility functions 2024-05-21 19:14:49 +02:00
Brian Bjarke Jensen 1579b15b4e added minio interface 2024-05-21 19:14:14 +02:00
Brian Bjarke Jensen 9fbed5a9c4 isntalled packages 2024-05-21 19:13:39 +02:00
Brian Bjarke Jensen 6f0513ea2c added docs 2024-05-21 19:13:24 +02:00
Brian Bjarke Jensen 0bbf53d495 Merge pull request 'move_webui' (#35) from move_webui into main
Code Quality Pipeline / Test (push) Successful in 4m3s
Reviewed-on: http://192.168.1.2:3000/brian/visual_critical_discourse_analysis/pulls/35
2024-05-21 16:19:31 +02:00
Brian Bjarke Jensen c6f4c81ddd updated to use new dockerfile in root dir
Code Quality Pipeline / Test (push) Successful in 4m16s
CI Pipeline / Test (pull_request) Successful in 4m17s
CI Pipeline / Build and Publish (./Dockerfile.web_ui, ${{ vars.docker_repo_url }}/${{ gitea.repository }}/web_ui) (pull_request) Successful in 8m32s
2024-05-21 08:13:07 +02:00
Brian Bjarke Jensen 1f4f9949fe updated compose to use new dockerfile in root dir 2024-05-21 08:12:00 +02:00
Brian Bjarke Jensen 8c5b58ebf3 added new dockerfile in root dir 2024-05-21 08:11:41 +02:00
Brian Bjarke Jensen 8db61b2b8a more ci
CI Pipeline / Build and Publish (pull_request) Failing after 13s
Code Quality Pipeline / Test (push) Successful in 4m10s
CI Pipeline / Test (pull_request) Successful in 4m31s
2024-05-20 22:25:09 +02:00
Brian Bjarke Jensen a500e8a687 fixed ci
Code Quality Pipeline / Test (push) Successful in 4m3s
CI Pipeline / Test (pull_request) Successful in 4m14s
CI Pipeline / Build and Publish (pull_request) Failing after 14s
2024-05-20 22:10:54 +02:00
Brian Bjarke Jensen 7d723285c8 updated workflow
Code Quality Pipeline / Test (push) Successful in 3m59s
CI Pipeline / Test (pull_request) Successful in 4m12s
CI Pipeline / Build and Publish (pull_request) Failing after 12s
2024-05-20 21:56:49 +02:00
Brian Bjarke Jensen 75e61bf05c updated relative path to dockerfile
Code Quality Pipeline / Test (push) Successful in 4m5s
CI Pipeline / Test (pull_request) Successful in 4m17s
CI Pipeline / Build and Publish (pull_request) Failing after 12s
2024-05-20 21:41:40 +02:00
Brian Bjarke Jensen 291407dfac updated dockerfile path
Code Quality Pipeline / Test (push) Successful in 4m12s
CI Pipeline / Test (pull_request) Successful in 4m20s
CI Pipeline / Build and Publish (pull_request) Failing after 20s
2024-05-20 20:56:10 +02:00
Brian Bjarke Jensen 8724bda2f1 updated publishing workflow
Code Quality Pipeline / Test (push) Successful in 4m15s
CI Pipeline / Test (pull_request) Successful in 4m13s
CI Pipeline / Build and Publish (pull_request) Failing after 13s
2024-05-20 20:38:03 +02:00
Brian Bjarke Jensen 4a6d0dbe09 updated workflows
Code Quality Pipeline / Test (push) Successful in 4m17s
CI Pipeline / Test (pull_request) Successful in 4m16s
CI Pipeline / Build and Publish (pull_request) Failing after 12s
2024-05-20 20:19:40 +02:00
Brian Bjarke Jensen fd9140093d moved webui and updated poetry packages
Code Quality Pipeline / Test (push) Successful in 4m5s
CI Pipeline / Test (pull_request) Failing after 3m42s
CI Pipeline / Build and Publish (pull_request) Has been skipped
2024-05-20 19:19:29 +02:00
Brian Bjarke Jensen 72c60170a7 fixed model attribute function
Code Quality Pipeline / Test (push) Successful in 4m23s
CI Pipeline / Build and Publish (pull_request) Has been skipped
CI Pipeline / Test (pull_request) Failing after 3m51s
2024-05-07 21:30:51 +02:00
Brian Bjarke Jensen e3e836c9fa fixed imports
Code Quality Pipeline / Test (push) Failing after 4m41s
CI Pipeline / Test (pull_request) Failing after 3m43s
CI Pipeline / Build and Publish (pull_request) Has been skipped
2024-05-07 21:22:25 +02:00
Brian Bjarke Jensen 9a625cb6be updated CI script 2024-05-07 21:21:52 +02:00
Brian Bjarke Jensen 38fc595725 fixed code import bug
Code Quality Pipeline / Test (push) Failing after 3m41s
CI Pipeline / Test (pull_request) Failing after 3m57s
CI Pipeline / Build and Publish (pull_request) Has been skipped
2024-05-07 20:23:40 +02:00
Brian Bjarke Jensen e04ce33891 removed unused files 2024-05-07 20:23:22 +02:00
Brian Bjarke Jensen bc653a0be4 added new folder and ran tests 2024-05-07 20:19:18 +02:00
Brian Bjarke Jensen d9d2dbab74 Merge pull request 'add_model_resnet18' (#34) from add_model_resnet18 into main
Code Quality Pipeline / Test (push) Successful in 3m55s
Reviewed-on: http://192.168.1.2:3000/brian/visual_critical_discourse_analysis/pulls/34
2024-05-07 19:02:32 +02:00
Brian Bjarke Jensen f10f9660a3 Merge branch 'main' of 192.168.1.2:brian/visual_critical_discourse_analysis into add_model_resnet18
Code Quality Pipeline / Test (push) Successful in 3m57s
CI Pipeline / Test (pull_request) Successful in 3m58s
CI Pipeline / Build and Publish (pull_request) Successful in 8m48s
2024-04-30 21:25:32 +02:00
Brian Bjarke Jensen 649dedeb29 fixed imports
Code Quality Pipeline / Test (push) Successful in 4m4s
2024-04-30 21:21:29 +02:00
Brian Bjarke Jensen 5391927c58 unfinished work 2024-04-30 21:06:53 +02:00
Brian Bjarke Jensen 7abd4b7403 added empty dockerfile 2024-04-30 21:06:33 +02:00
Brian Bjarke Jensen 7d86b75833 reran poetry lock 2024-04-30 21:06:11 +02:00
Brian Bjarke Jensen 3d6207bf57 Merge pull request 'add_model_resnet18' (#31) from add_model_resnet18 into main
Code Quality Pipeline / Test (push) Successful in 3m36s
Reviewed-on: http://192.168.1.2:3000/brian/visual_critical_discourse_analysis/pulls/31
2024-04-03 21:04:22 +02:00
Brian Bjarke Jensen df2610dc22 added copying in core code
Code Quality Pipeline / Test (push) Successful in 5m31s
CI Pipeline / Test (pull_request) Successful in 3m54s
CI Pipeline / Build and Publish (pull_request) Successful in 8m21s
2024-04-03 21:02:18 +02:00
Brian Bjarke Jensen 1975b13151 updated to use new data classes and bugfixes 2024-04-03 21:01:36 +02:00
Brian Bjarke Jensen 12d043b220 updated to use new data classes and bugfixes 2024-04-03 21:00:55 +02:00
Brian Bjarke Jensen 2ff5124239 deleted unused file 2024-04-03 21:00:08 +02:00
Brian Bjarke Jensen f983145dfb added database util functions 2024-04-03 20:58:55 +02:00
Brian Bjarke Jensen 0a3a07da12 fixed column without title-formatting 2024-04-03 20:04:14 +02:00
Brian Bjarke Jensen c3aba7831b updated function to list names of all vis com docs in database 2024-03-31 12:26:51 +02:00
Brian Bjarke Jensen 75b57bd28f updated function to get visual communication 2024-03-31 12:24:49 +02:00
Brian Bjarke Jensen bb95e78132 renamed argument for clarity 2024-03-31 12:20:03 +02:00
Brian Bjarke Jensen eb4185c129 added function to count documents 2024-03-31 12:19:31 +02:00
Brian Bjarke Jensen e09188453e defined database connect function 2024-03-31 12:17:59 +02:00
Brian Bjarke Jensen a49e55c159 updating exposed content 2024-03-31 12:17:15 +02:00
Brian Bjarke Jensen 2a85997aa7 deleted unused file 2024-03-31 12:10:41 +02:00
Brian Bjarke Jensen b430a15ece updated database classes 2024-03-31 11:24:28 +02:00
Brian Bjarke Jensen abd83aa542 added ModelData class 2024-03-31 11:24:04 +02:00
Brian Bjarke Jensen a2164b9ccf renamed files 2024-03-31 10:38:20 +02:00
Brian Bjarke Jensen 0e404015f1 commented out app for testing 2024-03-31 10:29:17 +02:00
Brian Bjarke Jensen e0eb1dd53c defined data models 2024-03-31 10:28:25 +02:00
Brian Bjarke Jensen 28c9d12a5b exposed Dataset object 2024-03-24 16:37:57 +01:00
Brian Bjarke Jensen 167f9ecb3c added create_time factory 2024-03-20 19:45:07 +01:00
Brian Bjarke Jensen dc976540bf added definition of dataset 2024-03-20 19:29:05 +01:00
Brian Bjarke Jensen 133019ad22 added init file 2024-03-20 19:22:39 +01:00
Brian Bjarke Jensen dc9f611e17 moved functions into separate files 2024-03-20 19:18:57 +01:00
Brian Bjarke Jensen 657e9c4b1d updated imports 2024-03-20 19:13:31 +01:00
Brian Bjarke Jensen 080c7e7a5c fixed bug when extracting names from list 2024-03-20 18:46:33 +01:00
Brian Bjarke Jensen 8965eb7192 moved object definitions into separate files 2024-03-17 18:41:05 +01:00
Brian Bjarke Jensen 6fec85adfe removed unused object 2024-03-17 18:19:29 +01:00
Brian Bjarke Jensen a7f2f67d05 removed unused object 2024-03-17 18:17:00 +01:00
Brian Bjarke Jensen 7f906068d7 exposed final model data object 2024-03-17 18:15:52 +01:00
Brian Bjarke Jensen 5021cfec51 renamed file for clarity 2024-03-17 10:50:22 +01:00
Brian Bjarke Jensen 68c2bb56d5 added definition of final model data 2024-03-17 10:47:50 +01:00
Brian Bjarke Jensen ccf2abda63 updated to new base data model name 2024-03-17 10:42:01 +01:00
Brian Bjarke Jensen 5651c9d200 renamed file for clarity 2024-03-17 10:40:55 +01:00
Brian Bjarke Jensen 7b3782a1ce renamed file for clarity 2024-03-17 10:38:20 +01:00
Brian Bjarke Jensen eceb6fba9e exposed model 2024-03-17 10:29:21 +01:00
Brian Bjarke Jensen 50756450e4 defined final model 2024-03-17 10:29:01 +01:00
Brian Bjarke Jensen 54eca39b6d renamed dir 2024-03-17 10:27:49 +01:00
Brian Bjarke Jensen b9f333c7d0 began defining visual communication model
Code Quality Pipeline / Test (push) Successful in 4m5s
2024-03-12 22:54:50 +01:00
Brian Bjarke Jensen 865a8ff2b7 added instantiation from list 2024-03-12 22:44:10 +01:00
Brian Bjarke Jensen 08c5c14da6 added definitions of data models 2024-03-12 22:38:53 +01:00
Brian Bjarke Jensen f139206758 added model definitions 2024-03-12 21:50:47 +01:00
Brian Bjarke Jensen ec48879327 updated to use data_name_list 2024-03-12 18:45:35 +01:00
Brian Bjarke Jensen 1065a35cf0 removed unused file 2024-03-12 18:42:42 +01:00
Brian Bjarke Jensen 93fb18fde2 installed packages
Code Quality Pipeline / Test (push) Successful in 3m52s
2024-03-11 20:39:20 +01:00
Brian Bjarke Jensen 1e339cecf4 added database util functions 2024-03-11 20:38:48 +01:00
Brian Bjarke Jensen ded50d8095 moved shared functions 2024-03-11 20:36:14 +01:00
Brian Bjarke Jensen 9fa3574a7d added dataloader, criterion, model definition and began defining training 2024-03-11 20:33:49 +01:00
Brian Bjarke Jensen 94ad3d28f1 Merge pull request 'delete_unused_workflow' (#25) from delete_unused_workflow into main
Code Quality Pipeline / Test (push) Successful in 54s
Reviewed-on: http://192.168.1.2:3000/brian/visual_critical_discourse_analysis/pulls/25
2024-03-06 20:20:34 +01:00
143 changed files with 6939 additions and 2247 deletions
-2
View File
@@ -1,2 +0,0 @@
[flake8]
per-file-ignores = __init__.py:F401
-27
View File
@@ -1,27 +0,0 @@
name: Code Quality Pipeline
run-name: ${{ gitea.actor }} is running the Code Quality Pipeline
runs-on: ubuntu-latest
on: push
image: python:3.12
jobs:
test:
name: Test
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v3
- name: Setup Environment
uses: https://github.com/actions/setup-python@v3
with:
python-verison: "3.12"
architecture: "x64"
- name: Install Packages
run: |
pip install poetry
poetry install
- name: PEP8 Check
run: |
poetry run flake8 ./src --benchmark
- name: Type Check
run: |
poetry run mypy ./src --disable-error-code=import-untyped
+36 -13
View File
@@ -1,10 +1,8 @@
name: CI Pipeline name: CI Pipeline
run-name: ${{ gitea.actor }} is running the CI Pipeline run-name: ${{ gitea.actor }} is running the CI Pipeline
runs-on: ubuntu-latest
on: on:
pull_request: release:
branches: types: [published]
- main
jobs: jobs:
test: test:
name: Test name: Test
@@ -18,35 +16,60 @@ jobs:
python-verison: "3.12" python-verison: "3.12"
architecture: "x64" architecture: "x64"
- name: Install Packages - name: Install Packages
env:
PIP_INDEX_URL: http://192.168.1.2:5001/index/
PIP_TRUSTED_HOST: 192.168.1.2
run: | run: |
pip install poetry pip install poetry
poetry install poetry install
- name: PEP8 Check - name: PEP8 Check
run: | run: |
poetry run flake8 ./src --benchmark poetry run flake8 . --benchmark
- name: Type Check - name: Type Check
run: | run: |
poetry run mypy ./src --disable-error-code=import-untyped poetry run mypy .
publish: publish:
name: Build and Publish name: Build and Publish
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: [test] needs: [test]
strategy:
fail-fast: false
matrix:
include:
-
dockerfile: ./Dockerfile.web_ui
image: ${{ vars.docker_repo_url }}/${{ gitea.repository }}/web_ui
-
dockerfile: ./Dockerfile.model
image: ${{ vars.docker_repo_url }}/${{ gitea.repository }}/model
steps: steps:
- name: Checkout Code -
name: Checkout Code
uses: actions/checkout@v3 uses: actions/checkout@v3
- name: Set Environment Variables -
name: Set Environment Variables
run: | run: |
echo "sha_short=$(git rev-parse --short ${{ gitea.sha }} )" >> "$GITHUB_ENV" echo "sha_short=$(git rev-parse --short ${{ gitea.sha }} )" >> "$GITHUB_ENV"
- name: Show Environment Variables -
name: Show Environment Variables
run: | run: |
echo "DOCKER_REPO_URL: ${{ vars.docker_repo_url }}" echo "DOCKER_REPO_URL: ${{ vars.docker_repo_url }}"
echo "REPOSITORY: ${{ gitea.repository }}" echo "REPOSITORY: ${{ gitea.repository }}"
echo "COMMIT_SHA: ${{ env.sha_short }}" echo "COMMIT_SHA: ${{ env.sha_short }}"
- name: Build and Push Image -
uses: docker/build-push-action@v2 name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ matrix.image }}
-
name: Build and Push Image
uses: docker/build-push-action@v4
with: with:
context: . context: .
file: ${{ matrix.dockerfile }}
push: true push: true
tags: | tags: |
${{ vars.docker_repo_url }}/${{ gitea.repository }}:${{ env.sha_short }} ${{ matrix.image }}:${{ env.sha_short }}
${{ vars.docker_repo_url }}/${{ gitea.repository }}:latest ${{ matrix.image }}:latest
labels: ${{ steps.meta.outputs.labels }}
+50
View File
@@ -0,0 +1,50 @@
name: Code Quality Pipeline
on:
pull_request:
branches:
- main
jobs:
job:
name: Check Code
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v4
- name: Setup Environment
uses: https://github.com/actions/setup-python@v5
with:
python-version: "3.12"
architecture: "x64"
- name: Setup poetry
env:
POETRY_VERSION: 2.1.1
POETRY_HOME: /opt/poetry
POETRY_NO_INTERACTION: 1
POETRY_NO_CACHE: 1
run: |
curl -sSL https://install.python-poetry.org | python3 -
export PATH=$POETRY_HOME/bin:$PATH
poetry --version
- name: Install Dependencies
env:
PIP_INDEX_URL: ${{ vars.PIP_INDEX_URL }}
PIP_TRUSTED_HOST: ${{ vars.PIP_TRUSTED_HOST }}
run: |
/opt/poetry/bin/poetry install
- name: PEP8 Check
run: |
/opt/poetry/bin/poetry run flake8 . --benchmark
- name: Type Check
run: |
/opt/poetry/bin/poetry run mypy .
- name: Pytest & Calculate Coverage
env:
MINIO_ENDPOINT: ${{ vars.MINIO_ENDPOINT }}
MINIO_ACCESS_KEY: ${{ vars.MINIO_ACCESS_KEY }}
MINIO_SECRET_KEY: ${{ secrets.MINIO_SECRET_KEY }}
MONGO_ENDPOINT: ${{ vars.MONGO_ENDPOINT }}
run: |
/opt/poetry/bin/poetry run coverage run -m pytest .
- name: Coverage Report
run: |
/opt/poetry/bin/poetry run coverage report -m
+17
View File
@@ -0,0 +1,17 @@
name: release-tag
on:
workflow_dispatch:
inputs:
version:
description: 'The version number to deploy'
required: true
jobs:
release-image:
runs-on: ubuntu-latest
steps:
-
run: echo "manual trigger executed"
-
run: echo "$MESSAGE"
env:
MESSAGE; ${{ github.event.inputs.version}}
+3
View File
@@ -177,3 +177,6 @@ ipython_config.py
# Remove previous ipynb_checkpoints # Remove previous ipynb_checkpoints
# git rm -r .ipynb_checkpoints/ # git rm -r .ipynb_checkpoints/
# Logging data
runs/*
+33 -15
View File
@@ -1,44 +1,62 @@
repos: repos:
- repo: https://github.com/pre-commit/pre-commit-hooks - repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.5.0 rev: v5.0.0
hooks: hooks:
- id: trailing-whitespace - id: trailing-whitespace
- id: end-of-file-fixer - id: end-of-file-fixer
- id: check-yaml - id: check-yaml
- id: check-added-large-files
- id: debug-statements - id: debug-statements
- id: double-quote-string-fixer - id: double-quote-string-fixer
- id: name-tests-test - id: name-tests-test
- id: requirements-txt-fixer
- repo: https://github.com/asottile/setup-cfg-fmt - repo: https://github.com/asottile/setup-cfg-fmt
rev: v2.5.0 rev: v2.7.0
hooks: hooks:
- id: setup-cfg-fmt - id: setup-cfg-fmt
- repo: https://github.com/asottile/reorder-python-imports - repo: https://github.com/pre-commit/mirrors-isort
rev: v3.12.0 rev: v5.10.1
hooks: hooks:
- id: reorder-python-imports - id: isort
exclude: ^(pre_commit/resources/|testing/resources/python3_hooks_repo/) language_version: python3.10
args: [--py39-plus, --add-import, 'from __future__ import annotations'] args: [ --tc ]
- repo: https://github.com/asottile/add-trailing-comma - repo: https://github.com/asottile/add-trailing-comma
rev: v3.1.0 rev: v3.1.0
hooks: hooks:
- id: add-trailing-comma - id: add-trailing-comma
- repo: https://github.com/asottile/pyupgrade - repo: https://github.com/asottile/pyupgrade
rev: v3.15.1 rev: v3.19.1
hooks: hooks:
- id: pyupgrade - id: pyupgrade
args: [--py39-plus] args: [--py39-plus]
- repo: https://github.com/hhatto/autopep8
rev: v2.0.4
hooks:
- id: autopep8
- repo: https://github.com/PyCQA/flake8 - repo: https://github.com/PyCQA/flake8
rev: 7.0.0 rev: 7.0.0
hooks: hooks:
- id: flake8 - id: flake8
entry: pflake8
additional_dependencies:
- "pyproject-flake8"
- repo: https://github.com/pre-commit/mirrors-mypy - repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.8.0 rev: v1.15.0
hooks: hooks:
- id: mypy - id: mypy
additional_dependencies: [types-all]
exclude: ^testing/resources/ exclude: ^testing/resources/
- repo: https://github.com/psf/black
rev: 25.1.0
hooks:
- id: black
language_version: python3.12
args: [ --skip-string-normalization ]
- repo: https://github.com/myint/docformatter
rev: v1.5.0
hooks:
- id: docformatter
name: docformatter
description: 'Formats docstrings to follow PEP 257.'
entry: docformatter
language: python
types: [ python ]
- repo: https://github.com/jendrikseipp/vulture
rev: 'v2.14'
hooks:
- id: vulture
entry: vulture . --min-confidence 90 --exclude */.venv/*.py,*/tests/*.py
+54
View File
@@ -0,0 +1,54 @@
# build stage
FROM python:3.12-slim-bookworm AS builder
# set environment variables
ENV PYTHONUNBUFFERED=1 \
PYTHONDONTWRITEBYTECODE=1 \
PIP_NO_CACHE_DIR=off \
PIP_DISABLE_PIP_VERSION_CHECK=ON \
PIP_DEFAULT_TIMEOUT=100 \
DEBIAN_FRONTEND=noninteractive \
POETRY_HOME=/etc/poetry \
POETRY_VERSION=1.7.1 \
POETRY_VIRTUALENVS_IN_PROJECT=1 \
POETRY_VIRTUALENVS_CREATE=1 \
POETRY_NO_INTERACTION=1 \
POETRY_CACHE_DIR=/tmp/poetry_cache \
APP_HOME=/home/app
# update system
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
build-essential \
curl \
&& apt-get clean
# install poetry
RUN curl -sSL https://install.python-poetry.org | python3 -
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 \
--with shared,model \
--no-root
# final stage
FROM python:3.12-slim-bookworm
# copy virtualenv made by poetry
ENV APP_HOME=/home/app \
VIRTUAL_ENV=/home/app/.venv
COPY --from=builder ${VIRTUAL_ENV} ${VIRTUAL_ENV}
ENV PATH="${VIRTUAL_ENV}/bin:${PATH}"
# create home directory and app user
RUN mkdir -p $APP_HOME
# add code while changing ownership
WORKDIR $APP_HOME
COPY ./shared ./shared
COPY ./model/src ./
ENTRYPOINT [ "python", "main.py" ]
+59
View File
@@ -0,0 +1,59 @@
# build stage
FROM python:3.12-slim-bookworm AS builder
# set environment variables
ENV PYTHONUNBUFFERED=1 \
PYTHONDONTWRITEBYTECODE=1 \
PIP_NO_CACHE_DIR=off \
PIP_DISABLE_PIP_VERSION_CHECK=ON \
PIP_DEFAULT_TIMEOUT=100 \
DEBIAN_FRONTEND=noninteractive \
POETRY_HOME=/etc/poetry \
POETRY_VERSION=1.7.1 \
POETRY_VIRTUALENVS_IN_PROJECT=1 \
POETRY_VIRTUALENVS_CREATE=1 \
POETRY_NO_INTERACTION=1 \
POETRY_CACHE_DIR=/tmp/poetry_cache \
APP_HOME=/home/app
# update system
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
build-essential \
curl \
&& apt-get clean
# install poetry
RUN curl -sSL https://install.python-poetry.org | python3 -
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 \
--with shared,web_ui \
--no-root
# final stage
FROM python:3.12-slim-bookworm
# copy virtualenv made by poetry
ENV APP_HOME=/home/app \
VIRTUAL_ENV=/home/app/.venv
COPY --from=builder ${VIRTUAL_ENV} ${VIRTUAL_ENV}
ENV PATH="${VIRTUAL_ENV}/bin:${PATH}"
# create home directory and app user
RUN mkdir -p /home/app && \
addgroup --system app && \
adduser --system --group app
# add code while changing ownership
WORKDIR $APP_HOME
COPY --chown=app:app ./shared ./shared
COPY --chown=app:app ./web_ui/src ./src
# change to the app user
USER app
ENTRYPOINT [ "gunicorn", "src.main:server", "-b", "0.0.0.0:8050" ]
+1 -2
View File
@@ -1,11 +1,10 @@
version: '3.7'
services: services:
app: app:
image: visual_critical_discourse_analysis:dev image: visual_critical_discourse_analysis:dev
container_name: visual_critical_discourse_analysis container_name: visual_critical_discourse_analysis
build: build:
context: . context: .
dockerfile: Dockerfile dockerfile: ./web_ui/Dockerfile
env_file: env_file:
- local.env - local.env
environment: environment:
+1 -2
View File
@@ -1,11 +1,10 @@
version: '3.7'
services: services:
app: app:
image: visual_critical_discourse_analysis:dev image: visual_critical_discourse_analysis:dev
container_name: visual_critical_discourse_analysis container_name: visual_critical_discourse_analysis
build: build:
context: . context: .
dockerfile: Dockerfile dockerfile: ./web_ui/Dockerfile
env_file: env_file:
- server.env - server.env
ports: ports:
+1 -2
View File
@@ -6,10 +6,9 @@ from pathlib import Path
import pandas as pd import pandas as pd
import requests import requests
from classes import Instagram
from retry import retry from retry import retry
from image_download.classes import Instagram
def get_sources() -> pd.DataFrame: def get_sources() -> pd.DataFrame:
"""Get sources dateframe.""" """Get sources dateframe."""
@@ -3,10 +3,11 @@ from __future__ import annotations
import os import os
from pathlib import Path from pathlib import Path
from database import connect
from database import VisualCommunication
from dotenv import load_dotenv from dotenv import load_dotenv
from shared.docstore import connect_mongodb
from shared.docstore.classes import VisualCommunication
if __name__ == '__main__': if __name__ == '__main__':
# prepare env vars # prepare env vars
env_path = Path(__file__).parent.parent / 'local.env' env_path = Path(__file__).parent.parent / 'local.env'
@@ -14,7 +15,7 @@ if __name__ == '__main__':
load_dotenv(env_path) load_dotenv(env_path)
os.environ['MONGO_HOST'] = 'localhost' os.environ['MONGO_HOST'] = 'localhost'
# connect to database # connect to database
collection, db, client = connect() collection, db, client = connect_mongodb()
print(client.server_info()) print(client.server_info())
# download images # download images
data = None data = None

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 26 KiB

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 29 KiB

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 37 KiB

+9
View File
@@ -0,0 +1,9 @@
from shared.docstore.classes import ModelData
if __name__ == '__main__':
# instantiate data object
vis_com_list = [ModelData.from_random() for i in range(3)]
# generate random predictions
[vis_com.from_random() for vis_com in vis_com_list]
for vis_com in vis_com_list:
print(vis_com)
@@ -3,10 +3,10 @@ from __future__ import annotations
import os import os
from pathlib import Path from pathlib import Path
from database import connect
from database import total_annotated
from dotenv import load_dotenv from dotenv import load_dotenv
from shared.docstore import connect_mongodb, count_documents
if __name__ == '__main__': if __name__ == '__main__':
# prepare env vars # prepare env vars
env_path = Path(__file__).parent.parent / 'local.env' env_path = Path(__file__).parent.parent / 'local.env'
@@ -14,7 +14,10 @@ if __name__ == '__main__':
load_dotenv(env_path) load_dotenv(env_path)
os.environ['MONGO_HOST'] = 'localhost' os.environ['MONGO_HOST'] = 'localhost'
# connect to database # connect to database
collection, db, client = connect() collection, db, client = connect_mongodb()
# get visual communication # get visual communication
num_docs = total_annotated(collection) num_docs = count_documents(
collection=collection,
only_with_annotation=True,
)
print(f"number of annotated documents in database: {num_docs}") print(f"number of annotated documents in database: {num_docs}")
@@ -3,10 +3,10 @@ from __future__ import annotations
import os import os
from pathlib import Path from pathlib import Path
from database import connect
from database import total_documents
from dotenv import load_dotenv from dotenv import load_dotenv
from shared.docstore import connect_mongodb, count_documents
if __name__ == '__main__': if __name__ == '__main__':
# prepare env vars # prepare env vars
env_path = Path(__file__).parent.parent / 'local.env' env_path = Path(__file__).parent.parent / 'local.env'
@@ -14,7 +14,10 @@ if __name__ == '__main__':
load_dotenv(env_path) load_dotenv(env_path)
os.environ['MONGO_HOST'] = 'localhost' os.environ['MONGO_HOST'] = 'localhost'
# connect to database # connect to database
collection, db, client = connect() collection, db, client = connect_mongodb()
# get visual communication # get visual communication
num_docs = total_documents(collection) num_docs = count_documents(
collection=collection,
only_with_annotation=False,
)
print(f"total number of documents in database: {num_docs}") print(f"total number of documents in database: {num_docs}")
+16
View File
@@ -0,0 +1,16 @@
services:
vcda_model:
image: vcda_model:test
container_name: vcda_model_alone
build:
context: ../
dockerfile: ./Dockerfile.model
env_file:
- ../server.env
environment:
- ENV=TEST
deploy:
resources:
limits:
cpus: '2.000'
memory: 8G
View File
+14
View File
@@ -0,0 +1,14 @@
{
"datasets": {
"train": {
"filelist": "datasets/train.csv"
},
"val": {
"filelist": "datasets/val.csv"
}
},
"backbone": {
"class": "model.src.models.VisualCommunicationModel",
"model_name": "pretrained"
}
}
View File
View File
+43
View File
@@ -0,0 +1,43 @@
"""Main script to be run by service."""
import json
import logging
from traceback import print_exc
import torch
from models import VisualCommunicationModel
from tqdm import tqdm
from utils import DEVICE, VCDADataset, load_model
from shared.utils import setup_logging
if __name__ == '__main__':
# setup logging
setup_logging()
# instantiate model
model: VisualCommunicationModel = load_model()
model.eval()
# setup dataset
dataset = VCDADataset(
data_name_list=[
'02dbaf48d713e4e6d3a6b98fd2dc866e',
],
do_augment=False,
)
# make prediction
with torch.no_grad():
for i in tqdm(range(len(dataset))):
try:
# get image
image = dataset[i]
image = torch.unsqueeze(image, 0) # add artificial batch dimension
image = image.to(DEVICE)
# make prediction
pred: dict = model(image)
except Exception:
print_exc()
continue
else:
print(json.dumps(pred, indent=4))
logging.debug('finished')
+1
View File
@@ -0,0 +1 @@
2bbe2be17a663f4299657378ac5e993e
+3
View File
@@ -0,0 +1,3 @@
from __future__ import annotations
from .visual_communication import VisualCommunicationModel
+6
View File
@@ -0,0 +1,6 @@
from .fully_connected import FullyConnectedModel
class AngleTail(FullyConnectedModel):
def __init__(self):
super().__init__(num_out_features=3)
+6
View File
@@ -0,0 +1,6 @@
from .fully_connected import FullyConnectedModel
class ContactTail(FullyConnectedModel):
def __init__(self):
super().__init__(num_out_features=2)
+6
View File
@@ -0,0 +1,6 @@
from .fully_connected import FullyConnectedModel
class DistanceTail(FullyConnectedModel):
def __init__(self):
super().__init__(num_out_features=3)
+6
View File
@@ -0,0 +1,6 @@
from .fully_connected import FullyConnectedModel
class FramingTail(FullyConnectedModel):
def __init__(self):
super().__init__(num_out_features=4)
+24
View File
@@ -0,0 +1,24 @@
from torch import nn
class FullyConnectedModel(nn.Module):
"""Fully connected layers model template for intrepreting feature space-
output from ResNet18 head."""
def __init__(self, num_out_features: int):
super().__init__()
# define layers
self.fc1 = nn.Linear(in_features=512, out_features=128)
self.af1 = nn.ReLU()
self.fc2 = nn.Linear(in_features=128, out_features=32)
self.af2 = nn.ReLU()
self.fc3 = nn.Linear(in_features=32, out_features=num_out_features)
def forward(self, x):
"""Pass input through model."""
x = self.fc1(x)
x = self.af1(x)
x = self.fc2(x)
x = self.af2(x)
x = self.fc3(x)
return x
+6
View File
@@ -0,0 +1,6 @@
from .fully_connected import FullyConnectedModel
class InformationValueTail(FullyConnectedModel):
def __init__(self):
super().__init__(num_out_features=3)
+6
View File
@@ -0,0 +1,6 @@
from .fully_connected import FullyConnectedModel
class ModalityColorTail(FullyConnectedModel):
def __init__(self):
super().__init__(num_out_features=3)
+6
View File
@@ -0,0 +1,6 @@
from .fully_connected import FullyConnectedModel
class ModalityDepthTail(FullyConnectedModel):
def __init__(self):
super().__init__(num_out_features=3)
+6
View File
@@ -0,0 +1,6 @@
from .fully_connected import FullyConnectedModel
class ModalityLightingTail(FullyConnectedModel):
def __init__(self):
super().__init__(num_out_features=3)
+6
View File
@@ -0,0 +1,6 @@
from .fully_connected import FullyConnectedModel
class PointOfViewTail(FullyConnectedModel):
def __init__(self):
super().__init__(num_out_features=2)
+41
View File
@@ -0,0 +1,41 @@
from __future__ import annotations
import torch.nn as nn
import torchvision
class ResNet18Head(nn.Module):
def __init__(self, download_resnet_weights: bool = False):
super().__init__()
# copy out parts from ResNet18 with weights
if download_resnet_weights:
weights = torchvision.models.ResNet18_Weights.IMAGENET1K_V1
else:
weights = None
resnet18 = torchvision.models.resnet18(
weights=weights,
)
# save relevant layers
self.conv1 = resnet18.conv1
self.bn1 = resnet18.bn1
self.relu = resnet18.relu
self.maxpool = resnet18.maxpool
self.layer1 = resnet18.layer1
self.layer2 = resnet18.layer2
self.layer3 = resnet18.layer3
self.layer4 = resnet18.layer4
self.avgpool = resnet18.avgpool
self.flat = nn.Flatten() # size 512
def forward(self, x):
x = self.conv1(x)
x = self.bn1(x)
x = self.relu(x)
x = self.maxpool(x)
x = self.layer1(x)
x = self.layer2(x)
x = self.layer3(x)
x = self.layer4(x)
x = self.avgpool(x)
x = self.flat(x)
return x
+6
View File
@@ -0,0 +1,6 @@
from .fully_connected import FullyConnectedModel
class SalienceTail(FullyConnectedModel):
def __init__(self):
super().__init__(num_out_features=5)
+58
View File
@@ -0,0 +1,58 @@
"""Definition of VisualCommunicationModel class."""
from __future__ import annotations
from torch import nn
from .angle import AngleTail
from .contact import ContactTail
from .distance import DistanceTail
from .framing import FramingTail
from .information_value import InformationValueTail
from .modality_color import ModalityColorTail
from .modality_depth import ModalityDepthTail
from .modality_lighting import ModalityLightingTail
from .point_of_view import PointOfViewTail
from .resnet18_head import ResNet18Head
from .salience import SalienceTail
from .visual_syntax import VisualSyntaxTail
class VisualCommunicationModel(nn.Module):
"""Visual communication model."""
def __init__(self, download_resnet_weights: bool = False):
super().__init__()
# store other models
self.resnet_head = ResNet18Head(download_resnet_weights)
self.visual_syntax_tail = VisualSyntaxTail()
self.contact_tail = ContactTail()
self.angle_tail = AngleTail()
self.point_of_view_tail = PointOfViewTail()
self.distance_tail = DistanceTail()
self.modality_lighting_tail = ModalityLightingTail()
self.modality_color_tail = ModalityColorTail()
self.modality_depth_tail = ModalityDepthTail()
self.information_value_tail = InformationValueTail()
self.framing_tail = FramingTail()
self.salience_tail = SalienceTail()
def forward(self, x) -> dict:
"""Calculate model output on data."""
# generate visual representation
features = self.resnet_head(x)
# make predictions
prediction_dict = {
'visual_syntax': self.visual_syntax_tail(features),
'contact': self.contact_tail(features),
'angle': self.angle_tail(features),
'point_of_view': self.point_of_view_tail(features),
'distance': self.distance_tail(features),
'modality_lighting': self.modality_lighting_tail(features),
'modality_color': self.modality_color_tail(features),
'modality_depth': self.modality_depth_tail(features),
'information_value': self.information_value_tail(features),
'framing': self.framing_tail(features),
'salience': self.salience_tail(features),
}
return prediction_dict
+6
View File
@@ -0,0 +1,6 @@
from .fully_connected import FullyConnectedModel
class VisualSyntaxTail(FullyConnectedModel):
def __init__(self):
super().__init__(num_out_features=18)
+3
View File
@@ -0,0 +1,3 @@
from .get_class import get_class
from .load_model import DEVICE, load_model
from .vcda_dataset import VCDADataset
+11
View File
@@ -0,0 +1,11 @@
from importlib import import_module
from torch.nn import Module
def get_class(path: str) -> Module:
parts = path.split('.')
module_path = '.'.join(parts[:-1])
class_name = parts[-1]
module = import_module(module_path)
return getattr(module, class_name)
+19
View File
@@ -0,0 +1,19 @@
"""Definition of get_model_name function."""
import logging
from pathlib import Path
def get_model_name(
path: Path,
) -> str:
"""Get model name from model_name file."""
assert isinstance(path, Path)
assert path.exists(), f'{path} does not exist'
# read file contents
with open(file=path, encoding='utf-8') as fh:
model_name = fh.read()
# remove newline character
model_name = model_name.strip()
logging.debug('finished')
return model_name
+37
View File
@@ -0,0 +1,37 @@
"""Definition of load_model function."""
import logging
from pathlib import Path
import torch
from model.src.models import VisualCommunicationModel
from shared.repositories import ModelRepository
from .get_model_name import get_model_name
DEVICE = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
def load_model() -> VisualCommunicationModel:
"""Instantiate model with weights loaded from latest model saved in
MinIO."""
# instantiate model
model = VisualCommunicationModel()
# get model object name
model_name_path = Path('model_name.txt')
model_object_name = get_model_name(path=model_name_path)
logging.info('using model: %s', model_object_name)
# load model data
with ModelRepository() as repo:
model_data = repo.get_data(model_object_name)
if model_data is None:
raise FileNotFoundError(f'model {model_object_name} not found')
model_checkpoint = torch.load(model_data.buffer)
model.load_state_dict(model_checkpoint)
# clean memory
model_checkpoint.clear()
# move model to selected device
model = model.to(DEVICE)
logging.debug('finished')
return model
+49
View File
@@ -0,0 +1,49 @@
from __future__ import annotations
from datetime import datetime
import torch
import torch.nn as nn
from dataloader import VCDADataset # noqa: F401
from ..models import VisualCommunicationModel
PRE_WARMUP_LR = 1e-10
POST_WARMUP_LR = 1e-5
BATCH_SIZE = 32
MODEL_NAME = 'visual_communication_model_v1'
MAX_EPOCHS = 200
RUN_NAME = datetime.now().strftime('%Y-%m-%d-%H%M') + '_' + MODEL_NAME
DEVICE = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
# create loss function
criterion = nn.MSELoss()
def loss_fn(pred, target):
"""Create loss function."""
return criterion(pred, target.unsqueeze(-1))
# create model
model = VisualCommunicationModel().to(DEVICE)
# create optimizer
optim = torch.optim.Adam(model.parameters(), lr=POST_WARMUP_LR)
# create datasets
# train_dataset
# validation_dataset
# create dataloaders
# create trainer and evaluator
# setup progressbar
# setup lr scheduler
# setup checkpoint saving
# load in checkpoint if exists
+134
View File
@@ -0,0 +1,134 @@
"""Definition of Visual Critial Discourse Analysis dataloader."""
import random
from PIL import Image
from torch import Tensor
from torch.utils.data import Dataset
from torchvision.transforms import ColorJitter, InterpolationMode, Normalize
from torchvision.transforms.functional import (
hflip,
pad,
resize,
rotate,
to_pil_image,
to_tensor,
)
from shared.repositories import ImageRepository
# resnet18 original normalization values
RESNET_NORMALIZE_MEAN = [0.485, 0.456, 0.406]
RESNET_NORMALIZE_STD = [0.229, 0.224, 0.225]
class VCDADataset(Dataset):
"""VCDA dataset class."""
def __init__(
self,
data_name_list: list[str],
do_augment: bool = False,
random_annotations: bool = False,
):
super().__init__()
self.data_name_list = data_name_list
self.do_augment = do_augment
self.random_annotations = random_annotations
self.normalize_mean = RESNET_NORMALIZE_MEAN
self.normalize_std = RESNET_NORMALIZE_STD
# prepare augmentation functions
self.normalize = Normalize(
mean=self.normalize_mean,
std=self.normalize_std,
)
self.color_jitter = ColorJitter(
brightness=1e-1,
contrast=8e-2,
saturation=8e-2,
)
def __len__(self):
return len(self.data_name_list)
def __getitem__(self, idx):
# get image from database
image_name = self.data_name_list[idx]
with ImageRepository() as repo:
image_data = repo.get_data(image_name)
assert image_data is not None
tensor = self.image_to_tensor(image_data.image)
if self.do_augment:
tensor = self.augment(tensor)
return tensor
def image_to_tensor(
self,
image: Image.Image,
) -> Tensor:
"""Load images tensor from bytes."""
tensor = to_tensor(image)
tensor /= 255 # normalize 8-bit image
tensor = self.square_pad(tensor=tensor)
tensor = resize(
img=tensor,
size=(512, 512),
interpolation=InterpolationMode.BICUBIC,
)
return tensor
@staticmethod
def square_pad(
tensor: Tensor,
) -> Tensor:
"""Pads image to a square with side length equal to the largest side of
the input image."""
assert isinstance(tensor, Tensor)
# B, nc, w, h = img.shape
h = tensor.shape[-2]
w = tensor.shape[-1]
if h == w:
return tensor
max_wh = max([h, w])
hp = int((max_wh - w) / 2)
vp = int((max_wh - h) / 2)
padding = (hp, vp, hp, vp)
tensor = pad(tensor, padding, 0, 'constant')
return tensor
def augment(
self,
tensor: Tensor,
) -> Tensor:
"""Augment image with random horizontal flips, rotations and color
jitter."""
assert isinstance(tensor, Tensor)
# left-right flip
if random.random() >= 0.5:
tensor = hflip(tensor)
# rotation
rnd = random.random()
if rnd < 0.25:
tensor = rotate(tensor, angle=90)
if rnd < 0.5:
tensor = rotate(tensor, angle=180)
if rnd < 0.75:
tensor = rotate(tensor, angle=270)
# color jitter
tensor = self.color_jitter(tensor)
return tensor
def reverse_normalise(
self,
tensor: Tensor,
) -> Image.Image:
"""Reverse normalization to get an image that can be interpreted by
humans."""
assert isinstance(tensor, Tensor)
tensor *= Tensor(self.normalize_std).reshape((3, 1, 1))
tensor += Tensor(self.normalize_mean).reshape((3, 1, 1))
image = to_pil_image(
pic=tensor,
mode='RGB',
)
return image
+163
View File
@@ -0,0 +1,163 @@
import json
import os
from argparse import ArgumentParser
from datetime import datetime, timezone
from pathlib import Path
import torch
from dotenv import load_dotenv
from ignite.engine import Events, create_supervised_evaluator, create_supervised_trainer
from ignite.handlers import global_step_from_engine
from ignite.handlers.checkpoint import Checkpoint
from ignite.handlers.param_scheduler import create_lr_scheduler_with_warmup
from ignite.handlers.tensorboard_logger import TensorboardLogger
from ignite.handlers.tqdm_logger import ProgressBar
from ignite.metrics import Average, Loss, RunningAverage
from torch import nn
from torch.optim.lr_scheduler import ExponentialLR
from torch.utils.data import DataLoader
from model.src.utils import VCDADataset, get_class
def parse_arguments():
parser = ArgumentParser()
parser.add_argument('-c', '--config', type=Path, required=True)
parser.add_argument('-d', '--device', type=str, default='cpu')
parser.add_argument('-b', '--batch-size', type=int, default=32)
parser.add_argument('--epochs', type=int, default=50)
parser.add_argument('--epoch-length', type=int, default=128)
parser.add_argument('--checkpoint', type=Path)
parser.add_argument('--lr', type=float, default=1e-5)
parser.add_argument('--lr-gamma', type=float, default=0.95)
parser.add_argument('--lr-warmup-start', type=float, default=1e-8)
parser.add_argument('--lr-warmup-duration', type=int, default=5)
parser.add_argument('--train-seed', type=int, default=1312)
parser.add_argument('--val-seed', type=int, default=1313)
parser.add_argument('--loader-workers', type=int, default=8)
return parser.parse_args()
args = parse_arguments()
load_dotenv('server.env')
# read model config
with open(args.config) as fh:
config = json.load(fh)
model_name = args.config.stem
run_name = datetime.now(timezone.utc).strftime('%Y-%m-%d-%H%M') + '_' + model_name
device = torch.device(args.device)
# create model
model_class = get_class(config['backbone']['class'])
model = model_class().to(device)
optimizer = torch.optim.Adam(model.parameters(), lr=args.lr)
loss_fn = nn.CrossEntropyLoss()
# create datasets and loaders
with open('model/src/dataset/train.csv', encoding='utf-8') as fh:
train_data_name_list = fh.read().split('\n')
train_dataset = VCDADataset(
data_name_list=train_data_name_list,
)
train_loader = DataLoader(dataset=train_dataset, num_workers=args.loader_workers)
with open('model/src/dataset/val.csv', encoding='utf-8') as fh:
val_data_name_list = fh.read().split('\n')
val_dataset = VCDADataset(data_name_list=val_data_name_list)
val_loader = DataLoader(dataset=val_dataset, num_workers=args.loader_workers)
# create trainer and evaluator
trainer = create_supervised_trainer(
model=model,
optimizer=optimizer,
loss_fn=loss_fn,
device=device,
)
Average(output_transform=lambda x: x).attach(trainer, name='loss')
RunningAverage(output_transform=lambda x: x, alpha=0.5).attach(
trainer,
'running_avg_loss',
)
ProgressBar(desc='Train', ncols=80).attach(trainer, ['running_avg_loss'])
val_metrics = {
'loss': Loss(loss_fn=loss_fn, device=device),
}
evaluator = create_supervised_evaluator(
model=model,
metrics=val_metrics, # type: ignore
device=device,
)
ProgressBar(desc='Val', ncols=80).attach(evaluator)
# create lr scheduler
lr_scheduler = ExponentialLR(
optimizer=optimizer,
gamma=args.lr_gamma,
)
lr_handler = create_lr_scheduler_with_warmup(
lr_scheduler=lr_scheduler,
warmup_start_value=args.lr_warmup_start,
warmup_duration=args.lr_warmup_duration,
warmup_end_value=args.lr,
)
trainer.add_event_handler(Events.EPOCH_STARTED, lr_handler)
# log metrics to TensorBoard
@trainer.on(Events.EPOCH_COMPLETED)
def evaluate():
evaluator.run(val_loader, epoch_length=args.val_epoch_length)
tb_logger = TensorboardLogger(log_dir=f'runs/logs/{run_name}')
tb_logger.attach_opt_params_handler(
engine=trainer,
event_name=Events.EPOCH_COMPLETED,
optimizer=optimizer,
)
for tag, engine in [('train', trainer), ('val', evaluator)]:
tb_logger.attach_output_handler(
engine,
event_name=Events.EPOCH_COMPLETED,
tag=tag,
metric_names='all',
global_step_transform=global_step_from_engine(trainer),
)
# Set up checkpoint saving
to_save = {
'model': model,
'optimizer': optimizer,
'trainer': trainer,
'lr_scheduler': lr_scheduler,
'lr_handler': lr_handler,
}
checkpoint_handler = Checkpoint(
to_save,
f'runs/checkpoints/{run_name}',
n_saved=3,
filename_prefix='best',
score_function=lambda engine: -engine.state.metrics['loss'],
score_name='neg_val_loss',
global_step_transform=global_step_from_engine(trainer),
)
evaluator.add_event_handler(Events.COMPLETED, checkpoint_handler)
if args.checkpoint:
Checkpoint.load_objects(to_load=to_save, checkpoint=str(args.checkpoint))
# save model config
os.makedirs('runs/configs/', exist_ok=True)
with open(f'runs/configs/{run_name}.json', 'w', encoding='utf-8') as fh:
json.dump(config, fh)
# start training
trainer.run(
train_loader,
max_epochs=args.epochs,
epoch_length=args.epoch_length,
)
tb_logger.close()
+124
View File
@@ -0,0 +1,124 @@
"""Script to move all images from MongoDB to MinIO."""
from __future__ import annotations
import logging
from pathlib import Path
from bson import ObjectId
from dotenv import load_dotenv
from pymongo.collection import Collection
from shared.datastore import Datastore
from shared.docstore import connect_mongodb
from shared.docstore.src.classes import VisualCommunication
from shared.utils import check_env, setup_logging
from web_ui.src.main import NECESSARY_ENV_VAR_LIST
def list_mongo_document_ids(
collection: Collection,
) -> list[ObjectId]:
"""Get list of all VisualCommunication documents in MongoDB."""
# prepare query
query = collection.find(
filter={},
projection={'_id': True},
)
# execute query
res_list = list(query)
logging.debug('got %s document(s)', len(res_list))
# convert result
id_list = [elem['_id'] for elem in res_list]
return id_list
def get_visual_communication(
collection: Collection,
doc_id: ObjectId,
) -> VisualCommunication:
"""Get image from MongoDB."""
# prepare query
query = collection.find(
filter={'_id': doc_id},
projection={'_id': False},
)
# execute query
res_list = list(query)
logging.debug('got %s document(s)', len(res_list))
# convert result
vis_com = VisualCommunication.model_validate(res_list[0])
return vis_com
def update_visual_communication(
collection: Collection,
vis_com: VisualCommunication,
) -> None:
"""Update VisualCommunication document in MongoDB."""
try:
collection.update_one(
filter={
'name': vis_com.name,
},
update={
'$set': vis_com.model_dump(),
},
)
except Exception as exc:
logging.error('failed saving %s', vis_com.name)
logging.debug(exc)
raise exc
if __name__ == '__main__':
# load in env file
env_path = Path(__file__).parent.parent / 'server.env'
assert env_path.exists()
load_dotenv(env_path)
# ensure env vars set
check_env(NECESSARY_ENV_VAR_LIST)
# setup logging
setup_logging()
# connect to minIO
datastore = Datastore()
datastore.connect()
assert datastore._client is not None
# connect to MongoDB
collection, db, client = connect_mongodb()
# list documents in mongoDB
id_list = list_mongo_document_ids(collection)
for doc_id in id_list:
try:
# get image from MongoDB
vis_com = get_visual_communication(collection, doc_id)
except Exception as exc:
logging.debug(exc)
logging.error('failed getting image from document: %s', doc_id)
continue
try:
# get image
image = vis_com.get_image(
minio_client=datastore._client,
)
# put buffer in minio
object_name = datastore.put_image(
image=image,
)
except Exception as exc:
logging.debug(exc)
logging.error('failed saving image to minio')
continue
# update visual communication in mongodb
try:
vis_com.image = None # type: ignore
vis_com.object_name = object_name
update_visual_communication(collection, vis_com)
except Exception as exc:
logging.debug(exc)
logging.error(
'failed updating visual communication %s',
vis_com.name,
)
continue
logging.debug('updated visual communication %s', vis_com.name)
Generated
+2837 -741
View File
File diff suppressed because it is too large Load Diff
+96 -10
View File
@@ -5,26 +5,26 @@ description = ""
authors = ["Brian Bjarke Jensen <bbj@skov.dk>"] authors = ["Brian Bjarke Jensen <bbj@skov.dk>"]
readme = "README.md" readme = "README.md"
packages = [ packages = [
{ include = "src" }, { include = "shared" },
] ]
[tool.poetry.dependencies] [tool.poetry.dependencies]
python = "^3.12" 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] [tool.poetry.group.test.dependencies]
flake8 = "^7.0.0" flake8 = "^7.0.0"
mypy = "^1.8.0" mypy = "^1.8.0"
types-pillow = "^10.2.0.20240213" types-pillow = "^10.2.0.20240213"
types-requests = "^2.32.0.20240602"
types-retry = "^0.9.9.4"
flake8-pyproject = "^1.2.3"
pandas-stubs = "^2.2.2.240603"
types-tqdm = "^4.66.0.20240417"
pytest = "^8.3.3"
testcontainers = "^4.8.2"
coverage = "^7.6.4"
pytest-cov = "^6.0.0"
[tool.poetry.group.dev.dependencies] [tool.poetry.group.dev.dependencies]
@@ -32,7 +32,93 @@ pandas = "^2.2.1"
selenium = "^4.18.1" selenium = "^4.18.1"
webdriver-manager = "^4.0.1" webdriver-manager = "^4.0.1"
retry = "^0.9.2" retry = "^0.9.2"
pre-commit = "^4.1.0"
[tool.poetry.group.model.dependencies]
torch = "^2.0.0"
torchvision = "^0.17.1"
torchinfo = "^1.8.0"
minio = "^7.2.7"
tqdm = "^4.66.4"
pytorch-ignite = "^0.5.1"
tensorboard = "^2.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"
[[tool.poetry.source]]
name = "threadripper"
url = "http://192.168.1.2:5001/index/"
priority = "primary"
[build-system] [build-system]
requires = ["poetry-core"] requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api" build-backend = "poetry.core.masonry.api"
[tool.isort]
profile = "black"
[tool.flake8]
per-file-ignores = "__init__.py:F401"
max-line-length = 88
extend-ignore = "E203"
[tool.mypy]
exclude = "image_download"
[[tool.mypy.overrides]]
module = "dash.*"
ignore_missing_imports = true
[[tool.mypy.overrides]]
module = "dash_auth.*"
ignore_missing_imports = true
[[tool.mypy.overrides]]
module = "dash_mantine_components.*"
ignore_missing_imports = true
[[tool.mypy.overrides]]
module = "dash_bootstrap_components.*"
ignore_missing_imports = true
[[tool.mypy.overrides]]
module = "torchvision.*"
ignore_missing_imports = true
[[tool.mypy.overrides]]
module = "image_download.*"
ignore_missing_imports = true
[[tool.mypy.overrides]]
module = "dataloader.*"
ignore_missing_imports = true
[[tool.mypy.overrides]]
module = "utils.*"
ignore_missing_imports = true
[[tool.mypy.overrides]]
module = "shared.datastore.*"
ignore_missing_imports = true
[[tool.mypy.overrides]]
module = "models.*"
ignore_missing_imports = true
[[tool.mypy.overrides]]
module = "shared.docstore.*"
ignore_missing_imports = true
View File
+8
View File
@@ -0,0 +1,8 @@
from .src import ImageRepository, ModelRepository, VisualCommunicationRepository
from .src.dto import (
HexadecimalString,
ImageData,
ModelData,
VisualCommunicationData,
VisualCommunicationValues,
)
+3
View File
@@ -0,0 +1,3 @@
from .image_repository import ImageRepository
from .model_repository import ModelRepository
from .visual_communication_repository import VisualCommunicationRepository
+7
View File
@@ -0,0 +1,7 @@
from .hexadecimal_string import HexadecimalString
from .image_data import ImageData
from .model_data import ModelData
from .visual_communication_data import (
VisualCommunicationData,
VisualCommunicationValues,
)
@@ -0,0 +1,37 @@
"""Definition of BytesIO Pydantic Annotation."""
from io import BytesIO
from typing import Any
from pydantic.json_schema import JsonSchemaValue
from pydantic_core import core_schema
class BytesIOPydanticAnnotation:
"""Pydantic annotation that defines input validation, as well as general
and json serialization."""
@classmethod
def validate_input(cls, v: Any, handler) -> BytesIO:
"""Pydantic-related function to validate input on instantiation."""
if isinstance(v, BytesIO):
return v
s = handler(v)
return BytesIO(s)
@classmethod
def __get_pydantic_core_schema__(
cls,
source_type,
_handler,
) -> core_schema.CoreSchema:
assert source_type is BytesIO
return core_schema.no_info_wrap_validator_function(
function=cls.validate_input,
schema=core_schema.str_schema(),
serialization=core_schema.to_string_ser_schema(),
)
@classmethod
def __get_pydantic_json_schema__(cls, _core_schema, handler) -> JsonSchemaValue:
return handler(core_schema.str_schema())
@@ -0,0 +1,24 @@
"""Definition of Checksum DTO."""
from __future__ import annotations
import re
class HexadecimalString(str):
"""Hexadecimal-string class."""
def __new__(cls, string):
# ensure proper input format
pattern = r'[0-9-a-fA-F]{32}'
match = re.match(pattern, string)
if match is None:
raise ValueError(f'format does not match a hexadecimal-string: {string}')
return super().__new__(cls, string)
def __repr__(self) -> str:
class_name = self.__class__.__name__
return f"{class_name}('{self}')"
def __reduce__(self):
return self.__class__, (self,)
@@ -0,0 +1,38 @@
"""Definition of HexadecimalString Pydantic Annotation."""
from typing import Any
from pydantic.json_schema import JsonSchemaValue
from pydantic_core import core_schema
from .hexadecimal_string import HexadecimalString
class HexadecimalStringPydanticAnnotation:
"""Pydantic annotation that defines input validation, as well as general
and json serialization."""
@classmethod
def validate_input(cls, v: Any, handler) -> HexadecimalString:
"""Pydantic-related function to validate input on instantiation."""
if isinstance(v, HexadecimalString):
return v
s = handler(v)
return HexadecimalString(s)
@classmethod
def __get_pydantic_core_schema__(
cls,
source_type,
_handler,
) -> core_schema.CoreSchema:
assert source_type is HexadecimalString
return core_schema.no_info_wrap_validator_function(
function=cls.validate_input,
schema=core_schema.str_schema(),
serialization=core_schema.to_string_ser_schema(),
)
@classmethod
def __get_pydantic_json_schema__(cls, _core_schema, handler) -> JsonSchemaValue:
return handler(core_schema.str_schema())
+16
View File
@@ -0,0 +1,16 @@
"""Definition of VisualData DTO."""
from typing import Annotated
from PIL import Image
from pydantic import Field
from .image_pydantic_annotation import ImagePydanticAnnotation
from .type_checking_base_model import TypeCheckingBaseModel
class ImageData(TypeCheckingBaseModel):
"""Visual data class."""
image: Annotated[Image.Image, ImagePydanticAnnotation]
name: str = Field(min_length=1)
@@ -0,0 +1,37 @@
"""Definition of BytesIO Pydantic Annotation."""
from typing import Any
from PIL import Image
from pydantic.json_schema import JsonSchemaValue
from pydantic_core import core_schema
class ImagePydanticAnnotation:
"""Pydantic annotation that defines input validation, as well as general
and json serialization."""
@classmethod
def validate_input(cls, v: Any, handler) -> Image.Image:
"""Pydantic-related function to validate input on instantiation."""
if isinstance(v, Image.Image):
return v
s = handler(v)
return Image.open(s)
@classmethod
def __get_pydantic_core_schema__(
cls,
source_type,
_handler,
) -> core_schema.CoreSchema:
assert source_type is Image.Image
return core_schema.no_info_wrap_validator_function(
function=cls.validate_input,
schema=core_schema.str_schema(),
serialization=core_schema.to_string_ser_schema(),
)
@classmethod
def __get_pydantic_json_schema__(cls, _core_schema, handler) -> JsonSchemaValue:
return handler(core_schema.str_schema())
+60
View File
@@ -0,0 +1,60 @@
"""Definition of ModelData DTO."""
from __future__ import annotations
from hashlib import md5
from io import BytesIO
from typing import Annotated
import torch
from pydantic import Field
from .bytes_io_pydantic_annotation import BytesIOPydanticAnnotation
from .hexadecimal_string import HexadecimalString
from .hexadecimal_string_pydantic_annotation import HexadecimalStringPydanticAnnotation
from .type_checking_base_model import TypeCheckingBaseModel
class ModelData(TypeCheckingBaseModel):
"""Model Data DTO."""
buffer: Annotated[BytesIO, BytesIOPydanticAnnotation]
buffer_checksum: Annotated[HexadecimalString, HexadecimalStringPydanticAnnotation]
class_name: str = Field(
min_length=1,
description='name of model class to generate data.',
)
@staticmethod
def calculate_checksum(buffer: BytesIO) -> HexadecimalString:
"""Calculate buffer checksum."""
checksum = md5(buffer.getbuffer()).hexdigest()
return HexadecimalString(checksum)
@staticmethod
def model_to_buffer(model: torch.nn.Module) -> BytesIO:
"""Save model to buffer."""
assert isinstance(model, torch.nn.Module)
buffer = BytesIO()
torch.save(model.state_dict(), buffer)
return buffer
@classmethod
def from_model(cls, model: torch.nn.Module) -> ModelData:
"""Instantiate from torch module."""
assert isinstance(model, torch.nn.Module)
# get model name
class_name = type(model).__name__
# save data to buffer
buffer = cls.model_to_buffer(model)
buffer = BytesIO()
torch.save(model.state_dict(), buffer)
# calculate checksum
buffer_checksum = cls.calculate_checksum(buffer)
# instantiate from buffer
data = cls(
buffer=buffer,
buffer_checksum=buffer_checksum,
class_name=class_name,
)
return data
@@ -0,0 +1,11 @@
"""Definition of TypeCheckingBaseModel class."""
from pydantic import BaseModel, ConfigDict
class TypeCheckingBaseModel(BaseModel):
"""BaseModel with added type checking on input types."""
model_config = ConfigDict(
frozen=True, # ensure data immutability
)
@@ -0,0 +1,2 @@
from .visual_communication_data import VisualCommunicationData
from .visual_communication_values import VisualCommunicationValues
@@ -0,0 +1,11 @@
"""Definition of AngleValues DTO."""
from .values_model import ValuesModel
class AngleValues(ValuesModel):
"""Angle values DTO."""
high: float
eye_level: float
low: float
@@ -0,0 +1,10 @@
"""Definition of ContactValues DTO."""
from .values_model import ValuesModel
class ContactValues(ValuesModel):
"""Contact values DTO."""
offer: float
demand: float
@@ -0,0 +1,11 @@
"""Definition of DistanceValues DTO."""
from .values_model import ValuesModel
class DistanceValues(ValuesModel):
"""Distance values DTO."""
long: float
medium: float
close: float
@@ -0,0 +1,12 @@
"""Definition of FramingValues DTO."""
from .values_model import ValuesModel
class FramingValues(ValuesModel):
"""Framing values DTO."""
frame_lines: float
empty_space: float
colour_contrast: float
form_contrast: float
@@ -0,0 +1,11 @@
"""Definition of InformationValueValues DTO."""
from .values_model import ValuesModel
class InformationValueValues(ValuesModel):
"""Information value values DTO."""
given_new: float
ideal_real: float
central_marginal: float
@@ -0,0 +1,11 @@
"""Definition of ModalityColorValues DTO."""
from .values_model import ValuesModel
class ModalityColorValues(ValuesModel):
"""Modality color values DTO."""
high: float
medium: float
low: float
@@ -0,0 +1,11 @@
"""Definition of ModalityDepthValues DTO."""
from .values_model import ValuesModel
class ModalityDepthValues(ValuesModel):
"""Modality depth values DTO."""
high: float
medium: float
low: float
@@ -0,0 +1,11 @@
"""Definition of ModalityLightingValues DTO."""
from .values_model import ValuesModel
class ModalityLightingValues(ValuesModel):
"""Modality lighting values DTO."""
high: float
medium: float
low: float
@@ -0,0 +1,10 @@
"""Definition of PointOfViewValues DTO."""
from .values_model import ValuesModel
class PointOfViewValues(ValuesModel):
"""Point-of-view values DTO."""
frontal: float
oblique: float
@@ -0,0 +1,13 @@
"""Definition of SalienceValues DTO."""
from .values_model import ValuesModel
class SalienceValues(ValuesModel):
"""Salience values DTO."""
size: float
colour: float
tone: float
form: float
positioning: float
@@ -0,0 +1,60 @@
"""Definition of ValuesModel base class."""
from __future__ import annotations
import random
from pydantic import BaseModel, ConfigDict
from torch import Tensor
class ValuesModel(BaseModel):
"""ValuesModel base class."""
model_config = ConfigDict(
validate_assignment=True, # argument type checking
frozen=True, # ensure data immutability
)
@classmethod
def list_fields(cls) -> list[str]:
"""List options that are stored as attributes."""
return list(cls.model_fields.keys())
@classmethod
def from_random(cls):
"""Instantiate with random numbers."""
kwargs = {field: random.random() for field in cls.list_fields()}
return cls(**kwargs)
@classmethod
def from_choice(cls, option: str) -> ValuesModel:
"""Instantiate from choice."""
assert isinstance(option, str)
assert len(option) > 0
allowed_options_list = cls.list_fields()
if option not in allowed_options_list:
raise ValueError(f'option {option} must be in {allowed_options_list}')
# generate field values
kwargs = {field: 0 for field in allowed_options_list}
# set chosen value to max probability
kwargs[option] = 1
return cls(**kwargs)
@classmethod
def from_tensor(cls, tensor: Tensor):
"""Instantiate from list of values."""
assert tensor.size(dim=0) == 1, f'tensor batch larger than 1: {tensor}'
data_list = [float(t.item()) for t in tensor[0]]
kwargs = dict(zip(cls.list_fields(), data_list))
return cls(**kwargs)
def highest_score_field(self) -> str:
"""Return name of field with highest score."""
model_dict = self.model_dump()
return max(model_dict, key=lambda k: model_dict[k])
def highest_score_value(self) -> float:
"""Return value of field with highest score."""
model_dict = self.model_dump()
return max(model_dict.values())
@@ -0,0 +1,14 @@
"""Definition of VisualCommunicationData DTO."""
from pydantic import Field
from ..type_checking_base_model import TypeCheckingBaseModel
from .visual_communication_values import VisualCommunicationValues
class VisualCommunicationData(TypeCheckingBaseModel):
"""Visual communication data class."""
name: str = Field(min_length=1)
annotation: VisualCommunicationValues | None = None
prediction: VisualCommunicationValues | None = None
@@ -0,0 +1,49 @@
"""Definition of VisualCommunicationValues DTO."""
from __future__ import annotations
from ..type_checking_base_model import TypeCheckingBaseModel
from .angle_values import AngleValues
from .contact_values import ContactValues
from .distance_values import DistanceValues
from .framing_values import FramingValues
from .information_value_values import InformationValueValues
from .modality_color_values import ModalityColorValues
from .modality_depth_values import ModalityDepthValues
from .modality_lighting_values import ModalityLightingValues
from .point_of_view_values import PointOfViewValues
from .salience_values import SalienceValues
from .visual_syntax_values import VisualSyntaxValues
class VisualCommunicationValues(TypeCheckingBaseModel):
"""Visual communication values class."""
visual_syntax: VisualSyntaxValues
contact: ContactValues
angle: AngleValues
point_of_view: PointOfViewValues
distance: DistanceValues
modality_lighting: ModalityLightingValues
modality_color: ModalityColorValues
modality_depth: ModalityDepthValues
information_value: InformationValueValues
framing: FramingValues
salience: SalienceValues
@classmethod
def from_random(cls) -> VisualCommunicationValues:
"""Create a random instance."""
return cls(
visual_syntax=VisualSyntaxValues.from_random(),
contact=ContactValues.from_random(),
angle=AngleValues.from_random(),
point_of_view=PointOfViewValues.from_random(),
distance=DistanceValues.from_random(),
modality_lighting=ModalityLightingValues.from_random(),
modality_color=ModalityColorValues.from_random(),
modality_depth=ModalityDepthValues.from_random(),
information_value=InformationValueValues.from_random(),
framing=FramingValues.from_random(),
salience=SalienceValues.from_random(),
)
@@ -0,0 +1,26 @@
"""Definition of VisualSyntaxValues DTO."""
from .values_model import ValuesModel
class VisualSyntaxValues(ValuesModel):
"""Visual syntax values DTO."""
non_transactional_action: float
non_transactional_reaction: float
unidirectional_transactional_action: float
unidirectional_transactional_reaction: float
bidirectional_transactional_action: float
bidirectional_transactional_reaction: float
conversion: float
speech_process: float
classification_overt_taxonomy: float
analytical_exhaustive: float
analytical_disarranged: float
analytical_temporal: float
analytical_distributed: float
analytical_topological: float
analytical_exploded: float
analytical_inclusive: float
symbolic_suggestive: float
symbolic_attributive: float
@@ -0,0 +1,74 @@
"""Definition of ImageRepository class."""
from __future__ import annotations
from io import BytesIO
from pathlib import Path
from PIL import Image
from .dto import ImageData
from .implementations import MinioImplementation
from .interfaces import ImageInterface
class ImageRepository(ImageInterface, MinioImplementation):
"""Image repository class that handles CRUD functionality for
VisualData."""
def __enter__(self) -> ImageRepository:
self.connect()
return self
@staticmethod
def _build_path(image_name: str) -> Path:
"""Build object path."""
assert isinstance(image_name, str)
path = Path('images') / image_name
return path
def get_data(self, image_name: str) -> ImageData | None:
"""Get Visual data."""
assert isinstance(image_name, str)
assert len(image_name) > 0
# build path
path = self._build_path(image_name)
# get object from bucket
buffer = self._get(path)
# handle if no data found
if not buffer:
return None
# convert data
image = Image.open(buffer)
data = ImageData(image=image, name=image_name)
return data
def put_data(self, data: ImageData) -> None:
"""Put visual data."""
assert isinstance(data, ImageData)
# build path
path = self._build_path(data.name)
# save image to buffer
buffer = BytesIO()
data.image.save(buffer, 'png')
# put object in bucket
self._put(path, buffer)
def remove_data(self, image_name: str) -> None:
"""Remove visual data."""
assert isinstance(image_name, str)
assert len(image_name) > 0
# build path
path = self._build_path(image_name)
# remove object
self._delete(path)
def list_names(self) -> list[str]:
"""List names of all images."""
# build path
path = self._build_path('')
# list object paths
obj_path_list = self._list_objects(path)
# strip prefix
name_list = [obj_path.split('/')[-1] for obj_path in obj_path_list]
return name_list
@@ -0,0 +1,2 @@
from .minio_implementation import MinioImplementation
from .mongo_implementation import MongoImplementation
@@ -0,0 +1,186 @@
"""MinIO implementation."""
from __future__ import annotations
import logging
import os
from io import BytesIO
from pathlib import Path
from traceback import format_exc
from minio import Minio
from shared.utils import check_env
from ..interfaces import DatabaseInterface
class MinioImplementation(DatabaseInterface):
"""MinIO basic CRUD implementation."""
def __init__(self):
# ensure necessary env vars available
var_list = {
'MINIO_ENDPOINT',
'MINIO_ACCESS_KEY',
'MINIO_SECRET_KEY',
'MINIO_BUCKET_NAME',
}
check_env(var_list)
# prepare internal variables
self._client: Minio | None = None
self._bucket_name: str | None = None
def connect(self):
"""Connect to MinIO server."""
# prepare arguments
minio_endpoint = str(os.getenv('MINIO_ENDPOINT'))
minio_access_key = str(os.getenv('MINIO_ACCESS_KEY'))
minio_secret_key = str(os.getenv('MINIO_SECRET_KEY'))
minio_bucket_name = str(os.getenv('MINIO_BUCKET_NAME'))
# connect client
client = Minio(
endpoint=minio_endpoint,
access_key=minio_access_key,
secret_key=minio_secret_key,
secure=False,
)
# ensure bucket exists
if not client.bucket_exists(bucket_name=minio_bucket_name):
logging.debug('creating bucket: %s', minio_bucket_name)
client.make_bucket(bucket_name=minio_bucket_name)
# persist state
self._client = client
self._bucket_name = minio_bucket_name
def close(self) -> None:
"""Close connection to MinIO server.
N.B. MinIO connection cannot be closed manually.
"""
self._client = None
self._bucket_name = None
def connected(self):
"""Check connection to Minio."""
res = isinstance(self._client, Minio)
logging.debug(res)
return res
def __enter__(self) -> MinioImplementation:
self.connect()
return self
def __exit__(self, exc_type, exc_val, exc_tb) -> None:
if any(
(
exc_type is not None,
exc_val is not None,
exc_tb is not None,
),
):
logging.error('error while exiting context')
self.close()
def _put(
self,
path: Path,
buffer: BytesIO,
) -> None:
"""Save in-memory buffer as object in MinIO."""
assert isinstance(path, Path)
assert isinstance(buffer, BytesIO)
assert isinstance(self._client, Minio)
assert isinstance(self._bucket_name, str)
# prepare for saving
num_bytes = len(buffer.getvalue())
buffer.seek(0)
# send data to bucket
try:
self._client.put_object(
bucket_name=self._bucket_name,
object_name=path.as_posix(),
length=num_bytes,
data=buffer,
)
logging.debug('saved data to %s', path)
except Exception as exc:
logging.error('failed saving data to MinIO')
raise exc
def _get(
self,
path: Path,
) -> BytesIO | None:
"""Get object from MinIO as in-memory buffer."""
assert isinstance(path, Path)
assert isinstance(self._client, Minio)
assert isinstance(self._bucket_name, str)
try:
# make request
response = self._client.get_object(
bucket_name=self._bucket_name,
object_name=path.as_posix(),
)
assert response.status == 200
# get buffer
buffer = BytesIO()
chunk_size = 2**14
while chunk := response.read(chunk_size):
buffer.write(chunk)
buffer.seek(0)
logging.debug('got %s', path)
return buffer
except Exception:
logging.error('failed getting data from MinIO')
logging.debug(format_exc())
return None
finally:
# close connection if established
if 'response' in locals():
response.close()
response.release_conn()
def _delete(
self,
path: Path,
) -> None:
"""Delete object from MinIO."""
assert isinstance(path, Path)
assert isinstance(self._client, Minio)
assert isinstance(self._bucket_name, str)
# remove object
try:
self._client.remove_object(
bucket_name=self._bucket_name,
object_name=path.as_posix(),
)
logging.debug('deleted %s', path)
except Exception as exc:
logging.error('failed deleting %s', path)
logging.debug(format_exc())
raise exc
def _list_objects(
self,
path: Path,
) -> list[str]:
"""List objects in bucket under path."""
assert isinstance(path, Path)
assert isinstance(self._client, Minio)
assert isinstance(self._bucket_name, str)
try:
# list objects
obj_list = self._client.list_objects(
bucket_name=self._bucket_name,
prefix=path.as_posix(),
recursive=True,
)
# extract info
name_list = [obj.object_name for obj in obj_list]
logging.debug('got %s objects matching %s', len(name_list), path)
return name_list
except Exception as exc:
logging.error('failed listing objects under %s', path)
logging.debug(format_exc())
raise exc
@@ -0,0 +1,145 @@
"""Mongo implementation."""
from __future__ import annotations
import logging
import os
import traceback
from pymongo import MongoClient
from pymongo.collection import Collection
from pymongo.database import Database
from pymongo.errors import ServerSelectionTimeoutError
from shared.utils import check_env
from ..interfaces import DatabaseInterface
class MongoImplementation(DatabaseInterface):
"""MongoDB basic CRUD implementation."""
def __init__(self):
# ensure necessary env vars available
var_list = {
'MONGO_ENDPOINT',
'MONGO_DB',
'MONGO_COLLECTION',
}
check_env(var_list)
# prepare internal variables
self.client: MongoClient | None = None
self.db: Database | None = None
self.collection: Collection | None = None
def connect(self) -> None:
"""Connect to Mongo server."""
# prepare arguments
mongo_endpoint = str(os.getenv('MONGO_ENDPOINT'))
mongo_database = str(os.getenv('MONGO_DB'))
mongo_collection = str(os.getenv('MONGO_COLLECTION'))
# connect client
client: MongoClient = MongoClient(mongo_endpoint)
database = client[mongo_database]
collection = database[mongo_collection]
# set unique index on 'name'
collection.create_index(keys='name', unique=True)
# persist state
self._client = client
self._database = database
self._collection = collection
def close(self) -> None:
"""Close connection to Mongo server."""
self._client.close()
self._client = None # type: ignore
self._database = None # type: ignore
self._collection = None # type: ignore
def connected(self) -> bool:
"""Check connection to Mongo."""
if self._client is None:
return False
try:
# trigger fetch data
_ = self._client.server_info()
res = True
except ServerSelectionTimeoutError:
res = False
logging.debug(res)
return res
def __enter__(self) -> MongoImplementation:
self.connect()
return self
def __exit__(self, exc_type, exc_val, exc_tb) -> None:
if any(
(
exc_type is not None,
exc_val is not None,
exc_tb is not None,
),
):
logging.error('error while exiting context')
traceback.print_exception(exc_type, exc_val, exc_tb)
self.close()
def _save(
self,
data: dict,
query: dict,
) -> None:
"""Save document in Mongo."""
assert isinstance(data, dict)
assert isinstance(query, dict)
assert 'name' in data
assert self.connected()
self._collection.update_one(
filter=query,
update={
'$set': data.copy(),
},
upsert=True,
)
logging.debug('Save %s', data)
def _get(
self,
query: dict,
) -> dict | None:
"""Get document from Mongo."""
assert isinstance(query, dict)
assert self.connected()
doc = self._collection.find_one(query, projection={'_id': False})
logging.debug('Found %s', doc)
return doc
def _delete(
self,
query: dict,
) -> None:
"""Remove document from Mongo."""
assert isinstance(query, dict)
assert self.connected()
doc = self._collection.delete_one(query)
logging.debug('Deleted %s', doc)
def _list_documents(self, key='name') -> list[str]:
"""List documents in Mongo."""
assert isinstance(key, str)
assert len(key) > 0
# build query
doc_list = list(
self._collection.find(
filter={},
projection={
'_id': False,
key: True,
},
),
)
# extract values
value_list = [doc[key] for doc in doc_list]
logging.debug('Got %s document(s)', len(doc_list))
return value_list
@@ -0,0 +1,4 @@
from .database_interface import DatabaseInterface
from .image_interface import ImageInterface
from .model_interface import ModelInterface
from .visual_communication_interface import VisualCommunicationInterface
@@ -0,0 +1,28 @@
"""Definition of DatabaseInterface class."""
from abc import ABC, abstractmethod
class DatabaseInterface(ABC):
"""Interface base class adding 'connect', 'close' and context
functionalities."""
@abstractmethod
def connect(self):
raise NotImplementedError()
@abstractmethod
def close(self):
raise NotImplementedError()
@abstractmethod
def connected(self) -> bool:
raise NotImplementedError()
@abstractmethod
def __enter__(self):
raise NotImplementedError()
@abstractmethod
def __exit__(self, exc_type, exc_val, exc_tb):
raise NotImplementedError()
@@ -0,0 +1,26 @@
"""Definition of ImageInterface."""
from abc import abstractmethod
from ..dto import ImageData
from .database_interface import DatabaseInterface
class ImageInterface(DatabaseInterface):
"""Image interface class."""
@abstractmethod
def get_data(self, image_name: str) -> ImageData | None:
raise NotImplementedError()
@abstractmethod
def put_data(self, data: ImageData) -> None:
raise NotImplementedError()
@abstractmethod
def remove_data(self, image_name: str) -> None:
raise NotImplementedError()
@abstractmethod
def list_names(self) -> list[str]:
raise NotImplementedError()
@@ -0,0 +1,26 @@
"""Definition of ModelInterface."""
from abc import abstractmethod
from ..dto import ModelData
from .database_interface import DatabaseInterface
class ModelInterface(DatabaseInterface):
"""Model interface class."""
@abstractmethod
def get_data(self, object_name: str) -> ModelData | None:
raise NotImplementedError()
@abstractmethod
def put_data(self, data: ModelData) -> None:
raise NotImplementedError()
@abstractmethod
def remove_data(self, object_name: str) -> None:
raise NotImplementedError()
@abstractmethod
def list_names(self) -> list[str]:
raise NotImplementedError()
@@ -0,0 +1,26 @@
"""Definition of VisualCommunicationInterface."""
from abc import abstractmethod
from ..dto import VisualCommunicationData
from .database_interface import DatabaseInterface
class VisualCommunicationInterface(DatabaseInterface):
"""Visual communication interface class."""
@abstractmethod
def get_data(self, name: str) -> VisualCommunicationData | None:
raise NotImplementedError()
@abstractmethod
def put_data(self, data: VisualCommunicationData) -> None:
raise NotImplementedError()
@abstractmethod
def remove_data(self, name: str) -> None:
raise NotImplementedError()
@abstractmethod
def list_names(self) -> list[str]:
raise NotImplementedError()
@@ -0,0 +1,77 @@
"""Definition of ModelRepository class."""
from __future__ import annotations
from pathlib import Path
from .dto import HexadecimalString, ModelData
from .implementations import MinioImplementation
from .interfaces import ModelInterface
class ModelRepository(ModelInterface, MinioImplementation):
"""Model repository class that handles CRUD functionality for ModelData."""
def __enter__(self) -> ModelRepository:
self.connect()
return self
@staticmethod
def _prefix() -> Path:
"""Object name prefix."""
return Path('models')
@classmethod
def _build_object_name(cls, data: ModelData) -> str:
"""Build object name from data."""
return f'{data.class_name}-{data.buffer_checksum}'
def get_data(self, object_name: str) -> ModelData | None:
"""Get model data."""
assert isinstance(object_name, str)
# build path
path = self._prefix() / object_name
# get object from bucket
buffer = self._get(path)
# handle if no data found
if not buffer:
return None
# extract info
class_name, buffer_checksum_str = object_name.split('-')
# convert data
buffer_checksum = HexadecimalString(buffer_checksum_str)
# instantiate data
data = ModelData(
buffer=buffer,
buffer_checksum=buffer_checksum,
class_name=class_name,
)
return data
def put_data(self, data: ModelData) -> None:
"""Put model data."""
assert isinstance(data, ModelData)
# build object name
object_name = self._build_object_name(data)
# build path
path = self._prefix() / object_name
# put object in bucket
self._put(path, data.buffer)
def remove_data(self, object_name: str) -> None:
"""Remove model data."""
assert isinstance(object_name, str)
# build path
path = self._prefix() / object_name
# remove object
self._delete(path)
def list_names(self) -> list[str]:
"""List names of all models."""
# build path
path = self._prefix()
# list object paths
obj_path_list = self._list_objects(path)
# strip prefix
name_list = [obj_path.split('/')[-1] for obj_path in obj_path_list]
return name_list
@@ -0,0 +1,56 @@
"""Definition of VisualCommunicationRepository class."""
from __future__ import annotations
from .dto import VisualCommunicationData
from .implementations import MongoImplementation
from .interfaces import VisualCommunicationInterface
class VisualCommunicationRepository(VisualCommunicationInterface, MongoImplementation):
"""Visual communication repository class that handles CRUD functionality
for VisualCommunicationData."""
def __enter__(self) -> VisualCommunicationRepository:
self.connect()
return self
def get_data(self, name: str) -> VisualCommunicationData | None:
"""Get visual communication data."""
assert isinstance(name, str)
assert len(name) > 0
# build query
query = {'name': name}
# get document from mongo
doc = self._get(query)
# handle if no data found
if doc is None:
return None
# instantiate object
data = VisualCommunicationData(**doc)
return data
def put_data(self, data: VisualCommunicationData) -> None:
"""Put visual communication data."""
assert isinstance(data, VisualCommunicationData)
# convert to dict
data_dict: dict = data.model_dump(mode='json')
# build query
query = {'name': data.name}
# save to mongo
self._save(data_dict, query)
def remove_data(self, name: str) -> None:
"""Remove visual communication data."""
assert isinstance(name, str)
assert len(name) > 0
# build query
query = {'name': name}
# remove document from mongo
self._delete(query)
def list_names(self) -> list[str]:
"""List names of all documents."""
# list documents
name_list = self._list_documents(key='name')
return name_list
@@ -0,0 +1,403 @@
"""Integration tests configuration."""
import os
import random
from collections.abc import Iterator
from io import BytesIO
from pathlib import Path
import minio
import pytest
from dotenv import load_dotenv
from PIL import Image
from pymongo import MongoClient
from model.src.models import VisualCommunicationModel
from shared.repositories import (
HexadecimalString,
ImageData,
ImageRepository,
ModelData,
ModelRepository,
VisualCommunicationData,
VisualCommunicationRepository,
VisualCommunicationValues,
)
from shared.repositories.src.implementations import (
MinioImplementation,
MongoImplementation,
)
# set random seed for reproducibility
random.seed(13)
# define test environment variables
necessary_env_vars = {
'MINIO_ENDPOINT',
'MINIO_ACCESS_KEY',
'MINIO_SECRET_KEY',
'MONGO_ENDPOINT',
}
env_var_map = {
'MINIO_BUCKET_NAME': 'test-bucket',
'MINIO_OBJECT_NAME': 'test-object',
'MINIO_IMAGE_NAME': 'test-image',
'MONGO_DB': 'test-db',
'MONGO_COLLECTION': 'test-collection',
}
@pytest.fixture(scope='session', autouse=True)
def setup_env(
request: pytest.FixtureRequest,
) -> None:
"""Populate environment with variables used for testing."""
# load in optional local test environment variables
test_env_path = Path(__file__).parent.parent.parent.parent.parent / 'test.env'
load_dotenv(test_env_path)
# check if necessary env vars are set
for key in necessary_env_vars:
assert key in os.environ, f'{key} not set'
# set env vars unique to this test
for key, val in env_var_map.items():
# set env var
os.environ[key] = val
# ensure cleanup
def cleanup_env():
for key in env_var_map:
_ = os.environ.pop(key, default=None)
request.addfinalizer(cleanup_env)
@pytest.fixture(scope='session')
def raw_minio_client(
setup_env,
) -> Iterator[minio.Minio]:
"""Raw Minio client fixture."""
# prepare arguments
minio_endpoint = str(os.getenv('MINIO_ENDPOINT'))
minio_access_key = str(os.getenv('MINIO_ACCESS_KEY'))
minio_secret_key = str(os.getenv('MINIO_SECRET_KEY'))
minio_bucket_name = str(os.getenv('MINIO_BUCKET_NAME'))
# connect client
client = minio.Minio(
endpoint=minio_endpoint,
access_key=minio_access_key,
secret_key=minio_secret_key,
secure=False,
)
# ensure bucket exists
if not client.bucket_exists(bucket_name=minio_bucket_name):
client.make_bucket(bucket_name=minio_bucket_name)
# expose client
yield client
# cleanup
object_list = client.list_objects(minio_bucket_name, recursive=True)
for obj in object_list:
client.remove_object(
bucket_name=obj.bucket_name,
object_name=obj.object_name,
)
client.remove_bucket(minio_bucket_name)
@pytest.fixture
def minio_client(
setup_env,
) -> Iterator[MinioImplementation]:
"""MinioImplementation fixture."""
# instantiate and connect client
minio_client = MinioImplementation()
minio_client.connect()
# expose client
yield minio_client
# cleanup
object_name_list = minio_client._list_objects(Path('*'))
for name in object_name_list:
minio_client._delete(Path(name))
minio_client.close()
@pytest.fixture
def buffer() -> Iterator[BytesIO]:
"""Bytes buffer fixture."""
# generate reproducible random data
data = random.randbytes(n=2**21) # 2 MB
# convert data
buffer = BytesIO(data)
# expose buffer
yield buffer
@pytest.fixture
def buffer_in_minio(
raw_minio_client: minio.Minio,
buffer: BytesIO,
) -> Iterator[tuple[Path, BytesIO]]:
"""Buffer in Minio fixture."""
# prepare arguments
minio_bucket_name = str(os.getenv('MINIO_BUCKET_NAME'))
minio_object_name = str(os.getenv('MINIO_OBJECT_NAME'))
# prepare for saving
num_bytes = len(buffer.getvalue())
buffer.seek(0)
# put data in bucket
raw_minio_client.put_object(
bucket_name=minio_bucket_name,
object_name=minio_object_name,
length=num_bytes,
data=buffer,
)
# expose data
yield Path(minio_object_name), buffer
# cleanup
raw_minio_client.remove_object(
bucket_name=minio_bucket_name,
object_name=minio_object_name,
)
@pytest.fixture
def image_data() -> Iterator[ImageData]:
"""Image data fixture."""
# prepare arguments
name = str(os.getenv('MINIO_IMAGE_NAME'))
image = Image.new(mode='RGB', size=(480, 480))
# instantiate data
image_data = ImageData(image=image, name=name)
# expose data
yield image_data
@pytest.fixture
def image_data_in_minio(
raw_minio_client: minio.Minio,
image_data: ImageData,
) -> Iterator[ImageData]:
"""Image data in Minio fixture."""
# prepare arguments
minio_bucket_name = str(os.getenv('MINIO_BUCKET_NAME'))
image_name = image_data.name
# build object path
object_path = ImageRepository._build_path(image_name)
# save image to buffer
buffer = BytesIO()
image_data.image.save(buffer, 'png')
# prepare for saving
num_bytes = len(buffer.getvalue())
buffer.seek(0)
# put data in bucket
raw_minio_client.put_object(
bucket_name=minio_bucket_name,
object_name=object_path.as_posix(),
length=num_bytes,
data=buffer,
)
# expose data
yield image_data
# cleanup
raw_minio_client.remove_object(
bucket_name=minio_bucket_name,
object_name=object_path.as_posix(),
)
@pytest.fixture(scope='session')
def image_repo(
setup_env,
) -> Iterator[ImageRepository]:
"""Image repository fixture."""
# define test environment variables
assert 'MINIO_ENDPOINT' in os.environ, 'MINIO_ENDPOINT not set'
assert 'MINIO_ACCESS_KEY' in os.environ, 'MINIO_ACCESS_KEY not set'
assert 'MINIO_SECRET_KEY' in os.environ, 'MINIO_SECRET_KEY not set'
assert 'MONGO_ENDPOINT' in os.environ, 'MONGO_ENDPOINT not set'
repo = ImageRepository()
repo.connect()
yield repo
repo.close()
@pytest.fixture
def model_data() -> Iterator[ModelData]:
"""Model data fixture."""
# prepare arguments
vis_com_model = VisualCommunicationModel().to('cpu')
class_name = type(vis_com_model).__name__
buffer = ModelData.model_to_buffer(vis_com_model)
buffer_checksum = HexadecimalString('77dcab1769563654a6e24f92d40f29bd')
# instantiate data
model_data = ModelData(
buffer=buffer,
buffer_checksum=buffer_checksum,
class_name=class_name,
)
# expose model
yield model_data
@pytest.fixture
def model_data_in_minio(
raw_minio_client: minio.Minio,
model_data: ModelData,
) -> Iterator[ModelData]:
"""Model data in Minio fixture."""
# prepare arguments
minio_bucket_name = str(os.getenv('MINIO_BUCKET_NAME'))
object_name = ModelRepository._build_object_name(model_data)
buffer = model_data.buffer
# build object path
object_path = ModelRepository._prefix() / object_name
# prepare for saving
num_bytes = len(buffer.getvalue())
buffer.seek(0)
# put data in bucket
raw_minio_client.put_object(
bucket_name=minio_bucket_name,
object_name=object_path.as_posix(),
length=num_bytes,
data=buffer,
)
# expose data
yield model_data
# cleanup
raw_minio_client.remove_object(
bucket_name=minio_bucket_name,
object_name=object_path.as_posix(),
)
@pytest.fixture(scope='session')
def model_repo(
setup_env,
) -> Iterator[ModelRepository]:
"""Model repository fixture."""
repo = ModelRepository()
repo.connect()
yield repo
repo.close()
@pytest.fixture
def raw_mongo_client(
setup_env,
) -> Iterator[MongoClient]:
"""Raw mongo client fixture."""
# prepare arguments
mongo_endpoint = str(os.getenv('MONGO_ENDPOINT'))
mongo_database = str(os.getenv('MONGO_DB'))
# connect client
client: MongoClient = MongoClient(mongo_endpoint)
_ = client[mongo_database]
# expose client
yield client
# cleanup
client.drop_database(mongo_database)
@pytest.fixture
def mongo_client(
setup_env,
) -> Iterator[MongoImplementation]:
"""MongoImplementation fixture."""
# instantiate and connect client
mongo_client = MongoImplementation()
mongo_client.connect()
# expose client
yield mongo_client
# cleanup
mongo_client._collection.drop()
mongo_client.close()
@pytest.fixture
def dictionary() -> Iterator[dict]:
"""Dictionary fixture."""
# prepare data
data = {
'name': 'test-dictionary',
'str_key': 'value',
'int_key': 100,
'float_key': 3.14,
'list_key': [1, 2, 3],
'dict_key': {
'nested_key': 'nested_value',
},
}
# expose data
yield data
@pytest.fixture
def dictionary_in_mongo(
raw_mongo_client: MongoClient,
dictionary: dict,
) -> Iterator[dict]:
"""Dictionary in Mongo fixture."""
# prepare arguments
database = str(os.getenv('MONGO_DB'))
collection = str(os.getenv('MONGO_COLLECTION'))
# save data
_ = raw_mongo_client[database][collection].insert_one(dictionary.copy())
# expose data
yield dictionary
# cleanup
raw_mongo_client[database][collection].delete_one(dictionary)
@pytest.fixture
def visual_communication_values() -> Iterator[VisualCommunicationValues]:
"""Visual communication values fixture."""
# instantiate with random values
visual_communication_values = VisualCommunicationValues.from_random()
# expose values
yield visual_communication_values
@pytest.fixture
def visual_communication_data(
visual_communication_values: VisualCommunicationValues,
) -> Iterator[VisualCommunicationData]:
"""Visual communication data fixture."""
# prepare arguments
name = 'test-visual-communication'
annotation = visual_communication_values
# instantiate data
visual_communication_data = VisualCommunicationData(
name=name,
annotation=annotation,
)
# expose data
yield visual_communication_data
@pytest.fixture
def visual_communication_data_in_mongo(
raw_mongo_client: MongoClient,
visual_communication_data: VisualCommunicationData,
) -> Iterator[VisualCommunicationData]:
"""Visual communication data in Mongo fixture."""
# prepare arguments
database = str(os.getenv('MONGO_DB'))
collection = str(os.getenv('MONGO_COLLECTION'))
# convert data
dictionary = visual_communication_data.model_dump(mode='dict')
# save data
_ = raw_mongo_client[database][collection].insert_one(dictionary.copy())
# expose data
yield visual_communication_data
# cleanup
raw_mongo_client[database][collection].delete_one(dictionary)
@pytest.fixture(scope='session')
def visual_communication_repo(
setup_env,
) -> Iterator[VisualCommunicationRepository]:
"""Visual communication repository fixture."""
repo = VisualCommunicationRepository()
repo.connect()
yield repo
repo.close()
@@ -0,0 +1,156 @@
"""Integration tests for ImageRepository class."""
import numpy as np
import pytest
from PIL import Image
from shared.repositories import ImageData, ImageRepository
def same_image(
img_a: Image.Image,
img_b: Image.Image,
) -> bool:
"""Check if two images contain the same data."""
assert isinstance(img_a, Image.Image)
assert isinstance(img_b, Image.Image)
# check if images have a comparable number of channels
if img_a.getbands() != img_b.getbands():
return False
# calculate pixel difference between images
img_a_arr = np.asarray(img_a)
img_b_arr = np.asarray(img_b)
diff = np.subtract(img_a_arr, img_b_arr)
if np.sum(diff) != 0:
return False
return True
def same_image_data(
data_a: ImageData,
data_b: ImageData,
) -> bool:
"""Check if two ImageData-objects contain the same data."""
assert isinstance(data_a, ImageData)
assert isinstance(data_b, ImageData)
# compare names
if data_a.name != data_b.name:
return False
# compare images
if not same_image(data_a.image, data_b.image):
return False
return True
def test_should_have_context_handler():
"""Test that class has a working context handler implemented."""
# ACT
with ImageRepository() as repo:
# ASSERT
assert repo.connected()
def test_should_get_image_data(
image_repo: ImageRepository,
image_data_in_minio: ImageData,
):
"""Test getting image data."""
# ARRANGE
image_name = image_data_in_minio.name
# ACT
received_image_data = image_repo.get_data(image_name)
# ASSERT
assert received_image_data is not None
assert isinstance(received_image_data, ImageData)
assert same_image_data(image_data_in_minio, received_image_data)
def test_should_get_none_when_no_image_data(
image_repo: ImageRepository,
image_data: ImageData,
):
"""Test getting None when no data is available."""
# ARRANGE
image_name = image_data.name
# ACT
received_image_data = image_repo.get_data(image_name)
# ASSERT
assert received_image_data is None
def test_should_delete_image_data(
image_repo: ImageRepository,
image_data_in_minio: ImageData,
):
"""Test deleting image data."""
# ARRANGE
image_name = image_data_in_minio.name
# ACT
image_repo.remove_data(image_name)
received_image_data = image_repo.get_data(image_name)
# ASSERT
assert received_image_data is None
def test_should_put_image_data(
image_repo: ImageRepository,
image_data: ImageData,
):
"""Test putting image data."""
# ARRANGE
image_name = image_data.name
# ACT
image_repo.put_data(image_data)
received_image_data = image_repo.get_data(image_name)
# ASSERT
assert received_image_data is not None
assert same_image_data(image_data, received_image_data)
def test_should_update_image_data(
image_repo: ImageRepository,
image_data_in_minio: ImageData,
):
"""Test updating image data."""
# ARRANGE
updated_image_data = image_data_in_minio.model_copy(
update={
'image': Image.new(mode='RGB', size=(480, 480), color='white'),
},
)
image_name = updated_image_data.name
# ACT
image_repo.put_data(updated_image_data)
received_image_data = image_repo.get_data(image_name)
# ASSERT
assert not same_image_data(image_data_in_minio, updated_image_data)
assert received_image_data is not None
assert same_image_data(updated_image_data, received_image_data)
def test_should_list_names(
image_repo: ImageRepository,
image_data_in_minio: ImageData,
):
"""Test get all image names."""
# ARRANGE
updated_image_data = image_data_in_minio.model_copy(
update={
'name': 'updated-test-image',
},
)
image_repo.put_data(updated_image_data)
expected_name_list = [
image_data_in_minio.name,
updated_image_data.name,
]
# ACT
name_list = image_repo.list_names()
# ASSERT
assert len(name_list) == 2
for name in name_list:
assert name in expected_name_list
if __name__ == '__main__':
pytest.main(['-s', '-v', __file__])
@@ -0,0 +1,133 @@
"""Integration tests related to Minio implementation."""
import logging
import os
from io import BytesIO
from pathlib import Path
import pytest
from shared.repositories.src.implementations import MinioImplementation
def same_data(
data_a: BytesIO,
data_b: BytesIO,
) -> bool:
"""Check if two BytesIO-objects contain the same data."""
assert isinstance(data_a, BytesIO)
assert isinstance(data_b, BytesIO)
# prepare for being read
data_a.seek(0)
data_b.seek(0)
# convert to bytes
data_a_bytes = data_a.read()
data_b_bytes = data_b.read()
# compare size
if len(data_a_bytes) != len(data_b_bytes):
logging.error(
'data has different length: %s and %s',
len(data_a_bytes),
len(data_b_bytes),
)
return False
# compare content
if data_a_bytes != data_b_bytes:
logging.error('data has different bytes')
return False
return True
def test_should_connect_to_minio():
"""Test connection to Minio."""
# ARRANGE
client = MinioImplementation()
# ACT
client.connect()
# ASSERT
assert client.connected()
client.close()
def test_should_have_context_handler():
"""Test that class has a working context handler implemented."""
# ACT
with MinioImplementation() as client:
# ASSERT
assert client.connected()
def test_should_get_data(
minio_client: MinioImplementation,
buffer_in_minio: tuple[Path, BytesIO],
):
"""Test getting data from Minio."""
# ARRANGE
path, buffer = buffer_in_minio
# ACT
received_buffer = minio_client._get(path)
# ASSERT
assert received_buffer is not None
assert same_data(received_buffer, buffer)
def test_should_get_none_when_no_data(
minio_client: MinioImplementation,
):
"""Test getting None when no data is available in Minio."""
# ARRANGE
nonexistent_path = Path('nonexistent-object-name')
# ACT
received_buffer = minio_client._get(nonexistent_path)
# ASSERT
assert received_buffer is None
def test_should_delete_data(
minio_client: MinioImplementation,
buffer_in_minio: tuple[Path, BytesIO],
):
"""Test deleting data from Minio."""
# ARRANGE
path, _ = buffer_in_minio
# ACT
minio_client._delete(path)
# ASSERT
received_buffer = minio_client._get(path)
assert received_buffer is None
def test_should_put_data(
minio_client: MinioImplementation,
buffer: BytesIO,
):
"""Test putting data in Minio."""
# ARRANGE
path = Path(os.getenv('MINIO_OBJECT_NAME', default=''))
# ACT
minio_client._put(path, buffer)
received_buffer = minio_client._get(path)
# ASSERT
assert received_buffer is not None
assert same_data(received_buffer, buffer)
def test_should_update_data(
minio_client: MinioImplementation,
buffer_in_minio: tuple[Path, BytesIO],
):
"""Test updating data in Minio."""
# ARRANGE
path, buffer = buffer_in_minio
updated_buffer = BytesIO(buffer.getvalue() + b'extra data')
# ACT
minio_client._put(path, updated_buffer)
received_buffer = minio_client._get(path)
# ASSERT
assert not same_data(updated_buffer, buffer)
assert received_buffer is not None
assert same_data(received_buffer, updated_buffer)
if __name__ == '__main__':
pytest.main(['-s', '-v', __file__])
@@ -0,0 +1,136 @@
"""Integration tests for ModelRepository class."""
from io import BytesIO
import pytest
from shared.repositories import ModelData, ModelRepository
def same_buffer(
buffer_a: BytesIO,
buffer_b: BytesIO,
) -> bool:
"""Check if 2 buffers contain the same data."""
assert isinstance(buffer_a, BytesIO)
assert isinstance(buffer_b, BytesIO)
# read buffers
a_values = buffer_a.getvalue()
b_values = buffer_b.getvalue()
# compare length of buffers
if len(a_values) != len(b_values):
return False
# compare content of buffers
if a_values != b_values:
return False
return True
def same_model_data(
data_a: ModelData,
data_b: ModelData,
) -> bool:
"""Check if to ModelData-objects contain the same data."""
assert isinstance(data_a, ModelData)
assert isinstance(data_b, ModelData)
# compare names
if data_a.buffer_checksum != data_b.buffer_checksum:
return False
# compare buffer
if not same_buffer(data_a.buffer, data_b.buffer):
return False
return True
def test_should_have_context_handler():
"""Test that class has a working context handler implemented."""
# ACT
with ModelRepository() as repo:
# ASSERT
assert repo.connected()
def test_should_get_model_data(
model_repo: ModelRepository,
model_data_in_minio: ModelData,
):
"""Test getting model data."""
# ARRANGE
object_name = ModelRepository._build_object_name(model_data_in_minio)
# ACT
received_model_data = model_repo.get_data(object_name)
# ASSERT
assert received_model_data is not None
assert isinstance(received_model_data, ModelData)
assert same_model_data(model_data_in_minio, received_model_data)
def test_should_get_none_when_no_model_data(
model_repo: ModelRepository,
model_data: ModelData,
):
"""Test getting None whne no data is available."""
# ARRANGE
object_name = ModelRepository._build_object_name(model_data)
# ACT
received_model_data = model_repo.get_data(object_name)
# ASSERT
assert received_model_data is None
def test_should_delete_model_data(
model_repo: ModelRepository,
model_data_in_minio: ModelData,
):
"""Test deleting model data."""
# ARRANGE
object_name = ModelRepository._build_object_name(model_data_in_minio)
# ACT
model_repo.remove_data(object_name)
received_model_data = model_repo.get_data(object_name)
# ASSERT
assert received_model_data is None
def test_should_put_model_data(
model_repo: ModelRepository,
model_data: ModelData,
):
"""Test putting model data."""
# ARRANGE
object_name = ModelRepository._build_object_name(model_data)
# ACT
model_repo.put_data(model_data)
received_model_data = model_repo.get_data(object_name)
# ASSERT
assert received_model_data is not None
assert same_model_data(model_data, received_model_data)
def test_should_list_names(
model_repo: ModelRepository,
model_data_in_minio: ModelData,
):
"""Test get all model names."""
# ARRANGE
new_buffer_checksum = ModelData.calculate_checksum(model_data_in_minio.buffer)
updated_model_data = model_data_in_minio.model_copy(
update={
'buffer_checksum': new_buffer_checksum,
},
)
model_repo.put_data(updated_model_data)
expected_name_list = [
ModelRepository._build_object_name(model_data_in_minio),
ModelRepository._build_object_name(updated_model_data),
]
# ACT
name_list = model_repo.list_names()
# ASSERT
assert len(name_list) == 2
for name in name_list:
assert name in expected_name_list
if __name__ == '__main__':
pytest.main(['-s', '-v', __file__])
@@ -0,0 +1,113 @@
"""Integration tests related to Mongo implementation."""
import pytest
from shared.repositories.src.implementations import MongoImplementation
def test_should_connect_mongo():
"""Test connecting to Mongo."""
# ARRANGE
client = MongoImplementation()
# ACT
client.connect()
# ASSERT
assert client.connected()
client.close()
def test_should_have_context_handler():
"""Test that class has a working context handler implemented."""
# ACT
with MongoImplementation() as client:
# ASSERT
assert client.connected()
def test_should_get_data(
mongo_client: MongoImplementation,
dictionary_in_mongo: dict,
):
"""Test getting data from mongo."""
# ARRANGE
name = dictionary_in_mongo['name']
query = {'name': name}
# ACT
received_dictionary = mongo_client._get(query)
# ASSERT
assert received_dictionary is not None
assert isinstance(received_dictionary, dict)
assert received_dictionary == dictionary_in_mongo
def test_should_get_none_when_no_data(
mongo_client: MongoImplementation,
dictionary: dict,
):
"""Test getting None when no data is available in mongo."""
# ARRANGE
name = dictionary['name']
query = {'name': name}
# ACT
received_dictionary = mongo_client._get(query)
# ASSERT
assert received_dictionary is None
def test_should_delete_data(
mongo_client: MongoImplementation,
dictionary_in_mongo: dict,
):
"""Test deleting data from mongo."""
# ARRANGE
name = dictionary_in_mongo['name']
query = {'name': name}
# ACT
mongo_client._delete(query)
received_dictionary = mongo_client._get(query)
# ASSERT
assert received_dictionary is None
def test_should_save_data(
mongo_client: MongoImplementation,
dictionary: dict,
):
"""Test saving data to mongo."""
# ARRANGE
name = dictionary['name']
query = {'name': name}
# ACT
mongo_client._save(
data=dictionary,
query=query,
)
# ASSERT
received_dictionary = mongo_client._get(query)
assert received_dictionary is not None
assert isinstance(received_dictionary, dict)
assert received_dictionary == dictionary
def test_should_update_data(
mongo_client: MongoImplementation,
dictionary_in_mongo: dict,
):
"""Test updating data in mongo."""
# ARRANGE
name = dictionary_in_mongo['name']
query = {'name': name}
# ACT
dictionary_in_mongo['str-key'] = 'updated-value'
mongo_client._save(
data=dictionary_in_mongo,
query=query,
)
# ASSERT
received_dictionary = mongo_client._get(query)
assert received_dictionary is not None
assert received_dictionary == dictionary_in_mongo
if __name__ == '__main__':
pytest.main(['-s', '-v', __file__])
@@ -0,0 +1,125 @@
"""Integration tests for VisualCommunicationRepository class."""
import pytest
from shared.repositories import (
VisualCommunicationData,
VisualCommunicationRepository,
VisualCommunicationValues,
)
def test_should_have_context_handler():
"""Test that class has a working context handler implemented."""
# ACT
with VisualCommunicationRepository() as repo:
# ASSERT
assert repo.connected()
def test_should_get_visual_communication_data(
visual_communication_repo: VisualCommunicationRepository,
visual_communication_data_in_mongo: VisualCommunicationData,
):
"""Test getting visual communication data."""
# ARRANGE
name = visual_communication_data_in_mongo.name
# ACT
received_visual_communication_data = visual_communication_repo.get_data(name)
# ASSERT
assert received_visual_communication_data is not None
assert isinstance(received_visual_communication_data, VisualCommunicationData)
assert received_visual_communication_data == visual_communication_data_in_mongo
def test_should_get_none_when_no_visual_communication_data(
visual_communication_repo: VisualCommunicationRepository,
visual_communication_data: VisualCommunicationData,
):
"""Test getting None when no data is available."""
# ARRANGE
name = visual_communication_data.name
# ACT
received_visual_communication_data = visual_communication_repo.get_data(name)
# ASSERT
assert received_visual_communication_data is None
def test_should_delete_image_data(
visual_communication_repo: VisualCommunicationRepository,
visual_communication_data_in_mongo: VisualCommunicationData,
):
"""Test deleting visual communication data."""
# ARRANGE
name = visual_communication_data_in_mongo.name
# ACT
visual_communication_repo.remove_data(name)
received_visual_communication_data = visual_communication_repo.get_data(name)
# ASSERT
assert received_visual_communication_data is None
def test_should_put_visual_communication_data(
visual_communication_repo: VisualCommunicationRepository,
visual_communication_data: VisualCommunicationData,
):
"""Test putting visual communication data."""
# ARRANGE
name = visual_communication_data.name
# ACT
visual_communication_repo.put_data(visual_communication_data)
received_visual_communication_data = visual_communication_repo.get_data(name)
# ASSERT
assert received_visual_communication_data is not None
assert isinstance(received_visual_communication_data, VisualCommunicationData)
assert received_visual_communication_data == visual_communication_data
def test_should_update_visual_communication_data(
visual_communication_repo: VisualCommunicationRepository,
visual_communication_data_in_mongo: VisualCommunicationData,
):
"""Test updating visual communication data."""
# ARRANGE
name = visual_communication_data_in_mongo.name
updated_annotation = VisualCommunicationValues.from_random()
updated_visual_communication = VisualCommunicationData(
name=name,
annotation=updated_annotation,
)
# ACT
visual_communication_repo.put_data(updated_visual_communication)
received_visual_communication_data = visual_communication_repo.get_data(name)
# ASSERT
assert received_visual_communication_data is not None
assert isinstance(received_visual_communication_data, VisualCommunicationData)
assert received_visual_communication_data == updated_visual_communication
def test_should_list_names(
visual_communication_repo: VisualCommunicationRepository,
visual_communication_data_in_mongo: VisualCommunicationData,
):
"""Test listing names of all documents."""
# ARRANGE
name = 'test-visual-communication-2'
annotation = VisualCommunicationValues.from_random()
second_visual_communication = VisualCommunicationData(
name=name,
annotation=annotation,
)
visual_communication_repo.put_data(second_visual_communication)
expected_name_list = [
visual_communication_data_in_mongo.name,
second_visual_communication.name,
]
# ACT
name_list = visual_communication_repo.list_names()
# ASSERT
assert len(name_list) == 2
for name in name_list:
assert name in expected_name_list
if __name__ == '__main__':
pytest.main(['-s', '-v', __file__])
@@ -0,0 +1,46 @@
"""Definition of unittests for TypeCheckingBaseModel class."""
import unittest
import pytest
from pydantic import ValidationError
from shared.repositories.src.dto.type_checking_base_model import TypeCheckingBaseModel
class UUT(TypeCheckingBaseModel):
"""Test class for TypeCheckingBaseModel."""
string_field: str
int_field: int
class TestTypeCheckingBaseModel(unittest.TestCase):
"""Test class for TypeCheckingBaseModel."""
def setUp(self):
"""Set up the test case."""
self.uut = UUT
self.string_field = 'test'
self.int_field = 123
def test_type_checking_on_instantiation(self):
"""Test type checking on instantiation."""
with pytest.raises(ValidationError):
_ = self.uut(
string_field=self.int_field,
int_field=self.string_field,
)
def test_attributes_frozen(self):
"""Test that attributes cannot be updated."""
uut_instance = self.uut(
string_field=self.string_field,
int_field=self.int_field,
)
with pytest.raises(ValidationError):
uut_instance.string_field = self.int_field
if __name__ == '__main__':
pytest.main(['-s', '-v', __file__])
+4
View File
@@ -0,0 +1,4 @@
from __future__ import annotations
from .check_env import check_env
from .setup_logging import setup_logging
+18
View File
@@ -0,0 +1,18 @@
"""Definition of check_env function."""
import os
def check_env(
var_list: set[str],
) -> None:
"""Check necessary environment variables are set."""
assert isinstance(var_list, set)
assert all(isinstance(elem, str) for elem in var_list)
# check that env vars are set
missing_vars = []
for env_var in var_list:
if env_var not in os.environ:
missing_vars.append(env_var)
if missing_vars:
raise OSError(f"environment variable(s) not set: {missing_vars}")
+24
View File
@@ -0,0 +1,24 @@
"""Definition of setup_logging function."""
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 | '
'%(name)s | '
'%(funcName)s | '
'%(message)s'
)
datefmt = '%Y-%m-%d %H:%M:%S'
logging.basicConfig(format=fmt, datefmt=datefmt, level=level, force=True)
# change levels for modules that spam the log
logging.getLogger('pymongo').setLevel(logging.WARNING)
logging.getLogger('urllib3').setLevel(logging.INFO)
logging.getLogger('PIL').setLevel(logging.INFO)
logging.debug('finished')
+53
View File
@@ -0,0 +1,53 @@
"""Definition of tests for check_env function."""
import os
import unittest
from shared.utils import check_env
class TestFunctionCheckEnv(unittest.TestCase):
"""Testing function check_env."""
def setUp(self):
"""Prepare for tests."""
# define variables for test
self.not_set_env_var = 'NOT_SET_ENV_VAR'
self.set_env_var_map = {
'ENV_VAR_UPPER': 'ENV_VAR_UPPER',
'ENV_VAR_LOWER': 'env_var_lower',
}
# set env vars
for key, val in self.set_env_var_map.items():
os.environ[key] = val
def tearDown(self):
"""Clean up after tests."""
# remove env vars
for key in self.set_env_var_map:
_ = os.environ.pop(key, default=None)
def test_input_type(self):
"""Test that function fails on wrong input types."""
with self.assertRaises(AssertionError):
check_env('not_a_list')
with self.assertRaises(AssertionError):
check_env({1, 2, 3})
def test_env_var_not_set(self):
"""Test that function raises AssertionError when checking environment
variable that is not set."""
var_list = {self.not_set_env_var}
msg = f'environment variable not set: {self.not_set_env_var}'
with self.assertRaises(OSError, msg=msg):
check_env(var_list)
def test_env_vars_set(self):
"""Test that function does not raise any errors when environment
variables are set."""
var_list = set(self.set_env_var_map.keys())
check_env(var_list)
if __name__ == '__main__':
unittest.main()

Some files were not shown because too many files have changed in this diff Show More