Initial Demo
This commit is contained in:
17
.vscode/launch.json
vendored
Normal file
17
.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "Run Streamlit Demo",
|
||||||
|
"type": "python",
|
||||||
|
"request": "launch",
|
||||||
|
"program": "${workspaceFolder}/src/demo_streamlit.py",
|
||||||
|
"console": "integratedTerminal",
|
||||||
|
"args": [
|
||||||
|
"--server.port", "80",
|
||||||
|
"--server.address", "0.0.0.0"
|
||||||
|
],
|
||||||
|
"python": "${workspaceFolder}/.venv/bin/python"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
21
.vscode/tasks.json
vendored
Normal file
21
.vscode/tasks.json
vendored
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"version": "2.0.0",
|
||||||
|
"tasks": [
|
||||||
|
{
|
||||||
|
"label": "Run Streamlit Demo",
|
||||||
|
"type": "shell",
|
||||||
|
"command": "streamlit run src/demo_streamlit.py --server.port 80 --server.address 0.0.0.0",
|
||||||
|
"group": {
|
||||||
|
"kind": "build",
|
||||||
|
"isDefault": true
|
||||||
|
},
|
||||||
|
"presentation": {
|
||||||
|
"echo": true,
|
||||||
|
"reveal": "always",
|
||||||
|
"focus": true,
|
||||||
|
"panel": "shared"
|
||||||
|
},
|
||||||
|
"problemMatcher": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
from pip._internal.cli.main import main
|
from streamlit.web.cli import main
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||||
sys.exit(main())
|
sys.exit(main())
|
||||||
16
bin/streamlit.cmd
Executable file
16
bin/streamlit.cmd
Executable file
@@ -0,0 +1,16 @@
|
|||||||
|
rem Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022-2026)
|
||||||
|
rem
|
||||||
|
rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
rem you may not use this file except in compliance with the License.
|
||||||
|
rem You may obtain a copy of the License at
|
||||||
|
rem
|
||||||
|
rem http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
rem
|
||||||
|
rem Unless required by applicable law or agreed to in writing, software
|
||||||
|
rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
rem See the License for the specific language governing permissions and
|
||||||
|
rem limitations under the License.
|
||||||
|
|
||||||
|
@echo OFF
|
||||||
|
python -m streamlit %*
|
||||||
8
bin/watchmedo
Executable file
8
bin/watchmedo
Executable file
@@ -0,0 +1,8 @@
|
|||||||
|
#!/home/zin/projects/Thesis/bin/python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
from watchdog.watchmedo import main
|
||||||
|
if __name__ == '__main__':
|
||||||
|
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||||
|
sys.exit(main())
|
||||||
5
etc/jupyter/nbconfig/notebook.d/pydeck.json
Normal file
5
etc/jupyter/nbconfig/notebook.d/pydeck.json
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"load_extensions": {
|
||||||
|
"pydeck/extension": true
|
||||||
|
}
|
||||||
|
}
|
||||||
15
share/jupyter/nbextensions/pydeck/extensionRequires.js
Normal file
15
share/jupyter/nbextensions/pydeck/extensionRequires.js
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
/* eslint-disable */
|
||||||
|
define(function() {
|
||||||
|
'use strict';
|
||||||
|
requirejs.config({
|
||||||
|
map: {
|
||||||
|
'*': {
|
||||||
|
'@deck.gl/jupyter-widget': 'nbextensions/pydeck/index'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// Export the required load_ipython_extension function
|
||||||
|
return {
|
||||||
|
load_ipython_extension: function() {}
|
||||||
|
};
|
||||||
|
});
|
||||||
3702
share/jupyter/nbextensions/pydeck/index.js
Normal file
3702
share/jupyter/nbextensions/pydeck/index.js
Normal file
File diff suppressed because one or more lines are too long
7
share/jupyter/nbextensions/pydeck/index.js.map
Normal file
7
share/jupyter/nbextensions/pydeck/index.js.map
Normal file
File diff suppressed because one or more lines are too long
220
src/main.py
Normal file
220
src/main.py
Normal file
@@ -0,0 +1,220 @@
|
|||||||
|
import streamlit as st
|
||||||
|
from pathlib import Path
|
||||||
|
import pandas as pd
|
||||||
|
import numpy as np
|
||||||
|
from PIL import Image, ImageDraw, ImageFont
|
||||||
|
import random
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# Проверяем, запущен ли скрипт через Streamlit
|
||||||
|
import os
|
||||||
|
if "STREAMLIT_RUN" not in os.environ:
|
||||||
|
import sys
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
os.environ["STREAMLIT_RUN"] = "1"
|
||||||
|
|
||||||
|
# Формируем команду запуска
|
||||||
|
cmd = [
|
||||||
|
sys.executable,
|
||||||
|
"-m",
|
||||||
|
"streamlit",
|
||||||
|
"run",
|
||||||
|
__file__,
|
||||||
|
"--server.port", "8080",
|
||||||
|
"--server.address", "0.0.0.0"
|
||||||
|
]
|
||||||
|
subprocess.run(cmd)
|
||||||
|
sys.exit()
|
||||||
|
|
||||||
|
|
||||||
|
# ----------------------------
|
||||||
|
# 1️⃣ Конфигурация
|
||||||
|
# ----------------------------
|
||||||
|
DATA_ROOT = Path("./dataset/EmoSet-118K/test")
|
||||||
|
IMAGES_DIR = DATA_ROOT / "images"
|
||||||
|
LABELS_CSV = DATA_ROOT / "labels.csv"
|
||||||
|
|
||||||
|
EMBEDDINGS_PATH = Path("./src/emoset_test_embeddings.npy")
|
||||||
|
LABELS_PATH = Path("./src/emoset_test_labels.npy")
|
||||||
|
|
||||||
|
NUM_CHOICES = 6 # количество изображений за один раунд
|
||||||
|
TOTAL_ROUNDS = 10 # количество раундов выбора
|
||||||
|
|
||||||
|
st.set_page_config(page_title="EmoSet Demo", layout="wide")
|
||||||
|
|
||||||
|
# ----------------------------
|
||||||
|
# 2️⃣ Загрузка данных
|
||||||
|
# ----------------------------
|
||||||
|
if not IMAGES_DIR.exists():
|
||||||
|
st.error(f"Папка с изображениями не найдена: {IMAGES_DIR.resolve()}")
|
||||||
|
st.stop()
|
||||||
|
|
||||||
|
labels_df = pd.read_csv(LABELS_CSV)
|
||||||
|
embeddings = np.load(EMBEDDINGS_PATH)
|
||||||
|
labels_array = np.load(LABELS_PATH)
|
||||||
|
|
||||||
|
image_files = list(IMAGES_DIR.glob("*.jpg"))
|
||||||
|
|
||||||
|
# Проверка совпадения размеров
|
||||||
|
if len(image_files) != len(embeddings) or len(image_files) != len(labels_array):
|
||||||
|
st.error("Размеры массивов не совпадают!")
|
||||||
|
st.stop()
|
||||||
|
|
||||||
|
# Создадим mapping: filename -> embedding, label
|
||||||
|
filename2embedding = {f.name: emb for f, emb in zip(image_files, embeddings)}
|
||||||
|
filename2label = {f.name: lbl for f, lbl in zip(image_files, labels_array)}
|
||||||
|
|
||||||
|
# ----------------------------
|
||||||
|
# 3️⃣ Сессия Streamlit
|
||||||
|
# ----------------------------
|
||||||
|
if 'round_num' not in st.session_state:
|
||||||
|
st.session_state.round_num = 0
|
||||||
|
if 'chosen_files' not in st.session_state:
|
||||||
|
st.session_state.chosen_files = []
|
||||||
|
|
||||||
|
st.title("EmoSet: Выбор изображений")
|
||||||
|
|
||||||
|
# ----------------------------
|
||||||
|
# 4️⃣ Функция для overlay топ-3 эмоций
|
||||||
|
# ----------------------------
|
||||||
|
def get_font():
|
||||||
|
try:
|
||||||
|
return ImageFont.truetype("arial.ttf", 14)
|
||||||
|
except:
|
||||||
|
try:
|
||||||
|
return ImageFont.truetype("/usr/share/fonts/truetype/arial.ttf", 14)
|
||||||
|
except:
|
||||||
|
return ImageFont.load_default()
|
||||||
|
|
||||||
|
def overlay_top_emotions(img: Image.Image, label: int, top_n=3):
|
||||||
|
draw = ImageDraw.Draw(img)
|
||||||
|
font = get_font()
|
||||||
|
text = f"Label: {label}"
|
||||||
|
draw.rectangle([(0,0),(img.width,20)], fill=(0,0,0,150))
|
||||||
|
draw.text((2,2), text, fill=(255,255,255), font=font)
|
||||||
|
return img
|
||||||
|
|
||||||
|
# ----------------------------
|
||||||
|
# 5️⃣ Рандомный выбор 6 изображений для текущего раунда
|
||||||
|
# ----------------------------
|
||||||
|
# Инициализация состояния Streamlit
|
||||||
|
if "round_num" not in st.session_state:
|
||||||
|
st.session_state.round_num = 0
|
||||||
|
if "chosen_files" not in st.session_state:
|
||||||
|
st.session_state.chosen_files = []
|
||||||
|
if "current_choices" not in st.session_state:
|
||||||
|
st.session_state.current_choices = []
|
||||||
|
|
||||||
|
# Если все раунды уже завершены, блок пропускается
|
||||||
|
if st.session_state.round_num < TOTAL_ROUNDS:
|
||||||
|
st.subheader(f"Раунд {st.session_state.round_num + 1} из {TOTAL_ROUNDS}")
|
||||||
|
|
||||||
|
# Генерируем выбор для текущего раунда только если он ещё не создан
|
||||||
|
if len(st.session_state.current_choices) == 0:
|
||||||
|
already_chosen = set(st.session_state.chosen_files)
|
||||||
|
available_images = [f for f in image_files if f.name not in already_chosen]
|
||||||
|
|
||||||
|
if len(available_images) < NUM_CHOICES:
|
||||||
|
st.warning("Недостаточно изображений для выбора!")
|
||||||
|
st.stop()
|
||||||
|
|
||||||
|
st.session_state.current_choices = random.sample(available_images, NUM_CHOICES)
|
||||||
|
|
||||||
|
# Отображаем изображения и кнопки
|
||||||
|
cols = st.columns(NUM_CHOICES)
|
||||||
|
for col, img_path in zip(cols, st.session_state.current_choices):
|
||||||
|
# Загружаем изображение и накладываем overlay
|
||||||
|
img = Image.open(img_path).convert("RGB")
|
||||||
|
img_overlay = overlay_top_emotions(img, filename2label[img_path.name])
|
||||||
|
|
||||||
|
# Показываем изображение
|
||||||
|
col.image(img_overlay, width=250)
|
||||||
|
|
||||||
|
# Кнопка выбора
|
||||||
|
if col.button("Выбрать", key=img_path.name):
|
||||||
|
st.session_state.chosen_files.append(img_path.name)
|
||||||
|
st.session_state.round_num += 1
|
||||||
|
st.session_state.current_choices = [] # сброс для следующего раунда
|
||||||
|
st.rerun() # перезапуск для нового раунда
|
||||||
|
|
||||||
|
|
||||||
|
# ----------------------------
|
||||||
|
# 6️⃣ После всех выборов: отображение результатов
|
||||||
|
# ----------------------------
|
||||||
|
else:
|
||||||
|
st.subheader("Результаты выбора пользователя")
|
||||||
|
|
||||||
|
if not st.session_state.chosen_files:
|
||||||
|
st.warning("Вы не сделали ни одного выбора!")
|
||||||
|
if st.button("Начать заново"):
|
||||||
|
st.session_state.round_num = 0
|
||||||
|
st.session_state.chosen_files = []
|
||||||
|
st.rerun()
|
||||||
|
else:
|
||||||
|
# Отображение выбранных изображений
|
||||||
|
cols = st.columns(3)
|
||||||
|
for i, filename in enumerate(st.session_state.chosen_files):
|
||||||
|
with cols[i % 3]:
|
||||||
|
img = Image.open(IMAGES_DIR / filename).convert("RGB")
|
||||||
|
img_overlay = overlay_top_emotions(img, filename2label[filename])
|
||||||
|
st.image(img_overlay, caption=f"Выбор {i+1}")
|
||||||
|
|
||||||
|
# Расчет среднего embedding
|
||||||
|
chosen_embeddings = [filename2embedding[f] for f in st.session_state.chosen_files]
|
||||||
|
chosen_embeddings = np.stack(chosen_embeddings)
|
||||||
|
user_emotion_vector = np.mean(chosen_embeddings, axis=0)
|
||||||
|
|
||||||
|
# Отображение информации о векторе эмоций
|
||||||
|
with st.expander("Подробности о векторной модели эмоций"):
|
||||||
|
st.write(f"Размерность embedding: {len(user_emotion_vector)}")
|
||||||
|
st.write("Первые 10 значений:")
|
||||||
|
st.json({f"Dim {i}": float(val) for i, val in enumerate(user_emotion_vector[:10])})
|
||||||
|
|
||||||
|
# Визуализация
|
||||||
|
col1, col2 = st.columns(2)
|
||||||
|
|
||||||
|
with col1:
|
||||||
|
# Гистограмма первых 30 измерений
|
||||||
|
plt.figure(figsize=(8,4))
|
||||||
|
plt.bar(range(min(30, len(user_emotion_vector))), user_emotion_vector[:30])
|
||||||
|
plt.xlabel("Embedding dimension")
|
||||||
|
plt.ylabel("Value")
|
||||||
|
plt.title("Распределение значений embedding (первые 30 измерений)")
|
||||||
|
st.pyplot(plt)
|
||||||
|
|
||||||
|
with col2:
|
||||||
|
# Круговая диаграмма средних значений по блокам
|
||||||
|
if len(user_emotion_vector) > 16:
|
||||||
|
block_size = len(user_emotion_vector) // 4
|
||||||
|
block_means = [
|
||||||
|
np.mean(user_emotion_vector[i*block_size:(i+1)*block_size])
|
||||||
|
for i in range(4)
|
||||||
|
]
|
||||||
|
plt.figure(figsize=(8,4))
|
||||||
|
plt.pie(block_means, labels=[f"Block {i+1}" for i in range(4)], autopct='%1.1f%%')
|
||||||
|
plt.title("Распределение эмоциональных блоков")
|
||||||
|
st.pyplot(plt)
|
||||||
|
|
||||||
|
# Кнопка для сохранения результатов
|
||||||
|
if st.button("Сохранить результаты"):
|
||||||
|
# Сохранение в файл (например, JSON)
|
||||||
|
results = {
|
||||||
|
"chosen_files": st.session_state.chosen_files,
|
||||||
|
"user_emotion_vector": user_emotion_vector.tolist(),
|
||||||
|
"timestamp": pd.Timestamp.now().isoformat()
|
||||||
|
}
|
||||||
|
|
||||||
|
# Сохраняем в текущую директорию
|
||||||
|
save_path = Path("user_results.json")
|
||||||
|
with open(save_path, 'w') as f:
|
||||||
|
import json
|
||||||
|
json.dump(results, f)
|
||||||
|
|
||||||
|
st.success(f"Результаты сохранены в {save_path}")
|
||||||
|
|
||||||
|
if st.button("Начать заново"):
|
||||||
|
st.session_state.round_num = 0
|
||||||
|
st.session_state.chosen_files = []
|
||||||
|
st.rerun()
|
||||||
Reference in New Issue
Block a user