ASureevaA commited on
Commit
ed03824
·
1 Parent(s): a020532
Files changed (2) hide show
  1. app.py +86 -56
  2. requirements.txt +3 -2
app.py CHANGED
@@ -7,71 +7,93 @@ import soundfile as soundfile_module
7
  import torch
8
  import gradio as gradio_module
9
  from PIL import Image
 
10
  from transformers import (
11
  pipeline,
12
  VitsModel,
13
  AutoTokenizer,
14
  )
15
 
 
16
  # ============================
17
  # 1. Настройки устройства
18
  # ============================
19
 
20
- # TODO_USER: для нормальной работы olmOCR почти наверняка нужен GPU
21
- if torch.cuda.is_available():
22
- device_string: str = "cuda"
23
- pipeline_device_index: int = 0
24
- else:
25
- device_string = "cpu"
26
- pipeline_device_index = -1 # Gradio/transformers: -1 = CPU
27
 
28
 
29
  # ============================
30
- # 2. OCR на olmOCR-2-7B-1025-FP8
31
  # ============================
32
 
33
- # Модель: allenai/olmOCR-2-7B-1025-FP8
34
- # По README это image-to-text трансформер, так что используем стандартный pipeline.
35
- ocr_pipeline = pipeline(
36
- task="image-to-text",
37
- model="allenai/olmOCR-2-7B-1025-FP8",
38
- device=pipeline_device_index,
39
- # TODO_USER: при необходимости можно добавить torch_dtype=..., но лучше сначала проверить дефолт
40
  )
41
 
42
 
43
  def run_ocr(image_object: Image.Image) -> str:
44
  """
45
- OCR для печатного английского текста с помощью olmOCR-2-7B-1025-FP8.
46
-
47
- Вход: PIL.Image (страница/скриншот).
48
- Выход: строка текста, которую модель сгенерировала как распознавание.
49
  """
50
  if image_object is None:
51
  return ""
52
 
53
  rgb_image_object: Image.Image = image_object.convert("RGB")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
54
 
55
- # olmOCR поддерживает прямой вызов через pipeline("image-to-text").
56
- # Ожидаемый формат ответа: список dict вида [{"generated_text": "..."}].
57
- result = ocr_pipeline(rgb_image_object)
58
-
59
- if isinstance(result, list) and len(result) > 0:
60
- first_item = result[0]
61
- if isinstance(first_item, dict) and "generated_text" in first_item:
62
- text_value: str = str(first_item["generated_text"])
63
- else:
64
- # TODO_USER: непредвиденный формат ответа, логировать при необходимости
65
- text_value = str(first_item)
66
- else:
67
- text_value = str(result)
68
-
69
- recognized_text: str = text_value.strip()
70
  return recognized_text
71
 
72
 
73
  # ============================
74
- # 3. Суммаризация (английский DistilBART)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
75
  # ============================
76
 
