Compare commits

..
16 Commits
Author SHA1 Message Date
Brian Bjarke Jensen 1bc4072e52 applied new functions and fixed bugs 2024-02-24 22:44:28 +01:00
Brian Bjarke Jensen c3ad732448 updated tests 2024-02-24 22:43:56 +01:00
Brian Bjarke Jensen d3eac22706 added from_annotations classmethod and fixed bug when generating webencoded image 2024-02-24 22:43:26 +01:00
Brian Bjarke Jensen 3fb2fb6258 fixed bug when generating option labels 2024-02-24 22:42:44 +01:00
Brian Bjarke Jensen 5a147e9492 added from_choice classmethod 2024-02-24 22:42:11 +01:00
Brian Bjarke Jensen 59ad0104ad fixed bug when upserting annotations 2024-02-24 22:41:27 +01:00
Brian Bjarke Jensen 47cae849ff added env dependent storage type 2024-02-24 21:54:36 +01:00
Brian Bjarke Jensen e7d91a4886 added n_clicks default to button 2024-02-24 21:53:59 +01:00
Brian Bjarke Jensen e8ba277dab elaborated on init str generation 2024-02-24 21:53:36 +01:00
Brian Bjarke Jensen 8389a20745 added dev env var 2024-02-24 21:52:19 +01:00
Brian Bjarke Jensen b0ca86677f exposed new functions 2024-02-24 21:51:56 +01:00
Brian Bjarke Jensen c4f220a61a updated logging and added convenience functions 2024-02-24 21:51:37 +01:00
Brian Bjarke Jensen 7b0fb5ef5d added dynamic ids 2024-02-24 21:50:53 +01:00
Brian Bjarke Jensen e82a03f298 updated container name 2024-02-24 20:10:10 +01:00
Brian Bjarke Jensen 91d02f1ecb tested random prediction generation 2024-02-24 11:58:37 +01:00
Brian Bjarke Jensen 8196628981 added function to randomly generate prediction data 2024-02-24 11:58:00 +01:00
17 changed files with 386 additions and 68 deletions
+3
View File
@@ -8,6 +8,8 @@ services:
dockerfile: Dockerfile dockerfile: Dockerfile
env_file: env_file:
- local.env - local.env
environment:
- ENV=DEV
ports: ports:
- 8050:8050 - 8050:8050
networks: networks:
@@ -25,6 +27,7 @@ services:
- backend - backend
mongo-express: mongo-express:
image: mongo-express image: mongo-express
container_name: mongo_express
ports: ports:
- 8081:8081 - 8081:8081
env_file: env_file:
+3 -1
View File
@@ -7,5 +7,7 @@ from .database import connect
from .utils import ( from .utils import (
total_documents, total_documents,
total_annotated, total_annotated,
get_visual_communication get_visual_communication,
upsert_annotations,
upsert_predictions,
) )
+58 -3
View File
@@ -4,8 +4,12 @@ from PIL import Image
from io import BytesIO from io import BytesIO
from pathlib import Path from pathlib import Path
from base64 import b64encode from base64 import b64encode
import logging
from typing import List
from src.model_experiential import ExperientialModelOutput from src.model_experiential import (
VisualSyntaxModelOutput
)
from src.model_interpersonal import ( from src.model_interpersonal import (
ContactModelOutput, ContactModelOutput,
AngleModelOutput, AngleModelOutput,
@@ -22,7 +26,7 @@ from src.model_textual import (
) )
class ModelOutputs(BaseModel): class ModelOutputs(BaseModel):
experiential: ExperientialModelOutput visual_syntax: VisualSyntaxModelOutput
contact: ContactModelOutput contact: ContactModelOutput
angle: AngleModelOutput angle: AngleModelOutput
point_of_view: PointOfViewModelOutput point_of_view: PointOfViewModelOutput
@@ -34,6 +38,52 @@ class ModelOutputs(BaseModel):
framing: FramingModelOutput framing: FramingModelOutput
salience: SalienceModelOutput salience: SalienceModelOutput
@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) -> ModelOutputs:
"""Instantiate with random numbers."""
kwargs = {
field: field_info.annotation.from_random()
for field, field_info
in cls.model_fields.items()
}
return cls(**kwargs)
@classmethod
def from_annotations(
cls,
visual_syntax: str,
contact: str,
angle: str,
point_of_view: str,
distance: str,
modality_lighting: str,
modality_color: str,
modality_depth: str,
information_value: str,
framing: str,
salience: str
) -> ModelOutputs:
"""Instantiate from annotation."""
kwargs = {
"visual_syntax": VisualSyntaxModelOutput.from_choice(visual_syntax),
"contact": ContactModelOutput.from_choice(contact),
"angle": AngleModelOutput.from_choice(angle),
"point_of_view": PointOfViewModelOutput.from_choice(point_of_view),
"distance": DistanceModelOutput.from_choice(distance),
"modality_lighting": ModalityLightingModelOutput.from_choice(modality_lighting),
"modality_color": ModalityColorModelOutput.from_choice(modality_color),
"modality_depth": ModalityDepthModelOutput.from_choice(modality_depth),
"information_value": InformationValueModelOutput.from_choice(information_value),
"framing": FramingModelOutput.from_choice(framing),
"salience": SalienceModelOutput.from_choice(salience)
}
return cls(**kwargs)
class VisualCommunication(BaseModel): class VisualCommunication(BaseModel):
name: str name: str
@@ -81,8 +131,13 @@ class VisualCommunication(BaseModel):
buffer = BytesIO() buffer = BytesIO()
self.image.save(buffer, format="png") self.image.save(buffer, format="png")
img_enc = b64encode(buffer.getvalue()).decode("utf-8") img_enc = b64encode(buffer.getvalue()).decode("utf-8")
return img_enc return f"data:image/png;base64, {img_enc}"
def generate_random_prediction(self, force: bool = False) -> None:
"""Generate random prediction values."""
if not force and self.prediction is not None:
logging.warning("set force=True to overwrite existing values.")
self.prediction = ModelOutputs.from_random()
class NoDocumentFoundException(Exception): class NoDocumentFoundException(Exception):
pass pass
+51 -9
View File
@@ -1,8 +1,10 @@
from pymongo.collection import Collection from pymongo.collection import Collection
import logging
from .classes import ( from .classes import (
VisualCommunication, VisualCommunication,
NoDocumentFoundException NoDocumentFoundException,
ModelOutputs
) )
@@ -25,8 +27,7 @@ def total_annotated(
def get_visual_communication( def get_visual_communication(
collection: Collection, collection: Collection,
with_annotation: bool = False, with_annotation: bool = False
with_prediction: bool = False
) -> VisualCommunication: ) -> VisualCommunication:
"""Get a random visual communication from the database.""" """Get a random visual communication from the database."""
query = {} query = {}
@@ -34,15 +35,56 @@ def get_visual_communication(
query["annotation"] = {"$ne": None} query["annotation"] = {"$ne": None}
else: else:
query["annotation"] = None query["annotation"] = None
if with_prediction:
query["prediction"] = {"$ne": None}
else:
query["prediction"] = None
data = collection.aggregate([ data = collection.aggregate([
{ "$match": query }, # find using filters { "$match": query }, # find using filters
{ "$sample": { "size": 1 } } # get one random { "$sample": { "size": 1 } } # get one random
]) ])
data = list(data) # read data from cursor object data = list(data) # read data from cursor object
if len(data) == 0: if len(data) == 0:
raise NoDocumentFoundException logging.error("failed getting visual communication")
return VisualCommunication.model_validate(data[0]) raise NoDocumentFoundException()
data = data[0]
logging.info("finished")
return VisualCommunication.model_validate(data)
def upsert_predictions(
collection: Collection,
vis_com_name: str,
predictions: ModelOutputs,
) -> None:
"""Upsert prediction data in the database."""
query = {
"name": vis_com_name
}
update = {
"$set": { "prediction": predictions.model_dump() }
}
res = collection.update_one(
filter=query,
update=update,
upsert=True,
)
logging.debug("upserted document: %s", res)
logging.info("finished")
def upsert_annotations(
collection: Collection,
vis_com_name: str,
annotations: ModelOutputs,
) -> None:
"""Upserts annotation data in the database."""
query = {
"name": vis_com_name
}
update = {
"$set": { "annotation": annotations.model_dump() }
}
res = collection.update_one(
filter=query,
update=update,
upsert=True,
)
logging.info("upserted document: %s", res)
logging.info("finished")
+1 -1
View File
@@ -1,4 +1,4 @@
from .classes import ExperientialModelOutput from .classes import VisualSyntaxModelOutput
+18 -3
View File
@@ -1,8 +1,11 @@
from pydantic import BaseModel from pydantic import BaseModel, ValidationError
from typing import List from typing import List
import random import random
class OptionNotSetException(Exception):
pass
class ModelOutput(BaseModel): class ModelOutput(BaseModel):
@classmethod @classmethod
@@ -21,6 +24,18 @@ class ModelOutput(BaseModel):
kwargs = {field: random.random() for field in cls.list_fields()} kwargs = {field: random.random() for field in cls.list_fields()}
return cls(**kwargs) return cls(**kwargs)
@classmethod
def from_choice(cls, option: str):
"""Instantiate from choice."""
if option is None:
raise ValidationError()
assert isinstance(option, str), "option is not a string"
allowed_options_list = cls.list_fields()
assert option in allowed_options_list, f"{option} is not among allowed fields {allowed_options_list}"
kwargs = {field: 0 for field in cls.list_fields()}
kwargs[option] = 1
return cls(**kwargs)
def __repr__(self) -> str: def __repr__(self) -> str:
model_dict = self.model_dump() model_dict = self.model_dump()
model_repr_str = f"{self.classname()}(" model_repr_str = f"{self.classname()}("
@@ -39,7 +54,7 @@ class ModelOutput(BaseModel):
return max(model_dict.values()) return max(model_dict.values())
class ExperientialModelOutput(ModelOutput): class VisualSyntaxModelOutput(ModelOutput):
non_transactional_action: float non_transactional_action: float
non_transactional_reaction: float non_transactional_reaction: float
unidirectional_transactional_action: float unidirectional_transactional_action: float
@@ -61,7 +76,7 @@ class ExperientialModelOutput(ModelOutput):
if __name__ == '__main__': if __name__ == '__main__':
m = ExperientialModelOutput.from_random() m = VisualSyntaxModelOutput.from_random()
print(m) print(m)
print(repr(m)) print(repr(m))
print(m.highest_score_field()) print(m.highest_score_field())
+13 -1
View File
@@ -1,4 +1,4 @@
from pydantic import BaseModel from pydantic import BaseModel, ValidationError
from typing import List from typing import List
import random import random
@@ -21,6 +21,18 @@ class ModelOutput(BaseModel):
kwargs = {field: random.random() for field in cls.list_fields()} kwargs = {field: random.random() for field in cls.list_fields()}
return cls(**kwargs) return cls(**kwargs)
@classmethod
def from_choice(cls, option: str):
"""Instantiate from choice."""
if option is None:
raise ValidationError()
assert isinstance(option, str)
allowed_options_list = cls.list_fields()
assert option in allowed_options_list, f"{option} is not among allowed fields {allowed_options_list}"
kwargs = {field: 0 for field in cls.list_fields()}
kwargs[option] = 1
return cls(**kwargs)
def __repr__(self) -> str: def __repr__(self) -> str:
model_dict = self.model_dump() model_dict = self.model_dump()
model_repr_str = f"{self.classname()}(" model_repr_str = f"{self.classname()}("
+13 -1
View File
@@ -1,4 +1,4 @@
from pydantic import BaseModel from pydantic import BaseModel, ValidationError
from typing import List from typing import List
import random import random
@@ -21,6 +21,18 @@ class ModelOutput(BaseModel):
kwargs = {field: random.random() for field in cls.list_fields()} kwargs = {field: random.random() for field in cls.list_fields()}
return cls(**kwargs) return cls(**kwargs)
@classmethod
def from_choice(cls, option: str):
"""Instantiate from choice."""
if option is None:
raise ValidationError()
assert isinstance(option, str)
allowed_options_list = cls.list_fields()
assert option in allowed_options_list, f"{option} is not among allowed fields {allowed_options_list}"
kwargs = {field: 0 for field in cls.list_fields()}
kwargs[option] = 1
return cls(**kwargs)
def __repr__(self) -> str: def __repr__(self) -> str:
model_dict = self.model_dump() model_dict = self.model_dump()
model_repr_str = f"{self.classname()}(" model_repr_str = f"{self.classname()}("
+98 -22
View File
@@ -1,12 +1,16 @@
from dash import Dash, Input, Output from dash import Dash, Input, Output, State, ALL
import dash_bootstrap_components as dbc import dash_bootstrap_components as dbc
import logging import logging
from typing import List
from pydantic import ValidationError
from .layout import app_layout from .layout import app_layout
from src.database import ( from src.database import (
connect, connect,
get_visual_communication, get_visual_communication,
NoDocumentFoundException NoDocumentFoundException,
upsert_annotations,
ModelOutputs
) )
# setup app # setup app
@@ -27,39 +31,111 @@ collection, db, client = connect()
def show_alert( def show_alert(
msg: str | None msg: str | None
): ):
if msg is None: if msg is None or msg == "":
return False, "" return False, ""
logging.info(f"updated alert message: {msg}")
return True, msg return True, msg
@app.callback( @app.callback(
Output("alert-message", "data"), Output("alert-message", "data"),
Output("visual-communication-name", "data"), Output("vis-com-name", "data"),
Output("image-container", "src"), Output("image-container", "src"),
Output({"type": "annotation", "index": ALL}, "value"),
Input("next-button", "n_clicks"), Input("next-button", "n_clicks"),
prevent_initial_call=True State("vis-com-name", "data"),
State("image-container", "src"),
State({"type": "annotation", "index": ALL}, "id"),
State({"type": "annotation", "index": ALL}, "value"),
prevent_initial_call=True,
) )
def load_unannotated_visual_communication_data( def cycle_visual_communication_data(
n_clicks: int n_clicks: int,
vis_com_name: str,
image_src: str,
annotation_keys: List,
annotation_values: List,
): ):
logging.info("began cycling visual communication data")
global collection global collection
# prepare default response
response = [
"",
vis_com_name,
image_src,
annotation_values
]
# check if next-button clicked
if n_clicks == 0:
logging.info("stopping early: next-button has not yet been clicked")
return response
# check if visual communication name is set
if len(vis_com_name) > 0:
logging.info("saving annotations to database: %s", vis_com_name)
try: try:
# extract option keys
annotation_keys = [
elem["index"]
for elem in annotation_keys
]
# ensure all options are set
logging.info(annotation_keys)
for option, value in zip(annotation_keys, annotation_values):
if value is None:
raise ValueError(f"{option} is not set")
# prepare data to save
annotation_keys = [
elem.replace(' ', '_')
for elem
in annotation_keys
]
annotation_values = [
elem.replace(' ', '_').lower()
for elem
in annotation_values
]
annotations = {
key: value
for key, value
in zip(annotation_keys, annotation_values)
}
# instantiate ModelOutputs object
annotations = ModelOutputs.from_annotations(**annotations)
# save data to
upsert_annotations(
collection=collection,
vis_com_name=vis_com_name,
annotations=annotations
)
except (ValueError, ValidationError) as exc:
msg = f"failed saving annotation: {exc}"
logging.warning(msg)
response[0] = msg
return tuple(response)
# get new visual communication
logging.info("trying to get new visual communication")
try:
# get data
vis_com = get_visual_communication( vis_com = get_visual_communication(
collection=collection, collection=collection,
with_annotation=False with_annotation=False
) )
except NoDocumentFoundException: # set variables
return (
"Did not find any unannotated data in database",
None,
""
)
# prepare return values
alert_message = None
vis_com_name = vis_com.name vis_com_name = vis_com.name
img_src = f"data:image/png;base64, {vis_com.webencoded_image()}" image_src = vis_com.webencoded_image()
logging.info("updated visual communication") if vis_com.prediction is not None:
return ( # TODO: update to use optional predictions
alert_message, pass
vis_com_name, else:
img_src # reset annotations
) annotation_values = [None for elem in annotation_values]
except NoDocumentFoundException:
msg = f"no unannotated data in database"
logging.warning(msg)
response[0] = msg
return tuple(response)
else:
response[1] = vis_com_name
response[2] = image_src
response[3] = annotation_values
logging.info("finished getting visual communication: %s", vis_com_name)
return tuple(response)
+3 -1
View File
@@ -7,6 +7,8 @@ from base64 import b64encode
init_img_path = Path(__file__).parent / "init_img.png" init_img_path = Path(__file__).parent / "init_img.png"
with open(init_img_path.absolute(), "rb") as fh: with open(init_img_path.absolute(), "rb") as fh:
init_img_enc = b64encode(fh.read()).decode("utf-8") init_img_enc = b64encode(fh.read()).decode("utf-8")
# generate init img string
init_img_src = f"data:image/png;base64, {init_img_enc}"
image_element = dmc.Center( image_element = dmc.Center(
html.Img( html.Img(
@@ -14,6 +16,6 @@ image_element = dmc.Center(
"width": "100%", "width": "100%",
}, },
id="image-container", id="image-container",
src=f"data:image/png;base64, {init_img_enc}" src=init_img_src
) )
) )
+1
View File
@@ -5,6 +5,7 @@ from .labels import labels_element
next_button = dmc.Button( next_button = dmc.Button(
"next".title(), "next".title(),
id="next-button", id="next-button",
n_clicks=0,
fullWidth=True, fullWidth=True,
color="lime", color="lime",
radius="sm", radius="sm",
+40 -23
View File
@@ -2,7 +2,9 @@ from dash import html, dcc
import dash_mantine_components as dmc import dash_mantine_components as dmc
from typing import List from typing import List
from src.model_experiential import ExperientialModelOutput from src.model_experiential import (
VisualSyntaxModelOutput
)
from src.model_interpersonal import ( from src.model_interpersonal import (
ContactModelOutput, ContactModelOutput,
AngleModelOutput, AngleModelOutput,
@@ -26,46 +28,53 @@ def generate_option_labels(model) -> List[str]:
] ]
return labels return labels
def generate_experiential_options_map(): def generate_visual_syntax_options_map():
"""Generate map of titles and options for experiential labels.""" """Generate map of titles and options for visual syntax labels."""
options_map = {} options_map = {}
# add experiential labels # add experiential labels
options_map["experiential".title()] = generate_option_labels(ExperientialModelOutput) options_map["visual syntax"] = generate_option_labels(VisualSyntaxModelOutput)
return options_map return options_map
def generate_interpersonal_options_map(): def generate_interpersonal_options_map():
"""Generate map of titles and options for interpersonal labels.""" """Generate map of titles and options for interpersonal labels."""
options_map = {} options_map = {}
# add interpersonal labels # add interpersonal labels
options_map["contact".title()] = generate_option_labels(ContactModelOutput) options_map["contact"] = generate_option_labels(ContactModelOutput)
options_map["angle".title()] = generate_option_labels(AngleModelOutput) options_map["angle"] = generate_option_labels(AngleModelOutput)
options_map["point of view".title()] = generate_option_labels(PointOfViewModelOutput) options_map["point of view"] = generate_option_labels(PointOfViewModelOutput)
options_map["distance".title()] = generate_option_labels(DistanceModelOutput) options_map["distance"] = generate_option_labels(DistanceModelOutput)
options_map["modality lighting".title()] = generate_option_labels(ModalityLightingModelOutput) options_map["modality lighting"] = generate_option_labels(ModalityLightingModelOutput)
options_map["modality color".title()] = generate_option_labels(ModalityColorModelOutput) options_map["modality color"] = generate_option_labels(ModalityColorModelOutput)
options_map["modality depth".title()] = generate_option_labels(ModalityDepthModelOutput) options_map["modality depth"] = generate_option_labels(ModalityDepthModelOutput)
return options_map return options_map
def generate_textual_options_map(): def generate_textual_options_map():
"""Generate map of titles and options for textual labels.""" """Generate map of titles and options for textual labels."""
options_map = {} options_map = {}
# add textual labels # add textual labels
options_map["information value".title()] = generate_option_labels(InformationValueModelOutput) options_map["information value"] = generate_option_labels(InformationValueModelOutput)
options_map["framing".title()] = generate_option_labels(FramingModelOutput) options_map["framing"] = generate_option_labels(FramingModelOutput)
options_map["salience".title()] = generate_option_labels(SalienceModelOutput) options_map["salience"] = generate_option_labels(SalienceModelOutput)
return options_map return options_map
# prepare experiential container # prepare experiential container
experiential_map = generate_experiential_options_map() experiential_map = generate_visual_syntax_options_map()
experiential_container = dmc.Col( experiential_container = dmc.Col(
children=[ children=[
dmc.Container([ html.H4("experiential".title()),
html.H4(list(experiential_map.keys())[0]),
html.B("visual syntax".title()),
dcc.RadioItems(options=list(experiential_map.values())[0]),
])
], span=5 ], span=5
) )
for title, options in experiential_map.items():
id_dict = {"type": "annotation", "index": title.replace('_', '-')}
experiential_container.children.append(
dmc.Container([
html.B(title.title()),
dcc.RadioItems(
options=options,
id=id_dict,
),
])
)
# prepare interpersonal container # prepare interpersonal container
interpersonal_map = generate_interpersonal_options_map() interpersonal_map = generate_interpersonal_options_map()
interpersonal_container = dmc.Col( interpersonal_container = dmc.Col(
@@ -74,10 +83,14 @@ interpersonal_container = dmc.Col(
], span=3 ], span=3
) )
for title, options in interpersonal_map.items(): for title, options in interpersonal_map.items():
id_dict = {"type": "annotation", "index": title.replace('_', '-')}
interpersonal_container.children.append( interpersonal_container.children.append(
dmc.Container([ dmc.Container([
html.B(title), html.B(title.title()),
dcc.RadioItems(options) dcc.RadioItems(
options=options,
id=id_dict,
),
]) ])
) )
# prepare textual container # prepare textual container
@@ -88,10 +101,14 @@ textual_container = dmc.Col(
], span=4 ], span=4
) )
for title, options in textual_map.items(): for title, options in textual_map.items():
id_dict = {"type": "annotation", "index": title.replace('_', '-')}
textual_container.children.append( textual_container.children.append(
dmc.Container([ dmc.Container([
html.B(title), html.B(title),
dcc.RadioItems(options) dcc.RadioItems(
options=options,
id=id_dict,
),
]) ])
) )
+9 -2
View File
@@ -1,9 +1,16 @@
from dash import html, dcc from dash import html, dcc
import logging
import os
storage_type = "session"
if "ENV" in os.environ and os.getenv("ENV") == "DEV":
storage_type = "memory"
logging.info(f"ENV=DEV -> dcc.Stores changed to storage_type={storage_type}")
stores_element = html.Div( stores_element = html.Div(
children=[ children=[
dcc.Store(id="alert-message", storage_type="session"), dcc.Store(id="alert-message", storage_type=storage_type, data=""),
dcc.Store(id="visual-communication-name", storage_type="session"), dcc.Store(id="vis-com-name", storage_type=storage_type, data=""),
] ]
) )
+17
View File
@@ -0,0 +1,17 @@
from pathlib import Path
from src.database import VisualCommunication
if __name__ == "__main__":
# get list of image paths
test_dir = Path(__file__).parent
img_dir = test_dir / "imgs"
img_path_list = [path for path in img_dir.glob("*.jpeg") if path.is_file()]
print(img_path_list)
# instantiate data object
vis_com_list = [VisualCommunication.from_file(path) for path in img_path_list]
# generate random predictions
[vis_com.generate_random_prediction() for vis_com in vis_com_list]
for vis_com in vis_com_list:
print(vis_com)
+1 -1
View File
@@ -18,4 +18,4 @@ if __name__ == "__main__":
print(client.server_info()) print(client.server_info())
# get visual communication # get visual communication
vis_com = get_visual_communication(collection) vis_com = get_visual_communication(collection)
print(vis_com.image) print(vis_com)
@@ -0,0 +1,13 @@
from src.database import ModelOutputs
if __name__ == "__main__":
# instantiate data object
annotation = {
}
vis_com_list = [ModelOutputs.from_annotation(path) for path in img_path_list]
# generate random predictions
[vis_com.generate_random_prediction() for vis_com in vis_com_list]
for vis_com in vis_com_list:
print(vis_com)
+44
View File
@@ -0,0 +1,44 @@
from pathlib import Path
from dotenv import load_dotenv
import os
import logging
from src.database import (
VisualCommunication,
connect,
upsert_predictions
)
if __name__ == "__main__":
# setup logging
fmt = (
'%(asctime)s | '
'%(levelname)s | '
'%(filename)s | '
'%(funcName)s | '
'%(message)s'
)
datefmt = '%Y-%m-%d %H:%M:%S'
logging.basicConfig(format=fmt, datefmt=datefmt, level=logging.INFO)
# get list of image paths
test_dir = Path(__file__).parent
img_dir = test_dir / "imgs"
img_path_list = [path for path in img_dir.glob("*.jpeg") if path.is_file()]
# instantiate data object
vis_com_list = [VisualCommunication.from_file(path) for path in img_path_list]
# generate random predictions
[vis_com.generate_random_prediction() for vis_com in vis_com_list]
# prepare env vars
env_path = test_dir.parent / "local.env"
assert env_path.exists()
load_dotenv(env_path)
os.environ["MONGO_HOST"] = "localhost"
# connect to database
collection, db, client = connect()
# upload visual communication
for vis_com in vis_com_list:
upsert_predictions(
collection=collection,
vis_com_name=vis_com.name,
predictions=vis_com.prediction,
)