Compare commits

...
8 Commits
Author SHA1 Message Date
Brian Bjarke Jensen d537cdf11d updated to use new vis com class
CI Pipeline / Test (pull_request) Failing after 2m12s
CI Pipeline / Build and Publish (./Dockerfile.web_ui, ${{ vars.docker_repo_url }}/${{ gitea.repository }}/web_ui) (pull_request) Has been skipped
CI Pipeline / Build and Publish (./Dockerfile.model, ${{ vars.docker_repo_url }}/${{ gitea.repository }}/model) (pull_request) Has been skipped
2024-06-15 16:31:35 +02:00
Brian Bjarke Jensen b8061466ce updated to use new vis com class 2024-06-15 16:31:19 +02:00
Brian Bjarke Jensen 119282fee9 tested loading images from minio 2024-06-15 16:20:16 +02:00
Brian Bjarke Jensen 2e060f0776 updated to match images being stored in minio 2024-06-15 16:19:51 +02:00
Brian Bjarke Jensen 9d0648f3c1 transferred images from mongo to minio 2024-06-15 16:19:04 +02:00
Brian Bjarke Jensen b093154193 silenced noisy modules 2024-06-15 16:15:45 +02:00
Brian Bjarke Jensen f3aab68d5f added minio interface 2024-06-15 16:14:43 +02:00
Brian Bjarke Jensen 0ebf7e6ce1 updated logging formatting 2024-06-15 13:00:28 +02:00
10 changed files with 293 additions and 46 deletions
+122
View File
@@ -0,0 +1,122 @@
"""Script to move all images from MongoDB to MinIO."""
from __future__ import annotations
import logging
from io import BytesIO
from pathlib import Path
from bson import ObjectId
from dotenv import load_dotenv
from pymongo.collection import Collection
from shared.data_store import connect as connect_minio
from shared.data_store import put
from shared.database import connect
from shared.database import VisualCommunication
from shared.utils import check_env
from shared.utils import setup_logging
def list_mongo_document_ids(
collection: Collection,
) -> list[ObjectId]:
"""Get list of all VisualCommunication documents in MongoDB."""
# prepare query
query = collection.find(
filter={},
projection={'_id': True},
)
# execute query
res_list = list(query)
logging.debug('got %s document(s)', len(res_list))
# convert result
id_list = [elem['_id'] for elem in res_list]
return id_list
def get_visual_communication(
collection: Collection,
doc_id: ObjectId,
) -> VisualCommunication:
"""Get image from MongoDB."""
# prepare query
query = collection.find(
filter={'_id': doc_id},
projection={'_id': False},
)
# execute query
res_list = list(query)
logging.debug('got %s document(s)', len(res_list))
# convert result
vis_com = VisualCommunication.model_validate(res_list[0])
return vis_com
def update_visual_communication(
collection: Collection,
vis_com: VisualCommunication,
) -> None:
"""Update VisualCommunication document in MongoDB."""
try:
collection.update_one(
filter={
'name': vis_com.name,
},
update={
'$set': vis_com.model_dump(),
},
)
except Exception as exc:
logging.error('failed saving %s', vis_com.name)
logging.debug(exc)
raise exc
if __name__ == '__main__':
# load in env file
env_path = Path(__file__).parent.parent / 'server.env'
assert env_path.exists()
load_dotenv(env_path)
# ensure env vars set
check_env()
# setup logging
setup_logging()
# connect to minIO
minio_client = connect_minio()
# connect to MongoDB
collection, db, client = connect()
# list documents in mongoDB
id_list = list_mongo_document_ids(collection)
for doc_id in id_list:
try:
# get image from MongoDB
vis_com = get_visual_communication(collection, doc_id)
except Exception as exc:
logging.debug(exc)
logging.error('failed getting image from document: %s', doc_id)
continue
try:
# save image to buffer
buffer = BytesIO()
vis_com.image.save(buffer, 'png')
# put buffer in minio
object_name = put(
client=minio_client,
buffer=buffer,
)
except Exception as exc:
logging.debug(exc)
logging.error('failed saving image to minio')
continue
# update visual communication in mongodb
try:
vis_com.image = None # type: ignore
vis_com.object_name = object_name
update_visual_communication(collection, vis_com)
except Exception as exc:
logging.debug(exc)
logging.error(
'failed updating visual communication %s', vis_com.name,
)
continue
logging.debug('updated visual communication %s', vis_com.name)
+5
View File
@@ -0,0 +1,5 @@
from __future__ import annotations
from .connect import connect
from .get import get
from .put import put
+30
View File
@@ -0,0 +1,30 @@
"""Definition of connect function."""
from __future__ import annotations
import os
from minio import Minio
def connect(
) -> Minio:
"""Connect to MinIO server"""
minio_endpoint = os.getenv('MINIO_ENDPOINT', default=None)
assert isinstance(minio_endpoint, str)
minio_access_key = os.getenv('MINIO_ACCESS_KEY', default=None)
assert isinstance(minio_access_key, str)
minio_secret_key = os.getenv('MINIO_SECRET_KEY', default=None)
assert isinstance(minio_secret_key, str)
minio_bucket_name = os.getenv('MINIO_BUCKET_NAME', default=None)
assert isinstance(minio_bucket_name, str)
# connect client
client = Minio(
endpoint=minio_endpoint,
access_key=minio_access_key,
secret_key=minio_secret_key,
secure=False,
)
# ensure bucket exists
if not client.bucket_exists(bucket_name=minio_bucket_name):
client.make_bucket(bucket_name=minio_bucket_name)
return client
+27
View File
@@ -0,0 +1,27 @@
"""Definition of get function."""
from __future__ import annotations
import os
from io import BytesIO
from minio import Minio
def get(
client: Minio,
object_name: str,
) -> BytesIO:
"""Get buffer from bucket in MinIO."""
assert isinstance(client, Minio)
assert isinstance(object_name, str)
bucket_name = os.getenv('MINIO_BUCKET_NAME', default=None)
assert isinstance(bucket_name, str)
# get buffer
try:
response = client.get_object(bucket_name, object_name)
buffer = BytesIO(response.data)
finally:
response.close()
response.release_conn()
buffer.seek(0)
return buffer
+38
View File
@@ -0,0 +1,38 @@
"""Definition of put function."""
from __future__ import annotations
import logging
import os
from hashlib import md5
from io import BytesIO
from minio import Minio
def put(
client: Minio,
buffer: BytesIO,
) -> str:
"""Put buffer in bucket in MinIO and return MD5 checksum as object name."""
assert isinstance(client, Minio)
assert isinstance(buffer, BytesIO)
bucket_name = os.getenv('MINIO_BUCKET_NAME', default=None)
assert isinstance(bucket_name, str)
# get md5 of image
checksum = md5(buffer.getbuffer()).hexdigest()
# prepare for saving
num_bytes = buffer.tell()
buffer.seek(0)
# send data to bucket
try:
client.put_object(
bucket_name=bucket_name,
object_name=checksum,
length=num_bytes,
data=buffer,
)
except Exception as exc:
logging.error('failed saving data to MinIO')
raise exc
logging.debug('saved data to %s', checksum)
return checksum
+32 -30
View File
@@ -7,11 +7,12 @@ from base64 import b64encode
from io import BytesIO from io import BytesIO
from pathlib import Path from pathlib import Path
from minio import Minio
from PIL import Image from PIL import Image
from pydantic import BaseModel from pydantic import BaseModel
from pydantic import field_serializer
from pydantic import field_validator
from shared.data_store import get
from shared.data_store import put
from shared.dto import ModelData from shared.dto import ModelData
@@ -19,7 +20,7 @@ class VisualCommunication(BaseModel):
"""Visual communication model.""" """Visual communication model."""
name: str name: str
image: Image.Image object_name: str
annotation: ModelData | None = None annotation: ModelData | None = None
prediction: ModelData | None = None prediction: ModelData | None = None
@@ -33,12 +34,22 @@ class VisualCommunication(BaseModel):
return cls.__name__ return cls.__name__
@classmethod @classmethod
def from_file(cls, path: Path) -> VisualCommunication: def from_file(cls, path: Path, minio_client: Minio) -> VisualCommunication:
"""Instantiate from file.""" """Instantiate from file."""
assert isinstance(path, Path)
assert isinstance(minio_client, Minio)
# determine name
name = path.stem name = path.stem
# open and upload image to minio
image = Image.open(path) image = Image.open(path)
image.load() image.load()
return VisualCommunication(name=name, image=image) buffer = BytesIO()
image.save(buffer, 'png')
object_name = put(
client=minio_client,
buffer=buffer,
)
return VisualCommunication(name=name, object_name=object_name)
@classmethod @classmethod
def decode_image(cls, content: str) -> Image.Image: def decode_image(cls, content: str) -> Image.Image:
@@ -46,35 +57,26 @@ class VisualCommunication(BaseModel):
_, content_data = content.split(',') _, content_data = content.split(',')
return Image.open(BytesIO(b64decode(content_data))) return Image.open(BytesIO(b64decode(content_data)))
@field_serializer('image') def get_image(self, minio_client: Minio) -> Image.Image:
@classmethod """Load image data from minio."""
def serialize_image(cls, image: Image.Image) -> bytes: # type: ignore assert isinstance(minio_client, Minio)
"""Convert image to bytes for storage in database.""" # get buffer from minio
buffer = BytesIO() buffer = get(
image.save(buffer, format='JPEG') client=minio_client,
return buffer.getvalue() object_name=self.object_name,
)
# convert data to image
im = Image.open(buffer)
return im
@field_validator('image', mode='before') def webencoded_image(self, minio_client: Minio) -> str:
@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 image to be displayed on webpage."""
assert isinstance(minio_client, Minio)
# get image from minio
image = self.get_image(minio_client)
# convert images to bytes string # convert images to bytes string
buffer = BytesIO() buffer = BytesIO()
self.image.save(buffer, format='png') image.save(buffer, format='png')
img_enc = b64encode(buffer.getvalue()).decode('utf-8') img_enc = b64encode(buffer.getvalue()).decode('utf-8')
return f"data:image/png;base64, {img_enc}" return f"data:image/png;base64, {img_enc}"
+5 -1
View File
@@ -12,10 +12,14 @@ def setup_logging() -> None:
fmt = ( fmt = (
'%(asctime)s | ' '%(asctime)s | '
'%(levelname)s | ' '%(levelname)s | '
'%(filename)s | ' '%(name)s | '
'%(funcName)s | ' '%(funcName)s | '
'%(message)s' '%(message)s'
) )
datefmt = '%Y-%m-%d %H:%M:%S' datefmt = '%Y-%m-%d %H:%M:%S'
logging.basicConfig(format=fmt, datefmt=datefmt, level=level) logging.basicConfig(format=fmt, datefmt=datefmt, level=level)
# change levels for modules that spam the log
logging.getLogger('pymongo').setLevel(logging.WARNING)
logging.getLogger('urllib3').setLevel(logging.INFO)
logging.getLogger('PIL').setLevel(logging.INFO)
logging.debug('finished') logging.debug('finished')
+17 -7
View File
@@ -1,22 +1,32 @@
from __future__ import annotations from __future__ import annotations
import os
from pathlib import Path from pathlib import Path
from dotenv import load_dotenv from dotenv import load_dotenv
from shared.data_store import (
connect as connect_minio,
)
from shared.database import connect from shared.database import connect
from shared.database import get_visual_communication from shared.database import get_visual_communication
from shared.utils import check_env
from shared.utils import setup_logging
if __name__ == '__main__': if __name__ == '__main__':
# prepare env vars # load in env file
env_path = Path(__file__).parent.parent / 'local.env' env_path = Path(__file__).parent.parent / 'server.env'
assert env_path.exists() assert env_path.exists()
load_dotenv(env_path) load_dotenv(env_path)
os.environ['MONGO_HOST'] = 'localhost' # ensure env vars set
# connect to database check_env()
# setup logging
setup_logging()
# connect to minIO
minio_client = connect_minio()
# connect to MongoDB
collection, db, client = connect() collection, db, client = connect()
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) print(repr(vis_com))
image = vis_com.get_image(minio_client=minio_client)
image.show()
+9 -7
View File
@@ -11,6 +11,7 @@ from dash import Input
from dash import Output from dash import Output
from dash import State from dash import State
from dash_auth import BasicAuth from dash_auth import BasicAuth
from minio import Minio
from pydantic import ValidationError from pydantic import ValidationError
from pymongo.collection import Collection from pymongo.collection import Collection
@@ -25,7 +26,8 @@ from shared.dto import ModelData
def init_app( def init_app(
collection: Collection, mongo_collection: Collection,
minio_client: Minio,
) -> Dash: ) -> Dash:
"""Initialise web UI application.""" """Initialise web UI application."""
# setup app # setup app
@@ -92,11 +94,11 @@ def init_app(
assert isinstance(filename_list, list) assert isinstance(filename_list, list)
# get values from database # get values from database
num_total = count_documents( num_total = count_documents(
collection=collection, collection=mongo_collection,
only_with_annotation=False, only_with_annotation=False,
) )
num_handled = count_documents( num_handled = count_documents(
collection=collection, collection=mongo_collection,
only_with_annotation=True, only_with_annotation=True,
) )
limit = int(num_total/20) limit = int(num_total/20)
@@ -138,7 +140,7 @@ def init_app(
vis_com_list.append(vis_com) vis_com_list.append(vis_com)
# upsert documents # upsert documents
success = upsert_visual_communication( success = upsert_visual_communication(
collection=collection, collection=mongo_collection,
visual_communication_list=vis_com_list, visual_communication_list=vis_com_list,
) )
if success: if success:
@@ -217,7 +219,7 @@ def init_app(
annotations = ModelData.from_annotations(**annotation_map) annotations = ModelData.from_annotations(**annotation_map)
# save data to database # save data to database
upsert_annotation( upsert_annotation(
collection=collection, collection=mongo_collection,
vis_com_name=vis_com_name, vis_com_name=vis_com_name,
annotations=annotations, annotations=annotations,
) )
@@ -231,12 +233,12 @@ def init_app(
try: try:
# get data # get data
vis_com = get_visual_communication( vis_com = get_visual_communication(
collection=collection, collection=mongo_collection,
with_annotation=False, with_annotation=False,
) )
# set variables # set variables
vis_com_name = vis_com.name vis_com_name = vis_com.name
image_src = vis_com.webencoded_image() image_src = vis_com.webencoded_image(minio_client=minio_client)
if vis_com.prediction is not None: if vis_com.prediction is not None:
# TODO: update to use optional predictions # TODO: update to use optional predictions
pass pass
+8 -1
View File
@@ -4,6 +4,7 @@ from __future__ import annotations
import os import os
from .app import init_app from .app import init_app
from shared.data_store import connect as connect_minio
from shared.database import connect from shared.database import connect
from shared.utils import check_env from shared.utils import check_env
from shared.utils import setup_logging from shared.utils import setup_logging
@@ -17,7 +18,13 @@ setup_logging()
# connect to database # connect to database
collection, db, client = connect() collection, db, client = connect()
# connect to minio
minio_client = connect_minio()
# initialise application # initialise application
app = init_app(collection=collection) app = init_app(
mongo_collection=collection,
minio_client=minio_client,
)
server = app.server server = app.server
server.config.update(SECRET_KEY=os.urandom(24)) server.config.update(SECRET_KEY=os.urandom(24))