Hayk Arutyunyan commited on
Commit
2ebce2f
·
1 Parent(s): 09c60d5

Update demo pipeline

Browse files
Files changed (4) hide show
  1. README.md +1 -3
  2. app.py +60 -28
  3. requirements.txt +9 -3
  4. src/pipeline_hf.py +131 -0
README.md CHANGED
@@ -1,12 +1,10 @@
1
  ---
2
  title: MNEMO OCR Demo
3
  emoji: 🔍
4
- colorFrom: purple
5
- colorTo: gray
6
  sdk: gradio
7
  sdk_version: 3.41.2
8
  app_file: app.py
9
  pinned: false
10
  ---
11
 
12
- MNEMO OCR simple OCR demo using PaddleOCR.
 
1
  ---
2
  title: MNEMO OCR Demo
3
  emoji: 🔍
 
 
4
  sdk: gradio
5
  sdk_version: 3.41.2
6
  app_file: app.py
7
  pinned: false
8
  ---
9
 
10
+ Production-like demo of sensor digitization pipeline.
app.py CHANGED
@@ -1,41 +1,73 @@
1
  import gradio as gr
2
- from paddleocr import PaddleOCR
3
- from PIL import Image
4
  import numpy as np
 
 
 
 
5
 
6
- # Инициализируем OCR один раз
7
- ocr = PaddleOCR(
8
- use_angle_cls=True,
9
- lang='en' # если нужно rus — скажи
10
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11
 
12
- def run_ocr(image):
13
- if image is None:
14
- return "No image uploaded."
15
 
16
- # Конвертируем PIL → numpy
17
- img = np.array(image)
18
 
19
- # Запуск OCR
20
- result = ocr.ocr(img)
 
21
 
22
- # Формируем вывод текстом
23
- lines = []
24
- for block in result:
25
- for line in block:
26
- text = line[1][0]
27
- conf = line[1][1]
28
- lines.append(f"{text} (conf: {conf:.2f})")
29
 
30
- return "\n".join(lines)
31
 
 
 
 
32
  demo = gr.Interface(
33
- fn=run_ocr,
34
- inputs=gr.Image(type="pil"),
35
- outputs=gr.Textbox(label="Recognized Text"),
36
- title="MNEMO OCR Demo",
37
- description="Upload an image and extract text using PaddleOCR.",
 
 
 
 
 
38
  )
39
 
40
  if __name__ == "__main__":
41
- demo.launch()
 
1
  import gradio as gr
 
 
2
  import numpy as np
3
+ import cv2
4
+ import pandas as pd
5
+ from pathlib import Path
6
+ from tempfile import NamedTemporaryFile
7
 
8
+ from src.pipeline_hf import process_single_image
9
+ from src.config_loader import load_config
10
+
11
+
12
+ # ================================
13
+ # Вспомогательная функция: подсветка сенсоров
14
+ # ================================
15
+ def draw_boxes(img_rgb, sensors):
16
+ img = img_rgb.copy()
17
+
18
+ for s in sensors:
19
+ x, y, w, h = s["x"], s["y"], s["w"], s["h"]
20
+ cv2.rectangle(img, (x, y), (x + w, y + h), (0, 255, 0), 2)
21
+
22
+ return img
23
+
24
+
25
+ # ================================
26
+ # Основная функция демо
27
+ # ================================
28
+ def hf_process(img_rgb):
29
+
30
+ cfg = load_config("configs/config.yaml")
31
+
32
+ # Запуск пайплайна
33
+ result = process_single_image(img_rgb, cfg_path="configs/config.yaml")
34
+
35
+ title = result["title"]
36
+ sensors = result["sensors"]
37
 
38
+ # --- визуализация сенсоров ---
39
+ boxed = draw_boxes(img_rgb, sensors)
 
40
 
41
+ # --- DataFrame сенсоров ---
42
+ df = pd.DataFrame(sensors)[["text", "score", "x", "y", "w", "h"]]
43
 
44
+ # --- Excel на скачивание ---
45
+ tmp = NamedTemporaryFile(delete=False, suffix=".xlsx")
46
+ df.to_excel(tmp.name, index=False)
47
 
48
+ return (
49
+ boxed,
50
+ title,
51
+ df,
52
+ tmp.name # путь к скачиваемому файлу
53
+ )
 
54
 
 
55
 
56
+ # ================================
57
+ # Gradio UI
58
+ # ================================
59
  demo = gr.Interface(
60
+ fn=hf_process,
61
+ inputs=gr.Image(type="numpy", label="Upload image"),
62
+ outputs=[
63
+ gr.Image(label="Detected sensors"),
64
+ gr.Textbox(label="Title"),
65
+ gr.Dataframe(label="Recognized sensors"),
66
+ gr.File(label="Download Excel")
67
+ ],
68
+ title="MNEMO OCR — HF Demo",
69
+ description="Продовая демо-версия оцифровщика MNEMO."
70
  )
71
 
72
  if __name__ == "__main__":
73
+ demo.launch()
requirements.txt CHANGED
@@ -1,5 +1,11 @@
1
- gradio==3.41.2
 
 
 
 
2
  paddlepaddle==2.6.1
3
  paddleocr==2.6.1.0
4
- numpy==1.23.5
5
- pillow==10.4.0
 
 
 
1
+ numpy==1.23.5
2
+ pandas==2.2.3
3
+ pillow==10.4.0
4
+ opencv-python-headless==4.7.0.72
5
+
6
  paddlepaddle==2.6.1
7
  paddleocr==2.6.1.0
8
+
9
+ gradio==3.41.2
10
+ pyyaml==6.0.2
11
+ openpyxl==3.1.5
src/pipeline_hf.py ADDED
@@ -0,0 +1,131 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import cv2
2
+ import numpy as np
3
+ import pandas as pd
4
+
5
+ from pathlib import Path
6
+ from src.config_loader import load_config
7
+ from src.ocr_utils_demo import ocr_title, ocr_sensors
8
+
9
+ def process_single_image(img: np.ndarray, cfg_path: str | Path = "configs/config.yaml"):
10
+ """
11
+ Обрабатывает одно изображение:
12
+ - вытаскивает титул;
13
+ - находит и оцифровывает сенсоры;
14
+ - возвращает структуру с результатом.
15
+ """
16
+ cfg = load_config(cfg_path)
17
+
18
+ # Цветовые диапазоны сенсоров
19
+ color_ranges = []
20
+
21
+ for key, rng in color_ranges.items():
22
+ lo = np.array(rng["from"], dtype=np.uint8)
23
+ hi = np.array(rng["to"], dtype=np.uint8)
24
+ color_ranges.append((lo, hi))
25
+
26
+ # ---------- 1. Оцифровка титула ----------
27
+ h, w = img.shape[:2]
28
+
29
+ img_bgr = cv2.cvtColor(img, cv2.COLOR_RGB2BGR)
30
+
31
+ title_roi = img_bgr[:45, :int(w / 2.4)]
32
+ title_text = ocr_title(title_roi)
33
+
34
+ # ---------- 2. Оцифровка сенсоров ----------
35
+ hsv = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2HSV)
36
+
37
+ mask = None
38
+
39
+ for lo_np, hi_np in color_ranges:
40
+ cur = cv2.inRange(hsv, lo_np, hi_np)
41
+ mask = cur if mask is None else cv2.bitwise_or(mask, cur)
42
+
43
+ contours, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
44
+
45
+ rois, positions = [], []
46
+ for cnt in contours:
47
+ x, y, ww, hh = cv2.boundingRect(cnt)
48
+ if ww < 90 or hh < 17:
49
+ continue
50
+
51
+ hh_clamped = min(hh, 17)
52
+
53
+ roi = img_bgr[y:y + hh_clamped, x:x + ww]
54
+
55
+ rois.append(roi)
56
+ positions.append((x, y, ww, hh_clamped))
57
+
58
+ # ---------- 3. OCR сенсоров ----------
59
+ sensors = []
60
+ if rois:
61
+ ocr_results = ocr_sensors(rois)
62
+
63
+ for (x, y, ww, hh), r in zip(positions, ocr_results):
64
+ sensors.append({
65
+ "text": r["text"],
66
+ "score": r["score"],
67
+ "x": x,
68
+ "y": y,
69
+ "w": ww,
70
+ "h": hh
71
+ })
72
+
73
+ return {
74
+ "title": title_text,
75
+ "sensors": sensors
76
+ }
77
+
78
+
79
+ # ---------------------------------------------------------
80
+ # Основной pipeline → Excel
81
+ # ---------------------------------------------------------
82
+ def process_image_to_excel(cfg: dict):
83
+ input_dir = Path(cfg["paths"]["input"])
84
+ output_dir = Path(cfg["paths"]["output"])
85
+ excel_name = cfg["export"]["excel_filename"]
86
+
87
+ output_dir.mkdir(parents=True, exist_ok=True)
88
+ excel_path = output_dir / excel_name
89
+
90
+ results = []
91
+
92
+ for img_path in input_dir.glob("*.png"):
93
+ name = img_path.name
94
+
95
+ img = cv2.imread(str(img_path))
96
+ if img is None:
97
+ print(f"Не могу прочитать файл {name}")
98
+ continue
99
+
100
+ img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
101
+
102
+ res = process_single_image(img_rgb, cfg_path="configs/config.yaml")
103
+
104
+ title_text = res["title"]
105
+ sensors = res["sensors"]
106
+
107
+ for sen in sensors:
108
+ results.append({
109
+ "filename": name,
110
+ "title": title_text,
111
+ "sensor_name": sen["text"],
112
+ "score": sen["score"]
113
+ })
114
+
115
+ if not results:
116
+ print("⚠ Нет данных для записи.")
117
+ return
118
+
119
+ df = pd.DataFrame(results)
120
+ df.to_excel(excel_path, index=False, engine="openpyxl")
121
+
122
+ print(f"✅ Готово! Excel сохранён: {excel_path.resolve()}")
123
+
124
+
125
+ # ---------------------------------------------------------
126
+ # Запуск
127
+ # ---------------------------------------------------------
128
+ if __name__ == "__main__":
129
+ cfg = load_config("configs/config.yaml")
130
+ process_image_to_excel(cfg)
131
+