Lyti4 commited on
Commit
b6d9912
·
verified ·
1 Parent(s): 1ff8cc3

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +265 -66
app.py CHANGED
@@ -53,33 +53,67 @@ class FreeAIOrchestrator:
53
  def __init__(self):
54
  print("🚀 Инициализация SkladBot Free AI...")
55
 
56
- # TrOCR для печатного текста (БЕСПЛАТНО)
57
- self.printed_processor = TrOCRProcessor.from_pretrained("microsoft/trocr-base-printed")
58
- self.printed_model = VisionEncoderDecoderModel.from_pretrained("microsoft/trocr-base-printed")
59
-
60
- # TrOCR для рукописного текста (БЕСПЛАТНО)
61
- self.handwritten_processor = TrOCRProcessor.from_pretrained("microsoft/trocr-base-handwritten")
62
- self.handwritten_model = VisionEncoderDecoderModel.from_pretrained("microsoft/trocr-base-handwritten")
63
-
64
- # LayoutLM для понимания документов (БЕСПЛАТНО)
65
- self.document_qa = pipeline(
66
- "document-question-answering",
67
- model="impira/layoutlm-document-qa"
68
- )
69
 
70
- # Table Transformer для таблиц (БЕСПЛАТНО)
71
- self.table_detector = pipeline(
72
- "object-detection",
73
- model="microsoft/table-transformer-structure-recognition"
74
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
75
 
76
- # NEW: Добавляем интеграцию с Surya Table (БЕСПЛАТНО)
 
77
  try:
78
- # Регистрируем кастомный токенайзер перед загрузкой модели
79
- print("🔄 Инициализация кастомного токенайзера для Surya Table...")
80
 
81
- # Используем пайплайн с указанием стандартного токенайзера вместо кастомного
82
- # Это решает проблему совместимости
83
  self.surya_table_model = pipeline(
84
  "image-to-text",
85
  model="vikp/surya_tablerec",
@@ -90,7 +124,7 @@ class FreeAIOrchestrator:
90
  self.surya_table_available = True
91
  except Exception as e:
92
  print(f"⚠️ Не удалось загрузить Surya Table: {e}")
93
- self.surya_table_available = False
94
 
95
  self.stats = {
96
  "total_requests": 0,
@@ -192,6 +226,11 @@ class FreeAIOrchestrator:
192
  async def extract_printed_text(self, image):
193
  """Извлечение печатного текста через TrOCR"""
194
  try:
 
 
 
 
 
195
  pixel_values = self.printed_processor(image, return_tensors="pt").pixel_values
196
  generated_ids = self.printed_model.generate(pixel_values)
197
  generated_text = self.printed_processor.batch_decode(generated_ids, skip_special_tokens=True)[0]
@@ -203,6 +242,11 @@ class FreeAIOrchestrator:
203
  async def extract_handwritten_text(self, image):
204
  """Извлечение рукописного текста через TrOCR"""
205
  try:
 
 
 
 
 
206
  pixel_values = self.handwritten_processor(image, return_tensors="pt").pixel_values
207
  generated_ids = self.handwritten_model.generate(pixel_values)
208
  generated_text = self.handwritten_processor.batch_decode(generated_ids, skip_special_tokens=True)[0]
@@ -214,6 +258,11 @@ class FreeAIOrchestrator:
214
  async def extract_structured_data(self, image, doc_type):
215
  """Структурированное понимание документа через LayoutLM"""
216
  try:
 
 
 
 
 
217
  # Определяем вопросы на основе типа документа
218
  questions = self.get_document_questions(doc_type)
219
 
@@ -222,7 +271,8 @@ class FreeAIOrchestrator:
222
  try:
223
  result = self.document_qa(image=image, question=question)
224
  results[question] = result["answer"]
225
- except:
 
226
  results[question] = ""
227
 
228
  return results
@@ -230,11 +280,43 @@ class FreeAIOrchestrator:
230
  print(f"❌ Ошибка LayoutLM: {e}")
231
  return {}
232
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
233
  async def extract_table_data(self, image):
234
  """Извлечение табличных данных через специализированные модели"""
235
  try:
236
  # Проверка наличия модели Surya Table
237
- if hasattr(self, 'surya_table_available') and self.surya_table_available:
238
  try:
239
  # Попытка использования Surya Table для структурированного распознавания таблиц
240
  print("🔍 Используем Surya Table для структурированного распознавания таблицы...")
@@ -435,8 +517,11 @@ class FreeAIOrchestrator:
435
 
436
  # Используем NER для извлечения сущностей
437
  try:
438
- entities = self.ner_pipeline(text)
439
- except:
 
 
 
440
  entities = []
441
 
442
  # Регулярные выражения для складских данных
@@ -539,13 +624,107 @@ class FreeAIOrchestrator:
539
  return 0.0
540
  return round(self.stats["successful_extractions"] / self.stats["total_requests"] * 100, 1)
541
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
542
  # Инициализация AI
543
  ai_orchestrator = FreeAIOrchestrator()
544
 
545
- # Gradio интерфейс
 
 
 
 
 
 
 
 
546
  def process_warehouse_document(image, document_type):
547
  """Обработка складского документа через Gradio"""
548
  try:
 
 
 
 
 
 
549
  import asyncio
550
  loop = asyncio.new_event_loop()
551
  asyncio.set_event_loop(loop)
@@ -565,56 +744,76 @@ def process_warehouse_document(image, document_type):
565
 
566
  def get_service_stats():
567
  """Получение статистики сервиса"""
 
 
 
 
 
 
 
 
568
  stats = ai_orchestrator.get_stats()
569
  return json.dumps(stats, ensure_ascii=False, indent=2)
570
 
571
- # Gradio интерфейс
572
- with gr.Blocks(title="SkladBot Free AI") as app:
573
- gr.Markdown("# 🤖 SkladBot Free AI Microservice")
574
- gr.Markdown("**БЕСПЛАТНАЯ** обработка складских документов через AI")
575
-
576
- with gr.Tab("Обработка документов"):
577
- image_input = gr.Image(type="pil", label="Загрузите изображение документа")
578
- doc_type = gr.Dropdown(
579
- choices=["auto", "invoice", "table", "form", "handwritten"],
580
- value="auto",
581
- label="Тип документа"
582
- )
 
583
 
584
- process_btn = gr.Button("🔍 Обработать документ", variant="primary")
585
 
586
- result_output = gr.Textbox(
587
- label="Результат обработки",
588
- lines=20,
589
- max_lines=30
590
- )
591
 
592
- process_btn.click(
593
- process_warehouse_document,
594
- inputs=[image_input, doc_type],
595
- outputs=result_output
596
- )
597
 
598
- with gr.Tab("Статистика"):
599
- stats_btn = gr.Button("📊 Обновить статистику")
600
- stats_output = gr.Textbox(
601
- label="Статистика сервиса",
602
- lines=10
603
- )
604
 
605
- stats_btn.click(
606
- get_service_stats,
607
- outputs=stats_output
608
- )
 
 
 
 
 
609
 
610
- gr.Markdown("---")
611
- gr.Markdown("💰 **Стоимость**: $0 (100% бесплатно)")
612
- gr.Markdown("📊 **Лимит**: 20,000 запросов/месяц")
613
- gr.Markdown("🧠 **AI модели**: TrOCR, LayoutLM, Table Transformer, RuBERT, SuryaTable")
614
 
 
 
 
 
615
  if __name__ == "__main__":
 
 
 
 
616
  app.launch(
617
  server_name="0.0.0.0",
618
  server_port=7860,
 
619
  show_error=True
620
  )
 
53
  def __init__(self):
54
  print("🚀 Инициализация SkladBot Free AI...")
55
 
56
+ # Для предотвращения ошибок при запуске в HF Space
57
+ try:
58
+ # TrOCR для печатного текста (БЕСПЛАТНО)
59
+ self.printed_processor = TrOCRProcessor.from_pretrained("microsoft/trocr-base-printed")
60
+ self.printed_model = VisionEncoderDecoderModel.from_pretrained("microsoft/trocr-base-printed")
61
+ print("✅ TrOCR (печатный) загружен успешно")
62
+ except Exception as e:
63
+ print(f"⚠️ Ошибка при загрузке TrOCR печатный: {e}")
64
+ self.printed_processor = None
65
+ self.printed_model = None
 
 
 
66
 
67
+ try:
68
+ # TrOCR для рукописного текста (БЕСПЛАТНО)
69
+ self.handwritten_processor = TrOCRProcessor.from_pretrained("microsoft/trocr-base-handwritten")
70
+ self.handwritten_model = VisionEncoderDecoderModel.from_pretrained("microsoft/trocr-base-handwritten")
71
+ print("✅ TrOCR (рукописный) загружен успешно")
72
+ except Exception as e:
73
+ print(f"⚠️ Ошибка при загрузке TrOCR рукописный: {e}")
74
+ self.handwritten_processor = None
75
+ self.handwritten_model = None
76
+
77
+ try:
78
+ # LayoutLM для понимания документов (БЕСПЛАТНО)
79
+ self.document_qa = pipeline(
80
+ "document-question-answering",
81
+ model="impira/layoutlm-document-qa"
82
+ )
83
+ print("✅ LayoutLM загружен успешно")
84
+ except Exception as e:
85
+ print(f"⚠️ Ошибка при загрузке LayoutLM: {e}")
86
+ self.document_qa = None
87
+
88
+ try:
89
+ # Table Transformer для таблиц (БЕСПЛАТНО)
90
+ self.table_detector = pipeline(
91
+ "object-detection",
92
+ model="microsoft/table-transformer-structure-recognition"
93
+ )
94
+ print("✅ Table Transformer загружен успешно")
95
+ except Exception as e:
96
+ print(f"⚠️ Ошибка при загрузке Table Transformer: {e}")
97
+ self.table_detector = None
98
+
99
+ # Проинициализируем переменную для NER
100
+ self.ner_pipeline = None
101
+ try:
102
+ # Инициализируем NER модель для русского языка
103
+ self.ner_pipeline = pipeline(
104
+ "token-classification",
105
+ model="Gherman/bert-base-NER-Russian"
106
+ )
107
+ print("✅ Russian NER загружен успешно")
108
+ except Exception as e:
109
+ print(f"⚠️ Ошибка при загрузке NER: {e}")
110
 
111
+ # Пробуем инициализировать Surya Table модель
112
+ self.surya_table_available = False
113
  try:
114
+ print("🔄 Инициализация модели Surya Table...")
 
115
 
116
+ # Используем стандартный токенайзер вместо кастомного
 
117
  self.surya_table_model = pipeline(
118
  "image-to-text",
119
  model="vikp/surya_tablerec",
 
124
  self.surya_table_available = True
125
  except Exception as e:
126
  print(f"⚠️ Не удалось загрузить Surya Table: {e}")
127
+ self.surya_table_model = None
128
 
129
  self.stats = {
130
  "total_requests": 0,
 
226
  async def extract_printed_text(self, image):
227
  """Извлечение печатного текста через TrOCR"""
228
  try:
229
+ # Проверяем, инициализированы ли необходимые модели
230
+ if self.printed_processor is None or self.printed_model is None:
231
+ print("⚠️ TrOCR для печатного текста не инициализирован")
232
+ return ""
233
+
234
  pixel_values = self.printed_processor(image, return_tensors="pt").pixel_values
235
  generated_ids = self.printed_model.generate(pixel_values)
236
  generated_text = self.printed_processor.batch_decode(generated_ids, skip_special_tokens=True)[0]
 
242
  async def extract_handwritten_text(self, image):
243
  """Извлечение рукописного текста через TrOCR"""
244
  try:
245
+ # Проверяем, инициализированы ли необходимые модели
246
+ if self.handwritten_processor is None or self.handwritten_model is None:
247
+ print("⚠️ TrOCR для рукописного текста не инициализирован")
248
+ return ""
249
+
250
  pixel_values = self.handwritten_processor(image, return_tensors="pt").pixel_values
251
  generated_ids = self.handwritten_model.generate(pixel_values)
252
  generated_text = self.handwritten_processor.batch_decode(generated_ids, skip_special_tokens=True)[0]
 
258
  async def extract_structured_data(self, image, doc_type):
259
  """Структурированное понимание документа через LayoutLM"""
260
  try:
261
+ # Проверяем, инициализирована ли модель
262
+ if self.document_qa is None:
263
+ print("⚠️ LayoutLM не инициализирован")
264
+ return {}
265
+
266
  # Определяем вопросы на основе типа документа
267
  questions = self.get_document_questions(doc_type)
268
 
 
271
  try:
272
  result = self.document_qa(image=image, question=question)
273
  results[question] = result["answer"]
274
+ except Exception as inner_e:
275
+ print(f"⚠️ Ошибка запроса к LayoutLM: {inner_e}")
276
  results[question] = ""
277
 
278
  return results
 
280
  print(f"❌ Ошибка LayoutLM: {e}")
281
  return {}
282
 
283
+ def get_document_questions(self, doc_type):
284
+ """Формирует набор вопросов для структурированного извлечения через LayoutLM"""
285
+ # Базовые вопросы для всех типов документов
286
+ base_questions = [
287
+ "Что это за документ?",
288
+ "Какие товары указаны в документе?",
289
+ "Какое количество товаров указано?"
290
+ ]
291
+
292
+ # Специфичные вопросы в зависимости от типа документа
293
+ if doc_type == "invoice":
294
+ return base_questions + [
295
+ "Какая общая сумма?",
296
+ "Кто поставщик?",
297
+ "Какая дата документа?",
298
+ "Какой номер документа?"
299
+ ]
300
+ elif doc_type == "table":
301
+ return base_questions + [
302
+ "Сколько строк в таблице?",
303
+ "Какие колонки есть в таблице?",
304
+ "Есть ли в таблице артикулы товаров?"
305
+ ]
306
+ elif doc_type == "form":
307
+ return base_questions + [
308
+ "Кто заполнил форму?",
309
+ "Какой статус документа?",
310
+ "Требуется ли подпись?"
311
+ ]
312
+ else:
313
+ return base_questions
314
+
315
  async def extract_table_data(self, image):
316
  """Извлечение табличных данных через специализированные модели"""
317
  try:
318
  # Проверка наличия модели Surya Table
319
+ if hasattr(self, 'surya_table_available') and self.surya_table_available and self.surya_table_model is not None:
320
  try:
321
  # Попытка использования Surya Table для структурированного распознавания таблиц
322
  print("🔍 Используем Surya Table для структурированного распознавания таблицы...")
 
517
 
518
  # Используем NER для извлечения сущностей
519
  try:
520
+ entities = []
521
+ if self.ner_pipeline is not None:
522
+ entities = self.ner_pipeline(text)
523
+ except Exception as e:
524
+ print(f"⚠️ Ошибка при использовании NER: {e}")
525
  entities = []
526
 
527
  # Регулярные выражения для складских данных
 
624
  return 0.0
625
  return round(self.stats["successful_extractions"] / self.stats["total_requests"] * 100, 1)
626
 
627
+ async def classify_document_type(self, image):
628
+ """Определяет тип документа на основе визуальных характеристик"""
629
+ try:
630
+ # Преобразуем в numpy массив для анализа
631
+ if not isinstance(image, np.ndarray):
632
+ if hasattr(image, 'convert'):
633
+ image_np = np.array(image.convert('RGB'))
634
+ else:
635
+ # Если не можем получить numpy массив, возвращаем значение по умолчанию
636
+ return "auto"
637
+ else:
638
+ image_np = image
639
+
640
+ # Проверяем размеры и соотношение сторон
641
+ height, width = image_np.shape[:2]
642
+ aspect_ratio = width / height
643
+
644
+ # Анализируем изображение для определения типа документа
645
+
646
+ # 1. Проверка на таблицу: регулярная структура, сетка, преобладание линий
647
+ # Упрощенная эвристика: таблицы часто имеют большое кол-во горизонтальных/вертикальных линий
648
+ try:
649
+ # Преобразование в оттенки серого
650
+ if len(image_np.shape) == 3:
651
+ gray = np.mean(image_np, axis=2).astype(np.uint8)
652
+ else:
653
+ gray = image_np
654
+
655
+ # Определение градиентов для обнаружения линий
656
+ grad_x = np.abs(np.diff(gray, axis=1)).sum()
657
+ grad_y = np.abs(np.diff(gray, axis=0)).sum()
658
+
659
+ # Нормализация по размеру изображения
660
+ grad_x_norm = grad_x / (width * height)
661
+ grad_y_norm = grad_y / (width * height)
662
+
663
+ # Если много градиентов (линий) в обоих направлениях - вероятно, это таблица
664
+ if grad_x_norm > 0.1 and grad_y_norm > 0.1:
665
+ return "table"
666
+ except Exception as e:
667
+ print(f"⚠️ Ошибка при анализе таблиц: {e}")
668
+
669
+ # 2. Проверка на счет/накладную: обычно содержит адреса, суммы и реквизиты
670
+ # Здесь упрощенно используем соотношение сторон: счета часто в портретной ориентации
671
+ if aspect_ratio < 0.9: # Портретная ориентация
672
+ return "invoice"
673
+
674
+ # 3. Проверка на форму: структурированный документ с полями для заполнения
675
+ # Упрощенная эвристика: много белых областей с текстом по краям
676
+ try:
677
+ # Анализ распределения пикселей
678
+ if len(image_np.shape) == 3:
679
+ # Для цветных изображений
680
+ lightness = np.mean(image_np, axis=2)
681
+ light_pixels = (lightness > 200).sum()
682
+ light_ratio = light_pixels / (width * height)
683
+
684
+ if light_ratio > 0.7: # Много светлых областей
685
+ return "form"
686
+ else:
687
+ # Для ч/б изображений
688
+ light_pixels = (image_np > 200).sum()
689
+ light_ratio = light_pixels / (width * height)
690
+
691
+ if light_ratio > 0.7:
692
+ return "form"
693
+ except Exception as e:
694
+ print(f"⚠️ Ошибка при анализе формы: {e}")
695
+
696
+ # 4. Проверка на рукописный текст: более нерегулярная структура
697
+ # Рукописный текст сложно определить только по изображению без ML
698
+ # Это заглушка для демонстрации, в реальности нужно использовать ML классификатор
699
+
700
+ # По умолчанию считаем документ печатным
701
+ return "auto"
702
+
703
+ except Exception as e:
704
+ print(f"❌ Ошибка при определении типа документа: {e}")
705
+ return "auto" # По умолчанию для безопасности
706
+
707
  # Инициализация AI
708
  ai_orchestrator = FreeAIOrchestrator()
709
 
710
+ # Создаем глобальный экземпляр AI Orchestrator
711
+ ai_orchestrator = None
712
+
713
+ # Отложенная загрузка моделей
714
+ def load_ai_models():
715
+ global ai_orchestrator
716
+ ai_orchestrator = FreeAIOrchestrator()
717
+
718
+ # Обработчик для Gradio
719
  def process_warehouse_document(image, document_type):
720
  """Обработка складского документа через Gradio"""
721
  try:
722
+ # Инициализируем AI если еще не инициализирован
723
+ global ai_orchestrator
724
+ if ai_orchestrator is None:
725
+ print("🔄 Инициализация AI при первом запросе...")
726
+ ai_orchestrator = FreeAIOrchestrator()
727
+
728
  import asyncio
729
  loop = asyncio.new_event_loop()
730
  asyncio.set_event_loop(loop)
 
744
 
745
  def get_service_stats():
746
  """Получение статистики сервиса"""
747
+ global ai_orchestrator
748
+ if ai_orchestrator is None:
749
+ return json.dumps({
750
+ "status": "AI модели еще не загружены",
751
+ "uptime": "0 часов",
752
+ "ready": False
753
+ }, ensure_ascii=False, indent=2)
754
+
755
  stats = ai_orchestrator.get_stats()
756
  return json.dumps(stats, ensure_ascii=False, indent=2)
757
 
758
+ # Создаем интерфейс
759
+ def create_interface():
760
+ with gr.Blocks(title="SkladBot Free AI", theme=gr.themes.Default()) as app:
761
+ gr.Markdown("# 🤖 SkladBot Free AI Microservice")
762
+ gr.Markdown("**БЕСПЛАТНАЯ** обработка складских документов через AI")
763
+
764
+ with gr.Tab("Обработка документов"):
765
+ image_input = gr.Image(type="pil", label="Загрузите изображение документа")
766
+ doc_type = gr.Dropdown(
767
+ choices=["auto", "invoice", "table", "form", "handwritten"],
768
+ value="auto",
769
+ label="Тип документа"
770
+ )
771
 
772
+ process_btn = gr.Button("🔍 Обработать документ", variant="primary")
773
 
774
+ result_output = gr.Textbox(
775
+ label="Результат обработки",
776
+ lines=20,
777
+ max_lines=30
778
+ )
779
 
780
+ process_btn.click(
781
+ process_warehouse_document,
782
+ inputs=[image_input, doc_type],
783
+ outputs=result_output
784
+ )
785
 
786
+ with gr.Tab("Статистика"):
787
+ stats_btn = gr.Button("📊 Обновить статистику")
788
+ stats_output = gr.Textbox(
789
+ label="Статистика сервиса",
790
+ lines=10
791
+ )
792
 
793
+ stats_btn.click(
794
+ get_service_stats,
795
+ outputs=stats_output
796
+ )
797
+
798
+ gr.Markdown("---")
799
+ gr.Markdown("💰 **Стоимость**: $0 (100% бесплатно)")
800
+ gr.Markdown("📊 **Лимит**: 20,000 запросов/месяц")
801
+ gr.Markdown("🧠 **AI модели**: TrOCR, LayoutLM, Table Transformer, RuBERT, SuryaTable")
802
 
803
+ return app
 
 
 
804
 
805
+ # Создаем интерфейс
806
+ app = create_interface()
807
+
808
+ # Запуск для Hugging Face Spaces
809
  if __name__ == "__main__":
810
+ # Запускаем интерфейс Gradio сначала, затем инициализируем модели в фоне
811
+ import threading
812
+ threading.Thread(target=load_ai_models).start()
813
+
814
  app.launch(
815
  server_name="0.0.0.0",
816
  server_port=7860,
817
+ debug=True,
818
  show_error=True
819
  )