From 9316bd059f87be2dc62e612e068406567ed4a97d Mon Sep 17 00:00:00 2001 From: acederys Date: Sat, 25 May 2024 23:40:51 +0500 Subject: [PATCH 1/4] =?UTF-8?q?=D0=A1=D0=B4=D0=B5=D0=BB=D0=B0=D0=BD=D0=BE?= =?UTF-8?q?=20=D1=80=D0=B0=D0=B7=D0=B1=D0=B8=D0=B0=D0=B5=D0=BD=D0=B8=D0=B5?= =?UTF-8?q?=20=D0=BF=D0=BE=20=D1=84=D0=B0=D0=B9=D0=BB=D0=B0=D0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- exceptions.py | 11 +++ image_classification_streamlit.py => main.py | 72 ++------------------ model.py | 13 ++++ utils.py | 31 +++++++++ 4 files changed, 62 insertions(+), 65 deletions(-) create mode 100644 exceptions.py rename image_classification_streamlit.py => main.py (51%) create mode 100644 model.py create mode 100644 utils.py diff --git a/exceptions.py b/exceptions.py new file mode 100644 index 0000000..83cc6b2 --- /dev/null +++ b/exceptions.py @@ -0,0 +1,11 @@ +"Определение пользовательских исключений." + + +class MissingSourceError(Exception): + """Класс представляет ошибку, возникающую при отсутствии источника изображения.""" + pass + + +class TwoSourcesError(Exception): + """Класс представляет ошибку, возникающую при указании двух источников изображений.""" + pass diff --git a/image_classification_streamlit.py b/main.py similarity index 51% rename from image_classification_streamlit.py rename to main.py index abcf3b5..810d6a2 100644 --- a/image_classification_streamlit.py +++ b/main.py @@ -1,39 +1,11 @@ -"""Приложение Streamlit для классификации изображений.""" +"Файл для запуска приложения streamlit." -import requests import streamlit as st -import translators as ts -from PIL import Image, UnidentifiedImageError +from PIL import UnidentifiedImageError from requests.exceptions import MissingSchema -from transformers import ViTForImageClassification, ViTImageProcessor - - -class MissingSourceError(Exception): - """Класс представляет ошибку, - возникающую при отсутствии - источника изображения.""" - pass - - -class TwoSourcesError(Exception): - """Класс представляет ошибку, - возникающую при указании - двух источников изображений.""" - pass - - -@st.cache_resource -def load_model(): - """Загрузка модели""" - return (ViTForImageClassification - .from_pretrained("google/vit-base-patch16-224")) - - -@st.cache_resource -def load_processor(): - """Загрузка процессора для обработки изображений.""" - return (ViTImageProcessor - .from_pretrained("google/vit-base-patch16-224")) +from utils import load_image_from_url, load_image_from_file, image_classification, translate_text +from model import load_model, load_processor +from exceptions import MissingSourceError, TwoSourcesError def get_image_link(): @@ -46,33 +18,6 @@ def get_image_file(): return st.file_uploader("Или загрузите изображение из файла") -def load_image_from_url(url): - """Загрузка изображения из указанного URL-адреса - с помощью библиотеки requests.""" - img = Image.open(requests.get(url, stream=True).raw) - return img - - -def load_image_from_file(file): - """Загрузка изображения из файла.""" - img = Image.open(file) - return img - - -def image_classification(picture): - """Обработка и распознавание изображения. - - Принимает изображение, преобразует его в требуемый формат - с помощью процессора, пропускает его через модель, - получает вероятности классов и возвращает предсказанный класс. - """ - inputs = processor(images=picture, return_tensors="pt") - outputs = model(**inputs) - logits = outputs.logits - predicted_class_idx = logits.argmax(-1).item() - return model.config.id2label[predicted_class_idx] - - def show_results(results): """Вывод результатов""" st.write(results) @@ -101,11 +46,8 @@ def show_results(results): raise MissingSourceError st.image(loaded_image) with st.spinner("Идет обработка... Пожалуйста, подождите..."): - result = image_classification(loaded_image) - translated_result = ts.translate_text(result, - translator="bing", - from_language="en", - to_language="ru") + result = image_classification(loaded_image, processor, model) + translated_result = translate_text(result, "en", "ru") st.markdown(f"Результаты распознавания: {translated_result}") except MissingSourceError: st.error( diff --git a/model.py b/model.py new file mode 100644 index 0000000..4478c0c --- /dev/null +++ b/model.py @@ -0,0 +1,13 @@ +"Загрузка и работа с моделью и процессором." + +from transformers import ViTForImageClassification, ViTImageProcessor + + +def load_model(): + """Загрузка модели.""" + return ViTForImageClassification.from_pretrained("google/vit-base-patch16-224") + + +def load_processor(): + """Загрузка процессора для обработки изображений.""" + return ViTImageProcessor.from_pretrained("google/vit-base-patch16-224") diff --git a/utils.py b/utils.py new file mode 100644 index 0000000..31b3349 --- /dev/null +++ b/utils.py @@ -0,0 +1,31 @@ +"Вспомогательные функции для обработки изображений и работы с моделью." + +import requests +from PIL import Image +import translators as ts + + +def load_image_from_url(url): + """Загрузка изображения из указанного URL-адреса.""" + img = Image.open(requests.get(url, stream=True).raw) + return img + + +def load_image_from_file(file): + """Загрузка изображения из файла.""" + img = Image.open(file) + return img + + +def image_classification(picture, processor, model): + """Обработка и распознавание изображения.""" + inputs = processor(images=picture, return_tensors="pt") + outputs = model(**inputs) + logits = outputs.logits + predicted_class_idx = logits.argmax(-1).item() + return model.config.id2label[predicted_class_idx] + + +def translate_text(text, from_language, to_language): + """Перевод текста с одного языка на другой.""" + return ts.translate_text(text, translator="bing", from_language=from_language, to_language=to_language) From 7c1e4d14256969343434581beccd7bbb236d55dd Mon Sep 17 00:00:00 2001 From: acederys Date: Sat, 25 May 2024 23:55:12 +0500 Subject: [PATCH 2/4] =?UTF-8?q?=D0=B8=D1=81=D0=BF=D1=80=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D1=8B=20=D1=82=D0=B5=D1=81=D1=82=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test_app.py | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/test_app.py b/test_app.py index 23671a1..54764b6 100644 --- a/test_app.py +++ b/test_app.py @@ -2,19 +2,17 @@ import time -from streamlit.testing.v1 import AppTest -from image_classification_streamlit import image_classification -from PIL import UnidentifiedImageError -from PIL import Image import io +from PIL import Image, UnidentifiedImageError +from streamlit.testing.v1 import AppTest +from utils import image_classification -at = AppTest.from_file("image_classification_streamlit.py", - default_timeout=1000).run() +# Запускаем приложение из main.py +at = AppTest.from_file("main.py", default_timeout=1000).run() def test_no_image_url(): - """Проверка ввода URL-адреса на объект, - который не является изображением""" + """Проверка ввода URL-адреса на объект, который не является изображением.""" at.text_input[0].set_value("https://www.google.com/").run() at.button[0].click().run() assert at.error[0].value == ( @@ -55,7 +53,7 @@ def test_correct_url(): def test_incorrect_url(): - """Проверка ввода некорректного URL-адреса""" + """Проверка ввода некорректного URL-адреса.""" at.text_input[0].set_value("1234").run() at.button[0].click().run() assert at.error[0].value == ( @@ -71,7 +69,16 @@ def test_correct_image_file(): test_image_bytes = file.read() test_image = Image.open(io.BytesIO(test_image_bytes)) try: - result = image_classification(test_image) + # Загрузка модели и процессора для тестирования + from model import load_model, load_processor + model = load_model() + processor = load_processor() + + result = image_classification(test_image, model, processor) assert result == "Egyptian cat" except UnidentifiedImageError: assert False, "Ошибка при обработке изображения" + +if __name__ == "__main__": + import pytest + pytest.main() From ad448c46d341d898de90374055d6dbecca1d578a Mon Sep 17 00:00:00 2001 From: acederys Date: Sun, 26 May 2024 00:38:11 +0500 Subject: [PATCH 3/4] =?UTF-8?q?=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D1=8B=20=D1=84=D0=B0=D0=B9=D0=BB=D1=8B=20=D0=B4?= =?UTF-8?q?=D0=BB=D1=8F=20=D1=82=D0=B5=D1=81=D1=82=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.py | 4 ++-- model.py | 10 ++++++++-- test_app.py | 35 +++++++++++++++-------------------- utils.py | 9 --------- 4 files changed, 25 insertions(+), 33 deletions(-) diff --git a/main.py b/main.py index 810d6a2..f160f52 100644 --- a/main.py +++ b/main.py @@ -3,8 +3,8 @@ import streamlit as st from PIL import UnidentifiedImageError from requests.exceptions import MissingSchema -from utils import load_image_from_url, load_image_from_file, image_classification, translate_text -from model import load_model, load_processor +from utils import load_image_from_url, load_image_from_file, translate_text +from model import load_model, load_processor, image_classification from exceptions import MissingSourceError, TwoSourcesError diff --git a/model.py b/model.py index 4478c0c..bf01982 100644 --- a/model.py +++ b/model.py @@ -2,12 +2,18 @@ from transformers import ViTForImageClassification, ViTImageProcessor - def load_model(): """Загрузка модели.""" return ViTForImageClassification.from_pretrained("google/vit-base-patch16-224") - def load_processor(): """Загрузка процессора для обработки изображений.""" return ViTImageProcessor.from_pretrained("google/vit-base-patch16-224") + +def image_classification(picture, processor, model): + """Обработка и распознавание изображения.""" + inputs = processor(images=picture, return_tensors="pt") + outputs = model(**inputs) + logits = outputs.logits + predicted_class_idx = logits.argmax(-1).item() + return model.config.id2label[predicted_class_idx] diff --git a/test_app.py b/test_app.py index 54764b6..577a448 100644 --- a/test_app.py +++ b/test_app.py @@ -1,18 +1,24 @@ """Тесты для проверки приложения Streamlit.""" import time - +import pytest import io from PIL import Image, UnidentifiedImageError from streamlit.testing.v1 import AppTest -from utils import image_classification +from model import image_classification, load_processor, load_model -# Запускаем приложение из main.py +# Создаем объект AppTest для тестирования приложения Streamlit at = AppTest.from_file("main.py", default_timeout=1000).run() +@pytest.fixture(scope="module") +def processor_and_model(): + """Фикстура для загрузки процессора и модели.""" + processor = load_processor() + model = load_model() + return processor, model def test_no_image_url(): - """Проверка ввода URL-адреса на объект, который не является изображением.""" + """Проверка ввода URL-адреса на объект, который не является изображением""" at.text_input[0].set_value("https://www.google.com/").run() at.button[0].click().run() assert at.error[0].value == ( @@ -21,7 +27,6 @@ def test_no_image_url(): "и попробуйте снова!" ) - def test_null_url(): """Проверка ввода пустого URL-адреса.""" at.text_input[0].set_value("").run() @@ -33,7 +38,6 @@ def test_null_url(): "и попробуйте снова!" ) - def test_correct_url(): """Проверка ввода корректного URL-адреса на изображение.""" ( @@ -46,14 +50,14 @@ def test_correct_url(): .run() ) at.button[0].click().run() - time.sleep(5) + time.sleep(5) # Добавляем ожидание 5 секунд assert at.markdown[0].value == ( "Результаты распознавания: табби, полосатый кот" ) def test_incorrect_url(): - """Проверка ввода некорректного URL-адреса.""" + """Проверка ввода некорректного URL-адреса""" at.text_input[0].set_value("1234").run() at.button[0].click().run() assert at.error[0].value == ( @@ -62,23 +66,14 @@ def test_incorrect_url(): "и попробуйте снова!" ) - -def test_correct_image_file(): +def test_correct_image_file(processor_and_model): """Проверка загрузки изображения через файл.""" + processor, model = processor_and_model with open("test_image.jpg", "rb") as file: test_image_bytes = file.read() test_image = Image.open(io.BytesIO(test_image_bytes)) try: - # Загрузка модели и процессора для тестирования - from model import load_model, load_processor - model = load_model() - processor = load_processor() - - result = image_classification(test_image, model, processor) + result = image_classification(test_image, processor, model) assert result == "Egyptian cat" except UnidentifiedImageError: assert False, "Ошибка при обработке изображения" - -if __name__ == "__main__": - import pytest - pytest.main() diff --git a/utils.py b/utils.py index 31b3349..16a2920 100644 --- a/utils.py +++ b/utils.py @@ -17,15 +17,6 @@ def load_image_from_file(file): return img -def image_classification(picture, processor, model): - """Обработка и распознавание изображения.""" - inputs = processor(images=picture, return_tensors="pt") - outputs = model(**inputs) - logits = outputs.logits - predicted_class_idx = logits.argmax(-1).item() - return model.config.id2label[predicted_class_idx] - - def translate_text(text, from_language, to_language): """Перевод текста с одного языка на другой.""" return ts.translate_text(text, translator="bing", from_language=from_language, to_language=to_language) From bba286f263fb6227bc096051d52e36b80a987e31 Mon Sep 17 00:00:00 2001 From: acederys Date: Sun, 26 May 2024 00:43:16 +0500 Subject: [PATCH 4/4] =?UTF-8?q?=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D1=8B=20=D0=B7=D0=B0=D0=BC=D0=B5=D1=87=D0=B0=D0=BD?= =?UTF-8?q?=D0=B8=D1=8F=20=20flake8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- model.py | 3 +++ test_app.py | 5 +++++ 2 files changed, 8 insertions(+) diff --git a/model.py b/model.py index bf01982..12825b4 100644 --- a/model.py +++ b/model.py @@ -2,14 +2,17 @@ from transformers import ViTForImageClassification, ViTImageProcessor + def load_model(): """Загрузка модели.""" return ViTForImageClassification.from_pretrained("google/vit-base-patch16-224") + def load_processor(): """Загрузка процессора для обработки изображений.""" return ViTImageProcessor.from_pretrained("google/vit-base-patch16-224") + def image_classification(picture, processor, model): """Обработка и распознавание изображения.""" inputs = processor(images=picture, return_tensors="pt") diff --git a/test_app.py b/test_app.py index 577a448..c8ba9c8 100644 --- a/test_app.py +++ b/test_app.py @@ -10,6 +10,7 @@ # Создаем объект AppTest для тестирования приложения Streamlit at = AppTest.from_file("main.py", default_timeout=1000).run() + @pytest.fixture(scope="module") def processor_and_model(): """Фикстура для загрузки процессора и модели.""" @@ -17,6 +18,7 @@ def processor_and_model(): model = load_model() return processor, model + def test_no_image_url(): """Проверка ввода URL-адреса на объект, который не является изображением""" at.text_input[0].set_value("https://www.google.com/").run() @@ -27,6 +29,7 @@ def test_no_image_url(): "и попробуйте снова!" ) + def test_null_url(): """Проверка ввода пустого URL-адреса.""" at.text_input[0].set_value("").run() @@ -38,6 +41,7 @@ def test_null_url(): "и попробуйте снова!" ) + def test_correct_url(): """Проверка ввода корректного URL-адреса на изображение.""" ( @@ -66,6 +70,7 @@ def test_incorrect_url(): "и попробуйте снова!" ) + def test_correct_image_file(processor_and_model): """Проверка загрузки изображения через файл.""" processor, model = processor_and_model