feat: Init

This commit is contained in:
zin
2026-06-02 22:39:11 +00:00
parent 875616730b
commit 3850b15053
7 changed files with 210 additions and 120 deletions
+53
View File
@@ -0,0 +1,53 @@
import io
import os
from fastapi import FastAPI, UploadFile, File, HTTPException
from fastapi.responses import JSONResponse
from PIL import Image
# Импортируем твои существующие загрузчики (они теперь работают только на бэкенде)
from data_loader import load_music_engine, load_image_processor
app = FastAPI(title="EmoM Inference API", version="1.0.0")
# Глобальный кэш для удержания моделей в памяти
ml_context = {
"image_processor": None,
"music_matcher": None
}
@app.on_event("startup")
async def startup_event():
print("Инициализация нейросетевого ядра EmoM...")
ml_context["image_processor"] = load_image_processor()
ml_context["music_matcher"] = load_music_engine()
if not ml_context["image_processor"] or not ml_context["music_matcher"]:
raise RuntimeError("Отказ системы: Артефакты моделей не найдены.")
print("Вычислительный конвейер готов к работе.")
@app.post("/analyze")
async def analyze_image_endpoint(file: UploadFile = File(...)):
"""
Принимает изображение, прогоняет через ResNet и возвращает треки из DEAM.
"""
try:
# 1. Чтение бинарных данных из запроса
image_bytes = await file.read()
image = Image.open(io.BytesIO(image_bytes)).convert("RGB")
# 2. Инференс (ВНИМАНИЕ: здесь используй реальные названия методов из своих классов!)
# Предположим, твой процессор выдает координаты V/A
v_a_coords = ml_context["image_processor"].extract_va(image)
# 3. Поиск треков в базе
matched_tracks = ml_context["music_matcher"].find_tracks(v_a_coords)
# 4. Формирование ответа
return JSONResponse(content={
"status": "success",
"valence_arousal": v_a_coords,
"tracks": matched_tracks
})
except Exception as e:
raise HTTPException(status_code=500, detail=f"Ошибка инференса: {str(e)}")
+45 -56
View File
@@ -1,73 +1,62 @@
import sys
import os
import subprocess
import requests
import streamlit as st
import streamlit.components.v1 as components
from data_loader import load_music_engine, load_emoset_data, load_image_processor
from tabs.tab_dataset import render_dataset_tab
from tabs.tab_live import render_live_tab
# Костыль для прямого запуска
if __name__ == "__main__":
if "STREAMLIT_RUN" not in os.environ:
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()
viewport_mode = st.query_params.get("viewport", "desktop")
page_layout = "centered" if viewport_mode == "mobile" else "wide"
st.set_page_config(page_title="Thesis Demo", layout=page_layout)
# Определения ширины экрана и смены верстки
components.html(
"""
<script>
const w = window.parent.innerWidth;
const h = window.parent.innerHeight;
const url = new URL(window.parent.location.href);
// Считаем мобилкой, если ушли в портретный режим или экран уже 768px
const isMobile = (h > w) || (w < 768);
const target = isMobile ? "mobile" : "desktop";
if (url.searchParams.get("viewport") !== target) {
url.searchParams.set("viewport", target);
window.parent.location.href = url.href;
}
</script>
""",
height=0,
width=0,
# Конфигурация UI
st.set_page_config(
page_title="EmoM | EmotionMusic",
layout="wide",
initial_sidebar_state="collapsed"
)
st.markdown(
"""
<style>
img { max-width: 100%; height: auto; object-fit: contain; }
[data-testid="stMetricValue"] { font-size: 1.8rem; }
img { max-width: 100%; height: auto; object-fit: contain; border-radius: 4px; }
[data-testid="stMetricValue"] { font-size: 1.8rem; font-weight: 600; }
#MainMenu {visibility: hidden;}
footer {visibility: hidden;}
</style>
""",
unsafe_allow_html=True
)
# Подгрузка ML-моделей и датасета
music_matcher = load_music_engine()
img_processor = load_image_processor()
emoset_files, emoset_embeddings, emoset_labels, emoset_path = load_emoset_data()
# Маршрутизация к нашему новому микросервису (берется из .env, либо локалхост)
API_URL = os.getenv("BACKEND_API_URL", "http://localhost:8000") + "/analyze"
st.title("Генератор саундтреков (Research Demo)")
def main():
st.title("Система генерации саундтреков (EmoM)")
st.caption("Микросервисная архитектура: Frontend (Streamlit) -> REST API -> PyTorch/DEAM")
tab_live, tab_debug = st.tabs(["Анализ событий (Свои фото)", "Отладка (Датасет EmoSet)"])
uploaded_file = st.file_uploader("Загрузите изображение для анализа", type=["jpg", "jpeg", "png"])
with tab_live:
if img_processor:
render_live_tab(music_matcher, img_processor)
else:
st.error("Ошибка загрузки: не найдены веса ResNet для image_processor.")
if uploaded_file is not None:
st.image(uploaded_file, caption="Входной визуальный контент")
if st.button("Анализировать"):
with st.spinner("Отправка данных в вычислительный кластер..."):
try:
# Отправляем POST-запрос в наш FastAPI микросервис
files = {"file": (uploaded_file.name, uploaded_file.getvalue(), uploaded_file.type)}
response = requests.post(API_URL, files=files, timeout=30)
if response.status_code == 200:
data = response.json()
st.success("Анализ успешно завершен!")
# Вывод результатов
st.subheader("Результаты анализа")
st.write(f"Координаты Valence/Arousal: {data.get('valence_arousal')}")
st.write("Подобранные треки:")
st.json(data.get('tracks'))
# Здесь в будущем можно добавить обращение к Ollama для генерации красивого описания
else:
st.error(f"Ошибка сервера: {response.text}")
except requests.exceptions.ConnectionError:
st.error("Ошибка сети: Микросервис инференса недоступен. Проверьте статус Docker-контейнера emom_inference.")
with tab_debug:
render_dataset_tab(music_matcher, emoset_files, emoset_embeddings, emoset_labels, emoset_path)
if __name__ == "__main__":
main()