Merge pull request 'add_upload_button' (#21) from add_upload_button into main
Reviewed-on: http://192.168.1.2:3000/brian/visual_critical_discourse_analysis/pulls/21
This commit was merged in pull request #21.
This commit is contained in:
+11
-12
@@ -1,13 +1,12 @@
|
|||||||
from .classes import (
|
from __future__ import annotations
|
||||||
ModelOutputs,
|
|
||||||
VisualCommunication,
|
from .classes import ModelOutputs
|
||||||
NoDocumentFoundException
|
from .classes import NoDocumentFoundException
|
||||||
)
|
from .classes import VisualCommunication
|
||||||
from .database import connect
|
from .database import connect
|
||||||
from .utils import (
|
from .utils import get_visual_communication
|
||||||
total_documents,
|
from .utils import total_annotated
|
||||||
total_annotated,
|
from .utils import total_documents
|
||||||
get_visual_communication,
|
from .utils import upsert_annotations
|
||||||
upsert_annotations,
|
from .utils import upsert_predictions
|
||||||
upsert_predictions,
|
from .utils import upsert_visual_communication
|
||||||
)
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
from base64 import b64decode
|
||||||
from base64 import b64encode
|
from base64 import b64encode
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
@@ -122,6 +123,12 @@ class VisualCommunication(BaseModel):
|
|||||||
image.load()
|
image.load()
|
||||||
return VisualCommunication(name=name, image=image)
|
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')
|
@field_serializer('image')
|
||||||
def serialize_image(image: Image.Image) -> bytes: # type: ignore
|
def serialize_image(image: Image.Image) -> bytes: # type: ignore
|
||||||
buffer = BytesIO()
|
buffer = BytesIO()
|
||||||
|
|||||||
+52
-33
@@ -1,57 +1,58 @@
|
|||||||
from pymongo.collection import Collection
|
from __future__ import annotations
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from .classes import (
|
from pymongo.collection import Collection
|
||||||
VisualCommunication,
|
|
||||||
NoDocumentFoundException,
|
from .classes import ModelOutputs
|
||||||
ModelOutputs
|
from .classes import NoDocumentFoundException
|
||||||
)
|
from .classes import VisualCommunication
|
||||||
|
|
||||||
|
|
||||||
def total_documents(
|
def total_documents(
|
||||||
collection: Collection
|
collection: Collection,
|
||||||
) -> int:
|
) -> int:
|
||||||
"""Get total number of documents in database."""
|
"""Get total number of documents in database."""
|
||||||
return collection.count_documents(filter={})
|
return collection.count_documents(filter={})
|
||||||
|
|
||||||
|
|
||||||
def total_annotated(
|
def total_annotated(
|
||||||
collection: Collection
|
collection: Collection,
|
||||||
) -> int:
|
) -> int:
|
||||||
"""Get total number of annotated documents in database."""
|
"""Get total number of annotated documents in database."""
|
||||||
query = {
|
query = {
|
||||||
"annotation": {
|
'annotation': {
|
||||||
"$ne": None
|
'$ne': None,
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
return collection.count_documents(filter=query)
|
return collection.count_documents(filter=query)
|
||||||
|
|
||||||
|
|
||||||
def get_visual_communication(
|
def get_visual_communication(
|
||||||
collection: Collection,
|
collection: Collection,
|
||||||
with_annotation: bool = False
|
with_annotation: bool = False,
|
||||||
) -> VisualCommunication:
|
) -> VisualCommunication:
|
||||||
"""Get a random visual communication from the database."""
|
"""Get a random visual communication from the database."""
|
||||||
query = {}
|
query = {}
|
||||||
if with_annotation:
|
if with_annotation:
|
||||||
query["annotation"] = {"$ne": None}
|
query['annotation'] = {'$ne': None}
|
||||||
else:
|
else:
|
||||||
query["annotation"] = {"$eq": None}
|
query['annotation'] = {'$eq': None}
|
||||||
data = collection.aggregate([
|
data = collection.aggregate([
|
||||||
{
|
{
|
||||||
"$match": query # find using filters
|
'$match': query, # find using filters
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"$sample": {
|
'$sample': {
|
||||||
"size": 1 # get one random
|
'size': 1, # get one random
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
])
|
])
|
||||||
data_list = list(data) # read data from cursor object
|
data_list = list(data) # read data from cursor object
|
||||||
if len(data_list) == 0:
|
if len(data_list) == 0:
|
||||||
logging.error("failed getting visual communication")
|
logging.error('failed getting visual communication')
|
||||||
raise NoDocumentFoundException()
|
raise NoDocumentFoundException()
|
||||||
logging.info("finished")
|
logging.info('finished')
|
||||||
return VisualCommunication.model_validate(data_list[0])
|
return VisualCommunication.model_validate(data_list[0])
|
||||||
|
|
||||||
|
|
||||||
@@ -62,20 +63,20 @@ def upsert_predictions(
|
|||||||
) -> None:
|
) -> None:
|
||||||
"""Upsert prediction data in the database."""
|
"""Upsert prediction data in the database."""
|
||||||
query = {
|
query = {
|
||||||
"name": vis_com_name
|
'name': vis_com_name,
|
||||||
}
|
}
|
||||||
update = {
|
update = {
|
||||||
"$set": {
|
'$set': {
|
||||||
"prediction": predictions.model_dump()
|
'prediction': predictions.model_dump(),
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
res = collection.update_one(
|
res = collection.update_one(
|
||||||
filter=query,
|
filter=query,
|
||||||
update=update,
|
update=update,
|
||||||
upsert=True,
|
upsert=True,
|
||||||
)
|
)
|
||||||
logging.debug("upserted document: %s", res)
|
logging.debug('upserted document: %s', res)
|
||||||
logging.info("finished")
|
logging.info('finished')
|
||||||
|
|
||||||
|
|
||||||
def upsert_annotations(
|
def upsert_annotations(
|
||||||
@@ -85,17 +86,35 @@ def upsert_annotations(
|
|||||||
) -> None:
|
) -> None:
|
||||||
"""Upserts annotation data in the database."""
|
"""Upserts annotation data in the database."""
|
||||||
query = {
|
query = {
|
||||||
"name": vis_com_name
|
'name': vis_com_name,
|
||||||
}
|
}
|
||||||
update = {
|
update = {
|
||||||
"$set": {
|
'$set': {
|
||||||
"annotation": annotations.model_dump()
|
'annotation': annotations.model_dump(),
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
res = collection.update_one(
|
res = collection.update_one(
|
||||||
filter=query,
|
filter=query,
|
||||||
update=update,
|
update=update,
|
||||||
upsert=True,
|
upsert=True,
|
||||||
)
|
)
|
||||||
logging.info("upserted document: %s", res)
|
logging.info('upserted document: %s', res)
|
||||||
logging.info("finished")
|
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
|
||||||
|
|||||||
+66
-2
@@ -18,6 +18,8 @@ from src.database import get_visual_communication
|
|||||||
from src.database import ModelOutputs
|
from src.database import ModelOutputs
|
||||||
from src.database import NoDocumentFoundException
|
from src.database import NoDocumentFoundException
|
||||||
from src.database import upsert_annotations
|
from src.database import upsert_annotations
|
||||||
|
from src.database import upsert_visual_communication
|
||||||
|
from src.database import VisualCommunication
|
||||||
|
|
||||||
# setup app
|
# setup app
|
||||||
app = Dash(__name__, external_stylesheets=[dbc.themes.BOOTSTRAP])
|
app = Dash(__name__, external_stylesheets=[dbc.themes.BOOTSTRAP])
|
||||||
@@ -46,12 +48,74 @@ def show_alert(
|
|||||||
):
|
):
|
||||||
if msg is None or msg == '':
|
if msg is None or msg == '':
|
||||||
return False, ''
|
return False, ''
|
||||||
logging.info(f"updated alert message: {msg}")
|
logging.info(f'updated alert message: {msg}')
|
||||||
return True, msg
|
return True, msg
|
||||||
|
|
||||||
|
|
||||||
@app.callback(
|
@app.callback(
|
||||||
Output('alert-message', 'data'),
|
Output('success-element', 'is_open'),
|
||||||
|
Output('success-element', 'children'),
|
||||||
|
Input('success-message', 'data'),
|
||||||
|
)
|
||||||
|
def show_success(
|
||||||
|
msg: str | None,
|
||||||
|
):
|
||||||
|
if msg is None or msg == '':
|
||||||
|
return False, ''
|
||||||
|
logging.info(f"updated success message: {msg}")
|
||||||
|
return True, msg
|
||||||
|
|
||||||
|
|
||||||
|
@app.callback(
|
||||||
|
Output('success-message', 'data', allow_duplicate=True),
|
||||||
|
Output('alert-message', 'data', allow_duplicate=True),
|
||||||
|
Input('upload-data', 'contents'),
|
||||||
|
State('upload-data', 'filename'),
|
||||||
|
prevent_initial_call=True,
|
||||||
|
)
|
||||||
|
def upload_images(
|
||||||
|
content_list: list[str] | None,
|
||||||
|
filename_list: list[str] | None,
|
||||||
|
):
|
||||||
|
logging.info('began uploading images')
|
||||||
|
global collection
|
||||||
|
# stop early if possible
|
||||||
|
if content_list is None or filename_list is None:
|
||||||
|
logging.warning('content_list is None -> nothing to upload')
|
||||||
|
return None, 'nothing selected to upload'.title()
|
||||||
|
# build list of visual communication
|
||||||
|
visual_communication_list = []
|
||||||
|
for content, filename in zip(content_list, filename_list):
|
||||||
|
try:
|
||||||
|
image = VisualCommunication.decode_image(content)
|
||||||
|
vis_com = VisualCommunication(
|
||||||
|
name=filename,
|
||||||
|
image=image,
|
||||||
|
)
|
||||||
|
except Exception as exc:
|
||||||
|
logging.warning(
|
||||||
|
(
|
||||||
|
'failed creating VisualCommunication object '
|
||||||
|
f"from file {filename}"
|
||||||
|
),
|
||||||
|
exc,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
visual_communication_list.append(vis_com)
|
||||||
|
# upsert documents
|
||||||
|
success = upsert_visual_communication(
|
||||||
|
collection=collection,
|
||||||
|
visual_communication_list=visual_communication_list,
|
||||||
|
)
|
||||||
|
if success:
|
||||||
|
logging.info(f"succesfully uploaded {len(filename_list)} images")
|
||||||
|
return 'successfully uploaded images'.title(), None
|
||||||
|
logging.warning('failed inserting images into database')
|
||||||
|
return None, 'failed uploading images'.title()
|
||||||
|
|
||||||
|
|
||||||
|
@app.callback(
|
||||||
|
Output('alert-message', 'data', allow_duplicate=True),
|
||||||
Output('vis-com-name', 'data'),
|
Output('vis-com-name', 'data'),
|
||||||
Output('image-container', 'src'),
|
Output('image-container', 'src'),
|
||||||
Output({'type': 'annotation', 'index': ALL}, 'value'),
|
Output({'type': 'annotation', 'index': ALL}, 'value'),
|
||||||
|
|||||||
@@ -1,15 +1,23 @@
|
|||||||
from dash import html
|
from __future__ import annotations
|
||||||
|
|
||||||
import dash_bootstrap_components as dbc
|
import dash_bootstrap_components as dbc
|
||||||
|
from dash import html
|
||||||
|
|
||||||
|
|
||||||
alerts_element = html.Div(
|
alerts_element = html.Div(
|
||||||
children=[
|
children=[
|
||||||
dbc.Alert(
|
dbc.Alert(
|
||||||
children="",
|
children='',
|
||||||
id="alert-element",
|
id='alert-element',
|
||||||
dismissable=True,
|
dismissable=True,
|
||||||
fade=False,
|
fade=False,
|
||||||
is_open=False,
|
is_open=False,
|
||||||
)
|
),
|
||||||
]
|
dbc.Alert(
|
||||||
|
children='',
|
||||||
|
id='success-element',
|
||||||
|
is_open=False,
|
||||||
|
duration=1000,
|
||||||
|
),
|
||||||
|
],
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,21 +1,49 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import dash_mantine_components as dmc
|
import dash_mantine_components as dmc
|
||||||
|
from dash import dcc
|
||||||
from dash import html
|
from dash import html
|
||||||
|
|
||||||
|
title_element = dmc.Center(
|
||||||
|
children=[
|
||||||
|
html.H2(
|
||||||
|
children=[
|
||||||
|
'Visual Critical Discourse Analysis Tool',
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
upload_button_element = dcc.Upload(
|
||||||
|
children=[
|
||||||
|
dmc.Button(
|
||||||
|
'upload images'.title(),
|
||||||
|
id='upload-button',
|
||||||
|
color='lime',
|
||||||
|
n_clicks=0,
|
||||||
|
variant='outline',
|
||||||
|
radius='sm',
|
||||||
|
size='md',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
multiple=True,
|
||||||
|
id='upload-data',
|
||||||
|
accept='image/png,image/jpeg,image/jpg',
|
||||||
|
)
|
||||||
|
|
||||||
header_element = dmc.Header(
|
header_element = dmc.Header(
|
||||||
height=80,
|
height=80,
|
||||||
children=[
|
children=[
|
||||||
dmc.Container(
|
dmc.Group(
|
||||||
children=[
|
children=[
|
||||||
html.H2(
|
title_element,
|
||||||
children="Visual Critical Discourse Analysis Tool",
|
upload_button_element,
|
||||||
|
],
|
||||||
|
position='apart',
|
||||||
style={
|
style={
|
||||||
"margin-left": "20px",
|
'margin': '10px',
|
||||||
"padding-top": "-5px"
|
'padding': '10px',
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
size="xl", px="xl",
|
|
||||||
),
|
|
||||||
]
|
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,18 +1,23 @@
|
|||||||
from dash import html, dcc
|
from __future__ import annotations
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
|
||||||
storage_type = "session"
|
from dash import dcc
|
||||||
if "ENV" in os.environ and os.getenv("ENV") == "DEV":
|
from dash import html
|
||||||
storage_type = "memory"
|
|
||||||
|
storage_type = 'session'
|
||||||
|
if 'ENV' in os.environ and os.getenv('ENV') == 'DEV':
|
||||||
|
storage_type = 'memory'
|
||||||
logging.info(
|
logging.info(
|
||||||
"ENV=DEV -> dcc.Stores changed to storage_type=%s",
|
'ENV=DEV -> dcc.Stores changed to storage_type=%s',
|
||||||
storage_type
|
storage_type,
|
||||||
)
|
)
|
||||||
|
|
||||||
stores_element = html.Div(
|
stores_element = html.Div(
|
||||||
children=[
|
children=[
|
||||||
dcc.Store(id="alert-message", storage_type=storage_type, data=""),
|
dcc.Store(id='alert-message', storage_type=storage_type, data=''),
|
||||||
dcc.Store(id="vis-com-name", storage_type=storage_type, data=""),
|
dcc.Store(id='success-message', storage_type=storage_type, data=''),
|
||||||
]
|
dcc.Store(id='vis-com-name', storage_type=storage_type, data=''),
|
||||||
|
],
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user