77
  summary_pipeline = pipeline(
@@ -98,7 +120,7 @@ def run_summarization(
98
  max(32, word_count + 20),
99
  )
100
 
101
- # Для совсем короткого текста суммаризация мало смысла
102
  if word_count < 8:
103
  return cleaned_text
104
 
@@ -114,7 +136,7 @@ def run_summarization(
114
 
115
 
116
  # ============================
117
- # 4. TTS (английский, MMS VITS)
118
  # ============================
119
 
120
  tts_model: VitsModel = VitsModel.from_pretrained("facebook/mms-tts-eng")
@@ -126,8 +148,8 @@ def run_tts(summary_text: str) -> Optional[str]:
126
  """
127
  Озвучка английского текста конспекта через VitsModel (facebook/mms-tts-eng).
128
 
129
- Если модель внутри упадёт (известный баг на некоторых странных инпутах),
130
- просто возвращаем None и не роняем всё приложение.
131
  """
132
  cleaned_text: str = summary_text.strip()
133
  if not cleaned_text:
@@ -154,6 +176,7 @@ def run_tts(summary_text: str) -> Optional[str]:
154
  print(f"[WARN] TTS RuntimeError: {runtime_error}")
155
  return None
156
 
 
157
  waveform_array = waveform_tensor.squeeze().cpu().numpy().astype("float32")
158
  waveform_array = numpy_module.clip(waveform_array, -1.0, 1.0)
159
 
@@ -172,21 +195,24 @@ def run_tts(summary_text: str) -> Optional[str]:
172
 
173
 
174
  # ============================
175
- # 5. Полный пайплайн
176
  # ============================
177
 
178
  def full_flow(
179
  image_object: Image.Image,
180
  max_summary_tokens: int = 128,
181
- ) -> Tuple[str, str, Optional[str]]:
182
  """
183
  Полный пайплайн:
184
- 1) OCR: изображение -> исходный английский текст (olmOCR)
185
- 2) Суммаризация: текст -> конспект (DistilBART)
186
- 3) TTS: конспект -> .wav файл (или None, если TTS не смог)
 
187
  """
188
  recognized_text: str = run_ocr(image_object=image_object)
189
 
 
 
190
  summary_text: str = run_summarization(
191
  input_text=recognized_text,
192
  max_summary_tokens=max_summary_tokens,
@@ -194,11 +220,11 @@ def full_flow(
194
 
195
  audio_file_path: Optional[str] = run_tts(summary_text=summary_text)
196
 
197
- return recognized_text, summary_text, audio_file_path
198
 
199
 
200
  # ============================
201
- # 6. Gradio UI (по-русски)
202
  # ============================
203
 
204
  gradio_interface = gradio_module.Interface(
@@ -218,25 +244,29 @@ gradio_interface = gradio_module.Interface(
218
  ],
219
  outputs=[
220
  gradio_module.Textbox(
221
- label="Распознанный текст (olmOCR)",
222
- lines=10,
 
 
 
 
223
  ),
224
  gradio_module.Textbox(
225
- label="Конспект (английский текст)",
226
  lines=6,
227
  ),
228
  gradio_module.Audio(
229
- label="Озвучка конспекта (английский TTS)",
230
  type="filepath",
231
  ),
232
  ],
233
- title="Картинка → Текст → Конспект → Озвучка (olmOCR + английские модели)",
234
  description=(
235
- "1) olmOCR-2-7B-1025-FP8 распознаёт текст с документа.\n"
236
- "2) Английский трансформер суммаризации делает краткий пересказ.\n"
237
- "3) VITS-модель MMS (facebook/mms-tts-eng) озвучивает конспект.\n\n"
238
- "Если озвучка не сгенерировалась, значит конкретный текст не понравился TTS-модели "
239
- "и она упала внутри пайплайн просто пропустит аудио."
240
  ),
241
  )
242
 
 
7
  import torch
8
  import gradio as gradio_module
9
  from PIL import Image
10
+ import easyocr
11
  from transformers import (
12
  pipeline,
13
  VitsModel,
14
  AutoTokenizer,
15
  )
16
 
17
+
18
  # ============================
19
  # 1. Настройки устройства
20
  # ============================
21
 
22
+ # Жёстко работаем на CPU: в Space нет доступа к GPU
23
+ device_string: str = "cpu"
 
 
 
 
 
24
 
25
 
26
  # ============================
27
+ # 2. OCR (easyocr, английский)
28
  # ============================
29
 
30
+ ocr_reader = easyocr.Reader(
31
+ ["en"], # язык OCR: английский
32
+ gpu=False, # принудительно без GPU
 
 
 
 
33
  )
34
 
35
 
36
  def run_ocr(image_object: Image.Image) -> str:
37
  """
38
+ OCR для печатного английского текста.
39
+ Используем easyocr, который достаточно устойчив к
40
+ реальным сканам и фотографиям документа на CPU.
 
41
  """
42
  if image_object is None:
43
  return ""
44
 
45
  rgb_image_object: Image.Image = image_object.convert("RGB")
46
+ numpy_image = numpy_module.array(rgb_image_object)
47
+
48
+ # detail=1 -> (bbox, текст, confidence), paragraph=True -> склейка в абзацы
49
+ ocr_results = ocr_reader.readtext(
50
+ numpy_image,
51
+ detail=1,
52
+ paragraph=True,
53
+ )
54
+
55
+ text_parts = []
56
+ for bounding_box, text_value, confidence_value in ocr_results:
57
+ if not text_value:
58
+ continue
59
+ text_parts.append(text_value)
60
 
61
+ recognized_text: str = "\n".join(text_parts).strip()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
62
  return recognized_text
63
 
64
 
65
  # ============================
66
+ # 3. Трансформер #1: классификация текста (английский)
67
+ # ============================
68
+
69
+ text_classifier_pipeline = pipeline(
70
+ task="text-classification",
71
+ model="distilbert-base-uncased-finetuned-sst-2-english",
72
+ )
73
+
74
+
75
+ def run_text_classification(input_text: str) -> str:
76
+ """
77
+ Анализ текста трансформером:
78
+ используем sentiment-классификатор как пример.
79
+ Возвращаем строку вида: "POSITIVE (score=0.982)".
80
+ """
81
+ cleaned_text: str = input_text.strip()
82
+ if not cleaned_text:
83
+ return ""
84
+
85
+ classifier_result_list = text_classifier_pipeline(cleaned_text)
86
+ classifier_result = classifier_result_list[0]
87
+
88
+ label_value: str = str(classifier_result.get("label", ""))
89
+ score_value: float = float(classifier_result.get("score", 0.0))
90
+
91
+ classification_text: str = f"{label_value} (score={score_value:.3f})"
92
+ return classification_text
93
+
94
+
95
+ # ============================
96
+ # 4. Трансформер #2: суммаризация (английский)
97
  # ============================
98
 
99
  summary_pipeline = pipeline(
 
120
  max(32, word_count + 20),
121
  )
122
 
123
+ # Для очень короткого текста сум��аризация мало смысла
124
  if word_count < 8:
125
  return cleaned_text
126
 
 
136
 
137
 
138
  # ============================
139
+ # 5. Трансформер #3: TTS (английский, MMS VITS)
140
  # ============================
141
 
142
  tts_model: VitsModel = VitsModel.from_pretrained("facebook/mms-tts-eng")
 
148
  """
149
  Озвучка английского текста конспекта через VitsModel (facebook/mms-tts-eng).
150
 
151
+ Если модель внутри упадёт на каком-то странном тексте (RuntimeError),
152
+ просто вернём None и не будем ронять всё приложение.
153
  """
154
  cleaned_text: str = summary_text.strip()
155
  if not cleaned_text:
 
176
  print(f"[WARN] TTS RuntimeError: {runtime_error}")
177
  return None
178
 
179
+ # Приводим к numpy и ограничиваем амплитуды
180
  waveform_array = waveform_tensor.squeeze().cpu().numpy().astype("float32")
181
  waveform_array = numpy_module.clip(waveform_array, -1.0, 1.0)
182
 
 
195
 
196
 
197
  # ============================
198
+ # 6. Полный пайплайн
199
  # ============================
200
 
201
  def full_flow(
202
  image_object: Image.Image,
203
  max_summary_tokens: int = 128,
204
+ ) -> Tuple[str, str, str, Optional[str]]:
205
  """
206
  Полный пайплайн:
207
+ 1) OCR (easyocr): изображение -> исходный текст (английский)
208
+ 2) Классификация текста трансформером (sentiment)
209
+ 3) Суммаризация: текст -> конспект
210
+ 4) TTS: конспект -> .wav файл (или None, если TTS не смог)
211
  """
212
  recognized_text: str = run_ocr(image_object=image_object)
213
 
214
+ classification_text: str = run_text_classification(recognized_text)
215
+
216
  summary_text: str = run_summarization(
217
  input_text=recognized_text,
218
  max_summary_tokens=max_summary_tokens,
 
220
 
221
  audio_file_path: Optional[str] = run_tts(summary_text=summary_text)
222
 
223
+ return recognized_text, classification_text, summary_text, audio_file_path
224
 
225
 
226
  # ============================
227
+ # 7. Gradio UI (на русском)
228
  # ============================
229
 
230
  gradio_interface = gradio_module.Interface(
 
244
  ],
245
  outputs=[
246
  gradio_module.Textbox(
247
+ label="Распознанный текст (OCR, easyocr)",
248
+ lines=8,
249
+ ),
250
+ gradio_module.Textbox(
251
+ label="Анализ текста (классификация, DistilBERT)",
252
+ lines=2,
253
  ),
254
  gradio_module.Textbox(
255
+ label="Конспект (английский текст, DistilBART)",
256
  lines=6,
257
  ),
258
  gradio_module.Audio(
259
+ label="Озвучка конспекта (английский TTS, VITS)",
260
  type="filepath",
261
  ),
262
  ],
263
+ title="Картинка → Текст → Анализ → Конспект → Озвучка",
264
  description=(
265
+ "1) easyocr распознаёт печатный англ��йский текст с картинки.\n"
266
+ "2) Трансформер-классификатор (DistilBERT) оценивает тон текста.\n"
267
+ "3) Трансформер-суммаризатор (DistilBART) делает краткий конспект.\n"
268
+ "4) Трансформер TTS (MMS VITS) озвучивает конспект.\n"
269
+ "В проекте используются три трансформера с Hugging Face, OCR сделан через easyocr."
270
  ),
271
  )
272
 
requirements.txt CHANGED
@@ -1,8 +1,9 @@
1
- transformers>=4.40.0
2
  torch
3
- compressed-tensors
4
  sentencepiece
5
  gradio
6
  Pillow
7
  numpy
8
  soundfile
 
 
 
1
+ transformers>=4.33.0
2
  torch
 
3
  sentencepiece
4
  gradio
5
  Pillow
6
  numpy
7
  soundfile
8
+ easyocr
9
+ opencv-python-headless