From 5da1ecb2e2fffc86d01a84a0e0bb9134f00e5243 Mon Sep 17 00:00:00 2001 From: Vladislav Moiseev Date: Thu, 6 Jul 2023 07:32:59 +0400 Subject: [PATCH 01/12] =?UTF-8?q?=D0=9F=D0=B5=D1=80=D0=B2=D0=B0=D1=8F=20?= =?UTF-8?q?=D0=B2=D0=B5=D1=80=D1=81=D0=B8=D1=8F=20webapp?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .vscode/launch.json | 21 ++++++++++++++ imageWorking.py | 16 +++++++++-- main.py | 48 ++++++++++++++++++++++---------- requirements.txt | 67 +++++++++++++++++++-------------------------- static/index.html | 49 +++++++++++++++++++++++++++++++++ webApp.py | 36 ++++++++++++++++++++++++ 6 files changed, 180 insertions(+), 57 deletions(-) create mode 100644 .vscode/launch.json create mode 100644 static/index.html create mode 100644 webApp.py diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..657cb3b --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,21 @@ +{ + + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Python: Current File", + "type": "python", + "request": "launch", + "module": "flask", + "env": { + "FLASK_APP": "webApp.py", + "FLASK_DEBUG": "1" + }, + "args": ["run", "--no-debugger", "--no-reload"] + //"args": [ "5cc5570b-6ed9-3b33-9db4-bdb8ecb9f890", "test-data/lectionAudi/2021-03-12 13-51-19.JPG" ] + } + ] +} diff --git a/imageWorking.py b/imageWorking.py index eb63ade..f5e30c9 100644 --- a/imageWorking.py +++ b/imageWorking.py @@ -1,18 +1,18 @@ import cv2 as cv import numpy as np -from main import img_size as size +img_size = (1280, 720) # Размер изображения для нормализации. def image_transform(image: np.ndarray) -> np.ndarray: ''' Трансформирует изображение нужным образом. @param image: Исходная матрица с представлением изображения. ''' - image = cv.resize(image, (size[0], size[1])) + image = cv.resize(image, (img_size[0], img_size[1])) return image[:, :, ::-1] -def get_image_as_array(image_name: str) -> np.ndarray: +def get_image_file_as_array(image_name: str) -> np.ndarray: ''' Получает изображение из файла и нормализует его. @param image_name: Путь до изображения. @@ -21,3 +21,13 @@ def get_image_as_array(image_name: str) -> np.ndarray: image: np.ndarray # приведение типов image = image_transform(image) return image + +def get_image_buf_as_array(buf) -> np.ndarray: + ''' + Получает изображение из буфера и нормализует его. + @param image_name: Путь до изображения. + ''' + image = cv.imdecode(buf, cv.IMREAD_COLOR) + image: np.ndarray # приведение типов + image = image_transform(image) + return image diff --git a/main.py b/main.py index 643f279..5132f36 100644 --- a/main.py +++ b/main.py @@ -2,6 +2,7 @@ import os import sys import cv2 as cv +import numpy as np import requests import imageWorking @@ -9,24 +10,22 @@ import neuralNetwork import ontologyWorking url = 'http://kb.athene.tech/api/1.0/ontology/' -img_path = 'data' -img_size = (1280, 720) # Размер изображения для нормализации. -def analyze_file(uid: str, image_path: str) -> None: +def analyze_base(ontology_uid: str, image: np.ndarray, queries: list[str]) -> tuple[2]: ''' - Анализирует файл и выводит результат в консоль. - @param uid: УИД онтологии. - @param url: Базовый URL сервиса. + Базовая функция анализа файла и вывода результатов обработки. + @param ontology_uid: УИД онтологии. + @param image: Изображение. ''' - if not ontologyWorking.is_ontology_exists(uid, url): - raise Exception(f'Онтология с uid {uid} не существует') - if not os.path.isfile(image_path): - raise Exception(f'Изображение {image_path} не существует') + if not ontologyWorking.is_ontology_exists(ontology_uid, url): + raise Exception(f'Онтология с uid {ontology_uid} не существует') + if image is None: + raise Exception(f'Изображение не указано') model = neuralNetwork.load_model() # Распознавание изображения. - results = model.predict(source=imageWorking.get_image_as_array(image_path)) + results = model.predict(source=image) # Создание аксиом онтологии на основе результатов распознавания. object_properties = list() @@ -47,15 +46,33 @@ def analyze_file(uid: str, image_path: str) -> None: 'dataPropertyAssertions': data_properties } } + params = '&'.join([f'names={query}' for query in queries]) + + # Выполнение запроса. + response = requests.post(url + f'{ontology_uid}/query/multi?{params}', json=data).json() + + return results, response + + +def analyze_file(ontology_uid: str, image_path: str) -> None: + ''' + Анализирует файл и выводит результат в консоль. + @param ontology_uid: УИД онтологии. + @param image_path: Путь до изображения. + ''' + if not os.path.isfile(image_path): + raise Exception(f'Изображение {image_path} не существует') + image = imageWorking.get_image_file_as_array(image_path) + queries = [ 'QueryGetNotEmpty', 'QueryGetCheck', 'QueryGetEmpty' ] + + # Распознавание изображения. + results, response = analyze_base(ontology_uid, image, queries) + result = { 'QueryGetNotEmpty': '', 'QueryGetCheck': '', 'QueryGetEmpty': '' } - params = '&'.join([f'names={query}' for query in result.keys()]) - - # Выполнение запроса. - response = requests.post(url + f'{uid}/query/multi?{params}', json=data).json() if response['error']: raise Exception(response['error']) for query in response['response']: @@ -79,6 +96,7 @@ def analyze_file(uid: str, image_path: str) -> None: cv.waitKey(0) cv.destroyAllWindows() + # Точка входа в приложение. if __name__ == '__main__': if len(sys.argv) != 3: diff --git a/requirements.txt b/requirements.txt index 545ff0b..008fb9b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,52 +1,41 @@ -absl-py==1.4.0 -astunparse==1.6.3 -cachetools==5.3.0 +blinker==1.6.2 certifi==2023.5.7 charset-normalizer==3.1.0 -flatbuffers==23.5.9 -gast==0.4.0 -google-auth==2.18.1 -google-auth-oauthlib==1.0.0 -google-pasta==0.2.0 -grpcio==1.54.2 -h5py==3.8.0 +click==8.1.3 +colorama==0.4.6 +contourpy==1.1.0 +cycler==0.11.0 +filelock==3.12.2 +Flask==2.3.2 +fonttools==4.40.0 idna==3.4 -jax==0.4.10 -keras==2.12.0 -libclang==16.0.0 -Markdown==3.4.3 -MarkupSafe==2.1.2 +itsdangerous==2.1.2 +Jinja2==3.1.2 +kiwisolver==1.4.4 +MarkupSafe==2.1.3 matplotlib==3.7.1 -ml-dtypes==0.1.0 -numpy==1.23.5 -oauthlib==3.2.2 +mpmath==1.3.0 +networkx==3.1 +numpy==1.25.0 opencv-python==4.7.0.72 -opt-einsum==3.3.0 packaging==23.1 -pandas==2.0.1 +pandas==2.0.2 Pillow==9.5.0 -protobuf==4.23.1 psutil==5.9.5 +pyparsing==3.1.0 +python-dateutil==2.8.2 +pytz==2023.3 PyYAML==6.0 -pyasn1==0.5.0 -pyasn1-modules==0.3.0 -requests==2.30.0 -requests-oauthlib==1.3.1 -rsa==4.9 -scipy==1.10.1 +requests==2.31.0 +scipy==1.11.0 +seaborn==0.12.2 six==1.16.0 -tensorboard==2.12.3 -tensorboard-data-server==0.7.0 -tensorflow==2.12.0 -tensorflow-estimator==2.12.0 -tensorflow-intel==2.12.0 -tensorflow-io-gcs-filesystem==0.31.0 -termcolor==2.3.0 +sympy==1.12 torch==2.0.1 torchvision==0.15.2 tqdm==4.65.0 -typing_extensions==4.5.0 -ultralytics==8.0.105 -urllib3==1.26.15 -Werkzeug==2.3.4 -wrapt==1.14.1 +typing_extensions==4.6.3 +tzdata==2023.3 +ultralytics==8.0.123 +urllib3==2.0.3 +Werkzeug==2.3.6 diff --git a/static/index.html b/static/index.html new file mode 100644 index 0000000..b2f9ab0 --- /dev/null +++ b/static/index.html @@ -0,0 +1,49 @@ + + + + + + + Анализ аудиторий + + +

+ Загрузите изображение в поле ниже, чтобы проверить, на фотография пустая + или заполненная аудитория. +

+
+
+ + +
+
+ + +
+
+ +
+
+ + Результат + + + + diff --git a/webApp.py b/webApp.py new file mode 100644 index 0000000..a364496 --- /dev/null +++ b/webApp.py @@ -0,0 +1,36 @@ +import base64 +import cv2 as cv +from flask import Flask, redirect, request +import numpy +from imageWorking import get_image_buf_as_array +from main import analyze_base + +app = Flask(__name__, static_url_path = "/") + +@app.route("/") +def main(): + return redirect('index.html') + +@app.route("/analyze", methods=["POST"]) +def analyze(): + if 'image' not in request.files or request.files['image'].filename == '': + return { + 'success': False, + 'error': 'Укажите изображение', + } + if 'ontology' in request.files and request.files['ontology'].filename != '': + return { + 'success': False, + 'error': 'Загрузка онтологии ещё не реализована', + } + img = request.files['image'].read(); + img = numpy.fromstring(img, numpy.uint8) + img = get_image_buf_as_array(img) + queries = [ 'QueryGetNotEmpty', 'QueryGetCheck', 'QueryGetEmpty' ] + results, response = analyze_base('5cc5570b-6ed9-3b33-9db4-bdb8ecb9f890', img, queries) + imencoded = cv.imencode(".jpg", results[0].plot())[1] + return { + 'success': True, + 'data': response, + 'image': base64.b64encode(imencoded).decode("utf-8"), + } From b7c7f8e066cd8cc35b107987cc1b0c35de49a400 Mon Sep 17 00:00:00 2001 From: Vladislav Moiseev Date: Fri, 7 Jul 2023 22:10:13 +0400 Subject: [PATCH 02/12] =?UTF-8?q?=D0=9D=D0=B5=D0=B1=D0=BE=D0=BB=D1=8C?= =?UTF-8?q?=D1=88=D0=BE=D0=B5=20=D0=BF=D1=80=D0=B8=D0=B2=D0=B5=D0=B4=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=20=D0=BA=D0=BE=D0=B4=D0=B0=20=D0=B8=20README?= =?UTF-8?q?=20=D0=B2=20=D0=BF=D0=BE=D1=80=D1=8F=D0=B4=D0=BE=D0=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 18 ++++++++++-------- webApp.py | 24 ++++++++++++++++++------ 2 files changed, 28 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 1730407..81b0c3a 100644 --- a/README.md +++ b/README.md @@ -2,21 +2,23 @@ Сервис: http://kb.athene.tech/swagger-ui/index.html -Ручная установка зависимостей: +Настройка виртуальной среды и установка зависимостей: ```commandline -pip install -r requirements.txt -``` +python -m venv --clear .venv -Ручная установка зависимостей для mac с Apple Silicon: +.venv\Scripts\activate.bat +# или +.\.venv\Scripts\Activate.ps1 +# или +source .venv/bin/activate -```commandline -pip install -r requirements-mac.txt +python -m pip install -r requirements.txt ``` Запуск: ```commandline -main.py -main.py 5cc5570b-6ed9-3b33-9db4-bdb8ecb9f890 "test-data/lectionAudi/2021-03-12 13-48-31.JPG" +python main.py +python main.py 5cc5570b-6ed9-3b33-9db4-bdb8ecb9f890 "test-data/lectionAudi/2021-03-12 13-48-31.JPG" ``` diff --git a/webApp.py b/webApp.py index a364496..df763ee 100644 --- a/webApp.py +++ b/webApp.py @@ -13,6 +13,7 @@ def main(): @app.route("/analyze", methods=["POST"]) def analyze(): + # Первоначальные проверки. if 'image' not in request.files or request.files['image'].filename == '': return { 'success': False, @@ -23,14 +24,25 @@ def analyze(): 'success': False, 'error': 'Загрузка онтологии ещё не реализована', } - img = request.files['image'].read(); - img = numpy.fromstring(img, numpy.uint8) - img = get_image_buf_as_array(img) + + # Подготовка исходного изображения. + image_source = request.files['image'].read(); + image_source = numpy.fromstring(image_source, numpy.uint8) + image_source = get_image_buf_as_array(image_source) + + # Подготовка прочих данных и выполнение запроса. queries = [ 'QueryGetNotEmpty', 'QueryGetCheck', 'QueryGetEmpty' ] - results, response = analyze_base('5cc5570b-6ed9-3b33-9db4-bdb8ecb9f890', img, queries) - imencoded = cv.imencode(".jpg", results[0].plot())[1] + results, response = analyze_base('5cc5570b-6ed9-3b33-9db4-bdb8ecb9f890', image_source, queries) + + # Подготовка изображения с ответом. + image_result = results[0].plot() + image_result = cv.cvtColor(image_result, cv.COLOR_BGR2RGB) + image_result = cv.imencode(".jpg", image_result)[1] + image_result = base64.b64encode(image_result).decode("utf-8") + + # Вывод ответа. return { 'success': True, 'data': response, - 'image': base64.b64encode(imencoded).decode("utf-8"), + 'image': image_result, } From 52335870fdffd79535e8d70922ec00a0f013f672 Mon Sep 17 00:00:00 2001 From: Vladislav Moiseev Date: Fri, 7 Jul 2023 22:14:30 +0400 Subject: [PATCH 03/12] =?UTF-8?q?=D0=9F=D0=B5=D1=80=D0=B5=D0=BC=D0=B5?= =?UTF-8?q?=D1=89=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=B8=D1=81=D1=85=D0=BE=D0=B4?= =?UTF-8?q?=D0=BD=D0=B8=D0=BA=D0=BE=D0=B2=20=D0=B2=20=D0=B4=D0=B8=D1=80?= =?UTF-8?q?=D0=B5=D0=BA=D1=82=D0=BE=D1=80=D0=B8=D1=8E=20src?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .vscode/launch.json | 4 ++-- imageWorking.py => src/imageWorking.py | 0 main.py => src/main.py | 0 neuralNetwork.py => src/neuralNetwork.py | 0 ontologyWorking.py => src/ontologyWorking.py | 0 webApp.py => src/webApp.py | 2 +- 6 files changed, 3 insertions(+), 3 deletions(-) rename imageWorking.py => src/imageWorking.py (100%) rename main.py => src/main.py (100%) rename neuralNetwork.py => src/neuralNetwork.py (100%) rename ontologyWorking.py => src/ontologyWorking.py (100%) rename webApp.py => src/webApp.py (95%) diff --git a/.vscode/launch.json b/.vscode/launch.json index 657cb3b..37437b4 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -1,5 +1,5 @@ { - + // Use IntelliSense to learn about possible attributes. // Hover to view descriptions of existing attributes. // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 @@ -11,7 +11,7 @@ "request": "launch", "module": "flask", "env": { - "FLASK_APP": "webApp.py", + "FLASK_APP": "src/webApp.py", "FLASK_DEBUG": "1" }, "args": ["run", "--no-debugger", "--no-reload"] diff --git a/imageWorking.py b/src/imageWorking.py similarity index 100% rename from imageWorking.py rename to src/imageWorking.py diff --git a/main.py b/src/main.py similarity index 100% rename from main.py rename to src/main.py diff --git a/neuralNetwork.py b/src/neuralNetwork.py similarity index 100% rename from neuralNetwork.py rename to src/neuralNetwork.py diff --git a/ontologyWorking.py b/src/ontologyWorking.py similarity index 100% rename from ontologyWorking.py rename to src/ontologyWorking.py diff --git a/webApp.py b/src/webApp.py similarity index 95% rename from webApp.py rename to src/webApp.py index df763ee..45e47cc 100644 --- a/webApp.py +++ b/src/webApp.py @@ -5,7 +5,7 @@ import numpy from imageWorking import get_image_buf_as_array from main import analyze_base -app = Flask(__name__, static_url_path = "/") +app = Flask(__name__, static_folder = "../static", static_url_path = "/") @app.route("/") def main(): From d70592fc6493c3110c8e890ec637c94efd754e33 Mon Sep 17 00:00:00 2001 From: Vladislav Moiseev Date: Fri, 7 Jul 2023 23:21:12 +0400 Subject: [PATCH 04/12] =?UTF-8?q?=D0=9D=D0=B5=D0=BC=D0=BD=D0=BE=D0=B3?= =?UTF-8?q?=D0=BE=20=D1=83=D0=BD=D0=B8=D1=84=D0=B8=D1=86=D0=B8=D1=80=D0=BE?= =?UTF-8?q?=D0=B2=D0=B0=D0=BD=D0=B0=20=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D0=B0?= =?UTF-8?q?=20=D1=81=20=D0=B7=D0=B0=D0=BF=D1=80=D0=BE=D1=81=D0=B0=D0=BC?= =?UTF-8?q?=D0=B8=20=D0=BA=20KB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main.py | 22 ++++---------------- src/ontologyWorking.py | 36 +++++++++++++++++++++++++++++--- src/webApp.py | 2 +- static/index.html | 47 +++++++++++++++++++++++++++++++++++++----- 4 files changed, 80 insertions(+), 27 deletions(-) diff --git a/src/main.py b/src/main.py index 5132f36..a2f7b01 100644 --- a/src/main.py +++ b/src/main.py @@ -3,14 +3,11 @@ import sys import cv2 as cv import numpy as np -import requests import imageWorking import neuralNetwork import ontologyWorking -url = 'http://kb.athene.tech/api/1.0/ontology/' - def analyze_base(ontology_uid: str, image: np.ndarray, queries: list[str]) -> tuple[2]: ''' @@ -18,8 +15,6 @@ def analyze_base(ontology_uid: str, image: np.ndarray, queries: list[str]) -> tu @param ontology_uid: УИД онтологии. @param image: Изображение. ''' - if not ontologyWorking.is_ontology_exists(ontology_uid, url): - raise Exception(f'Онтология с uid {ontology_uid} не существует') if image is None: raise Exception(f'Изображение не указано') model = neuralNetwork.load_model() @@ -38,19 +33,10 @@ def analyze_base(ontology_uid: str, image: np.ndarray, queries: list[str]) -> tu object_properties += request[0] data_properties += request[1] - # Формирование данных для запроса к сервису работы с онтологиями. - data = { - 'data': - { - 'objectPropertyAssertions': object_properties, - 'dataPropertyAssertions': data_properties - } - } - params = '&'.join([f'names={query}' for query in queries]) - - # Выполнение запроса. - response = requests.post(url + f'{ontology_uid}/query/multi?{params}', json=data).json() - + + # Выполнение запроса к сервису работы с онтологиями + response = ontologyWorking.analyze(ontology_uid, object_properties, data_properties, queries) + return results, response diff --git a/src/ontologyWorking.py b/src/ontologyWorking.py index 11e7f94..196c455 100644 --- a/src/ontologyWorking.py +++ b/src/ontologyWorking.py @@ -2,15 +2,18 @@ import numpy as np import requests -def is_ontology_exists(uid: str, url: str) -> bool: +url = 'http://kb.athene.tech/api/1.0/ontology/' + + +def is_ontology_exists(ontology_uid: str, url: str) -> bool: ''' Проверяет, существует ли онтология в сервисе. - @param uid: УИД онтологии. + @param ontology_uid: УИД онтологии. @param url: Базовый URL сервиса. ''' list_ontologies = requests.get(url).json()['response']['items'] for onto in list_ontologies: - if onto['uid'] == uid: + if onto['uid'] == ontology_uid: return True return False @@ -61,3 +64,30 @@ def get_request_data(entities: dict, objects: np.ndarray, confs: np.ndarray, box data_properties.append({'domain': entity, 'property': 'hasConfidence', 'value': float(conf)}) return object_properties, data_properties + + +def analyze(ontology_uid: str, object_properties: list, data_properties: list, queries: list[str]) -> tuple[2]: + ''' + Базовая функция анализа. + @param ontology_uid: УИД онтологии. + @param object_properties: Объектные свойства. + @param data_properties: Свойства данных. + @param queries: Список запросов для запуска. + ''' + if not is_ontology_exists(ontology_uid, url): + raise Exception(f'Онтология с uid {ontology_uid} не существует') + + # Формирование данных для запроса к сервису работы с онтологиями. + data = { + 'data': + { + 'objectPropertyAssertions': object_properties, + 'dataPropertyAssertions': data_properties + } + } + params = '&'.join([f'names={query}' for query in queries]) + + # Выполнение запроса. + response = requests.post(url + f'{ontology_uid}/query/multi?{params}', json=data).json() + + return response diff --git a/src/webApp.py b/src/webApp.py index 45e47cc..8b62c26 100644 --- a/src/webApp.py +++ b/src/webApp.py @@ -31,7 +31,7 @@ def analyze(): image_source = get_image_buf_as_array(image_source) # Подготовка прочих данных и выполнение запроса. - queries = [ 'QueryGetNotEmpty', 'QueryGetCheck', 'QueryGetEmpty' ] + queries = request.form['queries'].split(',') if request.form['queries'] is not None else [ ] results, response = analyze_base('5cc5570b-6ed9-3b33-9db4-bdb8ecb9f890', image_source, queries) # Подготовка изображения с ответом. diff --git a/static/index.html b/static/index.html index b2f9ab0..ca50dc9 100644 --- a/static/index.html +++ b/static/index.html @@ -5,6 +5,8 @@ Анализ аудиторий + +

@@ -20,12 +22,22 @@ +

+ + +
- Результат + Результат +
From 354d5fdbd56e85c2a7111df1caa4221ad70190c9 Mon Sep 17 00:00:00 2001 From: Vladislav Moiseev Date: Fri, 7 Jul 2023 23:34:24 +0400 Subject: [PATCH 05/12] =?UTF-8?q?=D0=9D=D0=B5=D0=BC=D0=BD=D0=BE=D0=B3?= =?UTF-8?q?=D0=BE=20=D0=BA=D1=80=D0=B0=D1=81=D0=BE=D1=82=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- static/index.html | 160 +++++++++++++++++++++++++--------------------- static/none.png | Bin 0 -> 13201 bytes 2 files changed, 87 insertions(+), 73 deletions(-) create mode 100644 static/none.png diff --git a/static/index.html b/static/index.html index ca50dc9..90aadb1 100644 --- a/static/index.html +++ b/static/index.html @@ -1,86 +1,100 @@ - - - - - - Анализ аудиторий - - - - -

- Загрузите изображение в поле ниже, чтобы проверить, на фотография пустая - или заполненная аудитория. -

-
-
- - -
-
- - -
-
- - -
-
- -
-
+ - Результат -
+ + + + + Анализ аудиторий + + + - - - + } + }); + }); + + + + \ No newline at end of file diff --git a/static/none.png b/static/none.png new file mode 100644 index 0000000000000000000000000000000000000000..087d75a80adbe0cb0c590d25d88abb880ecfa37e GIT binary patch literal 13201 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 G@BaXN%Q05~ literal 0 HcmV?d00001 From e1c1ca56ecd0cc1b995f3e31e8d75d4a1c4d727a Mon Sep 17 00:00:00 2001 From: Vladislav Moiseev Date: Sat, 8 Jul 2023 09:44:50 +0400 Subject: [PATCH 06/12] =?UTF-8?q?=D0=A1=D0=BA=D1=80=D0=B8=D0=BF=D1=82?= =?UTF-8?q?=D1=8B=20=D0=B8=20=D1=81=D1=82=D0=B8=D0=BB=D0=B8=20=D0=B2=D1=8B?= =?UTF-8?q?=D0=BD=D0=B5=D1=81=D0=B5=D0=BD=D1=8B=20=D0=B2=20=D0=BE=D1=82?= =?UTF-8?q?=D0=B4=D0=B5=D0=BB=D1=8C=D0=BD=D1=8B=D0=B5=20=D1=84=D0=B0=D0=B9?= =?UTF-8?q?=D0=BB=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .prettierrc.json | 8 +++ static/index.html | 143 ++++++++++++++++------------------------------ static/site.js | 43 ++++++++++++++ static/style.css | 0 4 files changed, 101 insertions(+), 93 deletions(-) create mode 100644 .prettierrc.json create mode 100644 static/site.js create mode 100644 static/style.css diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 0000000..b6d488b --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,8 @@ +{ + "semi": true, + "trailingComma": "all", + "singleQuote": true, + "printWidth": 160, + "tabWidth": 2, + "endOfLine": "auto" +} diff --git a/static/index.html b/static/index.html index 90aadb1..d572773 100644 --- a/static/index.html +++ b/static/index.html @@ -1,100 +1,57 @@ + + + + + Анализ аудиторий + + + + + - - - - - Анализ аудиторий - - - - - -
-
-
-
-
-

