From e04ce338912f854838c9e2faaebc85158d381115 Mon Sep 17 00:00:00 2001 From: Brian Bjarke Jensen Date: Tue, 7 May 2024 20:23:22 +0200 Subject: [PATCH] removed unused files --- src/__init__.py | 0 src/main.py | 52 ------ src/model_experiential/__init__.py | 1 - src/model_experiential/classes.py | 89 ---------- src/model_experiential/output.py | 20 --- src/model_interpersonal/__init__.py | 9 - src/model_interpersonal/classes.py | 137 --------------- src/model_interpersonal/output.py | 36 ---- src/model_textual/__init__.py | 5 - src/model_textual/classes.py | 92 ----------- src/model_textual/output.py | 21 --- src/web/__init__.py | 4 - src/web/app.py | 247 ---------------------------- src/web/layout/__init__.py | 1 - src/web/layout/alerts.py | 23 --- src/web/layout/body.py | 18 -- src/web/layout/header.py | 62 ------- src/web/layout/image.py | 21 --- src/web/layout/init_img.png | Bin 9479 -> 0 bytes src/web/layout/inputs.py | 23 --- src/web/layout/labels.py | 113 ------------- src/web/layout/layout.py | 34 ---- src/web/layout/stores.py | 13 -- 23 files changed, 1021 deletions(-) delete mode 100644 src/__init__.py delete mode 100644 src/main.py delete mode 100644 src/model_experiential/__init__.py delete mode 100644 src/model_experiential/classes.py delete mode 100644 src/model_experiential/output.py delete mode 100644 src/model_interpersonal/__init__.py delete mode 100644 src/model_interpersonal/classes.py delete mode 100644 src/model_interpersonal/output.py delete mode 100644 src/model_textual/__init__.py delete mode 100644 src/model_textual/classes.py delete mode 100644 src/model_textual/output.py delete mode 100644 src/web/__init__.py delete mode 100644 src/web/app.py delete mode 100644 src/web/layout/__init__.py delete mode 100644 src/web/layout/alerts.py delete mode 100644 src/web/layout/body.py delete mode 100644 src/web/layout/header.py delete mode 100644 src/web/layout/image.py delete mode 100644 src/web/layout/init_img.png delete mode 100644 src/web/layout/inputs.py delete mode 100644 src/web/layout/labels.py delete mode 100644 src/web/layout/layout.py delete mode 100644 src/web/layout/stores.py diff --git a/src/__init__.py b/src/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/main.py b/src/main.py deleted file mode 100644 index 803d203..0000000 --- a/src/main.py +++ /dev/null @@ -1,52 +0,0 @@ -from __future__ import annotations - -import logging -import os -from pathlib import Path - -from dotenv import load_dotenv - -from src.web import app - -# prepare optional local setup -env_path = Path(__file__).parent.parent / 'local.env' -load_dotenv(env_path) - -# ensure env vars set -necesasary_var_list = { - 'MONGO_HOST', - 'MONGO_DB', - 'MONGO_COLLECTION', - 'MONGO_USER', - 'MONGO_PASSWORD', - 'DASH_AUTH_USERNAME', - 'DASH_AUTH_PASSWORD', -} -for env_var in necesasary_var_list: - # ensure env var set - assert ( - env_var in os.environ - ), ( - f"environment variable not set: {env_var}" - ) - -# setup logging stream handler -fmt = ( - '%(asctime)s | ' - '%(levelname)s | ' - '%(filename)s | ' - '%(funcName)s | ' - '%(message)s' -) -datefmt = '%Y-%m-%d %H:%M:%S' -logging.basicConfig(format=fmt, datefmt=datefmt, level=logging.INFO) - -logging.info('initialized app') -server = app.server - -if __name__ == '__main__': - # prepare local env vars - os.environ['MONGO_HOST'] = 'localhost' - # run app - app.run(debug=True) - logging.info('started app') diff --git a/src/model_experiential/__init__.py b/src/model_experiential/__init__.py deleted file mode 100644 index a63e45d..0000000 --- a/src/model_experiential/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .classes import VisualSyntaxModelOutput diff --git a/src/model_experiential/classes.py b/src/model_experiential/classes.py deleted file mode 100644 index 1739af4..0000000 --- a/src/model_experiential/classes.py +++ /dev/null @@ -1,89 +0,0 @@ -from pydantic import BaseModel, ValidationError -from typing import List -import random - - -class OptionNotSetException(Exception): - pass - - -class ModelOutput(BaseModel): - - @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) - - 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()) - - -class VisualSyntaxModelOutput(ModelOutput): - 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 - - -if __name__ == '__main__': - m = VisualSyntaxModelOutput.from_random() - print(m) - print(repr(m)) - print(m.highest_score_field()) - print(m.highest_score_value()) diff --git a/src/model_experiential/output.py b/src/model_experiential/output.py deleted file mode 100644 index 188234f..0000000 --- a/src/model_experiential/output.py +++ /dev/null @@ -1,20 +0,0 @@ -CLASS_NAMES = [ - "non transactional action", - "non transactional reaction", - "unidirectional transactional action", - "unidirectional transactional reaction", - "bidirectional transactional action", - "bidirectional transactional reaction", - "conversion", - "speech process", - "classification overt taxonomy", - "analytical exhaustive", - "analytical disarranged", - "analytical temporal", - "analytical distributed", - "anaytical topological", - "analytical exploded", - "analytical inclusive", - "symbolic suggestive", - "symbolic attributive" -] diff --git a/src/model_interpersonal/__init__.py b/src/model_interpersonal/__init__.py deleted file mode 100644 index 4599135..0000000 --- a/src/model_interpersonal/__init__.py +++ /dev/null @@ -1,9 +0,0 @@ -from .classes import ( - ContactModelOutput, - AngleModelOutput, - PointOfViewModelOutput, - DistanceModelOutput, - ModalityLightingModelOutput, - ModalityColorModelOutput, - ModalityDepthModelOutput -) diff --git a/src/model_interpersonal/classes.py b/src/model_interpersonal/classes.py deleted file mode 100644 index 8444d64..0000000 --- a/src/model_interpersonal/classes.py +++ /dev/null @@ -1,137 +0,0 @@ -from pydantic import BaseModel, ValidationError -from typing import List -import random - - -class ModelOutput(BaseModel): - - @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) - allowed_options_list = cls.list_fields() - assert option in allowed_options_list, \ - f"{option} is not among allowed fields {allowed_options_list}" - kwargs = {field: 0 for field in cls.list_fields()} - kwargs[option] = 1 - return cls(**kwargs) - - def __repr__(self) -> str: - 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()) - - -class ContactModelOutput(ModelOutput): - offer: float - demand: float - - -class AngleModelOutput(ModelOutput): - high: float - eye_level: float - low: float - - -class PointOfViewModelOutput(ModelOutput): - frontal: float - oblique: float - - -class DistanceModelOutput(ModelOutput): - long: float - medium: float - close: float - - -class ModalityLightingModelOutput(ModelOutput): - high: float - medium: float - low: float - - -class ModalityColorModelOutput(ModelOutput): - high: float - medium: float - low: float - - -class ModalityDepthModelOutput(ModelOutput): - high: float - medium: float - low: float - - -# class InterpersonalModelOutput(BaseModel): -# contact: ContactModelOutput -# angle: AngleModelOutput -# point_of_view: PointOfViewModelOutput -# distance: DistanceModelOutput -# modality_lighting: ModalityLightingModelOutput -# modality_color: ModalityColorModelOutput -# modality_depth: ModalityDepthModelOutput - - -if __name__ == '__main__': - m = ContactModelOutput.from_random() - print(repr(m)) - print(m.highest_score_field()) - print(m.highest_score_value()) - m = AngleModelOutput.from_random() - print(repr(m)) - print(m.highest_score_field()) - print(m.highest_score_value()) - m = PointOfViewModelOutput.from_random() - print(repr(m)) - print(m.highest_score_field()) - print(m.highest_score_value()) - m = DistanceModelOutput.from_random() - print(repr(m)) - print(m.highest_score_field()) - print(m.highest_score_value()) - m = ModalityLightingModelOutput.from_random() - print(repr(m)) - print(m.highest_score_field()) - print(m.highest_score_value()) - m = ModalityColorModelOutput.from_random() - print(repr(m)) - print(m.highest_score_field()) - print(m.highest_score_value()) - m = ModalityDepthModelOutput.from_random() - print(repr(m)) - print(m.highest_score_field()) - print(m.highest_score_value()) diff --git a/src/model_interpersonal/output.py b/src/model_interpersonal/output.py deleted file mode 100644 index 14c84a9..0000000 --- a/src/model_interpersonal/output.py +++ /dev/null @@ -1,36 +0,0 @@ - -model_labels = { - "contact": [ - "offer", - "demand" - ], - "angle": [ - "high", - "eye-level", - "low" - ], - "point-of-view": [ - "frontal", - "oblique" - ], - "distance": [ - "long", - "medium", - "close" - ], - "modality lighting": [ - "high", - "medium", - "low" - ], - "modality color": [ - "high", - "medium", - "low" - ], - "modality depth": [ - "high", - "medium", - "low" - ] -} diff --git a/src/model_textual/__init__.py b/src/model_textual/__init__.py deleted file mode 100644 index a60055f..0000000 --- a/src/model_textual/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -from .classes import ( - InformationValueModelOutput, - FramingModelOutput, - SalienceModelOutput -) diff --git a/src/model_textual/classes.py b/src/model_textual/classes.py deleted file mode 100644 index e0c269d..0000000 --- a/src/model_textual/classes.py +++ /dev/null @@ -1,92 +0,0 @@ -from pydantic import BaseModel, ValidationError -from typing import List -import random - - -class ModelOutput(BaseModel): - - @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) - allowed_options_list = cls.list_fields() - assert option in allowed_options_list, \ - f"{option} is not among allowed fields {allowed_options_list}" - kwargs = {field: 0 for field in cls.list_fields()} - kwargs[option] = 1 - return cls(**kwargs) - - def __repr__(self) -> str: - 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()) - - -class InformationValueModelOutput(ModelOutput): - given_new: float - ideal_real: float - central_marginal: float - - -class FramingModelOutput(ModelOutput): - frame_lines: float - empty_space: float - colour_contrast: float - form_contrast: float - - -class SalienceModelOutput(ModelOutput): - size: float - colour: float - tone: float - form: float - positioning: float - - -if __name__ == '__main__': - m = InformationValueModelOutput.from_random() - print(repr(m)) - print(m.highest_score_field()) - print(m.highest_score_value()) - m = FramingModelOutput.from_random() - print(repr(m)) - print(m.highest_score_field()) - print(m.highest_score_value()) - m = SalienceModelOutput.from_random() - print(repr(m)) - print(m.highest_score_field()) - print(m.highest_score_value()) diff --git a/src/model_textual/output.py b/src/model_textual/output.py deleted file mode 100644 index 8a4e08a..0000000 --- a/src/model_textual/output.py +++ /dev/null @@ -1,21 +0,0 @@ - -model_labels = { - "information value": [ - "given-new", - "ideal-real", - "central-marginal" - ], - "framing": [ - "frame lines", - "empty space", - "colour contrast", - "form contrast" - ], - "salience": [ - "size", - "colour", - "tone", - "form", - "positioning" - ] -} diff --git a/src/web/__init__.py b/src/web/__init__.py deleted file mode 100644 index 3bb26dd..0000000 --- a/src/web/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -from __future__ import annotations - -from src.web.app import app -from src.web.app import server diff --git a/src/web/app.py b/src/web/app.py deleted file mode 100644 index 151fcd9..0000000 --- a/src/web/app.py +++ /dev/null @@ -1,247 +0,0 @@ -from __future__ import annotations - -import logging -import os - -import dash_bootstrap_components as dbc -from dash import ALL -from dash import Dash -from dash import Input -from dash import Output -from dash import State -from dash_auth import BasicAuth -from pydantic import ValidationError - -from .layout import app_layout -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]) -app.title = 'visual critical discourse analysis'.title() -app.layout = app_layout -server = app.server - -# setup authentication -AUTH_DICT = { - os.getenv('DASH_AUTH_USERNAME'): os.getenv('DASH_AUTH_PASSWORD'), -} -BasicAuth(app, AUTH_DICT) - -# connect to database -collection, db, client = connect() - - -# define callbacks -@app.callback( - Output('alert-element', 'is_open'), - Output('alert-element', 'children'), - Input('alert-message', 'data'), -) -def show_alert( - msg: str | None, -): - if msg is None or msg == '': - return False, '' - logging.info(f'updated alert message: {msg}') - return True, msg - - -@app.callback( - 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('progress-bar', 'value'), - Output('progress-bar', 'max'), - Output('progress-bar', 'label'), - Input('next-button', 'n_clicks'), - Input('upload-data', 'filename'), -) -def update_progress_bar( - n_clicks: int, - filename_list: list[str] | None, -): - logging.info('began updating progress bar') - global collection - # get values from database - 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 - - -@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('image-container', 'src'), - Output({'type': 'annotation', 'index': ALL}, 'value'), - Input('next-button', 'n_clicks'), - State('vis-com-name', 'data'), - State('image-container', 'src'), - State({'type': 'annotation', 'index': ALL}, 'id'), - State({'type': 'annotation', 'index': ALL}, 'value'), - prevent_initial_call=True, -) -def cycle_visual_communication_data( - n_clicks: int, - vis_com_name: str, - image_src: str, - annotation_keys: list, - annotation_values: list, -): - logging.info('began cycling visual communication data') - global collection - # prepare default response - response = [ - '', - vis_com_name, - image_src, - annotation_values, - ] - # check if next-button clicked - if n_clicks == 0: - logging.info('stopping early: next-button has not yet been clicked') - return response - # check if visual communication name is set - if len(vis_com_name) > 0: - logging.info('saving annotations to database: %s', vis_com_name) - try: - # extract option keys - annotation_keys = [ - elem['index'] - for elem in annotation_keys - ] - # ensure all options are set - logging.info(annotation_keys) - for option, value in zip(annotation_keys, annotation_values): - if value is None: - raise ValueError(f"{option} is not set") - # prepare data to save - annotation_keys = [ - elem.replace(' ', '_') - for elem - in annotation_keys - ] - annotation_values = [ - elem.replace(' ', '_').lower() - for elem - in annotation_values - ] - annotation_map = { - key: value - for key, value - in zip(annotation_keys, annotation_values) - } - # instantiate ModelOutputs object - annotations = ModelData.from_annotations(**annotation_map) - # save data to database - upsert_annotation( - collection=collection, - vis_com_name=vis_com_name, - annotations=annotations, - ) - except (ValueError, ValidationError) as exc: - msg = f"failed saving annotation: {exc}" - logging.warning(msg) - response[0] = msg - return tuple(response) - # get new visual communication - logging.info('trying to get new visual communication') - try: - # get data - vis_com = get_visual_communication( - collection=collection, - with_annotation=False, - ) - # set variables - vis_com_name = vis_com.name - image_src = vis_com.webencoded_image() - if vis_com.prediction is not None: - # TODO: update to use optional predictions - pass - else: - # reset annotations - annotation_values = [None for elem in annotation_values] - except NoDocumentFoundException: - msg = 'no unannotated data in database' - logging.warning(msg) - response[0] = msg - return tuple(response) - else: - response[1] = vis_com_name - response[2] = image_src - response[3] = annotation_values - logging.info('finished getting visual communication: %s', vis_com_name) - return tuple(response) diff --git a/src/web/layout/__init__.py b/src/web/layout/__init__.py deleted file mode 100644 index e3ea6c9..0000000 --- a/src/web/layout/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .layout import app_layout diff --git a/src/web/layout/alerts.py b/src/web/layout/alerts.py deleted file mode 100644 index 8be4a69..0000000 --- a/src/web/layout/alerts.py +++ /dev/null @@ -1,23 +0,0 @@ -from __future__ import annotations - -import dash_bootstrap_components as dbc -from dash import html - - -alerts_element = html.Div( - children=[ - dbc.Alert( - children='', - id='alert-element', - dismissable=True, - fade=False, - is_open=False, - ), - dbc.Alert( - children='', - id='success-element', - is_open=False, - duration=1000, - ), - ], -) diff --git a/src/web/layout/body.py b/src/web/layout/body.py deleted file mode 100644 index 0765e34..0000000 --- a/src/web/layout/body.py +++ /dev/null @@ -1,18 +0,0 @@ -import dash_mantine_components as dmc - -from .image import image_element -from .inputs import inputs_element - - -body_element = dmc.Container( - fluid=True, - children=[ - dmc.Grid( - grow=True, - children=[ - dmc.Col([image_element], span=5), - dmc.Col([inputs_element], span=7) - ], - ) - ], -) diff --git a/src/web/layout/header.py b/src/web/layout/header.py deleted file mode 100644 index a4436f5..0000000 --- a/src/web/layout/header.py +++ /dev/null @@ -1,62 +0,0 @@ -from __future__ import annotations - -import dash_bootstrap_components as dbc -import dash_mantine_components as dmc -from dash import dcc -from dash import html - -title_element = dmc.Center( - children=[ - html.H2( - children=[ - 'Visual Critical Discourse Analysis Tool', - ], - ), - ], -) - -progress_bar_element = dbc.Progress( - id='progress-bar', - max=100, - value=0, - color='lime', - label='', - style={ - 'width': '400px', - }, -) - -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( - height=80, - children=[ - dmc.Group( - children=[ - title_element, - progress_bar_element, - upload_button_element, - ], - position='apart', - style={ - 'margin': '10px', - 'padding': '10px', - }, - ), - ], -) diff --git a/src/web/layout/image.py b/src/web/layout/image.py deleted file mode 100644 index a32819a..0000000 --- a/src/web/layout/image.py +++ /dev/null @@ -1,21 +0,0 @@ -import dash_mantine_components as dmc -from dash import html -from pathlib import Path -from base64 import b64encode - -# read init img -init_img_path = Path(__file__).parent / "init_img.png" -with open(init_img_path.absolute(), "rb") as fh: - init_img_enc = b64encode(fh.read()).decode("utf-8") -# generate init img string -init_img_src = f"data:image/png;base64, {init_img_enc}" - -image_element = dmc.Center( - html.Img( - style={ - "width": "100%", - }, - id="image-container", - src=init_img_src - ) -) diff --git a/src/web/layout/init_img.png b/src/web/layout/init_img.png deleted file mode 100644 index 7d821dfcd976138a85122865a919288bf9b54707..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9479 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