diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 0000000..02e2e99
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,18 @@
+bin/
+lib/
+share/
+etc/
+include/
+pyvenv.cfg
+.idea/
+.vscode/
+__pycache__/
+*.pyc
+.git/
+runs/
+dataset/
+NFS/
+*.pth
+*.pkl
+*.npy
+.env
\ No newline at end of file
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 0000000..e8147e3
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,59 @@
+version: '3.8'
+
+networks:
+ emom_mesh:
+ driver: bridge
+
+services:
+ emom_ui:
+ build:
+ context: .
+ dockerfile: docker/Dockerfile.ui
+ container_name: emom_web_ui
+ restart: unless-stopped
+ ports:
+ - "8080:8080"
+ networks:
+ - emom_mesh
+ env_file:
+ - .env
+ depends_on:
+ - emom_inference
+
+ emom_inference:
+ build:
+ context: .
+ dockerfile: docker/Dockerfile.api
+ container_name: emom_pytorch_api
+ restart: unless-stopped
+ networks:
+ - emom_mesh
+ env_file:
+ - .env
+ volumes:
+ - ${HOST_ARTIFACTS_DIR}/emoset_resnet50_best.pth:/app/models/resnet50.pth:ro
+ - ${HOST_ARTIFACTS_DIR}/music_engine/va_regressor.pkl:/app/models/regressor.pkl:ro
+ - ${DATA_DEAM_DIR}:/app/dataset/DEAM:ro
+ deploy:
+ resources:
+ reservations:
+ devices:
+ - driver: nvidia
+ count: 1
+ capabilities: [gpu]
+
+ emom_ollama:
+ image: ollama/ollama:latest
+ container_name: emom_ollama_engine
+ restart: unless-stopped
+ networks:
+ - emom_mesh
+ volumes:
+ - ~/.ollama:/root/.ollama
+ deploy:
+ resources:
+ reservations:
+ devices:
+ - driver: nvidia
+ count: 1
+ capabilities: [gpu]
\ No newline at end of file
diff --git a/docker/Dockerfile.api b/docker/Dockerfile.api
new file mode 100644
index 0000000..d689560
--- /dev/null
+++ b/docker/Dockerfile.api
@@ -0,0 +1,20 @@
+FROM pytorch/pytorch:2.2.1-cuda12.1-cudnn8-runtime
+
+ENV PYTHONDONTWRITEBYTECODE=1
+ENV PYTHONUNBUFFERED=1
+
+WORKDIR /app
+
+RUN apt-get update && apt-get install -y \
+ libglib2.0-0 libsm6 libxext6 libxrender-dev \
+ && rm -rf /var/lib/apt/lists/*
+
+# Устанавливаем зависимости для ML и API напрямую, чтобы не плодить requirements.txt
+RUN pip install --no-cache-dir fastapi uvicorn timm scikit-learn pandas joblib python-multipart
+
+COPY src/ /app/src/
+
+EXPOSE 8000
+
+# Запускаем FastAPI сервер
+CMD ["uvicorn", "src.api:app", "--host", "0.0.0.0", "--port", "8000"]
\ No newline at end of file
diff --git a/docker/Dockerfile.ui b/docker/Dockerfile.ui
new file mode 100644
index 0000000..5eeb001
--- /dev/null
+++ b/docker/Dockerfile.ui
@@ -0,0 +1,15 @@
+FROM python:3.12-slim
+
+ENV PYTHONDONTWRITEBYTECODE=1
+ENV PYTHONUNBUFFERED=1
+
+WORKDIR /app
+
+# Только легкие зависимости для отображения интерфейса
+RUN pip install --no-cache-dir streamlit==1.32.0 requests pandas pillow
+
+COPY src/ /app/src/
+
+EXPOSE 8080
+
+CMD ["streamlit", "run", "src/main.py", "--server.port", "8080", "--server.address", "0.0.0.0"]
\ No newline at end of file
diff --git a/doecker-compose.yml b/doecker-compose.yml
deleted file mode 100644
index 2365e05..0000000
--- a/doecker-compose.yml
+++ /dev/null
@@ -1,64 +0,0 @@
-version: '3.8'
-
-# Определение общих сетей для изоляции трафика
-networks:
- ai_mesh:
- driver: bridge
-
-services:
- # ----------------------------------------------------
- # SERVICE 1: Frontend (Пользовательский интерфейс)
- # Не требует GPU, может быть вынесен на отдельный сервер
- # ----------------------------------------------------
- web_ui:
- build:
- context: .
- dockerfile: Dockerfile
- container_name: emom_frontend
- restart: always
- ports:
- - "8080:8080"
- networks:
- - ai_mesh
- environment:
- - STREAMLIT_RUN=1
- # Указываем UI, где искать LLM-бэкенд (внутри Docker-сети)
- - OLLAMA_HOST=http://llm_backend:11434
- volumes:
- - ./src:/app/src
- # Модели пока остаются здесь, так как код монолитный,
- # но архитектурно сервис уже изолирован
- - /home/zin/projects/Thesis/src/emoset_resnet50_best.pth:/app/emoset_resnet50_best.pth:ro
- - /home/zin/projects/Thesis/src/music_engine/va_regressor.pkl:/app/src/music_engine/va_regressor.pkl:ro
- - /home/zin/projects/Thesis/dataset/DEAM:/app/dataset/DEAM:ro
- # Временно оставляем GPU для PyTorch (пока он не вынесен в API)
- deploy:
- resources:
- reservations:
- devices:
- - driver: nvidia
- count: 1
- capabilities: [gpu]
-
- # ----------------------------------------------------
- # SERVICE 2: LLM Inference Backend (Ollama)
- # Изолированный сервис для языковой модели на GPU
- # ----------------------------------------------------
- llm_backend:
- image: ollama/ollama:latest
- container_name: ollama_gpu_inference
- restart: always
- networks:
- - ai_mesh
- ports:
- - "11434:11434"
- volumes:
- # Проброс локальных моделей Ollama, чтобы не качать их заново внутри докера
- - ~/.ollama:/root/.ollama
- deploy:
- resources:
- reservations:
- devices:
- - driver: nvidia
- count: 1
- capabilities: [gpu]
\ No newline at end of file
diff --git a/src/api.py b/src/api.py
new file mode 100644
index 0000000..2874a18
--- /dev/null
+++ b/src/api.py
@@ -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)}")
\ No newline at end of file
diff --git a/src/main.py b/src/main.py
index b0da338..30de989 100644
--- a/src/main.py
+++ b/src/main.py
@@ -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(
- """
-
- """,
- height=0,
- width=0,
+# Конфигурация UI
+st.set_page_config(
+ page_title="EmoM | EmotionMusic",
+ layout="wide",
+ initial_sidebar_state="collapsed"
)
st.markdown(
"""
""",
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)
\ No newline at end of file
+if __name__ == "__main__":
+ main()
\ No newline at end of file