- Загрузите изображение в поле ниже, чтобы проверить, на - фотография пустая или заполненная аудитория. -

-
-
- - -
-
- - -
-
- - -
-
- -
-
-
-
- Результат -
+ +
+
+
+
+
+

Загрузите изображение в поле ниже, чтобы проверить, на фотография пустая или заполненная аудитория.

+
+
+ + +
+
+ + +
+
+ + +
+
+ +
+
+
+
+ Результат +
+
-
- - - - \ No newline at end of file + + diff --git a/static/site.js b/static/site.js new file mode 100644 index 0000000..1e351e9 --- /dev/null +++ b/static/site.js @@ -0,0 +1,43 @@ +const renderResultMarkup = (query, result) => + ` +

${query}

+ + + ${result.columns.map((column) => ``).join('')} + +${result.rows.map( + (row) => + `${Object.entries(row) + .map(([key, value]) => ``) + .join('')}`, +)} +
${column}
${value.value}
`; + +const processAnalyzeResult = (data) => { + const img = document.getElementById('imgslot'); + const queriesResult = document.getElementById('queriesResult'); + + img.src = 'none.png'; + if (data.image) { + img.src = 'data:image/jpg;base64,' + data.image; + } + + queriesResult.innerHTML = ''; + if (data.data && data.data.response) { + for (const [query, result] of Object.entries(data.data.response)) { + queriesResult.innerHTML += renderResultMarkup(query, result); + } + } +}; + +const handleFormSubmit = (event) => { + event.preventDefault(); + const data = new FormData(event.target); + fetch('/analyze', { method: 'POST', body: data }) + .then((res) => res.json()) + .then(processAnalyzeResult); +}; + +document.addEventListener('DOMContentLoaded', () => { + document.getElementById('uploadForm').addEventListener('submit', handleFormSubmit); +}); diff --git a/static/style.css b/static/style.css new file mode 100644 index 0000000..e69de29 From 8e92237fec70e716c53fd02d07d71c26faccddf5 Mon Sep 17 00:00:00 2001 From: Vladislav Moiseev Date: Sat, 8 Jul 2023 10:18:50 +0400 Subject: [PATCH 07/12] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=20=D0=BF=D0=BE=D0=BA=D0=B0=D0=B7=D0=B0=D1=82=D0=B5?= =?UTF-8?q?=D0=BB=D1=8C=20=D0=B7=D0=B0=D0=B3=D1=80=D1=83=D0=B7=D0=BA=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- static/index.html | 5 +++++ static/site.js | 12 +++++++++++- static/style.css | 11 +++++++++++ 3 files changed, 27 insertions(+), 1 deletion(-) diff --git a/static/index.html b/static/index.html index d572773..fd3635f 100644 --- a/static/index.html +++ b/static/index.html @@ -46,6 +46,11 @@
+
+
+ Загрузка... +
+
Результат
diff --git a/static/site.js b/static/site.js index 1e351e9..40001a3 100644 --- a/static/site.js +++ b/static/site.js @@ -27,15 +27,25 @@ const processAnalyzeResult = (data) => { for (const [query, result] of Object.entries(data.data.response)) { queriesResult.innerHTML += renderResultMarkup(query, result); } + } else if (data.data && data.data.error) { + queriesResult.innerHTML = `
${JSON.stringify(data.data.error)}
`; } }; const handleFormSubmit = (event) => { event.preventDefault(); const data = new FormData(event.target); + const loaderWrapper = document.getElementById('loaderWrapper'); + loaderWrapper.classList.remove('d-none'); fetch('/analyze', { method: 'POST', body: data }) .then((res) => res.json()) - .then(processAnalyzeResult); + .then(processAnalyzeResult) + .catch(() => { + alert('Произошла внутренняя ошибка.'); + }) + .finally(() => { + loaderWrapper.classList.add('d-none'); + }); }; document.addEventListener('DOMContentLoaded', () => { diff --git a/static/style.css b/static/style.css index e69de29..85fa2cf 100644 --- a/static/style.css +++ b/static/style.css @@ -0,0 +1,11 @@ +#loaderWrapper { + position: absolute; + left: 0; + right: 0; + top: 0; + bottom: 0; + background-color: #33333333; + display: flex; + justify-content: center; + align-items: center; +} From 9a874a3b8d8cf3d3214660c9c5700e9580de675c Mon Sep 17 00:00:00 2001 From: Vladislav Moiseev Date: Sat, 8 Jul 2023 11:00:28 +0400 Subject: [PATCH 08/12] =?UTF-8?q?=D0=9E=D0=B1=D0=BD=D0=BE=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B0=20=D0=B8=D0=BD=D1=81=D1=82=D1=80=D1=83=D0=BA?= =?UTF-8?q?=D1=86=D0=B8=D1=8F=20=D0=BF=D0=BE=20=D0=B7=D0=B0=D0=BF=D1=83?= =?UTF-8?q?=D1=81=D0=BA=D1=83=20=D1=87=D0=B5=D1=80=D0=B5=D0=B7=20CLI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .vscode/launch.json | 1 - README.md | 22 ++++++++++++++++------ 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 37437b4..efd465f 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -15,7 +15,6 @@ "FLASK_DEBUG": "1" }, "args": ["run", "--no-debugger", "--no-reload"] - //"args": [ "5cc5570b-6ed9-3b33-9db4-bdb8ecb9f890", "test-data/lectionAudi/2021-03-12 13-51-19.JPG" ] } ] } diff --git a/README.md b/README.md index 81b0c3a..b32a89f 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,21 @@ -Окружение: Python 3.10 +# Сервис анализа фотографий при помощи базы знаний -Сервис: http://kb.athene.tech/swagger-ui/index.html +## Настройка и запуск + +Требуемое окружение: Python 3.10+. Настройка виртуальной среды и установка зависимостей: ```commandline +python3 -m venv --clear .venv +# или python -m venv --clear .venv +# для batch .venv\Scripts\activate.bat -# или +# для powershell .\.venv\Scripts\Activate.ps1 -# или +# для bash (в т.ч. mac os x) source .venv/bin/activate python -m pip install -r requirements.txt @@ -19,6 +24,11 @@ python -m pip install -r requirements.txt Запуск: ```commandline -python main.py -python main.py 5cc5570b-6ed9-3b33-9db4-bdb8ecb9f890 "test-data/lectionAudi/2021-03-12 13-48-31.JPG" +python -m flask --app src/webApp.py run ``` + +После этого в браузере необходимо перейти по ссылке . + +## Полезные ссылки + +* REST-сервис работы с онтологиями: . From 898a94a5414862f518b66fcd301734be82454f45 Mon Sep 17 00:00:00 2001 From: Vladislav Moiseev Date: Sat, 8 Jul 2023 21:18:37 +0400 Subject: [PATCH 09/12] =?UTF-8?q?=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=BF=D1=83=D1=82=D0=B8=20?= =?UTF-8?q?=D0=B4=D0=BE=20=D1=84=D0=B0=D0=B9=D0=BB=D0=BE=D0=B2=20=D0=B2=20?= =?UTF-8?q?=D0=BA=D0=BE=D0=BD=D1=84=D0=B8=D0=B3=D1=83=D1=80=D0=B0=D1=86?= =?UTF-8?q?=D0=B8=D0=B8=20PyCharm?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .run/main (check).run.xml | 2 +- .run/main (empty).run.xml | 2 +- .run/main (not empty).run.xml | 2 +- .run/webApp.run.xml | 25 +++++++++++++++++++++++++ 4 files changed, 28 insertions(+), 3 deletions(-) create mode 100644 .run/webApp.run.xml diff --git a/.run/main (check).run.xml b/.run/main (check).run.xml index 05df7e2..2e3cbb2 100644 --- a/.run/main (check).run.xml +++ b/.run/main (check).run.xml @@ -11,7 +11,7 @@