add_model_resnet18 #31

Merged
brian merged 47 commits from add_model_resnet18 into main 2024-04-03 21:04:23 +02:00
63 changed files with 1567 additions and 421 deletions
+2 -1
View File
@@ -57,8 +57,9 @@ RUN mkdir -p /home/app && \
# add code while changing ownership
WORKDIR $APP_HOME
COPY --chown=app:app ./src ./src
COPY --chown=app:app ./core ./core
# change to the app user
USER app
ENTRYPOINT [ "gunicorn", "src.main:server", "-b", "0.0.0.0:8050" ]
ENTRYPOINT [ "gunicorn", "src.main:server", "-b", "0.0.0.0:8050" ]
+13
View File
@@ -0,0 +1,13 @@
"""Database module content."""
from __future__ import annotations
from .classes import Dataset
from .classes import NoDocumentFoundException
from .classes import VisualCommunication
from .utils import connect
from .utils.count_documents import count_documents
from .utils.get_visual_communication import get_visual_communication
from .utils.list_names import list_names
from .utils.upsert_annotation import upsert_annotation
from .utils.upsert_prediction import upsert_prediction
from .utils.upsert_visual_communication import upsert_visual_communication
+6
View File
@@ -0,0 +1,6 @@
"""Database classes module content."""
from __future__ import annotations
from .dataset import Dataset
from .exceptions import NoDocumentFoundException
from .visual_communication import VisualCommunication
+71
View File
@@ -0,0 +1,71 @@
"""Definition of database Dataset class."""
from __future__ import annotations
import logging
import random
from datetime import datetime
from datetime import UTC
from pydantic import BaseModel
from pydantic import Field
from pymongo.collection import Collection
class Dataset(BaseModel):
"""Database Dataset model."""
create_time: datetime = Field(default_factory=lambda: datetime.now(UTC))
train_names: list[str]
test_names: list[str]
validation_names: list[str]
@classmethod
def fraction_map(cls) -> dict[str, float]:
"""Dict with train, test and validation fractions."""
# define map
split_map = {
'train': 0.7,
'test': 0.2,
'validation': 0.1,
}
# sanity check
assert sum(split_map.values()) == 1.0
return split_map
@classmethod
def new_from_name_list(
cls,
name_list: list[str],
) -> Dataset:
"""Generate new dataset from list of filenames."""
# calculate split fractions
fraction_map = cls.fraction_map()
num_total = len(name_list)
num_validation = round(num_total * fraction_map['validation'])
num_test = round(num_total * fraction_map['test'])
# split data
validation_name_list = random.choices(name_list, k=num_validation)
name_list = [
name for name in name_list if name not in validation_name_list
]
test_name_list = random.choices(name_list, k=num_test)
train_name_list = [
name for name in name_list if name not in test_name_list
]
# instantiate object
dataset = Dataset(
train_names=train_name_list,
test_names=test_name_list,
validation_names=validation_name_list,
)
logging.debug('finished')
return dataset
def save(
self,
collection: Collection,
) -> None:
"""Save dataset to database."""
res = collection.insert_one(
document=self.model_dump(),
)
logging.debug('inserted document: %s', res)
+6
View File
@@ -0,0 +1,6 @@
"""Definition of database exception."""
from __future__ import annotations
class NoDocumentFoundException(Exception):
"""Database exception for when no documents are found."""
+85
View File
@@ -0,0 +1,85 @@
"""Definition of VisualCommunication model."""
from __future__ import annotations
import logging
from base64 import b64decode
from base64 import b64encode
from io import BytesIO
from pathlib import Path
from PIL import Image
from pydantic import BaseModel
from pydantic import field_serializer
from pydantic import field_validator
from core.dto import ModelData
class VisualCommunication(BaseModel):
"""Visual communication model."""
name: str
image: Image.Image
annotation: ModelData | None = None
prediction: ModelData | None = None
class Config:
"""BaseModel configuration."""
arbitrary_types_allowed = True
@classmethod
def classname(cls) -> str:
"""Return classname."""
return cls.__name__
@classmethod
def from_file(cls, path: Path) -> VisualCommunication:
"""Instantiate from file."""
name = path.stem
image = Image.open(path)
image.load()
return VisualCommunication(name=name, image=image)
@classmethod
def decode_image(cls, content: str) -> Image.Image:
"""Extract image from webencoded content."""
_, content_data = content.split(',')
return Image.open(BytesIO(b64decode(content_data)))
@field_serializer('image')
@classmethod
def serialize_image(cls, image: Image.Image) -> bytes: # type: ignore
"""Convert image to bytes for storage in database."""
buffer = BytesIO()
image.save(buffer, format='JPEG')
return buffer.getvalue()
@field_validator('image', mode='before')
@classmethod
def convert_to_image(
cls,
image: Image.Image | BytesIO | bytes,
) -> Image.Image:
"""Convert bytes input from database into image."""
if isinstance(image, bytes):
image = BytesIO(image)
if isinstance(image, BytesIO):
image = Image.open(image)
return image
def __repr__(self) -> str:
return f"{self.classname()}(name='{self.name}')"
def webencoded_image(self) -> str:
"""Convert image to be displayed on webpage."""
# convert images to bytes string
buffer = BytesIO()
self.image.save(buffer, format='png')
img_enc = b64encode(buffer.getvalue()).decode('utf-8')
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 = ModelData.from_random()
+4
View File
@@ -0,0 +1,4 @@
"""Database utils module content."""
from __future__ import annotations
from .connect import connect
+33
View File
@@ -0,0 +1,33 @@
"""
Definition of function to connect to database
using environment variables.
"""
from __future__ import annotations
import logging
import os
from dotenv import load_dotenv
from pymongo import MongoClient
def connect():
"""Connect to MongoDB using env vars."""
# load env vars
load_dotenv()
necessary_env_vars = [
'MONGO_HOST',
'MONGO_DB',
'MONGO_COLLECTION',
]
for env_var in necessary_env_vars:
assert env_var in os.environ, f"{env_var} not found"
# connect to database
client = MongoClient(os.getenv('MONGO_HOST'))
db = client[os.getenv('MONGO_DB')]
# extract collection
collection = db[os.getenv('MONGO_COLLECTION')]
# set unique index on "name"
collection.create_index('name', unique=True)
logging.debug('finished')
return collection, db, client
+21
View File
@@ -0,0 +1,21 @@
"""Definition of function to count documents in database."""
from __future__ import annotations
from pymongo.collection import Collection
def count_documents(
collection: Collection,
only_with_annotation: bool = False,
) -> int:
"""
Get the total number of documents
in database that matches the filters.
"""
assert isinstance(collection, Collection)
assert isinstance(only_with_annotation, bool)
# build query
query = {}
if only_with_annotation:
query['annotation'] = {'$ne': None}
return collection.count_documents(filter=query)
+39
View File
@@ -0,0 +1,39 @@
"""Definition of function to get visual communication from database."""
from __future__ import annotations
import logging
from pymongo.collection import Collection
from core.database import NoDocumentFoundException
from core.database import VisualCommunication
def get_visual_communication(
collection: Collection,
with_annotation: bool = False,
) -> VisualCommunication:
"""Get a random visual communication from the database."""
query = {}
if with_annotation:
query['annotation'] = {'$ne': None}
else:
query['annotation'] = {'$eq': None}
data = collection.aggregate(
pipeline=[
{
'$match': query, # find using filters
},
{
'$sample': {
'size': 1, # get one random
},
},
],
)
data_list = list(data) # read data from cursor object
if len(data_list) == 0:
raise NoDocumentFoundException()
vis_com = VisualCommunication.model_validate(data_list[0])
logging.debug('finished')
return vis_com
+31
View File
@@ -0,0 +1,31 @@
"""
Definition of function to list names
of all visual communication documents in database.
"""
from __future__ import annotations
from pymongo.collection import Collection
def list_names(
collection: Collection,
only_with_annotation: bool = True,
) -> list[str]:
"""List the names of entries that match the filters."""
assert isinstance(collection, Collection)
assert isinstance(only_with_annotation, bool)
# build query
query = {}
if only_with_annotation:
query['annotation'] = {'$ne': None}
# execute query
res_list = collection.find(
filter=query,
projection={
'_id': False,
'name': True,
},
)
# extract information
name_list = [elem['name'] for elem in res_list]
return name_list
+30
View File
@@ -0,0 +1,30 @@
from __future__ import annotations
import logging
from pymongo.collection import Collection
from core.dto import ModelData
def upsert_annotation(
collection: Collection,
vis_com_name: str,
annotations: ModelData,
) -> 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')
+30
View File
@@ -0,0 +1,30 @@
from __future__ import annotations
import logging
from pymongo.collection import Collection
from core.dto import ModelData
def upsert_prediction(
collection: Collection,
vis_com_name: str,
predictions: ModelData,
) -> 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')
+23
View File
@@ -0,0 +1,23 @@
from __future__ import annotations
from pymongo.collection import Collection
from core.database import VisualCommunication
def upsert_visual_communication(
collection: Collection,
visual_communication_list: list[VisualCommunication],
) -> bool:
"""
Upsert VisualCommunication object in the database.
Returns bool stating success.
"""
response = collection.insert_many(
[
vis_com.model_dump()
for vis_com
in visual_communication_list
],
)
return response.acknowledged
+15
View File
@@ -0,0 +1,15 @@
"""Data transfer objects module content."""
from __future__ import annotations
from .angle import AngleData
from .contact import ContactData
from .distance import DistanceData
from .framing import FramingData
from .information_value import InformationValueData
from .modality_color import ModalityColorData
from .modality_depth import ModalityDepthData
from .modality_lighting import ModalityLightingData
from .model_data import ModelData
from .point_of_view import PointOfViewData
from .salience import SalienceData
from .visual_syntax import VisualSyntaxData
+11
View File
@@ -0,0 +1,11 @@
"""Definition of Angle data model."""
from __future__ import annotations
from .data_model import DataModel
class AngleData(DataModel):
"""Angle data model."""
high: float
eye_level: float
low: float
+11
View File
@@ -0,0 +1,11 @@
"""Definition of ContactData data model."""
from __future__ import annotations
from .data_model import DataModel
class ContactData(DataModel):
"""ContactData data model."""
offer: float
demand: float
+67
View File
@@ -0,0 +1,67 @@
"""Definition of DataModel base class."""
from __future__ import annotations
import random
from pydantic import BaseModel
from pydantic import ValidationError
class DataModel(BaseModel):
"""DataModel base class."""
@classmethod
def classname(cls) -> str:
"""Return classname."""
return cls.__name__
@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):
"""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)
@classmethod
def from_list(cls, data_list: list[float]):
"""Instantiate from list of values."""
kwargs = {key: val for key, val in zip(cls.list_fields(), data_list)}
return cls(**kwargs)
def __repr__(self) -> str:
model_dict = self.model_dump()
model_repr_str = f"{self.classname()}("
model_repr_str += ', '.join([
f"{field}={value:.3f}"
for field, value
in model_dict.items()
])
model_repr_str += ')'
return model_repr_str
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())
+12
View File
@@ -0,0 +1,12 @@
"""Definition of DistanceData data model."""
from __future__ import annotations
from .data_model import DataModel
class DistanceData(DataModel):
"""DistanceData data model."""
long: float
medium: float
close: float
+13
View File
@@ -0,0 +1,13 @@
"""Definition of FramingData data model."""
from __future__ import annotations
from .data_model import DataModel
class FramingData(DataModel):
"""FramingData data model."""
frame_lines: float
empty_space: float
colour_contrast: float
form_contrast: float
+12
View File
@@ -0,0 +1,12 @@
"""Definition of InformationValueData data model."""
from __future__ import annotations
from .data_model import DataModel
class InformationValueData(DataModel):
"""InformationValueData data model."""
given_new: float
ideal_real: float
central_marginal: float
+12
View File
@@ -0,0 +1,12 @@
"""Definition of ModalityColorData data model."""
from __future__ import annotations
from .data_model import DataModel
class ModalityColorData(DataModel):
"""ModalityColorData data model."""
high: float
medium: float
low: float
+12
View File
@@ -0,0 +1,12 @@
"""Definition of ModalityDepthData data model."""
from __future__ import annotations
from .data_model import DataModel
class ModalityDepthData(DataModel):
"""ModalityDepthData data model."""
high: float
medium: float
low: float
+12
View File
@@ -0,0 +1,12 @@
"""Definition of ModalityLightingData data model."""
from __future__ import annotations
from .data_model import DataModel
class ModalityLightingData(DataModel):
"""ModalityLightingData data model."""
high: float
medium: float
low: float
+82
View File
@@ -0,0 +1,82 @@
"""Definition of ModelData data model."""
from __future__ import annotations
from .angle import AngleData
from .contact import ContactData
from .data_model import DataModel
from .distance import DistanceData
from .framing import FramingData
from .information_value import InformationValueData
from .modality_color import ModalityColorData
from .modality_depth import ModalityDepthData
from .modality_lighting import ModalityLightingData
from .point_of_view import PointOfViewData
from .salience import SalienceData
from .visual_syntax import VisualSyntaxData
class ModelData(DataModel):
"""ModelData model for data IO with combined ML model."""
visual_syntax: VisualSyntaxData
contact: ContactData
angle: AngleData
point_of_view: PointOfViewData
distance: DistanceData
modality_lighting: ModalityLightingData
modality_color: ModalityColorData
modality_depth: ModalityDepthData
information_value: InformationValueData
framing: FramingData
salience: SalienceData
@classmethod
def from_random(cls) -> ModelData:
"""Instantiate with random numbers."""
kwargs = {
field: field_info.annotation.from_random() # type: ignore
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,
) -> ModelData:
"""Instantiate from annotation."""
kwargs = {
'visual_syntax': VisualSyntaxData
.from_choice(visual_syntax),
'contact': ContactData
.from_choice(contact),
'angle': AngleData
.from_choice(angle),
'point_of_view': PointOfViewData
.from_choice(point_of_view),
'distance': DistanceData
.from_choice(distance),
'modality_lighting': ModalityLightingData
.from_choice(modality_lighting),
'modality_color': ModalityColorData
.from_choice(modality_color),
'modality_depth': ModalityDepthData
.from_choice(modality_depth),
'information_value': InformationValueData
.from_choice(information_value),
'framing': FramingData
.from_choice(framing),
'salience': SalienceData
.from_choice(salience),
}
return cls(**kwargs)
+11
View File
@@ -0,0 +1,11 @@
"""Definition of PointOfViewData data model."""
from __future__ import annotations
from .data_model import DataModel
class PointOfViewData(DataModel):
"""PointOfViewData data model."""
frontal: float
oblique: float
+14
View File
@@ -0,0 +1,14 @@
"""Definition of SalienceData data model."""
from __future__ import annotations
from .data_model import DataModel
class SalienceData(DataModel):
"""SalienceData data model."""
size: float
colour: float
tone: float
form: float
positioning: float
+27
View File
@@ -0,0 +1,27 @@
"""Definition of VisualSyntaxData data model."""
from __future__ import annotations
from .data_model import DataModel
class VisualSyntaxData(DataModel):
"""VisualSyntaxData data model."""
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
+17 -17
View File
@@ -1,21 +1,21 @@
version: '3.7'
services:
app:
image: visual_critical_discourse_analysis:dev
container_name: visual_critical_discourse_analysis
build:
context: .
dockerfile: Dockerfile
env_file:
- local.env
environment:
- ENV=DEV
ports:
- 8050:8050
networks:
- backend
depends_on:
- mongo
# app:
# image: visual_critical_discourse_analysis:dev
# container_name: visual_critical_discourse_analysis
# build:
# context: .
# dockerfile: Dockerfile
# env_file:
# - local.env
# environment:
# - ENV=DEV
# ports:
# - 8050:8050
# networks:
# - backend
# depends_on:
# - mongo
mongo:
image: mongo:latest
container_name: mongo
@@ -41,4 +41,4 @@ services:
networks:
backend:
driver: bridge
driver: bridge
+146
View File
@@ -0,0 +1,146 @@
from __future__ import annotations
import random
from io import BytesIO
from torch import Tensor
from torch.utils.data import Dataset
from torchvision.io import read_image
from torchvision.transforms import ColorJitter
from torchvision.transforms import InterpolationMode
from torchvision.transforms import Normalize
from torchvision.transforms.functional import hflip
from torchvision.transforms.functional import pad
from torchvision.transforms.functional import resize
from torchvision.transforms.functional import rotate
from core.database import connect
# 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):
def __init__(
self,
data_name_list: list[str],
do_augment: bool = False,
random_annotations: bool = False,
normalize_mean: list[float] = RESNET_NORMALIZE_MEAN,
normalize_std: list[float] = RESNET_NORMALIZE_STD,
):
super().__init__()
self.data_name_list = data_name_list
self.do_augment = do_augment
self.random_annotations = random_annotations
self.normalize_mean = normalize_mean
self.normalize_std = normalize_std
# prepare augmentation functions
self.normalize = Normalize(
mean=normalize_mean,
std=normalize_std,
)
self.color_jitter = ColorJitter(
brightness=1e-1,
contrast=8e-2,
saturation=8e-2,
)
# connect to database
collection, _, _ = connect()
self.collection = collection
def __len__(self):
return len(self.data_name_list)
def __getitem__(self, idx):
# get image from database
name = self.data_name_list[idx]
query = {
'name': name,
}
projection = {
'_id': False,
'image': True,
}
img_bytes = self.collection.find_one(
filter=query,
projection=projection,
)
img = self.load_image(img_bytes)
if self.do_augment:
img = self.augment(img)
return img
def load_image(
self,
data: bytes,
) -> Tensor:
"""Load images tensor from bytes."""
assert isinstance(data, bytes)
img = read_image(BytesIO(data))
img /= 255 # normalize 8-bit image
img = self.square_pad(img)
img = resize(
img=img,
size=(512, 512),
interpolation=InterpolationMode.BICUBIC,
)
return img
@staticmethod
def square_pad(
img: Tensor,
) -> Tensor:
"""
Pads image to a square with side length
equal to the largest side of the input image.
"""
assert isinstance(img, Tensor)
# B, nc, w, h = img.shape
h = img.shape[-2]
w = img.shape[-1]
if h == w:
return img
max_wh = max([h, w])
hp = int((max_wh - w) / 2)
vp = int((max_wh - h) / 2)
padding = (hp, vp, hp, vp)
return pad(img, padding, 0, 'constant')
def augment(
self,
img: Tensor,
) -> Tensor:
"""
Augment image with random horizontal flips,
rotations and color jitter.
"""
assert isinstance(img, Tensor)
# left-right flip
if random.random() >= 0.5:
img = hflip(img)
# rotation
rnd = random.random()
if rnd < 0.25:
img = rotate(img, angle=90)
if rnd < 0.5:
img = rotate(img, angle=180)
if rnd < 0.75:
img = rotate(img, angle=270)
# color jitter
img = self.color_jitter(img)
return img
def reverse_normalise(
self,
img: Tensor,
) -> Tensor:
"""
Reverse normalization to get an image
that can be interpreted by humans.
"""
assert isinstance(img, Tensor)
img *= Tensor(self.normalize_std).reshape((3, 1, 1))
img += Tensor(self.normalize_mean).reshape((3, 1, 1))
return img
+3
View File
@@ -0,0 +1,3 @@
from __future__ import annotations
from .visual_communication import VisualCommunicationModel
+8
View File
@@ -0,0 +1,8 @@
from __future__ import annotations
from .fully_connected import FullyConnectedModel
class AngleTail(FullyConnectedModel):
def __init__(self):
super().__init__(num_out_features=3)
+8
View File
@@ -0,0 +1,8 @@
from __future__ import annotations
from .fully_connected import FullyConnectedModel
class ContactTail(FullyConnectedModel):
def __init__(self):
super().__init__(num_out_features=2)
+8
View File
@@ -0,0 +1,8 @@
from __future__ import annotations
from .fully_connected import FullyConnectedModel
class DistanceTail(FullyConnectedModel):
def __init__(self):
super().__init__(num_out_features=3)
+8
View File
@@ -0,0 +1,8 @@
from __future__ import annotations
from .fully_connected import FullyConnectedModel
class FramingTail(FullyConnectedModel):
def __init__(self):
super().__init__(num_out_features=4)
+26
View File
@@ -0,0 +1,26 @@
from __future__ import annotations
import torch.nn as nn
class FullyConnectedModel(nn.Module):
def __init__(self, num_out_features: int):
super().__init__()
# define layers
self.fc1 = nn.Linear(in_features=16*16*512, out_features=512)
self.af1 = nn.ReLU()
self.fc2 = nn.Linear(in_features=512, out_features=128)
self.af2 = nn.ReLU()
self.fc3 = nn.Linear(in_features=128, out_features=32)
self.af3 = nn.ReLU()
self.fc4 = nn.Linear(in_features=32, out_features=num_out_features)
def forward(self, x):
x = self.fc1(x)
x = self.af1(x)
x = self.fc2(x)
x = self.af2(x)
x = self.fc3(x)
x = self.af3(x)
x = self.fc4(x)
return x
+8
View File
@@ -0,0 +1,8 @@
from __future__ import annotations
from .fully_connected import FullyConnectedModel
class InformationValueTail(FullyConnectedModel):
def __init__(self):
super().__init__(num_out_features=3)
+8
View File
@@ -0,0 +1,8 @@
from __future__ import annotations
from .fully_connected import FullyConnectedModel
class ModalityColorTail(FullyConnectedModel):
def __init__(self):
super().__init__(num_out_features=3)
+8
View File
@@ -0,0 +1,8 @@
from __future__ import annotations
from .fully_connected import FullyConnectedModel
class ModalityDepthTail(FullyConnectedModel):
def __init__(self):
super().__init__(num_out_features=3)
+8
View File
@@ -0,0 +1,8 @@
from __future__ import annotations
from .fully_connected import FullyConnectedModel
class ModalityLightingTail(FullyConnectedModel):
def __init__(self):
super().__init__(num_out_features=3)
+8
View File
@@ -0,0 +1,8 @@
from __future__ import annotations
from .fully_connected import FullyConnectedModel
class PointOfViewTail(FullyConnectedModel):
def __init__(self):
super().__init__(num_out_features=2)
+37
View File
@@ -0,0 +1,37 @@
from __future__ import annotations
import torch.nn as nn
import torchvision
class ResNet18Head(nn.Module):
def __init__(self):
super().__init__()
# copy out parts from ResNet18 with weights
resnet18 = torchvision.models.resnet18(
weights=torchvision.models.ResNet18_Weights.IMAGENET1K_V1,
)
# 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
+8
View File
@@ -0,0 +1,8 @@
from __future__ import annotations
from .fully_connected import FullyConnectedModel
class SalienceTail(FullyConnectedModel):
def __init__(self):
super().__init__(num_out_features=5)
+118
View File
@@ -0,0 +1,118 @@
from __future__ import annotations
import torch.nn as 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
from core.dto import AngleData
from core.dto import ContactData
from core.dto import DistanceData
from core.dto import FramingData
from core.dto import InformationValueData
from core.dto import ModalityColorData
from core.dto import ModalityDepthData
from core.dto import ModalityLightingData
from core.dto import PointOfViewData
from core.dto import SalienceData
from core.dto import VisualSyntaxData
class VisualCommunicationModel(nn.Module):
def __init__(self):
super().__init__()
# store other models
self.resnet_head = ResNet18Head()
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):
# generate visual representation
vis_rep = self.resnet_head(x)
# prepare result map
results = {}
# predict visual syntax
results['visual_syntax'] = VisualSyntaxData.from_list(
self.visual_syntax_tail(
vis_rep,
).cpu(),
)
# predict contact
results['contact'] = ContactData.from_list(
self.contact_tail(
vis_rep,
).cpu(),
)
# predict angle
results['angle'] = AngleData.from_list(
self.angle_tail(
vis_rep,
).cpu(),
)
# predict point of view
results['point_of_view'] = PointOfViewData.from_list(
self.point_of_view_tail(
vis_rep,
).cpu(),
)
# predict distance
results['distance'] = DistanceData.from_list(
self.distance_tail(
vis_rep,
).cpu(),
)
# predict modality lighting
results['modality_lighting'] = ModalityLightingData.from_list(
self.modality_lighting_tail(
vis_rep,
).cpu(),
)
# predict modality color
results['modality_color'] = ModalityColorData.from_list(
self.modality_color_tail(
vis_rep,
).cpu(),
)
# predict modality depth
results['modality_depth'] = ModalityDepthData.from_list(
self.modality_depth_tail(
vis_rep,
).cpu(),
)
# predict information value
results['information_value'] = InformationValueData.from_list(
self.information_value_tail(
vis_rep,
).cpu(),
)
# predict framing
results['framing'] = FramingData.from_list(
self.framing_tail(
vis_rep,
).cpu(),
)
# predict salience
results['salience'] = SalienceData.from_list(
self.salience_tail(
vis_rep,
).cpu(),
)
return results
+8
View File
@@ -0,0 +1,8 @@
from __future__ import annotations
from .fully_connected import FullyConnectedModel
class VisualSyntaxTail(FullyConnectedModel):
def __init__(self):
super().__init__(num_out_features=18)
+6
View File
@@ -0,0 +1,6 @@
from __future__ import annotations
from dataloader import VCDADataset # noqa: F401
from loss_fn import setup_criterion # noqa: F401
from model import ResNet18Head # noqa: F401
Generated
+342 -2
View File
@@ -1,4 +1,4 @@
# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand.
# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand.
[[package]]
name = "annotated-types"
@@ -381,6 +381,22 @@ idna = ["idna (>=3.6)"]
trio = ["trio (>=0.23)"]
wmi = ["wmi (>=1.5.1)"]
[[package]]
name = "filelock"
version = "3.13.1"
description = "A platform independent file lock."
optional = false
python-versions = ">=3.8"
files = [
{file = "filelock-3.13.1-py3-none-any.whl", hash = "sha256:57dbda9b35157b05fb3e58ee91448612eb674172fab98ee235ccb0b5bee19a1c"},
{file = "filelock-3.13.1.tar.gz", hash = "sha256:521f5f56c50f8426f5e03ad3b281b490a87ef15bc6c526f168290f0c7148d44e"},
]
[package.extras]
docs = ["furo (>=2023.9.10)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.24)"]
testing = ["covdefaults (>=2.3)", "coverage (>=7.3.2)", "diff-cover (>=8)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)", "pytest-timeout (>=2.2)"]
typing = ["typing-extensions (>=4.8)"]
[[package]]
name = "flake8"
version = "7.0.0"
@@ -419,6 +435,41 @@ Werkzeug = ">=3.0.0"
async = ["asgiref (>=3.2)"]
dotenv = ["python-dotenv"]
[[package]]
name = "fsspec"
version = "2024.2.0"
description = "File-system specification"
optional = false
python-versions = ">=3.8"
files = [
{file = "fsspec-2024.2.0-py3-none-any.whl", hash = "sha256:817f969556fa5916bc682e02ca2045f96ff7f586d45110fcb76022063ad2c7d8"},
{file = "fsspec-2024.2.0.tar.gz", hash = "sha256:b6ad1a679f760dda52b1168c859d01b7b80648ea6f7f7c7f5a8a91dc3f3ecb84"},
]
[package.extras]
abfs = ["adlfs"]
adl = ["adlfs"]
arrow = ["pyarrow (>=1)"]
dask = ["dask", "distributed"]
devel = ["pytest", "pytest-cov"]
dropbox = ["dropbox", "dropboxdrivefs", "requests"]
full = ["adlfs", "aiohttp (!=4.0.0a0,!=4.0.0a1)", "dask", "distributed", "dropbox", "dropboxdrivefs", "fusepy", "gcsfs", "libarchive-c", "ocifs", "panel", "paramiko", "pyarrow (>=1)", "pygit2", "requests", "s3fs", "smbprotocol", "tqdm"]
fuse = ["fusepy"]
gcs = ["gcsfs"]
git = ["pygit2"]
github = ["requests"]
gs = ["gcsfs"]
gui = ["panel"]
hdfs = ["pyarrow (>=1)"]
http = ["aiohttp (!=4.0.0a0,!=4.0.0a1)"]
libarchive = ["libarchive-c"]
oci = ["ocifs"]
s3 = ["s3fs"]
sftp = ["paramiko"]
smb = ["smbprotocol"]
ssh = ["paramiko"]
tqdm = ["tqdm"]
[[package]]
name = "gunicorn"
version = "21.2.0"
@@ -588,6 +639,23 @@ files = [
{file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"},
]
[[package]]
name = "mpmath"
version = "1.3.0"
description = "Python library for arbitrary-precision floating-point arithmetic"
optional = false
python-versions = "*"
files = [
{file = "mpmath-1.3.0-py3-none-any.whl", hash = "sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c"},
{file = "mpmath-1.3.0.tar.gz", hash = "sha256:7a28eb2a9774d00c7bc92411c19a89209d5da7c4c9a9e227be8330a23a25b91f"},
]
[package.extras]
develop = ["codecov", "pycodestyle", "pytest (>=4.6)", "pytest-cov", "wheel"]
docs = ["sphinx"]
gmpy = ["gmpy2 (>=2.1.0a4)"]
tests = ["pytest (>=4.6)"]
[[package]]
name = "mypy"
version = "1.8.0"
@@ -656,6 +724,24 @@ files = [
{file = "nest_asyncio-1.6.0.tar.gz", hash = "sha256:6f172d5449aca15afd6c646851f4e31e02c598d553a667e38cafa997cfec55fe"},
]
[[package]]
name = "networkx"
version = "3.2.1"
description = "Python package for creating and manipulating graphs and networks"
optional = false
python-versions = ">=3.9"
files = [
{file = "networkx-3.2.1-py3-none-any.whl", hash = "sha256:f18c69adc97877c42332c170849c96cefa91881c99a7cb3e95b7c659ebdc1ec2"},
{file = "networkx-3.2.1.tar.gz", hash = "sha256:9f1bb5cf3409bf324e0a722c20bdb4c20ee39bf1c30ce8ae499c8502b0b5e0c6"},
]
[package.extras]
default = ["matplotlib (>=3.5)", "numpy (>=1.22)", "pandas (>=1.4)", "scipy (>=1.9,!=1.11.0,!=1.11.1)"]
developer = ["changelist (==0.4)", "mypy (>=1.1)", "pre-commit (>=3.2)", "rtoml"]
doc = ["nb2plots (>=0.7)", "nbconvert (<7.9)", "numpydoc (>=1.6)", "pillow (>=9.4)", "pydata-sphinx-theme (>=0.14)", "sphinx (>=7)", "sphinx-gallery (>=0.14)", "texext (>=0.6.7)"]
extra = ["lxml (>=4.6)", "pydot (>=1.4.2)", "pygraphviz (>=1.11)", "sympy (>=1.10)"]
test = ["pytest (>=7.2)", "pytest-cov (>=4.0)"]
[[package]]
name = "numpy"
version = "1.26.4"
@@ -701,6 +787,147 @@ files = [
{file = "numpy-1.26.4.tar.gz", hash = "sha256:2a02aba9ed12e4ac4eb3ea9421c420301a0c6460d9830d74a9df87efa4912010"},
]
[[package]]
name = "nvidia-cublas-cu12"
version = "12.1.3.1"
description = "CUBLAS native runtime libraries"
optional = false
python-versions = ">=3"
files = [
{file = "nvidia_cublas_cu12-12.1.3.1-py3-none-manylinux1_x86_64.whl", hash = "sha256:ee53ccca76a6fc08fb9701aa95b6ceb242cdaab118c3bb152af4e579af792728"},
{file = "nvidia_cublas_cu12-12.1.3.1-py3-none-win_amd64.whl", hash = "sha256:2b964d60e8cf11b5e1073d179d85fa340c120e99b3067558f3cf98dd69d02906"},
]
[[package]]
name = "nvidia-cuda-cupti-cu12"
version = "12.1.105"
description = "CUDA profiling tools runtime libs."
optional = false
python-versions = ">=3"
files = [
{file = "nvidia_cuda_cupti_cu12-12.1.105-py3-none-manylinux1_x86_64.whl", hash = "sha256:e54fde3983165c624cb79254ae9818a456eb6e87a7fd4d56a2352c24ee542d7e"},
{file = "nvidia_cuda_cupti_cu12-12.1.105-py3-none-win_amd64.whl", hash = "sha256:bea8236d13a0ac7190bd2919c3e8e6ce1e402104276e6f9694479e48bb0eb2a4"},
]
[[package]]
name = "nvidia-cuda-nvrtc-cu12"
version = "12.1.105"
description = "NVRTC native runtime libraries"
optional = false
python-versions = ">=3"
files = [
{file = "nvidia_cuda_nvrtc_cu12-12.1.105-py3-none-manylinux1_x86_64.whl", hash = "sha256:339b385f50c309763ca65456ec75e17bbefcbbf2893f462cb8b90584cd27a1c2"},
{file = "nvidia_cuda_nvrtc_cu12-12.1.105-py3-none-win_amd64.whl", hash = "sha256:0a98a522d9ff138b96c010a65e145dc1b4850e9ecb75a0172371793752fd46ed"},
]
[[package]]
name = "nvidia-cuda-runtime-cu12"
version = "12.1.105"
description = "CUDA Runtime native Libraries"
optional = false
python-versions = ">=3"
files = [
{file = "nvidia_cuda_runtime_cu12-12.1.105-py3-none-manylinux1_x86_64.whl", hash = "sha256:6e258468ddf5796e25f1dc591a31029fa317d97a0a94ed93468fc86301d61e40"},
{file = "nvidia_cuda_runtime_cu12-12.1.105-py3-none-win_amd64.whl", hash = "sha256:dfb46ef84d73fababab44cf03e3b83f80700d27ca300e537f85f636fac474344"},
]
[[package]]
name = "nvidia-cudnn-cu12"
version = "8.9.2.26"
description = "cuDNN runtime libraries"
optional = false
python-versions = ">=3"
files = [
{file = "nvidia_cudnn_cu12-8.9.2.26-py3-none-manylinux1_x86_64.whl", hash = "sha256:5ccb288774fdfb07a7e7025ffec286971c06d8d7b4fb162525334616d7629ff9"},
]
[package.dependencies]
nvidia-cublas-cu12 = "*"
[[package]]
name = "nvidia-cufft-cu12"
version = "11.0.2.54"
description = "CUFFT native runtime libraries"
optional = false
python-versions = ">=3"
files = [
{file = "nvidia_cufft_cu12-11.0.2.54-py3-none-manylinux1_x86_64.whl", hash = "sha256:794e3948a1aa71fd817c3775866943936774d1c14e7628c74f6f7417224cdf56"},
{file = "nvidia_cufft_cu12-11.0.2.54-py3-none-win_amd64.whl", hash = "sha256:d9ac353f78ff89951da4af698f80870b1534ed69993f10a4cf1d96f21357e253"},
]
[[package]]
name = "nvidia-curand-cu12"
version = "10.3.2.106"
description = "CURAND native runtime libraries"
optional = false
python-versions = ">=3"
files = [
{file = "nvidia_curand_cu12-10.3.2.106-py3-none-manylinux1_x86_64.whl", hash = "sha256:9d264c5036dde4e64f1de8c50ae753237c12e0b1348738169cd0f8a536c0e1e0"},
{file = "nvidia_curand_cu12-10.3.2.106-py3-none-win_amd64.whl", hash = "sha256:75b6b0c574c0037839121317e17fd01f8a69fd2ef8e25853d826fec30bdba74a"},
]
[[package]]
name = "nvidia-cusolver-cu12"
version = "11.4.5.107"
description = "CUDA solver native runtime libraries"
optional = false
python-versions = ">=3"
files = [
{file = "nvidia_cusolver_cu12-11.4.5.107-py3-none-manylinux1_x86_64.whl", hash = "sha256:8a7ec542f0412294b15072fa7dab71d31334014a69f953004ea7a118206fe0dd"},
{file = "nvidia_cusolver_cu12-11.4.5.107-py3-none-win_amd64.whl", hash = "sha256:74e0c3a24c78612192a74fcd90dd117f1cf21dea4822e66d89e8ea80e3cd2da5"},
]
[package.dependencies]
nvidia-cublas-cu12 = "*"
nvidia-cusparse-cu12 = "*"
nvidia-nvjitlink-cu12 = "*"
[[package]]
name = "nvidia-cusparse-cu12"
version = "12.1.0.106"
description = "CUSPARSE native runtime libraries"
optional = false
python-versions = ">=3"
files = [
{file = "nvidia_cusparse_cu12-12.1.0.106-py3-none-manylinux1_x86_64.whl", hash = "sha256:f3b50f42cf363f86ab21f720998517a659a48131e8d538dc02f8768237bd884c"},
{file = "nvidia_cusparse_cu12-12.1.0.106-py3-none-win_amd64.whl", hash = "sha256:b798237e81b9719373e8fae8d4f091b70a0cf09d9d85c95a557e11df2d8e9a5a"},
]
[package.dependencies]
nvidia-nvjitlink-cu12 = "*"
[[package]]
name = "nvidia-nccl-cu12"
version = "2.19.3"
description = "NVIDIA Collective Communication Library (NCCL) Runtime"
optional = false
python-versions = ">=3"
files = [
{file = "nvidia_nccl_cu12-2.19.3-py3-none-manylinux1_x86_64.whl", hash = "sha256:a9734707a2c96443331c1e48c717024aa6678a0e2a4cb66b2c364d18cee6b48d"},
]
[[package]]
name = "nvidia-nvjitlink-cu12"
version = "12.4.99"
description = "Nvidia JIT LTO Library"
optional = false
python-versions = ">=3"
files = [
{file = "nvidia_nvjitlink_cu12-12.4.99-py3-none-manylinux2014_x86_64.whl", hash = "sha256:c6428836d20fe7e327191c175791d38570e10762edc588fb46749217cd444c74"},
{file = "nvidia_nvjitlink_cu12-12.4.99-py3-none-win_amd64.whl", hash = "sha256:991905ffa2144cb603d8ca7962d75c35334ae82bf92820b6ba78157277da1ad2"},
]
[[package]]
name = "nvidia-nvtx-cu12"
version = "12.1.105"
description = "NVIDIA Tools Extension"
optional = false
python-versions = ">=3"
files = [
{file = "nvidia_nvtx_cu12-12.1.105-py3-none-manylinux1_x86_64.whl", hash = "sha256:dc21cf308ca5691e7c04d962e213f8a4aa9bbfa23d95412f452254c2caeb09e5"},
{file = "nvidia_nvtx_cu12-12.1.105-py3-none-win_amd64.whl", hash = "sha256:65f4d98982b31b60026e0e6de73fbdfc09d08a96f4656dd3665ca616a11e1e82"},
]
[[package]]
name = "outcome"
version = "1.3.0.post0"
@@ -1320,6 +1547,20 @@ files = [
{file = "sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88"},
]
[[package]]
name = "sympy"
version = "1.12"
description = "Computer algebra system (CAS) in Python"
optional = false
python-versions = ">=3.8"
files = [
{file = "sympy-1.12-py3-none-any.whl", hash = "sha256:c3588cd4295d0c0f603d0f2ae780587e64e2efeedb3521e46b9bb1d08d184fa5"},
{file = "sympy-1.12.tar.gz", hash = "sha256:ebf595c8dac3e0fdc4152c51878b498396ec7f30e7a914d6071e674d49420fb8"},
]
[package.dependencies]
mpmath = ">=0.19"
[[package]]
name = "tenacity"
version = "8.2.3"
@@ -1334,6 +1575,105 @@ files = [
[package.extras]
doc = ["reno", "sphinx", "tornado (>=4.5)"]
[[package]]
name = "torch"
version = "2.2.1"
description = "Tensors and Dynamic neural networks in Python with strong GPU acceleration"
optional = false
python-versions = ">=3.8.0"
files = [
{file = "torch-2.2.1-cp310-cp310-manylinux1_x86_64.whl", hash = "sha256:8d3bad336dd2c93c6bcb3268e8e9876185bda50ebde325ef211fb565c7d15273"},
{file = "torch-2.2.1-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:5297f13370fdaca05959134b26a06a7f232ae254bf2e11a50eddec62525c9006"},
{file = "torch-2.2.1-cp310-cp310-win_amd64.whl", hash = "sha256:5f5dee8433798888ca1415055f5e3faf28a3bad660e4c29e1014acd3275ab11a"},
{file = "torch-2.2.1-cp310-none-macosx_10_9_x86_64.whl", hash = "sha256:b6d78338acabf1fb2e88bf4559d837d30230cf9c3e4337261f4d83200df1fcbe"},
{file = "torch-2.2.1-cp310-none-macosx_11_0_arm64.whl", hash = "sha256:6ab3ea2e29d1aac962e905142bbe50943758f55292f1b4fdfb6f4792aae3323e"},
{file = "torch-2.2.1-cp311-cp311-manylinux1_x86_64.whl", hash = "sha256:d86664ec85902967d902e78272e97d1aff1d331f7619d398d3ffab1c9b8e9157"},
{file = "torch-2.2.1-cp311-cp311-manylinux2014_aarch64.whl", hash = "sha256:d6227060f268894f92c61af0a44c0d8212e19cb98d05c20141c73312d923bc0a"},
{file = "torch-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:77e990af75fb1675490deb374d36e726f84732cd5677d16f19124934b2409ce9"},
{file = "torch-2.2.1-cp311-none-macosx_10_9_x86_64.whl", hash = "sha256:46085e328d9b738c261f470231e987930f4cc9472d9ffb7087c7a1343826ac51"},
{file = "torch-2.2.1-cp311-none-macosx_11_0_arm64.whl", hash = "sha256:2d9e7e5ecbb002257cf98fae13003abbd620196c35f85c9e34c2adfb961321ec"},
{file = "torch-2.2.1-cp312-cp312-manylinux1_x86_64.whl", hash = "sha256:ada53aebede1c89570e56861b08d12ba4518a1f8b82d467c32665ec4d1f4b3c8"},
{file = "torch-2.2.1-cp312-cp312-manylinux2014_aarch64.whl", hash = "sha256:be21d4c41ecebed9e99430dac87de1439a8c7882faf23bba7fea3fea7b906ac1"},
{file = "torch-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:79848f46196750367dcdf1d2132b722180b9d889571e14d579ae82d2f50596c5"},
{file = "torch-2.2.1-cp312-none-macosx_10_9_x86_64.whl", hash = "sha256:7ee804847be6be0032fbd2d1e6742fea2814c92bebccb177f0d3b8e92b2d2b18"},
{file = "torch-2.2.1-cp312-none-macosx_11_0_arm64.whl", hash = "sha256:84b2fb322ab091039fdfe74e17442ff046b258eb5e513a28093152c5b07325a7"},
{file = "torch-2.2.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:5c0c83aa7d94569997f1f474595e808072d80b04d34912ce6f1a0e1c24b0c12a"},
{file = "torch-2.2.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:91a1b598055ba06b2c386415d2e7f6ac818545e94c5def597a74754940188513"},
{file = "torch-2.2.1-cp38-cp38-win_amd64.whl", hash = "sha256:8f93ddf3001ecec16568390b507652644a3a103baa72de3ad3b9c530e3277098"},
{file = "torch-2.2.1-cp38-none-macosx_10_9_x86_64.whl", hash = "sha256:0e8bdd4c77ac2584f33ee14c6cd3b12767b4da508ec4eed109520be7212d1069"},
{file = "torch-2.2.1-cp38-none-macosx_11_0_arm64.whl", hash = "sha256:6a21bcd7076677c97ca7db7506d683e4e9db137e8420eb4a68fb67c3668232a7"},
{file = "torch-2.2.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:f1b90ac61f862634039265cd0f746cc9879feee03ff962c803486301b778714b"},
{file = "torch-2.2.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:ed9e29eb94cd493b36bca9cb0b1fd7f06a0688215ad1e4b3ab4931726e0ec092"},
{file = "torch-2.2.1-cp39-cp39-win_amd64.whl", hash = "sha256:c47bc25744c743f3835831a20efdcfd60aeb7c3f9804a213f61e45803d16c2a5"},
{file = "torch-2.2.1-cp39-none-macosx_10_9_x86_64.whl", hash = "sha256:0952549bcb43448c8d860d5e3e947dd18cbab491b14638e21750cb3090d5ad3e"},
{file = "torch-2.2.1-cp39-none-macosx_11_0_arm64.whl", hash = "sha256:26bd2272ec46fc62dcf7d24b2fb284d44fcb7be9d529ebf336b9860350d674ed"},
]
[package.dependencies]
filelock = "*"
fsspec = "*"
jinja2 = "*"
networkx = "*"
nvidia-cublas-cu12 = {version = "12.1.3.1", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""}
nvidia-cuda-cupti-cu12 = {version = "12.1.105", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""}
nvidia-cuda-nvrtc-cu12 = {version = "12.1.105", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""}
nvidia-cuda-runtime-cu12 = {version = "12.1.105", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""}
nvidia-cudnn-cu12 = {version = "8.9.2.26", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""}
nvidia-cufft-cu12 = {version = "11.0.2.54", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""}
nvidia-curand-cu12 = {version = "10.3.2.106", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""}
nvidia-cusolver-cu12 = {version = "11.4.5.107", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""}
nvidia-cusparse-cu12 = {version = "12.1.0.106", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""}
nvidia-nccl-cu12 = {version = "2.19.3", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""}
nvidia-nvtx-cu12 = {version = "12.1.105", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""}
sympy = "*"
typing-extensions = ">=4.8.0"
[package.extras]
opt-einsum = ["opt-einsum (>=3.3)"]
optree = ["optree (>=0.9.1)"]
[[package]]
name = "torchvision"
version = "0.17.1"
description = "image and video datasets and models for torch deep learning"
optional = false
python-versions = ">=3.8"
files = [
{file = "torchvision-0.17.1-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:06418880212b66e45e855dd39f536e7fd48b4e6b034a11dd9fe9e2384afb51ec"},
{file = "torchvision-0.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:33d65d0c7fdcb3f7bc1dd8ed30ea3cd7e0587b4ad1b104b5677c8191a8bad9f1"},
{file = "torchvision-0.17.1-cp310-cp310-manylinux1_x86_64.whl", hash = "sha256:aaefef2be6a02f206085ce4bb6c0078b03ebf48cb6ff82bd762ff6248475e08e"},
{file = "torchvision-0.17.1-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:ebe5fdb466aff8a8e8e755de84a843418b6f8d500624752c05eaa638d7700f3d"},
{file = "torchvision-0.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:9d4d45a996f4313e9c5db4da71d31508d44f7ccfbf29d3442bdcc2ad13e0b6f3"},
{file = "torchvision-0.17.1-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:ea2ccdbf5974e0bf27fd6644a33b19cb0700297cf397bb0469e762c11c6c4105"},
{file = "torchvision-0.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9106e32c9f1e70afa8172cf1b064cf9c2998d8dff0769ec69d537b20209ee43d"},
{file = "torchvision-0.17.1-cp311-cp311-manylinux1_x86_64.whl", hash = "sha256:5966936c669a08870f6547cd0a90d08b157aeda03293f79e2adbb934687175ed"},
{file = "torchvision-0.17.1-cp311-cp311-manylinux2014_aarch64.whl", hash = "sha256:e74f5a26ef8190eab0c38b3f63914fea94e58e3b2f0e5466611c9f63bd91a80b"},
{file = "torchvision-0.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:a2109c1a1dcf71e8940d43e91f78c4dd5bf0fcefb3a0a42244102752009f5862"},
{file = "torchvision-0.17.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5d241d2a5fb4e608677fccf6f80b34a124446d324ee40c7814ce54bce888275b"},
{file = "torchvision-0.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e0fe98d9d92c23d2262ff82f973242951b9357fb640f8888ac50848bd00f5b45"},
{file = "torchvision-0.17.1-cp312-cp312-manylinux1_x86_64.whl", hash = "sha256:32dc5de86d2ade399e11087095674ca08a1649fb322cfe69336d28add467edcb"},
{file = "torchvision-0.17.1-cp312-cp312-manylinux2014_aarch64.whl", hash = "sha256:54902877410ffb5458ee52b6d0de4b25cf01496bee736d6825301a5f0398536e"},
{file = "torchvision-0.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:cc22c1ed0f1aba3f98fd72b6f60021f57aec1d2f6af518522e8a0a83848de3a8"},
{file = "torchvision-0.17.1-cp38-cp38-macosx_10_13_x86_64.whl", hash = "sha256:2621097065fa1c827885e2b52102e839a3541b933b7a90e0fa3c42c3de1bc3cf"},
{file = "torchvision-0.17.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5ce76466af2b5a30573939cae1e6e62e29316ceb3ee748091002f312ab0912f6"},
{file = "torchvision-0.17.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:bd5dcd14a32945c72f5c19341add94aa7c23dd7bca2bafde44d0f3c4344d17ed"},
{file = "torchvision-0.17.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:dca22795cc02ca0d5ddc08c1422ff620bc9899f63d15dc36f71ef37250e17b75"},
{file = "torchvision-0.17.1-cp38-cp38-win_amd64.whl", hash = "sha256:524405457dd97d9ab0e48df502f819d0f41a113ce8f00470bb9926d9d36efcf1"},
{file = "torchvision-0.17.1-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:58299a724b37b893c7ce4d0b32ea1480c30e467cc114167964b45f6013f6c2d3"},
{file = "torchvision-0.17.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8a1b17fb158b2b881f2c8796fe1839a624e49d5fd07aa61f6dae60ba4819421a"},
{file = "torchvision-0.17.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:429d63eb7551aa4d8f6cdf08d109b5570c20cbcce36d9cb95b24556418e4dc82"},
{file = "torchvision-0.17.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:0ecc9a58171bd555aed583bf2f72e7fd6cc4f767c14f8b80b6a8725eacf4ceb1"},
{file = "torchvision-0.17.1-cp39-cp39-win_amd64.whl", hash = "sha256:5f427ebee15521edcd836bfe05e86feb5189b5c943b9e3999ed0e3f391fbaa1d"},
]
[package.dependencies]
numpy = "*"
pillow = ">=5.3.0,<8.3.dev0 || >=8.4.dev0"
torch = "2.2.1"
[package.extras]
scipy = ["scipy"]
[[package]]
name = "trio"
version = "0.24.0"
@@ -1486,4 +1826,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p
[metadata]
lock-version = "2.0"
python-versions = "^3.12"
content-hash = "849178e665e9a43935d7508528a82d8e0e173020b9ee4256e6a25d3e607f1bd8"
content-hash = "6c4bb66c774e93a08e4180f3148942edf91e2b5620ea1f6c3e4c30045bb643e5"
+5
View File
@@ -33,6 +33,11 @@ selenium = "^4.18.1"
webdriver-manager = "^4.0.1"
retry = "^0.9.2"
[tool.poetry.group.model.dependencies]
torch = "^2.2.1"
torchvision = "^0.17.1"
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
-12
View File
@@ -1,12 +0,0 @@
from __future__ import annotations
from .classes import ModelOutputs
from .classes import NoDocumentFoundException
from .classes import VisualCommunication
from .database import connect
from .utils import get_visual_communication
from .utils import total_annotated
from .utils import total_documents
from .utils import upsert_annotations
from .utils import upsert_predictions
from .utils import upsert_visual_communication
-165
View File
@@ -1,165 +0,0 @@
from __future__ import annotations
import logging
from base64 import b64decode
from base64 import b64encode
from io import BytesIO
from pathlib import Path
from PIL import Image
from pydantic import BaseModel
from pydantic import field_serializer
from pydantic import field_validator
from src.model_experiential import (
VisualSyntaxModelOutput,
)
from src.model_interpersonal import AngleModelOutput
from src.model_interpersonal import ContactModelOutput
from src.model_interpersonal import DistanceModelOutput
from src.model_interpersonal import ModalityColorModelOutput
from src.model_interpersonal import ModalityDepthModelOutput
from src.model_interpersonal import ModalityLightingModelOutput
from src.model_interpersonal import PointOfViewModelOutput
from src.model_textual import FramingModelOutput
from src.model_textual import InformationValueModelOutput
from src.model_textual import SalienceModelOutput
class NoDocumentFoundException(Exception):
pass
class ModelOutputs(BaseModel):
visual_syntax: VisualSyntaxModelOutput
contact: ContactModelOutput
angle: AngleModelOutput
point_of_view: PointOfViewModelOutput
distance: DistanceModelOutput
modality_lighting: ModalityLightingModelOutput
modality_color: ModalityColorModelOutput
modality_depth: ModalityDepthModelOutput
information_value: InformationValueModelOutput
framing: FramingModelOutput
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() # type: ignore
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):
name: str
image: Image.Image
annotation: ModelOutputs | None = None
prediction: ModelOutputs | None = None
class Config:
arbitrary_types_allowed = True
@classmethod
def classname(cls) -> str:
"""Return classname."""
return cls.__name__
@classmethod
def from_file(cls, path: Path) -> VisualCommunication:
"""Instantiate from file."""
name = path.stem
image = Image.open(path)
image.load()
return VisualCommunication(name=name, image=image)
@classmethod
def decode_image(cls, content: str) -> Image.Image:
"""Decode image."""
_, content_data = content.split(',')
return Image.open(BytesIO(b64decode(content_data)))
@field_serializer('image')
def serialize_image(image: Image.Image) -> bytes: # type: ignore
buffer = BytesIO()
image.save(buffer, format='JPEG')
return buffer.getvalue()
@field_validator('image', mode='before')
@classmethod
def convert_to_image(
cls,
image: Image.Image | BytesIO | bytes,
) -> Image.Image:
if isinstance(image, bytes):
image = BytesIO(image)
if isinstance(image, BytesIO):
image = Image.open(image)
return image
def __repr__(self) -> str:
return f"{self.classname()}(name='{self.name}')"
def webencoded_image(self) -> str:
"""Convert image to be displayed on webpage."""
# convert images to bytes string
buffer = BytesIO()
self.image.save(buffer, format='png')
img_enc = b64encode(buffer.getvalue()).decode('utf-8')
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()
-24
View File
@@ -1,24 +0,0 @@
from pymongo import MongoClient
from dotenv import load_dotenv
import logging
import os
def connect():
"""Connect to MongoDB."""
# load env vars
load_dotenv()
necessary_env_vars = [
"MONGO_HOST",
"MONGO_DB",
"MONGO_COLLECTION"
]
for env_var in necessary_env_vars:
assert env_var in os.environ, f"{env_var} not found"
# connect to database
client = MongoClient(os.getenv("MONGO_HOST"))
db = client[os.getenv("MONGO_DB")]
collection = db[os.getenv("MONGO_COLLECTION")]
collection.create_index("name", unique=True)
logging.info("connected to database")
return collection, db, client
-120
View File
@@ -1,120 +0,0 @@
from __future__ import annotations
import logging
from pymongo.collection import Collection
from .classes import ModelOutputs
from .classes import NoDocumentFoundException
from .classes import VisualCommunication
def total_documents(
collection: Collection,
) -> int:
"""Get total number of documents in database."""
return collection.count_documents(filter={})
def total_annotated(
collection: Collection,
) -> int:
"""Get total number of annotated documents in database."""
query = {
'annotation': {
'$ne': None,
},
}
return collection.count_documents(filter=query)
def get_visual_communication(
collection: Collection,
with_annotation: bool = False,
) -> VisualCommunication:
"""Get a random visual communication from the database."""
query = {}
if with_annotation:
query['annotation'] = {'$ne': None}
else:
query['annotation'] = {'$eq': None}
data = collection.aggregate([
{
'$match': query, # find using filters
},
{
'$sample': {
'size': 1, # get one random
},
},
])
data_list = list(data) # read data from cursor object
if len(data_list) == 0:
logging.error('failed getting visual communication')
raise NoDocumentFoundException()
logging.info('finished')
return VisualCommunication.model_validate(data_list[0])
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')
def upsert_visual_communication(
collection: Collection,
visual_communication_list: list[VisualCommunication],
) -> bool:
"""
Upsert VisualCommunication object in the database.
Returns bool stating success.
"""
response = collection.insert_many(
[
vis_com.model_dump()
for vis_com
in visual_communication_list
],
)
return response.acknowledged
+18 -13
View File
@@ -13,15 +13,14 @@ from dash_auth import BasicAuth
from pydantic import ValidationError
from .layout import app_layout
from src.database import connect
from src.database import get_visual_communication
from src.database import ModelOutputs
from src.database import NoDocumentFoundException
from src.database import total_annotated
from src.database import total_documents
from src.database import upsert_annotations
from src.database import upsert_visual_communication
from src.database import VisualCommunication
from core.database import connect
from core.database import count_documents
from core.database import get_visual_communication
from core.database import NoDocumentFoundException
from core.database import upsert_annotation
from core.database import upsert_visual_communication
from core.database import VisualCommunication
from core.dto import ModelData
# setup app
app = Dash(__name__, external_stylesheets=[dbc.themes.BOOTSTRAP])
@@ -82,8 +81,14 @@ def update_progress_bar(
logging.info('began updating progress bar')
global collection
# get values from database
num_total = total_documents(collection=collection)
num_handled = total_annotated(collection=collection)
num_total = count_documents(
collection=collection,
only_with_annotation=False,
)
num_handled = count_documents(
collection=collection,
only_with_annotation=True,
)
limit = int(num_total/20)
label_str = f"{num_handled}/{num_total}" if num_handled >= limit else ''
return num_handled, num_total, label_str
@@ -200,9 +205,9 @@ def cycle_visual_communication_data(
in zip(annotation_keys, annotation_values)
}
# instantiate ModelOutputs object
annotations = ModelOutputs.from_annotations(**annotation_map)
annotations = ModelData.from_annotations(**annotation_map)
# save data to database
upsert_annotations(
upsert_annotation(
collection=collection,
vis_com_name=vis_com_name,
annotations=annotations,
+26 -47
View File
@@ -4,35 +4,24 @@ import dash_mantine_components as dmc
from dash import dcc
from dash import html
from src.model_experiential import VisualSyntaxModelOutput
from src.model_interpersonal import AngleModelOutput
from src.model_interpersonal import ContactModelOutput
from src.model_interpersonal import DistanceModelOutput
from src.model_interpersonal import ModalityColorModelOutput
from src.model_interpersonal import ModalityDepthModelOutput
from src.model_interpersonal import ModalityLightingModelOutput
from src.model_interpersonal import PointOfViewModelOutput
from src.model_textual import FramingModelOutput
from src.model_textual import InformationValueModelOutput
from src.model_textual import SalienceModelOutput
def generate_option_labels(model) -> list[str]:
"""Generate presentable list of attributes from an OutputModel."""
labels = [
label.replace('_', ' ').title()
for label in model.list_fields()
]
return labels
from core.dto import AngleData
from core.dto import ContactData
from core.dto import DistanceData
from core.dto import FramingData
from core.dto import InformationValueData
from core.dto import ModalityColorData
from core.dto import ModalityDepthData
from core.dto import ModalityLightingData
from core.dto import PointOfViewData
from core.dto import SalienceData
from core.dto import VisualSyntaxData
def generate_visual_syntax_options_map():
"""Generate map of titles and options for visual syntax labels."""
options_map = {}
# add experiential labels
options_map['visual syntax'] = generate_option_labels(
VisualSyntaxModelOutput,
)
options_map['visual syntax'] = VisualSyntaxData.list_fields()
return options_map
@@ -40,21 +29,13 @@ def generate_interpersonal_options_map():
"""Generate map of titles and options for interpersonal labels."""
options_map = {}
# add interpersonal labels
options_map['contact'] = generate_option_labels(ContactModelOutput)
options_map['angle'] = generate_option_labels(AngleModelOutput)
options_map['point of view'] = generate_option_labels(
PointOfViewModelOutput,
)
options_map['distance'] = generate_option_labels(DistanceModelOutput)
options_map['modality lighting'] = generate_option_labels(
ModalityLightingModelOutput,
)
options_map['modality color'] = generate_option_labels(
ModalityColorModelOutput,
)
options_map['modality depth'] = generate_option_labels(
ModalityDepthModelOutput,
)
options_map['contact'] = ContactData.list_fields()
options_map['angle'] = AngleData.list_fields()
options_map['point of view'] = PointOfViewData.list_fields()
options_map['distance'] = DistanceData.list_fields()
options_map['modality lighting'] = ModalityLightingData.list_fields()
options_map['modality color'] = ModalityColorData.list_fields()
options_map['modality depth'] = ModalityDepthData.list_fields()
return options_map
@@ -62,11 +43,9 @@ def generate_textual_options_map():
"""Generate map of titles and options for textual labels."""
options_map = {}
# add textual labels
options_map['information value'] = generate_option_labels(
InformationValueModelOutput,
)
options_map['framing'] = generate_option_labels(FramingModelOutput)
options_map['salience'] = generate_option_labels(SalienceModelOutput)
options_map['information value'] = InformationValueData.list_fields()
options_map['framing'] = FramingData.list_fields()
options_map['salience'] = SalienceData.list_fields()
return options_map
@@ -83,7 +62,7 @@ for title, options in experiential_map.items():
dmc.Container([
html.B(title.title()),
dcc.RadioItems(
options=options,
options=[text.replace('_', ' ') for text in options],
id=id_dict,
),
]),
@@ -101,7 +80,7 @@ for title, options in interpersonal_map.items():
dmc.Container([
html.B(title.title()),
dcc.RadioItems(
options=options,
options=[text.replace('_', ' ') for text in options],
id=id_dict,
),
]),
@@ -117,9 +96,9 @@ for title, options in textual_map.items():
id_dict = {'type': 'annotation', 'index': title.replace('_', '-')}
textual_container.children.append(
dmc.Container([
html.B(title),
html.B(title.title()),
dcc.RadioItems(
options=options,
options=[text.replace('_', ' ') for text in options],
id=id_dict,
),
]),
+1 -1
View File
@@ -2,7 +2,7 @@ from __future__ import annotations
from pathlib import Path
from database import VisualCommunication
from core.database.classes import VisualCommunication
if __name__ == '__main__':
+3 -2
View File
@@ -3,10 +3,11 @@ from __future__ import annotations
import os
from pathlib import Path
from database import connect
from database import get_visual_communication
from dotenv import load_dotenv
from core.database import connect
from core.database import get_visual_communication
if __name__ == '__main__':
# prepare env vars
env_path = Path(__file__).parent.parent / 'local.env'
+3 -2
View File
@@ -3,10 +3,11 @@ from __future__ import annotations
import os
from pathlib import Path
from database import connect
from database import VisualCommunication
from dotenv import load_dotenv
from core.database import connect
from core.database.classes import VisualCommunication
if __name__ == '__main__':
# prepare env vars
env_path = Path(__file__).parent.parent / 'local.env'
+3 -2
View File
@@ -3,11 +3,12 @@ from __future__ import annotations
import os
from pathlib import Path
from database import connect
from database import VisualCommunication
from dotenv import load_dotenv
from pymongo.errors import DuplicateKeyError
from core.database import connect
from core.database.classes import VisualCommunication
if __name__ == '__main__':
# get list of image paths
test_dir = Path(__file__).parent
+2 -2
View File
@@ -5,8 +5,8 @@ from pathlib import Path
from dotenv import load_dotenv
from pymongo.errors import DuplicateKeyError
from src.database import connect
from src.database import VisualCommunication
from core.database import connect
from core.database import VisualCommunication
if __name__ == '__main__':
# get list of image paths
+2 -2
View File
@@ -1,12 +1,12 @@
from __future__ import annotations
from database import ModelOutputs
from core.dto import ModelData
if __name__ == '__main__':
# instantiate data object
vis_com_list = [
ModelOutputs.from_random()
ModelData.from_random()
for i
in range(3)
]
+4 -3
View File
@@ -4,11 +4,12 @@ import logging
import os
from pathlib import Path
from database import connect
from database import upsert_predictions
from database import VisualCommunication
from dotenv import load_dotenv
from core.database import connect
from core.database import upsert_predictions
from core.database.classes import VisualCommunication
if __name__ == '__main__':
# setup logging
fmt = (
+6 -3
View File
@@ -3,10 +3,11 @@ from __future__ import annotations
import os
from pathlib import Path
from database import connect
from database import total_annotated
from dotenv import load_dotenv
from core.database import connect
from core.database import total_documents
if __name__ == '__main__':
# prepare env vars
env_path = Path(__file__).parent.parent / 'local.env'
@@ -16,5 +17,7 @@ if __name__ == '__main__':
# connect to database
collection, db, client = connect()
# get visual communication
num_docs = total_annotated(collection)
num_docs = total_documents(
collection=collection,
)
print(f"number of annotated documents in database: {num_docs}")
+6 -3
View File
@@ -3,10 +3,11 @@ from __future__ import annotations
import os
from pathlib import Path
from database import connect
from database import total_documents
from dotenv import load_dotenv
from core.database import connect
from core.database import total_documents
if __name__ == '__main__':
# prepare env vars
env_path = Path(__file__).parent.parent / 'local.env'
@@ -16,5 +17,7 @@ if __name__ == '__main__':
# connect to database
collection, db, client = connect()
# get visual communication
num_docs = total_documents(collection)
num_docs = total_documents(
collection=collection,
)
print(f"total number of documents in database: {num_docs}")