From d6b163cfd01f2d92334c46a4a8bdbdaa97ac62dc Mon Sep 17 00:00:00 2001 From: ardnaxelas Date: Sat, 28 Sep 2024 00:45:20 +0300 Subject: [PATCH 1/6] v1 --- app/db/db_methods.py | 13 ++++++++ app/db/db_types.py | 15 +++++++++ app/main/parser.py | 25 ++++++++++++-- .../presentations/pptx/presentation_pptx.py | 25 ++++++++++++++ .../reports/docx_uploader/docx_uploader.py | 33 +++++++++++++++++++ 5 files changed, 108 insertions(+), 3 deletions(-) diff --git a/app/db/db_methods.py b/app/db/db_methods.py index d80d92db..31bf4c10 100644 --- a/app/db/db_methods.py +++ b/app/db/db_methods.py @@ -21,11 +21,24 @@ logs_collection = db.create_collection( 'logs', capped=True, size=5242880) if not db['logs'] else db['logs'] celery_check_collection = db['celery_check'] # collection for mapping celery_task to check +images_collection = db['images'] # коллекция для хранения изображений def get_client(): return client +def save_image_to_db(check_id, image_data, caption): + from app.db.db_types import Image + + image = Image({ + 'check_id': check_id, + 'image_data': image_data, + 'caption': caption + }) + images_collection.insert_one(image.pack()) + + print("check_id----------",check_id) + # Returns user if user was created and None if already exists def add_user(username, password_hash='', is_LTI=False): diff --git a/app/db/db_types.py b/app/db/db_types.py index eeeb26d9..53dd04b1 100644 --- a/app/db/db_types.py +++ b/app/db/db_types.py @@ -145,3 +145,18 @@ def none_to_false(x): is_ended = none_to_true(self.is_ended) # None for old checks => True, True->True, False->False is_failed = none_to_false(self.is_failed) # None for old checks => False, True->True, False->False return {'is_ended': is_ended, 'is_failed': is_failed} + +class Image(PackableWithId): + def __init__(self, dictionary=None): + super().__init__(dictionary) + dictionary = dictionary or {} + self.check_id = dictionary.get('check_id') # Привязка к check_id + self.caption = dictionary.get('caption', '') # Подпись к изображению + self.image_data = dictionary.get('image_data') # Файл изображения в формате bindata + + def pack(self): + package = super().pack() + package['check_id'] = str(self.check_id) + package['caption'] = self.caption + package['image_data'] = self.image_data + return package diff --git a/app/main/parser.py b/app/main/parser.py index 593b8cfd..07754aab 100644 --- a/app/main/parser.py +++ b/app/main/parser.py @@ -8,8 +8,11 @@ from main.reports.md_uploader import MdUploader from utils import convert_to -logger = logging.getLogger('root_logger') +from os.path import basename +from app.db.db_methods import add_check +from app.db.db_types import Check +logger = logging.getLogger('root_logger') def parse(filepath, pdf_filepath): tmp_filepath = filepath.lower() @@ -19,7 +22,17 @@ def parse(filepath, pdf_filepath): if tmp_filepath.endswith(('.odp', '.ppt')): logger.info(f"Презентация {filepath} старого формата. Временно преобразована в pptx для обработки.") new_filepath = convert_to(filepath, target_format='pptx') - file_object = PresentationPPTX(new_filepath) + + presentation = PresentationPPTX(new_filepath) + + check = Check({ + 'filename': basename(new_filepath), + }) + check_id = add_check(23, check) + presentation.extract_images_with_captions(check_id) + file_object = presentation + + elif tmp_filepath.endswith(('.doc', '.odt', '.docx', )): new_filepath = filepath if tmp_filepath.endswith(('.doc', '.odt')): @@ -28,7 +41,13 @@ def parse(filepath, pdf_filepath): docx = DocxUploader() docx.upload(new_filepath, pdf_filepath) + # Создание проверки + check = Check({ + 'filename': basename(new_filepath), + }) + check_id = add_check(23, check) docx.parse() + docx.extract_images_with_captions(check_id) file_object = docx elif tmp_filepath.endswith('.md' ): @@ -54,4 +73,4 @@ def save_to_temp_file(file): temp_file.write(file.read()) temp_file.close() file.seek(0) - return temp_file.name + return temp_file.name \ No newline at end of file diff --git a/app/main/presentations/pptx/presentation_pptx.py b/app/main/presentations/pptx/presentation_pptx.py index dd909f8c..e78babb0 100644 --- a/app/main/presentations/pptx/presentation_pptx.py +++ b/app/main/presentations/pptx/presentation_pptx.py @@ -1,4 +1,5 @@ from pptx import Presentation +from pptx.enum.shapes import MSO_SHAPE_TYPE from .slide_pptx import SlidePPTX from ..presentation_basic import PresentationBasic @@ -17,3 +18,27 @@ def add_slides(self): def __str__(self): return super().__str__() + + def extract_images_with_captions(self, check_id): + from app.db.db_methods import save_image_to_db + + images_with_captions = [] + + for slide in self.prs.slides: + for shape in slide.shapes: + if shape.shape_type == MSO_SHAPE_TYPE.PICTURE: + image_data = shape.image.blob # Бинарные данные изображения + caption = "" + + # Определение подписи. Предполагается, что подпись находится рядом с изображением + if shape.has_text_frame: + caption = shape.text.strip() + else: + # Альтернативный способ: поиск текстового поля рядом с изображением + pass + + # Сохранение изображения и подписи в MongoDB + save_image_to_db(check_id, image_data, caption) + images_with_captions.append({"image_data": image_data, "caption": caption}) + + return images_with_captions \ No newline at end of file diff --git a/app/main/reports/docx_uploader/docx_uploader.py b/app/main/reports/docx_uploader/docx_uploader.py index ac30dee4..d988fed2 100644 --- a/app/main/reports/docx_uploader/docx_uploader.py +++ b/app/main/reports/docx_uploader/docx_uploader.py @@ -242,6 +242,39 @@ def show_chapters(self, work_type): chapters_str += "    " + header["text"] + "
" return chapters_str + def extract_images_with_captions(self, check_id): + from app.db.db_methods import save_image_to_db + images_with_captions = [] + + # Получение всех встроенных фигур (inline_shapes) + for inline_shape in self.file.inline_shapes: + # Проверка, является ли встроенный объект изображением + if inline_shape.type == 3: # Тип 3 соответствует изображению (PICTURE) + # Извлечение бинарных данных изображения + image_stream = inline_shape._inline.graphic.graphicData.pic.blipFill.blip.embed + image_data = self.file.part.related_parts[image_stream].blob # Бинарные данные изображения + + # Инициализация подписи + caption = "" + + # Поиск параграфа, следующего за изображением, для извлечения подписи + for i, paragraph in enumerate(self.file.paragraphs): + # Проверяем, находится ли изображение в параграфе + inline_shape_xml = inline_shape._inline.xml + if inline_shape_xml in paragraph._element.xml: + # Если есть следующий параграф, предположим, что это подпись + if i + 1 < len(self.file.paragraphs): + next_paragraph = self.file.paragraphs[i + 1].text.strip() + if next_paragraph: + caption = next_paragraph + break # Найдено изображение, больше искать не нужно + + # Сохранение изображения и подписи в MongoDB + save_image_to_db(check_id, image_data, caption) + images_with_captions.append({"image_data": image_data, "caption": caption}) + + return images_with_captions + def main(args): file = args.file From 88f199cabec056a2c658a82ea1cf996cd032fa13 Mon Sep 17 00:00:00 2001 From: ardnaxelas Date: Mon, 30 Sep 2024 01:30:16 +0300 Subject: [PATCH 2/6] v1.1 --- app/db/db_methods.py | 2 - app/main/parser.py | 20 ++++++- .../presentations/pptx/presentation_pptx.py | 56 ++++++++++++------- .../reports/docx_uploader/docx_uploader.py | 55 +++++++++--------- 4 files changed, 82 insertions(+), 51 deletions(-) diff --git a/app/db/db_methods.py b/app/db/db_methods.py index 31bf4c10..e63f1760 100644 --- a/app/db/db_methods.py +++ b/app/db/db_methods.py @@ -37,8 +37,6 @@ def save_image_to_db(check_id, image_data, caption): }) images_collection.insert_one(image.pack()) - print("check_id----------",check_id) - # Returns user if user was created and None if already exists def add_user(username, password_hash='', is_LTI=False): diff --git a/app/main/parser.py b/app/main/parser.py index 07754aab..fb60a19d 100644 --- a/app/main/parser.py +++ b/app/main/parser.py @@ -15,6 +15,8 @@ logger = logging.getLogger('root_logger') def parse(filepath, pdf_filepath): + from app.db.db_methods import files_info_collection + tmp_filepath = filepath.lower() try: if tmp_filepath.endswith(('.odp', '.ppt', '.pptx')): @@ -28,7 +30,13 @@ def parse(filepath, pdf_filepath): check = Check({ 'filename': basename(new_filepath), }) - check_id = add_check(23, check) + + file_id = 0 + file = files_info_collection.find_one({'name': basename(new_filepath)}) + if file: + file_id = file['_id'] + + check_id = add_check(file_id, check) presentation.extract_images_with_captions(check_id) file_object = presentation @@ -41,11 +49,17 @@ def parse(filepath, pdf_filepath): docx = DocxUploader() docx.upload(new_filepath, pdf_filepath) - # Создание проверки + check = Check({ 'filename': basename(new_filepath), }) - check_id = add_check(23, check) + + file_id = 0 + file = files_info_collection.find_one({'name': basename(new_filepath)}) + if file: + file_id = file['_id'] + + check_id = add_check(file_id, check) docx.parse() docx.extract_images_with_captions(check_id) file_object = docx diff --git a/app/main/presentations/pptx/presentation_pptx.py b/app/main/presentations/pptx/presentation_pptx.py index e78babb0..34f2081a 100644 --- a/app/main/presentations/pptx/presentation_pptx.py +++ b/app/main/presentations/pptx/presentation_pptx.py @@ -1,3 +1,5 @@ +from io import BytesIO + from pptx import Presentation from pptx.enum.shapes import MSO_SHAPE_TYPE @@ -22,23 +24,37 @@ def __str__(self): def extract_images_with_captions(self, check_id): from app.db.db_methods import save_image_to_db - images_with_captions = [] - - for slide in self.prs.slides: - for shape in slide.shapes: - if shape.shape_type == MSO_SHAPE_TYPE.PICTURE: - image_data = shape.image.blob # Бинарные данные изображения - caption = "" - - # Определение подписи. Предполагается, что подпись находится рядом с изображением - if shape.has_text_frame: - caption = shape.text.strip() - else: - # Альтернативный способ: поиск текстового поля рядом с изображением - pass - - # Сохранение изображения и подписи в MongoDB - save_image_to_db(check_id, image_data, caption) - images_with_captions.append({"image_data": image_data, "caption": caption}) - - return images_with_captions \ No newline at end of file + # Проход по каждому слайду в презентации + for slide in self.slides: + image_found = False + image_data = None + caption_text = None + + # Проход по всем шейпам на слайде + for shape in slide.slide.shapes: # Используем slide.slide для доступа к текущему слайду + if shape.shape_type == MSO_SHAPE_TYPE.PICTURE: # Тип 13 соответствует PICTURE + image_found = True + image_part = shape.image # Получаем объект изображения + + # Извлекаем бинарные данные изображения + image_stream = image_part.blob + image_data = BytesIO(image_stream) + print(f"Изображение найдено на слайде {slide.index}") + + # Если мы нашли изображение, ищем следующий непустой текст как подпись + if image_found: + for shape in slide.slide.shapes: + if not shape.has_text_frame: + continue + text = shape.text.strip() + if text: # Находим непустое текстовое поле (предположительно, это подпись) + caption_text = text + # Сохраняем изображение и его подпись + save_image_to_db(check_id, image_data.getvalue(), caption_text) + print(f"Подпись найдена: '{caption_text}' на слайде {slide.index}") + break # Предполагаем, что это подпись к текущему изображению + + # Сброс флага и данных изображения для следующего цикла + image_found = False + image_data = None + caption_text = None diff --git a/app/main/reports/docx_uploader/docx_uploader.py b/app/main/reports/docx_uploader/docx_uploader.py index d988fed2..7c462207 100644 --- a/app/main/reports/docx_uploader/docx_uploader.py +++ b/app/main/reports/docx_uploader/docx_uploader.py @@ -244,36 +244,39 @@ def show_chapters(self, work_type): def extract_images_with_captions(self, check_id): from app.db.db_methods import save_image_to_db - images_with_captions = [] - # Получение всех встроенных фигур (inline_shapes) - for inline_shape in self.file.inline_shapes: - # Проверка, является ли встроенный объект изображением - if inline_shape.type == 3: # Тип 3 соответствует изображению (PICTURE) - # Извлечение бинарных данных изображения - image_stream = inline_shape._inline.graphic.graphicData.pic.blipFill.blip.embed - image_data = self.file.part.related_parts[image_stream].blob # Бинарные данные изображения + image_found = False + image_data = None - # Инициализация подписи - caption = "" + # Проход по всем параграфам документа + for i, paragraph in enumerate(self.file.paragraphs): + # Проверяем, есть ли в параграфе встроенные объекты + for run in paragraph.runs: + if "graphic" in run._element.xml: # Это может быть изображение + image_found = True - # Поиск параграфа, следующего за изображением, для извлечения подписи - for i, paragraph in enumerate(self.file.paragraphs): - # Проверяем, находится ли изображение в параграфе - inline_shape_xml = inline_shape._inline.xml - if inline_shape_xml in paragraph._element.xml: - # Если есть следующий параграф, предположим, что это подпись - if i + 1 < len(self.file.paragraphs): - next_paragraph = self.file.paragraphs[i + 1].text.strip() - if next_paragraph: - caption = next_paragraph - break # Найдено изображение, больше искать не нужно + # Извлечение бинарных данных изображения + image_streams = run._element.findall('.//a:blip', namespaces={ + 'a': 'http://schemas.openxmlformats.org/drawingml/2006/main'}) + for image_stream in image_streams: + embed_id = image_stream.get( + '{http://schemas.openxmlformats.org/officeDocument/2006/relationships}embed') + if embed_id: + image_data = self.file.part.related_parts[embed_id].blob - # Сохранение изображения и подписи в MongoDB - save_image_to_db(check_id, image_data, caption) - images_with_captions.append({"image_data": image_data, "caption": caption}) - - return images_with_captions + # Если мы уже нашли изображение, ищем следующий непустой параграф для подписи + if image_found: + # Переход к следующему параграфу + next_paragraph_index = i + 1 + while next_paragraph_index < len(self.file.paragraphs): + next_paragraph_text = self.file.paragraphs[next_paragraph_index].text.strip() + if next_paragraph_text: # Находим непустой параграф + # Сохраняем изображение и его подпись + save_image_to_db(check_id, image_data, next_paragraph_text) + break + next_paragraph_index += 1 + image_found = False # Сброс флага, чтобы искать следующее изображение + image_data = None # Очистка данных изображения def main(args): From 5ecde02ca5b19355a9065b750b8c7253c4fb9de1 Mon Sep 17 00:00:00 2001 From: ardnaxelas Date: Mon, 30 Sep 2024 11:42:50 +0300 Subject: [PATCH 3/6] v2: edit cases --- app/db/db_methods.py | 1 + .../presentations/pptx/presentation_pptx.py | 6 ++-- .../reports/docx_uploader/docx_uploader.py | 34 ++++++++++++++----- 3 files changed, 28 insertions(+), 13 deletions(-) diff --git a/app/db/db_methods.py b/app/db/db_methods.py index e63f1760..841085b4 100644 --- a/app/db/db_methods.py +++ b/app/db/db_methods.py @@ -36,6 +36,7 @@ def save_image_to_db(check_id, image_data, caption): 'caption': caption }) images_collection.insert_one(image.pack()) + print(str(check_id) + " " + str(caption)) # Returns user if user was created and None if already exists diff --git a/app/main/presentations/pptx/presentation_pptx.py b/app/main/presentations/pptx/presentation_pptx.py index 34f2081a..a8b8581f 100644 --- a/app/main/presentations/pptx/presentation_pptx.py +++ b/app/main/presentations/pptx/presentation_pptx.py @@ -30,16 +30,15 @@ def extract_images_with_captions(self, check_id): image_data = None caption_text = None - # Проход по всем шейпам на слайде + # Проход по всем фигурам на слайде for shape in slide.slide.shapes: # Используем slide.slide для доступа к текущему слайду - if shape.shape_type == MSO_SHAPE_TYPE.PICTURE: # Тип 13 соответствует PICTURE + if shape.shape_type == MSO_SHAPE_TYPE.PICTURE: image_found = True image_part = shape.image # Получаем объект изображения # Извлекаем бинарные данные изображения image_stream = image_part.blob image_data = BytesIO(image_stream) - print(f"Изображение найдено на слайде {slide.index}") # Если мы нашли изображение, ищем следующий непустой текст как подпись if image_found: @@ -51,7 +50,6 @@ def extract_images_with_captions(self, check_id): caption_text = text # Сохраняем изображение и его подпись save_image_to_db(check_id, image_data.getvalue(), caption_text) - print(f"Подпись найдена: '{caption_text}' на слайде {slide.index}") break # Предполагаем, что это подпись к текущему изображению # Сброс флага и данных изображения для следующего цикла diff --git a/app/main/reports/docx_uploader/docx_uploader.py b/app/main/reports/docx_uploader/docx_uploader.py index 7c462207..be65067d 100644 --- a/app/main/reports/docx_uploader/docx_uploader.py +++ b/app/main/reports/docx_uploader/docx_uploader.py @@ -252,8 +252,7 @@ def extract_images_with_captions(self, check_id): for i, paragraph in enumerate(self.file.paragraphs): # Проверяем, есть ли в параграфе встроенные объекты for run in paragraph.runs: - if "graphic" in run._element.xml: # Это может быть изображение - image_found = True + if "graphic" in run._element.xml: # может быть изображение # Извлечение бинарных данных изображения image_streams = run._element.findall('.//a:blip', namespaces={ @@ -262,19 +261,36 @@ def extract_images_with_captions(self, check_id): embed_id = image_stream.get( '{http://schemas.openxmlformats.org/officeDocument/2006/relationships}embed') if embed_id: + image_found = True image_data = self.file.part.related_parts[embed_id].blob # Если мы уже нашли изображение, ищем следующий непустой параграф для подписи if image_found: # Переход к следующему параграфу next_paragraph_index = i + 1 - while next_paragraph_index < len(self.file.paragraphs): - next_paragraph_text = self.file.paragraphs[next_paragraph_index].text.strip() - if next_paragraph_text: # Находим непустой параграф - # Сохраняем изображение и его подпись - save_image_to_db(check_id, image_data, next_paragraph_text) - break - next_paragraph_index += 1 + + # Проверяем, есть ли следующий параграф + if next_paragraph_index < len(self.file.paragraphs): + while next_paragraph_index < len(self.file.paragraphs): + next_paragraph = self.file.paragraphs[next_paragraph_index] + next_paragraph_text = next_paragraph.text.strip() + + # Проверка, не содержит ли следующий параграф также изображение + contains_image = any( + "graphic" in run._element.xml for run in next_paragraph.runs + ) + + # Если параграф не содержит изображения и текст не пуст, то это подпись + if not contains_image and next_paragraph_text: + # Сохраняем изображение и его подпись + save_image_to_db(check_id, image_data, next_paragraph_text) + break + else: + save_image_to_db(check_id, image_data, "picture without caption") + break + else: + save_image_to_db(check_id, image_data, "picture without caption") + image_found = False # Сброс флага, чтобы искать следующее изображение image_data = None # Очистка данных изображения From 52d1afe6c4a00b6e487972b0f0461db19c25abdf Mon Sep 17 00:00:00 2001 From: Dariiiii Date: Fri, 7 Feb 2025 02:03:20 +0300 Subject: [PATCH 4/6] prototype: images readability check --- app/db/db_methods.py | 5 +- app/db/db_types.py | 2 + app/main/check_packs/pack_config.py | 1 + app/main/checks/report_checks/__init__.py | 1 + .../report_checks/image_readability_check.py | 60 +++++++++++++++++++ .../reports/docx_uploader/docx_uploader.py | 20 +++++-- 6 files changed, 81 insertions(+), 8 deletions(-) create mode 100644 app/main/checks/report_checks/image_readability_check.py diff --git a/app/db/db_methods.py b/app/db/db_methods.py index 841085b4..6335a181 100644 --- a/app/db/db_methods.py +++ b/app/db/db_methods.py @@ -27,13 +27,14 @@ def get_client(): return client -def save_image_to_db(check_id, image_data, caption): +def save_image_to_db(check_id, image_data, caption, image_size): from app.db.db_types import Image image = Image({ 'check_id': check_id, 'image_data': image_data, - 'caption': caption + 'caption': caption, + 'image_size': image_size }) images_collection.insert_one(image.pack()) print(str(check_id) + " " + str(caption)) diff --git a/app/db/db_types.py b/app/db/db_types.py index 53dd04b1..3a660fec 100644 --- a/app/db/db_types.py +++ b/app/db/db_types.py @@ -153,10 +153,12 @@ def __init__(self, dictionary=None): self.check_id = dictionary.get('check_id') # Привязка к check_id self.caption = dictionary.get('caption', '') # Подпись к изображению self.image_data = dictionary.get('image_data') # Файл изображения в формате bindata + self.image_size = dictionary.get('image_size') # Размер изображения в сантимерах def pack(self): package = super().pack() package['check_id'] = str(self.check_id) package['caption'] = self.caption package['image_data'] = self.image_data + package['image_size'] = self.image_size return package diff --git a/app/main/check_packs/pack_config.py b/app/main/check_packs/pack_config.py index c053ce0a..43976214 100644 --- a/app/main/check_packs/pack_config.py +++ b/app/main/check_packs/pack_config.py @@ -46,6 +46,7 @@ ["theme_in_report_check"], ['key_words_report_check'], ["empty_task_page_check"], + ['image_readability_check'], ] DEFAULT_TYPE = 'pres' diff --git a/app/main/checks/report_checks/__init__.py b/app/main/checks/report_checks/__init__.py index 50972ce3..ff3b5c6c 100644 --- a/app/main/checks/report_checks/__init__.py +++ b/app/main/checks/report_checks/__init__.py @@ -26,3 +26,4 @@ from .template_name import ReportTemplateNameCheck from .key_words_check import KeyWordsReportCheck from .empty_task_page_check import EmptyTaskPageCheck +from .image_readability_check import image_readability_check \ No newline at end of file diff --git a/app/main/checks/report_checks/image_readability_check.py b/app/main/checks/report_checks/image_readability_check.py new file mode 100644 index 00000000..023ca120 --- /dev/null +++ b/app/main/checks/report_checks/image_readability_check.py @@ -0,0 +1,60 @@ +from ..base_check import BaseReportCriterion, answer +import cv2 +import numpy as np +from io import BytesIO + +class ReportTaskTracker(BaseReportCriterion): + label = "Проверка читаемости изображений" + description = '' + id = 'image_readability_check' + + def __init__(self, file_info, images, min_laplacian = 100, min_entropy = 5, max_density=10): + super().__init__(file_info) + self.images = images # корректно извлечь данные об изображениях + self.min_laplacian = min_laplacian + self.min_entropy = min_entropy + self.max_density = max_density + self.laplacian_score = None + self.entropy_score = None + + def late_init(self): + self.chapters = self.file.make_chapters(self.file_type['report_type']) + + def check(self): + self.late_init() + for image in self.images: + image_array = np.frombuffer(image.image_data, dtype=np.uint8) + img = cv2.imdecode(image_array, cv2.IMREAD_COLOR) + laplacian, entropy = self.find_params(img) + width, height = image.image_size + # проанализровать текст на изображениях + # дописать сравнение с результатами + if False: + return answer(False, f'Изображения нечитаемы! {self.deny_list}! Обнаруженные слова: {word_in_docs}.') + else: + return answer(True, 'Изображения корректны!') + + def find_params(self, image): + if image is None or image.size == 0: + return None, None + gray_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) + + laplacian = cv2.Laplacian(gray_image, cv2.CV_64F).var() + + hist, _ = np.histogram(gray_image.flatten(), bins=256, range=[0, 256]) + hist = hist / hist.sum() + entropy = -np.sum(hist * np.log2(hist + 1e-10)) + return laplacian, entropy + + # def analyze_with_pytesseract(image): + # try: + # if image is None: + # raise ValueError("Не удалось загрузить изображение") + # text = pytesseract.image_to_string(image, lang='rus+eng') + # except Exception as e: + # print(f'Ошибка при обработке изображения: {e}') + # text = "" + + # return { + # 'text': text + # } \ No newline at end of file diff --git a/app/main/reports/docx_uploader/docx_uploader.py b/app/main/reports/docx_uploader/docx_uploader.py index be65067d..4a0a280a 100644 --- a/app/main/reports/docx_uploader/docx_uploader.py +++ b/app/main/reports/docx_uploader/docx_uploader.py @@ -244,7 +244,8 @@ def show_chapters(self, work_type): def extract_images_with_captions(self, check_id): from app.db.db_methods import save_image_to_db - + + emu_to_cm = 360000 image_found = False image_data = None @@ -262,8 +263,15 @@ def extract_images_with_captions(self, check_id): '{http://schemas.openxmlformats.org/officeDocument/2006/relationships}embed') if embed_id: image_found = True - image_data = self.file.part.related_parts[embed_id].blob - + image_part = self.file.part.related_parts[embed_id] + image_data = image_part.blob + extent = run._element.find('.//wp:extent', namespaces={ + 'wp': 'http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing'}) + if extent is not None: + width_emu = int(extent.get('cx')) + height_emu = int(extent.get('cy')) + width_cm = width_emu / emu_to_cm + height_cm = height_emu / emu_to_cm # Если мы уже нашли изображение, ищем следующий непустой параграф для подписи if image_found: # Переход к следующему параграфу @@ -283,13 +291,13 @@ def extract_images_with_captions(self, check_id): # Если параграф не содержит изображения и текст не пуст, то это подпись if not contains_image and next_paragraph_text: # Сохраняем изображение и его подпись - save_image_to_db(check_id, image_data, next_paragraph_text) + save_image_to_db(check_id, image_data, next_paragraph_text, (width_cm, height_cm)) break else: - save_image_to_db(check_id, image_data, "picture without caption") + save_image_to_db(check_id, image_data, "picture without caption", (width_cm, height_cm)) break else: - save_image_to_db(check_id, image_data, "picture without caption") + save_image_to_db(check_id, image_data, "picture without caption", (width_cm, height_cm)) image_found = False # Сброс флага, чтобы искать следующее изображение image_data = None # Очистка данных изображения From e783ed9b2f52403884cc5a484502eaf0c2515ec0 Mon Sep 17 00:00:00 2001 From: Dariiiii Date: Thu, 6 Mar 2025 23:16:11 +0300 Subject: [PATCH 5/6] fix image_quality_check --- app/db/db_methods.py | 14 ++- app/main/check_packs/pack_config.py | 2 +- app/main/checks/report_checks/__init__.py | 2 +- .../report_checks/image_quality_check.py | 52 +++++++++ .../report_checks/image_readability_check.py | 60 ----------- .../reports/docx_uploader/docx_uploader.py | 101 +++++++++--------- requirements.txt | 1 + 7 files changed, 118 insertions(+), 114 deletions(-) create mode 100644 app/main/checks/report_checks/image_quality_check.py delete mode 100644 app/main/checks/report_checks/image_readability_check.py diff --git a/app/db/db_methods.py b/app/db/db_methods.py index 6335a181..6cddc794 100644 --- a/app/db/db_methods.py +++ b/app/db/db_methods.py @@ -7,7 +7,7 @@ from pymongo import MongoClient from utils import convert_to -from .db_types import User, Presentation, Check, Consumers, Logs +from .db_types import User, Presentation, Check, Consumers, Logs, Image client = MongoClient("mongodb://mongodb:27017") db = client['pres-parser-db'] @@ -27,9 +27,17 @@ def get_client(): return client -def save_image_to_db(check_id, image_data, caption, image_size): - from app.db.db_types import Image +def get_images(check_id): + images = images_collection.find({'check_id': str(check_id)}) + if images is not None: + image_list = [] + for img in images: + image_list.append(Image(img)) + return image_list + else: + return None +def save_image_to_db(check_id, image_data, caption, image_size): image = Image({ 'check_id': check_id, 'image_data': image_data, diff --git a/app/main/check_packs/pack_config.py b/app/main/check_packs/pack_config.py index 43976214..17807dc1 100644 --- a/app/main/check_packs/pack_config.py +++ b/app/main/check_packs/pack_config.py @@ -46,7 +46,7 @@ ["theme_in_report_check"], ['key_words_report_check'], ["empty_task_page_check"], - ['image_readability_check'], + ['image_quality_check'], ] DEFAULT_TYPE = 'pres' diff --git a/app/main/checks/report_checks/__init__.py b/app/main/checks/report_checks/__init__.py index ff3b5c6c..c85430ae 100644 --- a/app/main/checks/report_checks/__init__.py +++ b/app/main/checks/report_checks/__init__.py @@ -26,4 +26,4 @@ from .template_name import ReportTemplateNameCheck from .key_words_check import KeyWordsReportCheck from .empty_task_page_check import EmptyTaskPageCheck -from .image_readability_check import image_readability_check \ No newline at end of file +from .image_quality_check import ImageQualityCheck \ No newline at end of file diff --git a/app/main/checks/report_checks/image_quality_check.py b/app/main/checks/report_checks/image_quality_check.py new file mode 100644 index 00000000..6590b6d1 --- /dev/null +++ b/app/main/checks/report_checks/image_quality_check.py @@ -0,0 +1,52 @@ +from ..base_check import BaseReportCriterion, answer +import cv2 +import numpy as np + +class ImageQualityCheck(BaseReportCriterion): + label = "Проверка качества изображений" + description = '' + id = 'image_quality_check' + + def __init__(self, file_info, min_laplacian=100, min_entropy=5): + super().__init__(file_info) + self.images = self.file.images + self.min_laplacian = min_laplacian + self.min_entropy = min_entropy + self.laplacian_score = None + self.entropy_score = None + + def check(self): + deny_list = [] + for img in self.images: + image_array = np.frombuffer(img.image_data, dtype=np.uint8) + img_cv = cv2.imdecode(image_array, cv2.IMREAD_COLOR) + + if img_cv is None: + deny_list.append(f"Изображение с подписью {img.caption} не может быть обработано.") + continue + + self.find_params(img_cv) + + if self.laplacian_score is None or self.entropy_score is None: + deny_list.append(f"Изображение с подписью {img.caption} не может быть обработано.") + continue + + if self.laplacian_score < self.min_laplacian: + deny_list.append(f"Изображение с подписью {img.caption} имеет низкий показатель лапласиана: {self.laplacian_score} (минимум {self.min_laplacian}).") + + if self.entropy_score < self.min_entropy: + deny_list.append(f"Изображение с подписью {img.caption} имеет низкую энтропию: {self.entropy_score} (минимум {self.min_entropy}).") + + if deny_list: + return answer(False, f'Изображения нечитаемы! {deny_list}') + else: + return answer(True, 'Изображения корректны!') + + def find_params(self, image): + if image is None or image.size == 0: + return None, None + gray_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) + self.laplacian_score = cv2.Laplacian(gray_image, cv2.CV_64F).var() + hist, _ = np.histogram(gray_image.flatten(), bins=256, range=[0, 256]) + hist = hist / hist.sum() + self.entropy_score = -np.sum(hist * np.log2(hist + 1e-10)) \ No newline at end of file diff --git a/app/main/checks/report_checks/image_readability_check.py b/app/main/checks/report_checks/image_readability_check.py deleted file mode 100644 index 023ca120..00000000 --- a/app/main/checks/report_checks/image_readability_check.py +++ /dev/null @@ -1,60 +0,0 @@ -from ..base_check import BaseReportCriterion, answer -import cv2 -import numpy as np -from io import BytesIO - -class ReportTaskTracker(BaseReportCriterion): - label = "Проверка читаемости изображений" - description = '' - id = 'image_readability_check' - - def __init__(self, file_info, images, min_laplacian = 100, min_entropy = 5, max_density=10): - super().__init__(file_info) - self.images = images # корректно извлечь данные об изображениях - self.min_laplacian = min_laplacian - self.min_entropy = min_entropy - self.max_density = max_density - self.laplacian_score = None - self.entropy_score = None - - def late_init(self): - self.chapters = self.file.make_chapters(self.file_type['report_type']) - - def check(self): - self.late_init() - for image in self.images: - image_array = np.frombuffer(image.image_data, dtype=np.uint8) - img = cv2.imdecode(image_array, cv2.IMREAD_COLOR) - laplacian, entropy = self.find_params(img) - width, height = image.image_size - # проанализровать текст на изображениях - # дописать сравнение с результатами - if False: - return answer(False, f'Изображения нечитаемы! {self.deny_list}! Обнаруженные слова: {word_in_docs}.') - else: - return answer(True, 'Изображения корректны!') - - def find_params(self, image): - if image is None or image.size == 0: - return None, None - gray_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) - - laplacian = cv2.Laplacian(gray_image, cv2.CV_64F).var() - - hist, _ = np.histogram(gray_image.flatten(), bins=256, range=[0, 256]) - hist = hist / hist.sum() - entropy = -np.sum(hist * np.log2(hist + 1e-10)) - return laplacian, entropy - - # def analyze_with_pytesseract(image): - # try: - # if image is None: - # raise ValueError("Не удалось загрузить изображение") - # text = pytesseract.image_to_string(image, lang='rus+eng') - # except Exception as e: - # print(f'Ошибка при обработке изображения: {e}') - # text = "" - - # return { - # 'text': text - # } \ No newline at end of file diff --git a/app/main/reports/docx_uploader/docx_uploader.py b/app/main/reports/docx_uploader/docx_uploader.py index 4a0a280a..6c1ba12c 100644 --- a/app/main/reports/docx_uploader/docx_uploader.py +++ b/app/main/reports/docx_uploader/docx_uploader.py @@ -243,64 +243,67 @@ def show_chapters(self, work_type): return chapters_str def extract_images_with_captions(self, check_id): - from app.db.db_methods import save_image_to_db + from app.db.db_methods import save_image_to_db, get_images emu_to_cm = 360000 image_found = False image_data = None + if not self.images: + # Проход по всем параграфам документа + for i, paragraph in enumerate(self.file.paragraphs): + # Проверяем, есть ли в параграфе встроенные объекты + for run in paragraph.runs: + if "graphic" in run._element.xml: # может быть изображение - # Проход по всем параграфам документа - for i, paragraph in enumerate(self.file.paragraphs): - # Проверяем, есть ли в параграфе встроенные объекты - for run in paragraph.runs: - if "graphic" in run._element.xml: # может быть изображение + # Извлечение бинарных данных изображения + image_streams = run._element.findall('.//a:blip', namespaces={ + 'a': 'http://schemas.openxmlformats.org/drawingml/2006/main'}) + for image_stream in image_streams: + embed_id = image_stream.get( + '{http://schemas.openxmlformats.org/officeDocument/2006/relationships}embed') + if embed_id: + image_found = True + image_part = self.file.part.related_parts[embed_id] + image_data = image_part.blob + extent = run._element.find('.//wp:extent', namespaces={ + 'wp': 'http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing'}) + if extent is not None: + width_emu = int(extent.get('cx')) + height_emu = int(extent.get('cy')) + width_cm = width_emu / emu_to_cm + height_cm = height_emu / emu_to_cm + # Если мы уже нашли изображение, ищем следующий непустой параграф для подписи + if image_found: + # Переход к следующему параграфу + next_paragraph_index = i + 1 - # Извлечение бинарных данных изображения - image_streams = run._element.findall('.//a:blip', namespaces={ - 'a': 'http://schemas.openxmlformats.org/drawingml/2006/main'}) - for image_stream in image_streams: - embed_id = image_stream.get( - '{http://schemas.openxmlformats.org/officeDocument/2006/relationships}embed') - if embed_id: - image_found = True - image_part = self.file.part.related_parts[embed_id] - image_data = image_part.blob - extent = run._element.find('.//wp:extent', namespaces={ - 'wp': 'http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing'}) - if extent is not None: - width_emu = int(extent.get('cx')) - height_emu = int(extent.get('cy')) - width_cm = width_emu / emu_to_cm - height_cm = height_emu / emu_to_cm - # Если мы уже нашли изображение, ищем следующий непустой параграф для подписи - if image_found: - # Переход к следующему параграфу - next_paragraph_index = i + 1 + # Проверяем, есть ли следующий параграф + if next_paragraph_index < len(self.file.paragraphs): + while next_paragraph_index < len(self.file.paragraphs): + next_paragraph = self.file.paragraphs[next_paragraph_index] + next_paragraph_text = next_paragraph.text.strip() - # Проверяем, есть ли следующий параграф - if next_paragraph_index < len(self.file.paragraphs): - while next_paragraph_index < len(self.file.paragraphs): - next_paragraph = self.file.paragraphs[next_paragraph_index] - next_paragraph_text = next_paragraph.text.strip() + # Проверка, не содержит ли следующий параграф также изображение + contains_image = any( + "graphic" in run._element.xml for run in next_paragraph.runs + ) - # Проверка, не содержит ли следующий параграф также изображение - contains_image = any( - "graphic" in run._element.xml for run in next_paragraph.runs - ) + # Если параграф не содержит изображения и текст не пуст, то это подпись + if not contains_image and next_paragraph_text: + # Сохраняем изображение и его подпись + save_image_to_db(check_id, image_data, next_paragraph_text, (width_cm, height_cm)) + break + else: + save_image_to_db(check_id, image_data, "picture without caption", (width_cm, height_cm)) + break + else: + save_image_to_db(check_id, image_data, "picture without caption", (width_cm, height_cm)) - # Если параграф не содержит изображения и текст не пуст, то это подпись - if not contains_image and next_paragraph_text: - # Сохраняем изображение и его подпись - save_image_to_db(check_id, image_data, next_paragraph_text, (width_cm, height_cm)) - break - else: - save_image_to_db(check_id, image_data, "picture without caption", (width_cm, height_cm)) - break - else: - save_image_to_db(check_id, image_data, "picture without caption", (width_cm, height_cm)) - - image_found = False # Сброс флага, чтобы искать следующее изображение - image_data = None # Очистка данных изображения + image_found = False # Сброс флага, чтобы искать следующее изображение + image_data = None # Очистка данных изображения + self.images = get_images(check_id) + + def main(args): diff --git a/requirements.txt b/requirements.txt index 082b7069..ab64f26c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -35,3 +35,4 @@ filetype==1.2.0 language-tool-python==2.7.1 markdown==3.4.4 md2pdf==1.0.1 +opencv-python==4.5.5.64 \ No newline at end of file From 5cc96ec37daea0dfa0af6d0df2608d83265d0bab Mon Sep 17 00:00:00 2001 From: Dariiiii Date: Fri, 7 Mar 2025 00:19:36 +0300 Subject: [PATCH 6/6] v1 image_quality_check --- .../report_checks/image_quality_check.py | 48 ++++++++++--------- app/main/reports/document_uploader.py | 1 + .../reports/docx_uploader/docx_uploader.py | 2 + 3 files changed, 28 insertions(+), 23 deletions(-) diff --git a/app/main/checks/report_checks/image_quality_check.py b/app/main/checks/report_checks/image_quality_check.py index 6590b6d1..68eca342 100644 --- a/app/main/checks/report_checks/image_quality_check.py +++ b/app/main/checks/report_checks/image_quality_check.py @@ -6,8 +6,8 @@ class ImageQualityCheck(BaseReportCriterion): label = "Проверка качества изображений" description = '' id = 'image_quality_check' - - def __init__(self, file_info, min_laplacian=100, min_entropy=5): + # необходимо подобрать min_laplacian и min_entropy + def __init__(self, file_info, min_laplacian=10, min_entropy=1): super().__init__(file_info) self.images = self.file.images self.min_laplacian = min_laplacian @@ -17,28 +17,30 @@ def __init__(self, file_info, min_laplacian=100, min_entropy=5): def check(self): deny_list = [] - for img in self.images: - image_array = np.frombuffer(img.image_data, dtype=np.uint8) - img_cv = cv2.imdecode(image_array, cv2.IMREAD_COLOR) - - if img_cv is None: - deny_list.append(f"Изображение с подписью {img.caption} не может быть обработано.") - continue - - self.find_params(img_cv) - - if self.laplacian_score is None or self.entropy_score is None: - deny_list.append(f"Изображение с подписью {img.caption} не может быть обработано.") - continue - - if self.laplacian_score < self.min_laplacian: - deny_list.append(f"Изображение с подписью {img.caption} имеет низкий показатель лапласиана: {self.laplacian_score} (минимум {self.min_laplacian}).") - - if self.entropy_score < self.min_entropy: - deny_list.append(f"Изображение с подписью {img.caption} имеет низкую энтропию: {self.entropy_score} (минимум {self.min_entropy}).") - + if self.images: + for img in self.images: + image_array = np.frombuffer(img.image_data, dtype=np.uint8) + img_cv = cv2.imdecode(image_array, cv2.IMREAD_COLOR) + + if img_cv is None: + deny_list.append(f"Изображение с подписью '{img.caption}' не может быть обработано.
") + continue + + self.find_params(img_cv) + + if self.laplacian_score is None or self.entropy_score is None: + deny_list.append(f"Изображение с подписью '{img.caption}' не может быть обработано.
") + continue + + if self.laplacian_score < self.min_laplacian: + deny_list.append(f"Изображение с подписью '{img.caption}' имеет низкий показатель лапласиана: {self.laplacian_score} (минимум {self.min_laplacian}).
") + + if self.entropy_score < self.min_entropy: + deny_list.append(f"Изображение с подписью '{img.caption}' имеет низкую энтропию: {self.entropy_score} (минимум {self.min_entropy}).
") + else: + return answer(False, 'Изображения не найдены!') if deny_list: - return answer(False, f'Изображения нечитаемы! {deny_list}') + return answer(False, f'Изображения нечитаемы!
{"".join(deny_list)}') else: return answer(True, 'Изображения корректны!') diff --git a/app/main/reports/document_uploader.py b/app/main/reports/document_uploader.py index d0653fae..8a6a7303 100644 --- a/app/main/reports/document_uploader.py +++ b/app/main/reports/document_uploader.py @@ -12,6 +12,7 @@ def __init__(self): self.literature_page = 0 self.first_lines = [] self.page_count = 0 + self.images = [] @abstractmethod def upload(self): diff --git a/app/main/reports/docx_uploader/docx_uploader.py b/app/main/reports/docx_uploader/docx_uploader.py index 6c1ba12c..421dfbe2 100644 --- a/app/main/reports/docx_uploader/docx_uploader.py +++ b/app/main/reports/docx_uploader/docx_uploader.py @@ -251,6 +251,8 @@ def extract_images_with_captions(self, check_id): if not self.images: # Проход по всем параграфам документа for i, paragraph in enumerate(self.file.paragraphs): + width_emu = None + height_emu = None # Проверяем, есть ли в параграфе встроенные объекты for run in paragraph.runs: if "graphic" in run._element.xml: # может быть изображение