Добавление веб-версии сервиса #5
8
.prettierrc.json
Normal file
8
.prettierrc.json
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"semi": true,
|
||||||
|
"trailingComma": "all",
|
||||||
|
"singleQuote": true,
|
||||||
|
"printWidth": 160,
|
||||||
|
"tabWidth": 2,
|
||||||
|
"endOfLine": "auto"
|
||||||
|
}
|
@ -11,7 +11,7 @@
|
|||||||
<option name="IS_MODULE_SDK" value="true" />
|
<option name="IS_MODULE_SDK" value="true" />
|
||||||
<option name="ADD_CONTENT_ROOTS" value="true" />
|
<option name="ADD_CONTENT_ROOTS" value="true" />
|
||||||
<option name="ADD_SOURCE_ROOTS" value="true" />
|
<option name="ADD_SOURCE_ROOTS" value="true" />
|
||||||
<option name="SCRIPT_NAME" value="$PROJECT_DIR$/main.py" />
|
<option name="SCRIPT_NAME" value="$PROJECT_DIR$/src/main.py" />
|
||||||
<option name="PARAMETERS" value="5cc5570b-6ed9-3b33-9db4-bdb8ecb9f890 "test-data/lectionAudi/2021-03-12 13-51-07.JPG"" />
|
<option name="PARAMETERS" value="5cc5570b-6ed9-3b33-9db4-bdb8ecb9f890 "test-data/lectionAudi/2021-03-12 13-51-07.JPG"" />
|
||||||
<option name="SHOW_COMMAND_LINE" value="false" />
|
<option name="SHOW_COMMAND_LINE" value="false" />
|
||||||
<option name="EMULATE_TERMINAL" value="false" />
|
<option name="EMULATE_TERMINAL" value="false" />
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
<option name="IS_MODULE_SDK" value="true" />
|
<option name="IS_MODULE_SDK" value="true" />
|
||||||
<option name="ADD_CONTENT_ROOTS" value="true" />
|
<option name="ADD_CONTENT_ROOTS" value="true" />
|
||||||
<option name="ADD_SOURCE_ROOTS" value="true" />
|
<option name="ADD_SOURCE_ROOTS" value="true" />
|
||||||
<option name="SCRIPT_NAME" value="$PROJECT_DIR$/main.py" />
|
<option name="SCRIPT_NAME" value="$PROJECT_DIR$/src/main.py" />
|
||||||
<option name="PARAMETERS" value="5cc5570b-6ed9-3b33-9db4-bdb8ecb9f890 "test-data/lectionAudi/2021-03-12 13-49-32.JPG"" />
|
<option name="PARAMETERS" value="5cc5570b-6ed9-3b33-9db4-bdb8ecb9f890 "test-data/lectionAudi/2021-03-12 13-49-32.JPG"" />
|
||||||
<option name="SHOW_COMMAND_LINE" value="false" />
|
<option name="SHOW_COMMAND_LINE" value="false" />
|
||||||
<option name="EMULATE_TERMINAL" value="false" />
|
<option name="EMULATE_TERMINAL" value="false" />
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
<option name="IS_MODULE_SDK" value="true" />
|
<option name="IS_MODULE_SDK" value="true" />
|
||||||
<option name="ADD_CONTENT_ROOTS" value="true" />
|
<option name="ADD_CONTENT_ROOTS" value="true" />
|
||||||
<option name="ADD_SOURCE_ROOTS" value="true" />
|
<option name="ADD_SOURCE_ROOTS" value="true" />
|
||||||
<option name="SCRIPT_NAME" value="$PROJECT_DIR$/main.py" />
|
<option name="SCRIPT_NAME" value="$PROJECT_DIR$/src/main.py" />
|
||||||
<option name="PARAMETERS" value="5cc5570b-6ed9-3b33-9db4-bdb8ecb9f890 "test-data/lectionAudi/2021-03-12 13-48-31.JPG"" />
|
<option name="PARAMETERS" value="5cc5570b-6ed9-3b33-9db4-bdb8ecb9f890 "test-data/lectionAudi/2021-03-12 13-48-31.JPG"" />
|
||||||
<option name="SHOW_COMMAND_LINE" value="false" />
|
<option name="SHOW_COMMAND_LINE" value="false" />
|
||||||
<option name="EMULATE_TERMINAL" value="false" />
|
<option name="EMULATE_TERMINAL" value="false" />
|
||||||
|
25
.run/webApp.run.xml
Normal file
25
.run/webApp.run.xml
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="webApp" type="PythonConfigurationType" factoryName="Python">
|
||||||
|
<module name="VideoAnalysis" />
|
||||||
|
<option name="INTERPRETER_OPTIONS" value="" />
|
||||||
|
<option name="PARENT_ENVS" value="true" />
|
||||||
|
<envs>
|
||||||
|
<env name="PYTHONUNBUFFERED" value="1" />
|
||||||
|
<env name="FLASK_APP" value="src/webApp.py" />
|
||||||
|
<env name="FLASK_DEBUG" value="1" />
|
||||||
|
</envs>
|
||||||
|
<option name="SDK_HOME" value="" />
|
||||||
|
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" />
|
||||||
|
<option name="IS_MODULE_SDK" value="true" />
|
||||||
|
<option name="ADD_CONTENT_ROOTS" value="true" />
|
||||||
|
<option name="ADD_SOURCE_ROOTS" value="true" />
|
||||||
|
<option name="SCRIPT_NAME" value="flask" />
|
||||||
|
<option name="PARAMETERS" value="run" />
|
||||||
|
<option name="SHOW_COMMAND_LINE" value="false" />
|
||||||
|
<option name="EMULATE_TERMINAL" value="false" />
|
||||||
|
<option name="MODULE_MODE" value="true" />
|
||||||
|
<option name="REDIRECT_INPUT" value="false" />
|
||||||
|
<option name="INPUT_FILE" value="" />
|
||||||
|
<method v="2" />
|
||||||
|
</configuration>
|
||||||
|
</component>
|
20
.vscode/launch.json
vendored
Normal file
20
.vscode/launch.json
vendored
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
|
||||||
|
// 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": "src/webApp.py",
|
||||||
|
"FLASK_DEBUG": "1"
|
||||||
|
},
|
||||||
|
"args": ["run", "--no-debugger", "--no-reload"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
32
README.md
32
README.md
@ -1,22 +1,34 @@
|
|||||||
Окружение: Python 3.10
|
# Сервис анализа фотографий при помощи базы знаний
|
||||||
|
|
||||||
Сервис: http://kb.athene.tech/swagger-ui/index.html
|
## Настройка и запуск
|
||||||
|
|
||||||
Ручная установка зависимостей:
|
Требуемое окружение: Python 3.10+.
|
||||||
|
|
||||||
|
Настройка виртуальной среды и установка зависимостей:
|
||||||
|
|
||||||
```commandline
|
```commandline
|
||||||
pip install -r requirements.txt
|
python3 -m venv --clear .venv
|
||||||
```
|
# или
|
||||||
|
python -m venv --clear .venv
|
||||||
|
|
||||||
Ручная установка зависимостей для mac с Apple Silicon:
|
# для batch
|
||||||
|
.venv\Scripts\activate.bat
|
||||||
|
# для powershell
|
||||||
|
.\.venv\Scripts\Activate.ps1
|
||||||
|
# для bash (в т.ч. mac os x)
|
||||||
|
source .venv/bin/activate
|
||||||
|
|
||||||
```commandline
|
python -m pip install -r requirements.txt
|
||||||
pip install -r requirements-mac.txt
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Запуск:
|
Запуск:
|
||||||
|
|
||||||
```commandline
|
```commandline
|
||||||
main.py <Ontology UID> <Image Path>
|
python -m flask --app src/webApp.py run
|
||||||
main.py 5cc5570b-6ed9-3b33-9db4-bdb8ecb9f890 "test-data/lectionAudi/2021-03-12 13-48-31.JPG"
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
После этого в браузере необходимо перейти по ссылке <http://127.0.0.1:5000/>.
|
||||||
|
|
||||||
|
## Полезные ссылки
|
||||||
|
|
||||||
|
* REST-сервис работы с онтологиями: <http://kb.athene.tech/swagger-ui/index.html>.
|
||||||
|
@ -1,63 +0,0 @@
|
|||||||
import numpy as np
|
|
||||||
import requests
|
|
||||||
|
|
||||||
|
|
||||||
def is_ontology_exists(uid: str, url: str) -> bool:
|
|
||||||
'''
|
|
||||||
Проверяет, существует ли онтология в сервисе.
|
|
||||||
@param uid: УИД онтологии.
|
|
||||||
@param url: Базовый URL сервиса.
|
|
||||||
'''
|
|
||||||
list_ontologies = requests.get(url).json()['response']['items']
|
|
||||||
for onto in list_ontologies:
|
|
||||||
if onto['uid'] == uid:
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def rename_entity(list_names: dict) -> dict:
|
|
||||||
'''
|
|
||||||
Нормализация названий объектов.
|
|
||||||
@param list_names: Список названий объектов.
|
|
||||||
'''
|
|
||||||
temp_list = list()
|
|
||||||
for entity in list_names.values():
|
|
||||||
entity: str
|
|
||||||
temp_list.append(entity.title().replace(' ', ''))
|
|
||||||
return temp_list
|
|
||||||
|
|
||||||
|
|
||||||
def get_entity_square(width: float, height: float) -> float:
|
|
||||||
'''
|
|
||||||
Получение площади занимаемой области.
|
|
||||||
@param width: Ширина области в px.
|
|
||||||
@param height: Высота области в px.
|
|
||||||
'''
|
|
||||||
return abs(width * height)
|
|
||||||
|
|
||||||
|
|
||||||
def get_request_data(entities: dict, objects: np.ndarray, confs: np.ndarray, boxes: np.ndarray) -> tuple[list, list]:
|
|
||||||
'''
|
|
||||||
Формирование данных для сервиса онтологий.
|
|
||||||
@param entities: Список имён объектов.
|
|
||||||
@param results_ndarray: Результат распознавания объектов.
|
|
||||||
'''
|
|
||||||
classroom = 'classroom'
|
|
||||||
entities = rename_entity(entities)
|
|
||||||
object_properties = list()
|
|
||||||
data_properties = list()
|
|
||||||
|
|
||||||
for entity_idx, entity in enumerate(entities):
|
|
||||||
if (entity_idx in objects):
|
|
||||||
object_properties.append({'domain': entity, 'property': 'locatedIn', 'range': classroom})
|
|
||||||
else:
|
|
||||||
object_properties.append({'domain': entity, 'property': 'notLocatedIn', 'range': classroom})
|
|
||||||
|
|
||||||
for object_idx, object in enumerate(objects):
|
|
||||||
conf = confs[object_idx]
|
|
||||||
box = boxes[object_idx]
|
|
||||||
entity = entities[object.item()]
|
|
||||||
data_properties.append({'domain': entity, 'property': 'hasArea', 'value': get_entity_square(float(box[2]), float(box[3]))})
|
|
||||||
data_properties.append({'domain': entity, 'property': 'hasConfidence', 'value': float(conf)})
|
|
||||||
|
|
||||||
return object_properties, data_properties
|
|
@ -1,50 +0,0 @@
|
|||||||
absl-py==1.4.0
|
|
||||||
astunparse==1.6.3
|
|
||||||
cachetools==5.3.0
|
|
||||||
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
|
|
||||||
idna==3.4
|
|
||||||
jax==0.4.10
|
|
||||||
keras==2.12.0
|
|
||||||
libclang==16.0.0
|
|
||||||
Markdown==3.4.3
|
|
||||||
MarkupSafe==2.1.2
|
|
||||||
matplotlib==3.7.1
|
|
||||||
ml-dtypes==0.1.0
|
|
||||||
numpy==1.23.5
|
|
||||||
oauthlib==3.2.2
|
|
||||||
opencv-python==4.7.0.72
|
|
||||||
opt-einsum==3.3.0
|
|
||||||
packaging==23.1
|
|
||||||
pandas==2.0.1
|
|
||||||
Pillow==9.5.0
|
|
||||||
protobuf==4.23.1
|
|
||||||
psutil==5.9.5
|
|
||||||
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
|
|
||||||
six==1.16.0
|
|
||||||
tensorboard==2.12.3
|
|
||||||
tensorboard-data-server==0.7.0
|
|
||||||
tensorflow_macos==2.12.0
|
|
||||||
tensorflow-estimator==2.12.0
|
|
||||||
termcolor==2.3.0
|
|
||||||
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
|
|
@ -1,52 +1,41 @@
|
|||||||
absl-py==1.4.0
|
blinker==1.6.2
|
||||||
astunparse==1.6.3
|
|
||||||
cachetools==5.3.0
|
|
||||||
certifi==2023.5.7
|
certifi==2023.5.7
|
||||||
charset-normalizer==3.1.0
|
charset-normalizer==3.1.0
|
||||||
flatbuffers==23.5.9
|
click==8.1.3
|
||||||
gast==0.4.0
|
colorama==0.4.6
|
||||||
google-auth==2.18.1
|
contourpy==1.1.0
|
||||||
google-auth-oauthlib==1.0.0
|
cycler==0.11.0
|
||||||
google-pasta==0.2.0
|
filelock==3.12.2
|
||||||
grpcio==1.54.2
|
Flask==2.3.2
|
||||||
h5py==3.8.0
|
fonttools==4.40.0
|
||||||
idna==3.4
|
idna==3.4
|
||||||
jax==0.4.10
|
itsdangerous==2.1.2
|
||||||
keras==2.12.0
|
Jinja2==3.1.2
|
||||||
libclang==16.0.0
|
kiwisolver==1.4.4
|
||||||
Markdown==3.4.3
|
MarkupSafe==2.1.3
|
||||||
MarkupSafe==2.1.2
|
|
||||||
matplotlib==3.7.1
|
matplotlib==3.7.1
|
||||||
ml-dtypes==0.1.0
|
mpmath==1.3.0
|
||||||
numpy==1.23.5
|
networkx==3.1
|
||||||
oauthlib==3.2.2
|
numpy==1.25.0
|
||||||
opencv-python==4.7.0.72
|
opencv-python==4.7.0.72
|
||||||
opt-einsum==3.3.0
|
|
||||||
packaging==23.1
|
packaging==23.1
|
||||||
pandas==2.0.1
|
pandas==2.0.2
|
||||||
Pillow==9.5.0
|
Pillow==9.5.0
|
||||||
protobuf==4.23.1
|
|
||||||
psutil==5.9.5
|
psutil==5.9.5
|
||||||
|
pyparsing==3.1.0
|
||||||
|
python-dateutil==2.8.2
|
||||||
|
pytz==2023.3
|
||||||
PyYAML==6.0
|
PyYAML==6.0
|
||||||
pyasn1==0.5.0
|
requests==2.31.0
|
||||||
pyasn1-modules==0.3.0
|
scipy==1.11.0
|
||||||
requests==2.30.0
|
seaborn==0.12.2
|
||||||
requests-oauthlib==1.3.1
|
|
||||||
rsa==4.9
|
|
||||||
scipy==1.10.1
|
|
||||||
six==1.16.0
|
six==1.16.0
|
||||||
tensorboard==2.12.3
|
sympy==1.12
|
||||||
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
|
|
||||||
torch==2.0.1
|
torch==2.0.1
|
||||||
torchvision==0.15.2
|
torchvision==0.15.2
|
||||||
tqdm==4.65.0
|
tqdm==4.65.0
|
||||||
typing_extensions==4.5.0
|
typing_extensions==4.6.3
|
||||||
ultralytics==8.0.105
|
tzdata==2023.3
|
||||||
urllib3==1.26.15
|
ultralytics==8.0.123
|
||||||
Werkzeug==2.3.4
|
urllib3==2.0.3
|
||||||
wrapt==1.14.1
|
Werkzeug==2.3.6
|
||||||
|
@ -1,18 +1,18 @@
|
|||||||
import cv2 as cv
|
import cv2 as cv
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
from main import img_size as size
|
img_size = (1280, 720) # Размер изображения для нормализации.
|
||||||
|
|
||||||
def image_transform(image: np.ndarray) -> np.ndarray:
|
def image_transform(image: np.ndarray) -> np.ndarray:
|
||||||
'''
|
'''
|
||||||
Трансформирует изображение нужным образом.
|
Трансформирует изображение нужным образом.
|
||||||
@param image: Исходная матрица с представлением изображения.
|
@param image: Исходная матрица с представлением изображения.
|
||||||
'''
|
'''
|
||||||
image = cv.resize(image, (size[0], size[1]))
|
image = cv.resize(image, (img_size[0], img_size[1]))
|
||||||
return image[:, :, ::-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: Путь до изображения.
|
@param image_name: Путь до изображения.
|
||||||
@ -21,3 +21,13 @@ def get_image_as_array(image_name: str) -> np.ndarray:
|
|||||||
image: np.ndarray # приведение типов
|
image: np.ndarray # приведение типов
|
||||||
image = image_transform(image)
|
image = image_transform(image)
|
||||||
return 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
|
@ -2,31 +2,25 @@ import os
|
|||||||
import sys
|
import sys
|
||||||
|
|
||||||
import cv2 as cv
|
import cv2 as cv
|
||||||
import requests
|
import numpy as np
|
||||||
|
|
||||||
import imageWorking
|
import imageWorking
|
||||||
import neuralNetwork
|
import neuralNetwork
|
||||||
import ontologyWorking
|
import ontologyWorking
|
||||||
|
|
||||||
url = 'http://kb.athene.tech/api/1.0/ontology/'
|
|
||||||
img_path = 'data'
|
|
||||||
img_size = (1280, 720) # Размер изображения для нормализации.
|
|
||||||
|
|
||||||
|
def analyze_base(ontology_uid: str, image: np.ndarray, queries: list[str]) -> tuple[2]:
|
||||||
def analyze_file(uid: str, image_path: str) -> None:
|
|
||||||
'''
|
'''
|
||||||
Анализирует файл и выводит результат в консоль.
|
Базовая функция анализа файла и вывода результатов обработки.
|
||||||
@param uid: УИД онтологии.
|
@param ontology_uid: УИД онтологии.
|
||||||
@param url: Базовый URL сервиса.
|
@param image: Изображение.
|
||||||
'''
|
'''
|
||||||
if not ontologyWorking.is_ontology_exists(uid, url):
|
if image is None:
|
||||||
raise Exception(f'Онтология с uid {uid} не существует')
|
raise Exception(f'Изображение не указано')
|
||||||
if not os.path.isfile(image_path):
|
|
||||||
raise Exception(f'Изображение {image_path} не существует')
|
|
||||||
model = neuralNetwork.load_model()
|
model = neuralNetwork.load_model()
|
||||||
|
|
||||||
# Распознавание изображения.
|
# Распознавание изображения.
|
||||||
results = model.predict(source=imageWorking.get_image_as_array(image_path))
|
results = model.predict(source=image)
|
||||||
|
|
||||||
# Создание аксиом онтологии на основе результатов распознавания.
|
# Создание аксиом онтологии на основе результатов распознавания.
|
||||||
object_properties = list()
|
object_properties = list()
|
||||||
@ -39,23 +33,32 @@ def analyze_file(uid: str, image_path: str) -> None:
|
|||||||
object_properties += request[0]
|
object_properties += request[0]
|
||||||
data_properties += request[1]
|
data_properties += request[1]
|
||||||
|
|
||||||
# Формирование данных для запроса к сервису работы с онтологиями.
|
|
||||||
data = {
|
# Выполнение запроса к сервису работы с онтологиями
|
||||||
'data':
|
response = ontologyWorking.analyze(ontology_uid, object_properties, data_properties, queries)
|
||||||
{
|
|
||||||
'objectPropertyAssertions': object_properties,
|
return results, response
|
||||||
'dataPropertyAssertions': data_properties
|
|
||||||
}
|
|
||||||
}
|
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 = {
|
result = {
|
||||||
'QueryGetNotEmpty': '',
|
'QueryGetNotEmpty': '',
|
||||||
'QueryGetCheck': '',
|
'QueryGetCheck': '',
|
||||||
'QueryGetEmpty': ''
|
'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']:
|
if response['error']:
|
||||||
raise Exception(response['error'])
|
raise Exception(response['error'])
|
||||||
for query in response['response']:
|
for query in response['response']:
|
||||||
@ -79,6 +82,7 @@ def analyze_file(uid: str, image_path: str) -> None:
|
|||||||
cv.waitKey(0)
|
cv.waitKey(0)
|
||||||
cv.destroyAllWindows()
|
cv.destroyAllWindows()
|
||||||
|
|
||||||
|
|
||||||
# Точка входа в приложение.
|
# Точка входа в приложение.
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
if len(sys.argv) != 3:
|
if len(sys.argv) != 3:
|
128
src/ontologyWorking.py
Normal file
128
src/ontologyWorking.py
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
import numpy as np
|
||||||
|
import requests
|
||||||
|
|
||||||
|
|
||||||
|
url = 'http://kb.athene.tech/api/1.0/ontology/'
|
||||||
|
|
||||||
|
|
||||||
|
def is_ontology_exists(ontology_uid: str, url: str) -> bool:
|
||||||
|
'''
|
||||||
|
Проверяет, существует ли онтология в сервисе.
|
||||||
|
@param ontology_uid: УИД онтологии.
|
||||||
|
@param url: Базовый URL сервиса.
|
||||||
|
'''
|
||||||
|
list_ontologies = requests.get(url).json()
|
||||||
|
list_ontologies = list_ontologies['response']['items']
|
||||||
|
for onto in list_ontologies:
|
||||||
|
if onto['uid'] == ontology_uid:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def upload_ontology(name: str, file_buf) -> str:
|
||||||
|
"""
|
||||||
|
Загружает файл.
|
||||||
|
:param name: Имя файла.
|
||||||
|
:param file_buf: Содержимое файла.
|
||||||
|
:return: УИД загруженной онтологии.
|
||||||
|
"""
|
||||||
|
files = {'file': file_buf}
|
||||||
|
response = requests.post(f'{url}?name={name}', files=files)
|
||||||
|
response = response.json()
|
||||||
|
return response['response']['uid']
|
||||||
|
|
||||||
|
|
||||||
|
def delete_ontology(ontology_uid: str) -> bool:
|
||||||
|
"""
|
||||||
|
Загружает файл.
|
||||||
|
:param name: Имя файла.
|
||||||
|
:param file_buf: Содержимое файла.
|
||||||
|
:return: УИД загруженной онтологии.
|
||||||
|
"""
|
||||||
|
response = requests.delete(url + ontology_uid)
|
||||||
|
response = response.json()
|
||||||
|
return False if response['error'] else True
|
||||||
|
|
||||||
|
|
||||||
|
def rename_entity(list_names: dict) -> dict:
|
||||||
|
'''
|
||||||
|
Нормализация названий объектов.
|
||||||
|
@param list_names: Список названий объектов.
|
||||||
|
'''
|
||||||
|
temp_list = list()
|
||||||
|
for entity in list_names.values():
|
||||||
|
entity: str
|
||||||
|
temp_list.append(entity.title().replace(' ', ''))
|
||||||
|
return temp_list
|
||||||
|
|
||||||
|
|
||||||
|
def get_entity_square(width: float, height: float) -> float:
|
||||||
|
'''
|
||||||
|
Получение площади занимаемой области.
|
||||||
|
@param width: Ширина области в px.
|
||||||
|
@param height: Высота области в px.
|
||||||
|
'''
|
||||||
|
return abs(width * height)
|
||||||
|
|
||||||
|
|
||||||
|
def get_request_data(entities: dict, objects: np.ndarray, confs: np.ndarray, boxes: np.ndarray) -> tuple[list, list]:
|
||||||
|
'''
|
||||||
|
Формирование данных для сервиса онтологий.
|
||||||
|
@param entities: Список имён объектов.
|
||||||
|
@param results_ndarray: Результат распознавания объектов.
|
||||||
|
'''
|
||||||
|
classroom = 'classroom'
|
||||||
|
entities = rename_entity(entities)
|
||||||
|
object_properties = list()
|
||||||
|
data_properties = list()
|
||||||
|
|
||||||
|
for entity_idx, entity in enumerate(entities):
|
||||||
|
if (entity_idx in objects):
|
||||||
|
object_properties.append(
|
||||||
|
{'domain': entity, 'property': 'locatedIn', 'range': classroom})
|
||||||
|
else:
|
||||||
|
object_properties.append(
|
||||||
|
{'domain': entity, 'property': 'notLocatedIn', 'range': classroom})
|
||||||
|
|
||||||
|
for object_idx, object in enumerate(objects):
|
||||||
|
conf = confs[object_idx]
|
||||||
|
box = boxes[object_idx]
|
||||||
|
entity = entities[object.item()]
|
||||||
|
data_properties.append(
|
||||||
|
{'domain': entity,
|
||||||
|
'property': 'hasArea',
|
||||||
|
'value': get_entity_square(float(box[2]), float(box[3]))})
|
||||||
|
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
|
58
src/webApp.py
Normal file
58
src/webApp.py
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
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
|
||||||
|
from ontologyWorking import delete_ontology, upload_ontology
|
||||||
|
|
||||||
|
app = Flask(__name__, static_folder="../static", 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': 'Укажите изображение',
|
||||||
|
}
|
||||||
|
|
||||||
|
ontology_uid = '5cc5570b-6ed9-3b33-9db4-bdb8ecb9f890'
|
||||||
|
remove_ontology = False
|
||||||
|
if 'ontology' in request.files and request.files['ontology'].filename != '':
|
||||||
|
ontology_uid = upload_ontology(
|
||||||
|
request.files['ontology'].filename, request.files['ontology'])
|
||||||
|
remove_ontology = True
|
||||||
|
|
||||||
|
# Подготовка исходного изображения.
|
||||||
|
image_source = request.files['image'].read()
|
||||||
|
image_source = numpy.fromstring(image_source, numpy.uint8)
|
||||||
|
image_source = get_image_buf_as_array(image_source)
|
||||||
|
|
||||||
|
# Подготовка прочих данных и выполнение запроса.
|
||||||
|
queries = request.form['queries'].split(
|
||||||
|
',') if request.form['queries'] is not None else []
|
||||||
|
results, response = analyze_base(ontology_uid, image_source, queries)
|
||||||
|
|
||||||
|
# Если требуется, чистим за собой.
|
||||||
|
if remove_ontology:
|
||||||
|
delete_ontology(ontology_uid)
|
||||||
|
|
||||||
|
# Подготовка изображения с ответом.
|
||||||
|
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': image_result,
|
||||||
|
}
|
73
static/index.html
Normal file
73
static/index.html
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en" class="h-100">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Анализ аудиторий</title>
|
||||||
|
<link
|
||||||
|
href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css"
|
||||||
|
rel="stylesheet"
|
||||||
|
integrity="sha384-9ndCyUaIbzAi2FUVXJi0CjmCapSmO7SnpJef0486qhLnuZ2cdeRhO02iuK6FUUVM"
|
||||||
|
crossorigin="anonymous"
|
||||||
|
/>
|
||||||
|
<script
|
||||||
|
src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"
|
||||||
|
integrity="sha384-geWF76RCwLtnZ8qwWowPQNguL3RmwHVBC9FhGdlKrxdiJJigb/j/68SIy3Te4Bkz"
|
||||||
|
crossorigin="anonymous"
|
||||||
|
></script>
|
||||||
|
<link rel="stylesheet" href="style.css" />
|
||||||
|
<script src="site.js"></script>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body class="bg-body-secondary h-100">
|
||||||
|
<div id="wrapper" class="d-flex align-items-center">
|
||||||
|
<div class="container py-3">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-lg">
|
||||||
|
<p class="alert alert-info">Загрузите изображение и, если требуется, собственную онтологию для анализа.</p>
|
||||||
|
<form id="uploadForm" action="analyze">
|
||||||
|
<div class="my-3">
|
||||||
|
<label for="image" class="form-label">Изображение для анализа</label>
|
||||||
|
<input type="file" class="form-control" name="image" id="image" />
|
||||||
|
</div>
|
||||||
|
<div class="my-3">
|
||||||
|
<label for="ontology" class="form-label">Онтология предметной области</label>
|
||||||
|
<input type="file" class="form-control" name="ontology" id="ontology" aria-describedby="ontologyHelp" />
|
||||||
|
<div id="ontologyHelp" class="form-text">Для анализа заполненности аудиторий оставьте это поле пустым.</div>
|
||||||
|
</div>
|
||||||
|
<div class="my-3">
|
||||||
|
<label for="queries" class="form-label">Набор запросов для запуска</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
class="form-control"
|
||||||
|
name="queries"
|
||||||
|
id="queries"
|
||||||
|
aria-describedby="queriesHelp"
|
||||||
|
value="QueryGetNotEmpty,QueryGetCheck,QueryGetEmpty"
|
||||||
|
/>
|
||||||
|
<div id="queriesHelp" class="form-text">Для анализа заполненности аудиторий не изменяйте это поле.</div>
|
||||||
|
</div>
|
||||||
|
<div class="my-3">
|
||||||
|
<button type="submit" class="btn btn-primary">Отправить</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div class="col-lg">
|
||||||
|
<div id="loaderWrapper" class="d-none">
|
||||||
|
<div class="spinner-border text-primary" role="status">
|
||||||
|
<span class="visually-hidden">Загрузка...</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<img src="none.png" alt="Результат" id="imgslot" class="w-100" />
|
||||||
|
<div id="queriesResult"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
BIN
static/none.png
Normal file
BIN
static/none.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 13 KiB |
53
static/site.js
Normal file
53
static/site.js
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
const renderResultMarkup = (query, result) =>
|
||||||
|
`
|
||||||
|
<h1 class="display-6">${query}</h1>
|
||||||
|
<table class="table table-bordered table-striped">
|
||||||
|
<tr>
|
||||||
|
${result.columns.map((column) => `<th>${column}</th>`).join('')}
|
||||||
|
</tr>
|
||||||
|
${result.rows.map(
|
||||||
|
(row) =>
|
||||||
|
`<tr>${Object.entries(row)
|
||||||
|
.map(([key, value]) => `<td>${value.value}</td>`)
|
||||||
|
.join('')}</tr>`,
|
||||||
|
)}
|
||||||
|
</table>`;
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
} else if (data.data && data.data.error) {
|
||||||
|
queriesResult.innerHTML = `<div class="alert alert-danger">${JSON.stringify(data.data.error)}</div>`;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
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)
|
||||||
|
.catch(() => {
|
||||||
|
alert('Произошла внутренняя ошибка.');
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
loaderWrapper.classList.add('d-none');
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
document.getElementById('uploadForm').addEventListener('submit', handleFormSubmit);
|
||||||
|
});
|
15
static/style.css
Normal file
15
static/style.css
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
#wrapper {
|
||||||
|
min-height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#loaderWrapper {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background-color: #33333333;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user