trashchenkov commited on
Commit
b0487df
·
verified ·
1 Parent(s): bfdbdd7

Upload 7 files

Browse files
Files changed (7) hide show
  1. README.md +66 -7
  2. app.py +245 -0
  3. dino_analyzer.py +202 -0
  4. models.py +19 -0
  5. requirements.txt +6 -0
  6. sample_dino.jpg +0 -0
  7. utils.py +180 -0
README.md CHANGED
@@ -1,13 +1,72 @@
1
  ---
2
- title: Dino Analyzer
3
- emoji: 📊
4
- colorFrom: pink
5
- colorTo: indigo
6
- sdk: gradio
7
- sdk_version: 5.31.0
8
  app_file: app.py
9
  pinned: false
10
  license: mit
11
  ---
12
 
13
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ title: DINO - Dinosaur Analyzer
3
+ emoji: 🦕
4
+ colorFrom: green
5
+ colorTo: gray
6
+ sdk: streamlit
7
+ sdk_version: 1.45.1
8
  app_file: app.py
9
  pinned: false
10
  license: mit
11
  ---
12
 
13
+ # 🦕 DINO - Dinosaur Image Neural Observer
14
+
15
+ An innovative **Image ORM** project that uses **Gemini API** to analyze plastic dinosaur figurines and extract structured information including species identification, color analysis, geological periods, and educational facts.
16
+
17
+ ## 🚀 Try it Live!
18
+
19
+ Upload a photo of your dinosaur figurine and get instant analysis with:
20
+ - 🔍 **Species identification** - Scientific name and classification
21
+ - 🎨 **Color analysis** - Description of the figurine's colors
22
+ - ⏰ **Geological period** - When this dinosaur lived
23
+ - 📚 **Educational facts** - Interesting information about the species
24
+
25
+ ## 🔑 API Key Required
26
+
27
+ This app requires a **Gemini API key** to function. You can:
28
+ 1. Get your free API key at [Google AI Studio](https://ai.google.dev/)
29
+ 2. Enter it in the sidebar when using the app
30
+
31
+ ## 🛠️ Technology Stack
32
+
33
+ - **AI Model**: Google Gemini API for image analysis
34
+ - **Framework**: Streamlit for web interface
35
+ - **Language**: Python with Pydantic for data validation
36
+ - **Image Processing**: PIL/Pillow for image optimization
37
+
38
+ ## 📊 Features
39
+
40
+ - ✅ **Real-time analysis** of dinosaur figurines
41
+ - ✅ **Structured JSON output** with consistent data format
42
+ - ✅ **Russian language support** for AI responses
43
+ - ✅ **Modern web interface** with drag-and-drop upload
44
+ - ✅ **Export functionality** for analysis results
45
+ - ✅ **Error handling** and validation
46
+
47
+ ## 🔗 Source Code
48
+
49
+ Full source code, documentation, and local setup instructions available on GitHub:
50
+ [https://github.com/trashchenkov/dino](https://github.com/trashchenkov/dino)
51
+
52
+ ## 📝 Example Output
53
+
54
+ ```json
55
+ {
56
+ "species_name": "Тираннозавр Рекс",
57
+ "color_description": "зеленый с коричневыми полосами",
58
+ "geological_period": "Поздний меловой период",
59
+ "brief_info": "Один из крупнейших наземных хищников всех времен"
60
+ }
61
+ ```
62
+
63
+ ## 💡 Tips for Best Results
64
+
65
+ - Use clear, well-lit photos
66
+ - Ensure the dinosaur figurine is clearly visible
67
+ - Avoid strong shadows or reflections
68
+ - Supported formats: PNG, JPG, JPEG
69
+
70
+ ---
71
+
72
+ **Made with ❤️ for dinosaur enthusiasts and AI technology lovers!**
app.py ADDED
@@ -0,0 +1,245 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import os
3
+ from PIL import Image
4
+ from dino_analyzer import DinosaurAnalyzer
5
+ from models import DinosaurInfo
6
+ from utils import format_file_size
7
+
8
+ def main():
9
+ """Основная функция веб-приложения для Hugging Face Spaces."""
10
+
11
+ # Настройка страницы
12
+ st.set_page_config(
13
+ page_title="🦕 DINO - Анализатор динозавров",
14
+ page_icon="🦕",
15
+ layout="wide",
16
+ initial_sidebar_state="expanded"
17
+ )
18
+
19
+ # Заголовок
20
+ st.title("🦕 DINO - Анализатор динозавров")
21
+ st.markdown("### Загрузите фотографию фигурки динозавра и получите подробную информацию!")
22
+
23
+ # Информация о проекте в верхней части
24
+ with st.expander("ℹ️ О проекте DINO", expanded=False):
25
+ st.markdown("""
26
+ **DINO (Dinosaur Image Neural Observer)** - это инновационный проект, демонстрирующий концепцию "Image ORM"
27
+ с использованием Gemini API.
28
+
29
+ **Возможности:**
30
+ - 🔍 **Идентификация вида динозавра** по изображению фигурки
31
+ - 🎨 **Анализ цветов** пластиковой фигурки
32
+ - ⏰ **Геологический период** обитания динозавра
33
+ - 📚 **Интересные факты** о виде
34
+
35
+ **🔗 Исходный код:** [GitHub Repository](https://github.com/trashchenkov/dino)
36
+ """)
37
+
38
+ # Боковая панель с настройками
39
+ with st.sidebar:
40
+ st.header("⚙️ Настройки")
41
+
42
+ # Проверяем наличие API ключа в секретах HF
43
+ api_key = st.secrets.get("GEMINI_API_KEY", None)
44
+
45
+ if api_key:
46
+ st.success("✅ API ключ настроен администратором")
47
+ st.info("💡 Вы можете сразу использовать приложение!")
48
+ else:
49
+ st.warning("⚠️ API ключ не настроен")
50
+ # Ввод API ключа
51
+ api_key = st.text_input(
52
+ "🔑 Введите ваш Gemini API Key:",
53
+ type="password",
54
+ placeholder="Введите ваш API ключ здесь...",
55
+ help="Получите API ключ на https://ai.google.dev/"
56
+ )
57
+
58
+ if not api_key:
59
+ st.error("❌ Для работы приложения необходим API ключ Gemini")
60
+ st.markdown("""
61
+ **Как получить API ключ:**
62
+ 1. Перейдите на [Google AI Studio](https://ai.google.dev/)
63
+ 2. Войдите в аккаунт Google
64
+ 3. Создайте новый API ключ
65
+ 4. Введите его в поле выше
66
+ """)
67
+
68
+ st.markdown("---")
69
+
70
+ # Информация о проекте
71
+ st.markdown("""
72
+ **Советы для лучших результатов:**
73
+ - Используйте четкие фотографии
74
+ - Хорошее освещение важно
75
+ - Фигурка должна быть хорошо видна
76
+ - Избегайте сильных теней
77
+
78
+ **Поддерживаемые форматы:**
79
+ - PNG, JPG, JPEG
80
+ - Максимум 200MB
81
+ """)
82
+
83
+ # Основная область
84
+ if api_key:
85
+ col1, col2 = st.columns([1, 1])
86
+
87
+ with col1:
88
+ st.header("📸 Загрузка изображения")
89
+
90
+ uploaded_file = st.file_uploader(
91
+ "Выберите изображение фигурки динозавра",
92
+ type=['png', 'jpg', 'jpeg'],
93
+ help="Поддерживаемые форматы: PNG, JPG, JPEG"
94
+ )
95
+
96
+ if uploaded_file is not None:
97
+ # Отображение загруженного изображения
98
+ image = Image.open(uploaded_file)
99
+ st.image(image, caption="Загруженное изображение", use_container_width=True)
100
+
101
+ # Информация о файле
102
+ file_size = len(uploaded_file.getvalue())
103
+ st.info(f"📁 Размер файла: {format_file_size(file_size)}")
104
+ st.info(f"📐 Размеры: {image.width} × {image.height} пикселей")
105
+
106
+ # Кнопка анализа
107
+ if st.button("🔍 Анализировать динозавра", type="primary", use_container_width=True):
108
+ analyze_dinosaur(image, api_key, col2)
109
+
110
+ with col2:
111
+ st.header("📊 Результаты анализа")
112
+ st.info("👆 Загрузите изображение и нажмите 'Анализировать' для получения результатов")
113
+ else:
114
+ st.warning("⚠️ Для использования приложения необходим API ключ Gemini")
115
+
116
+
117
+ def analyze_dinosaur(image: Image.Image, api_key: str, result_column):
118
+ """
119
+ Анализирует изображение динозавра и отображает результаты.
120
+
121
+ Args:
122
+ image: PIL изображение
123
+ api_key: API ключ для Gemini
124
+ result_column: Столбец Streamlit для отображения результатов
125
+ """
126
+ with result_column:
127
+ # Индикатор загрузки
128
+ with st.spinner("🔍 Анализируем динозавра..."):
129
+ try:
130
+ # Создаем анализатор и анализируем изображение
131
+ analyzer = DinosaurAnalyzer(api_key=api_key)
132
+ result = analyzer.analyze_image_from_pil(image)
133
+
134
+ if result:
135
+ display_results(result)
136
+ else:
137
+ st.error("❌ Не удалось проанализировать изображение")
138
+ st.info("💡 Попробуйте другое изображение или проверьте качество фото")
139
+
140
+ except ValueError as e:
141
+ st.error(f"❌ Ошибка конфигурации: {e}")
142
+ st.info("💡 Убедитесь, что API ключ корректный")
143
+ except Exception as e:
144
+ st.error(f"❌ Произошла ошибка: {e}")
145
+ st.info("💡 Попробуйте еще раз или проверьте подключение к интернету")
146
+
147
+
148
+ def display_results(info: DinosaurInfo):
149
+ """
150
+ Отображает результаты анализа в читаемом формате.
151
+
152
+ Args:
153
+ info: Информация о динозавре
154
+ """
155
+ # Основная информация
156
+ st.success("✅ Анализ завершен!")
157
+
158
+ # Используем более читаемый способ отображения данных
159
+ st.subheader("📛 Вид динозавра")
160
+ st.write(f"**{info.species_name}**")
161
+
162
+ st.subheader("🎨 Цвет фигурки")
163
+ st.write(f"{info.color_description}")
164
+
165
+ st.subheader("⏰ Геологический период")
166
+ st.write(f"{info.geological_period}")
167
+
168
+ # Интересный факт в отдельном блоке
169
+ st.subheader("📚 Интересный факт")
170
+ st.info(info.brief_info)
171
+
172
+ # Дополнительные действия
173
+ st.markdown("---")
174
+ st.subheader("💾 Экспорт данных")
175
+
176
+ col_actions1, col_actions2 = st.columns(2)
177
+
178
+ with col_actions1:
179
+ if st.button("📋 Показать текстовые данные", use_container_width=True):
180
+ data_text = f"""Вид: {info.species_name}
181
+ Цвет фигурки: {info.color_description}
182
+ Период: {info.geological_period}
183
+ Интересный факт: {info.brief_info}"""
184
+ st.text_area("Данные для копирования:", data_text, height=150)
185
+
186
+ with col_actions2:
187
+ json_data = info.model_dump_json(indent=2)
188
+ st.download_button(
189
+ label="💾 Скачать JSON",
190
+ data=json_data,
191
+ file_name="dinosaur_info.json",
192
+ mime="application/json",
193
+ use_container_width=True
194
+ )
195
+
196
+ # JSON данные (сворачиваемые)
197
+ with st.expander("🔧 Детальные данные (JSON)"):
198
+ st.json(info.model_dump())
199
+
200
+
201
+ # Футер
202
+ def show_footer():
203
+ """Отображает футер с дополнительной информацией."""
204
+ st.markdown("---")
205
+
206
+ # Статистика и дополнительная информация
207
+ col1, col2, col3 = st.columns(3)
208
+
209
+ with col1:
210
+ st.markdown("""
211
+ **🔬 Технологии:**
212
+ - Gemini AI API
213
+ - Streamlit
214
+ - Python
215
+ - Pydantic
216
+ """)
217
+
218
+ with col2:
219
+ st.markdown("""
220
+ **📊 Возможности:**
221
+ - Распознавание видов
222
+ - Анализ цветов
223
+ - Исторические данные
224
+ - Образовательные факты
225
+ """)
226
+
227
+ with col3:
228
+ st.markdown("""
229
+ **🚀 Ссылки:**
230
+ - [GitHub](https://github.com/trashchenkov/dino)
231
+ - [Документация](https://github.com/trashchenkov/dino#readme)
232
+ - [Gemini API](https://ai.google.dev/)
233
+ """)
234
+
235
+ st.markdown("""
236
+ <div style='text-align: center; margin-top: 2rem;'>
237
+ <p>🦕 <strong>DINO Project</strong> - Image ORM для анализа динозавров</p>
238
+ <p>Powered by <strong>Gemini API</strong> • Deployed on <strong>Hugging Face Spaces</strong> 🚀</p>
239
+ </div>
240
+ """, unsafe_allow_html=True)
241
+
242
+
243
+ if __name__ == "__main__":
244
+ main()
245
+ show_footer()
dino_analyzer.py ADDED
@@ -0,0 +1,202 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import json
3
+ from typing import Optional
4
+ from PIL import Image
5
+ import google.generativeai as genai
6
+
7
+ # Пытаемся импортировать dotenv, если доступен (для локальной разработки)
8
+ try:
9
+ from dotenv import load_dotenv
10
+ load_dotenv()
11
+ except ImportError:
12
+ # На HF Spaces dotenv может быть недоступен, это нормально
13
+ pass
14
+
15
+ from models import DinosaurInfo
16
+ from utils import optimize_image_for_api, save_temp_image, cleanup_temp_file, validate_image_file
17
+
18
+ class DinosaurAnalyzer:
19
+ """Класс для анализа изображений динозавров с помощью Gemini API."""
20
+
21
+ def __init__(self, api_key: Optional[str] = None):
22
+ """
23
+ Инициализация анализатора.
24
+
25
+ Args:
26
+ api_key: API ключ для Gemini. Если не указан, будет взят из переменной окружения.
27
+ """
28
+ if api_key is None:
29
+ api_key = os.getenv('GEMINI_API_KEY')
30
+
31
+ if not api_key:
32
+ raise ValueError(
33
+ "API ключ не найден. Укажите его в параметре api_key или "
34
+ "установите переменную окружения GEMINI_API_KEY"
35
+ )
36
+
37
+ genai.configure(api_key=api_key)
38
+
39
+ # Инициализация модели с системной инструкцией
40
+ self.model = genai.GenerativeModel(
41
+ model_name='gemini-1.5-flash-latest',
42
+ generation_config={
43
+ "response_mime_type": "application/json",
44
+ "response_schema": DinosaurInfo
45
+ },
46
+ system_instruction="""
47
+ ВАЖНО: Отвечай ТОЛЬКО на РУССКОМ языке! Весь твой ответ должен быть на русском языке.
48
+
49
+ Ты — эксперт-палеонтолог и ИИ для анализа изображений пластиковых фигурок динозавров.
50
+ Твоя задача — идентифицировать вид динозавра по фотографии игрушечной фигурки.
51
+
52
+ ИНСТРУКЦИИ ПО АНАЛИЗУ:
53
+ 1. 🔍 ОПРЕДЕЛИ ВИД: Внимательно изучи форму тела, голову, конечности, хвост, характерные особенности для определения точного вида динозавра. Назови вид на РУССКОМ языке.
54
+
55
+ 2. 🎨 ОПИШИ ЦВЕТА: Опиши основные цвета именно этой пластиковой фигурки (как они выглядят на фото). НЕ описывай реальные цвета динозавра, а только то, что видишь на игрушке.
56
+
57
+ 3. ⏰ УКАЖИ ПЕРИОД: Определи геологический период, в котором жил этот вид динозавра. Ответ дай на РУССКОМ языке (например, "Юрский период", "Поздний меловой период").
58
+
59
+ 4. 📚 РАССКАЖИ ФАКТ: Поделись интересным фактом об этом виде динозавра. Факт должен быть познавательным и написан на РУССКОМ языке.
60
+
61
+ ВАЖНЫЕ ТРЕБОВАНИЯ:
62
+ - ВСЕ поля заполняй только на РУССКОМ языке
63
+ - Если не можешь точно определить вид, напиши "Неопределенный вид" или опиши как "Динозавр семейства..."
64
+ - Для цветов используй простые русские названия (зеленый, коричневый, желтый и т.д.)
65
+ - Геологические периоды называй по-русски
66
+ - Факты должны быть интересными и понятными
67
+
68
+ Верни всю информацию в указанной JSON-схеме НА РУССКОМ ЯЗЫКЕ.
69
+ """
70
+ )
71
+
72
+ def analyze_image(self, image_path: str) -> Optional[DinosaurInfo]:
73
+ """
74
+ Анализирует изображение динозавра и возвращает структурированную информацию.
75
+
76
+ Args:
77
+ image_path: Путь к файлу изображения
78
+
79
+ Returns:
80
+ DinosaurInfo объект с информацией о динозавре или None при ошибке
81
+ """
82
+ try:
83
+ # Проверяе�� существование и валидность файла
84
+ if not os.path.exists(image_path):
85
+ print(f"Ошибка: файл {image_path} не найден")
86
+ return None
87
+
88
+ if not validate_image_file(image_path):
89
+ print(f"Ошибка: файл {image_path} не является корректным изображением")
90
+ return None
91
+
92
+ # Загружаем и оптимизируем изображение
93
+ img = Image.open(image_path)
94
+ optimized_img = optimize_image_for_api(img)
95
+
96
+ # Отправляем запрос к Gemini API
97
+ response = self.model.generate_content([optimized_img])
98
+
99
+ # Парсим JSON ответ в объект DinosaurInfo
100
+ dino_data = DinosaurInfo.model_validate_json(response.text)
101
+ return dino_data
102
+
103
+ except json.JSONDecodeError as e:
104
+ print(f"Ошибка парсинга JSON: {e}")
105
+ print(f"Ответ модели: {response.text}")
106
+ return None
107
+ except Exception as e:
108
+ print(f"Произошла ошибка при анализе изображения: {e}")
109
+ if 'response' in locals() and hasattr(response, 'prompt_feedback'):
110
+ print(f"Обратная связь: {response.prompt_feedback}")
111
+ return None
112
+
113
+ def analyze_image_from_pil(self, image: Image.Image) -> Optional[DinosaurInfo]:
114
+ """
115
+ Анализирует изображение динозавра из PIL.Image объекта.
116
+
117
+ Args:
118
+ image: PIL.Image объект
119
+
120
+ Returns:
121
+ DinosaurInfo объект с информацией о динозавре или None при ошибке
122
+ """
123
+ try:
124
+ print(f"📸 Анализируем изображение размера {image.size}...")
125
+
126
+ # Оптимизируем изображение для API
127
+ optimized_image = optimize_image_for_api(image)
128
+ print(f"✅ Изображение оптимизировано до размера {optimized_image.size}")
129
+
130
+ # Отправляем запрос к Gemini API
131
+ response = self.model.generate_content([
132
+ "Проанализируй эту фигурку динозавра согласно инструкциям:",
133
+ optimized_image
134
+ ])
135
+
136
+ # Парсим JSON ответ
137
+ result_text = response.text.strip()
138
+ print(f"📝 Получен ответ от API: {result_text[:100]}...")
139
+
140
+ # Парсим ответ как JSON и создаем объект DinosaurInfo
141
+ result_data = json.loads(result_text)
142
+ dinosaur_info = DinosaurInfo(**result_data)
143
+
144
+ print(f"🦕 Успешно идентифицирован: {dinosaur_info.species_name}")
145
+ return dinosaur_info
146
+
147
+ except json.JSONDecodeError as e:
148
+ print(f"❌ Ошибка парсинга JSON: {e}")
149
+ print(f"📄 Полученный ответ: {result_text}")
150
+ return None
151
+ except Exception as e:
152
+ print(f"❌ Ошибка при анализе изображения: {e}")
153
+ return None
154
+
155
+ def print_dinosaur_info(self, info: DinosaurInfo) -> None:
156
+ """
157
+ Красиво выводит информацию о динозавре.
158
+
159
+ Args:
160
+ info: Объект с информацией о динозавре
161
+ """
162
+ separator = "=" * 50
163
+ print(f"\n{separator}")
164
+ print("🦕 ИНФОРМАЦИЯ О ДИНОЗАВРЕ 🦕")
165
+ print(f"{separator}")
166
+ print(f"📛 Вид: {info.species_name}")
167
+ print(f"🎨 Цвет фигурки: {info.color_description}")
168
+ print(f"⏰ Период: {info.geological_period}")
169
+ print(f"📚 Интересный факт: {info.brief_info}")
170
+ print(f"{separator}\n")
171
+
172
+
173
+ def main():
174
+ """Основная функция для демонстрации работы анализатора."""
175
+ # Пример использования
176
+ try:
177
+ analyzer = DinosaurAnalyzer()
178
+
179
+ # Замените на путь к вашему изображению динозавра
180
+ image_path = input("Введите путь к изображению динозавра: ").strip()
181
+
182
+ if not image_path:
183
+ print("Путь к изображению не указан")
184
+ return
185
+
186
+ print("🔍 Анализируем изображение...")
187
+ info = analyzer.analyze_image(image_path)
188
+
189
+ if info:
190
+ analyzer.print_dinosaur_info(info)
191
+ else:
192
+ print("❌ Не удалось проанализировать изображение")
193
+
194
+ except ValueError as e:
195
+ print(f"❌ Ошибка конфигурации: {e}")
196
+ print("💡 Убедитесь, что у вас есть API ключ для Gemini")
197
+ except Exception as e:
198
+ print(f"❌ Неожиданная ошибка: {e}")
199
+
200
+
201
+ if __name__ == "__main__":
202
+ main()
models.py ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from pydantic import BaseModel, Field
2
+ from typing import Optional
3
+
4
+
5
+ class DinosaurInfo(BaseModel):
6
+ """Модель для структурированной информации о динозавре."""
7
+
8
+ species_name: str = Field(
9
+ description="Научное или общепринятое название вида динозавра"
10
+ )
11
+ color_description: str = Field(
12
+ description="Описание основных цветов фигурки динозавра"
13
+ )
14
+ geological_period: str = Field(
15
+ description="Геологический период, в котором обитал этот вид динозавра (например, Юрский, Меловой)"
16
+ )
17
+ brief_info: str = Field(
18
+ description="Краткая интересная информация о динозавре (1-2 предложения)"
19
+ )
requirements.txt ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ google-generativeai>=0.8.0
2
+ Pillow>=10.0.0
3
+ pydantic>=2.5.0
4
+ python-dotenv>=1.0.0
5
+ streamlit>=1.28.0
6
+ requests>=2.31.0
sample_dino.jpg ADDED
utils.py ADDED
@@ -0,0 +1,180 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from typing import Tuple, Optional
3
+ from PIL import Image, ImageOps
4
+ import io
5
+
6
+
7
+ def validate_image_file(file_path: str) -> bool:
8
+ """
9
+ Проверяет, является ли файл корректным изображением.
10
+
11
+ Args:
12
+ file_path: Путь к файлу изображения
13
+
14
+ Returns:
15
+ True если файл является корректным изображением
16
+ """
17
+ try:
18
+ with Image.open(file_path) as img:
19
+ img.verify()
20
+ return True
21
+ except Exception:
22
+ return False
23
+
24
+
25
+ def get_image_info(file_path: str) -> Optional[dict]:
26
+ """
27
+ Получает информацию об изображении.
28
+
29
+ Args:
30
+ file_path: Путь к файлу изображения
31
+
32
+ Returns:
33
+ Словарь с информацией об изображении или None при ошибке
34
+ """
35
+ try:
36
+ with Image.open(file_path) as img:
37
+ return {
38
+ "width": img.width,
39
+ "height": img.height,
40
+ "format": img.format,
41
+ "mode": img.mode,
42
+ "size_bytes": os.path.getsize(file_path)
43
+ }
44
+ except Exception:
45
+ return None
46
+
47
+
48
+ def resize_image_if_needed(image: Image.Image, max_size: Tuple[int, int] = (1024, 1024)) -> Image.Image:
49
+ """
50
+ Изменяет размер изображения, если оно слишком большое.
51
+
52
+ Args:
53
+ image: PIL изображение
54
+ max_size: Максимальный размер (ширина, высота)
55
+
56
+ Returns:
57
+ Изображение с измененным размером (если необходимо)
58
+ """
59
+ if image.width > max_size[0] or image.height > max_size[1]:
60
+ # Сохраняем пропорции
61
+ image.thumbnail(max_size, Image.Resampling.LANCZOS)
62
+ return image
63
+
64
+
65
+ def optimize_image_for_api(image: Image.Image, quality: int = 85) -> Image.Image:
66
+ """
67
+ Оптимизирует изображение для отправки в API.
68
+
69
+ Args:
70
+ image: PIL изображение
71
+ quality: Качество сжатия JPEG (1-100)
72
+
73
+ Returns:
74
+ Оптимизированное изображение
75
+ """
76
+ # Изменяем размер если нужно
77
+ optimized = resize_image_if_needed(image)
78
+
79
+ # Автоматически поворачиваем на основе EXIF данных
80
+ optimized = ImageOps.exif_transpose(optimized)
81
+
82
+ # Конвертируем в RGB если изображение в RGBA или другом формате
83
+ if optimized.mode in ('RGBA', 'LA', 'P'):
84
+ # Создаем белый фон для прозрачных изображений
85
+ background = Image.new('RGB', optimized.size, (255, 255, 255))
86
+ if optimized.mode == 'P':
87
+ optimized = optimized.convert('RGBA')
88
+ background.paste(optimized, mask=optimized.split()[-1] if optimized.mode == 'RGBA' else None)
89
+ optimized = background
90
+ elif optimized.mode != 'RGB':
91
+ optimized = optimized.convert('RGB')
92
+
93
+ return optimized
94
+
95
+
96
+ def save_temp_image(image: Image.Image, prefix: str = "temp_dino") -> str:
97
+ """
98
+ Сохраняет временное изображение и возвращает путь к нему.
99
+
100
+ Args:
101
+ image: PIL изображение
102
+ prefix: Префикс для имени файла
103
+
104
+ Returns:
105
+ Путь к временному файлу
106
+ """
107
+ import tempfile
108
+ import uuid
109
+
110
+ # Создаем уникальное имя файла
111
+ temp_name = f"{prefix}_{uuid.uuid4().hex[:8]}.jpg"
112
+ temp_path = os.path.join(tempfile.gettempdir(), temp_name)
113
+
114
+ # Оптимизируем и сохраняем
115
+ optimized_image = optimize_image_for_api(image)
116
+ optimized_image.save(temp_path, "JPEG", quality=85, optimize=True)
117
+
118
+ return temp_path
119
+
120
+
121
+ def cleanup_temp_file(file_path: str) -> bool:
122
+ """
123
+ Удаляет временный файл.
124
+
125
+ Args:
126
+ file_path: Путь к файлу для удаления
127
+
128
+ Returns:
129
+ True если файл успешно удален
130
+ """
131
+ try:
132
+ if os.path.exists(file_path):
133
+ os.remove(file_path)
134
+ return True
135
+ return False
136
+ except Exception:
137
+ return False
138
+
139
+
140
+ def convert_bytes_to_image(image_bytes: bytes) -> Optional[Image.Image]:
141
+ """
142
+ Конвертирует байты в PIL изображение.
143
+
144
+ Args:
145
+ image_bytes: Байты изображения
146
+
147
+ Returns:
148
+ PIL изображение или None при ошибке
149
+ """
150
+ try:
151
+ return Image.open(io.BytesIO(image_bytes))
152
+ except Exception:
153
+ return None
154
+
155
+
156
+ def get_supported_formats() -> list:
157
+ """
158
+ Возвращает список поддерживаемых форматов изображений.
159
+
160
+ Returns:
161
+ Список расширений файлов
162
+ """
163
+ return ['.jpg', '.jpeg', '.png', '.bmp', '.gif', '.tiff', '.webp']
164
+
165
+
166
+ def format_file_size(size_bytes: int) -> str:
167
+ """
168
+ Форматирует размер файла в читаемый вид.
169
+
170
+ Args:
171
+ size_bytes: Размер в байтах
172
+
173
+ Returns:
174
+ Отформатированная строка размера
175
+ """
176
+ for unit in ['B', 'KB', 'MB', 'GB']:
177
+ if size_bytes < 1024.0:
178
+ return f"{size_bytes:.1f} {unit}"
179
+ size_bytes /= 1024.0
180
+ return f"{size_bytes:.1f} TB"