MohammadReza-Halakoo commited on
Commit
8b084fc
·
verified ·
1 Parent(s): 7bc6dbb

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +393 -54
app.py CHANGED
@@ -1,4 +1,368 @@
1
- # app.py — TRUST OCR DEMO (Streamlit) — works even if batch_text_detection is missing
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2
 
3
  import os
4
  import io
@@ -10,57 +374,42 @@ import cv2
10
  from PIL import Image
11
  import pypdfium2
12
  import pytesseract
13
- # --- set safe dirs before importing streamlit ---
14
- safe_home = os.environ.get("HOME") or "/app"
15
- os.environ["HOME"] = safe_home
16
- cfg_dir = os.path.join(safe_home, ".streamlit")
17
- os.makedirs(cfg_dir, exist_ok=True)
18
-
19
 
20
- # --- قبل از import streamlit، احیاناً مسیر کش قابل‌نوشتن:
21
- import os, tempfile
 
 
 
22
  os.environ.setdefault("HF_HOME", "/tmp/hf_home")
23
- os.makedirs(os.environ["HF_HOME"], exist_ok=True)
24
- import tempfile, os
25
- temp_dir = os.path.join(tempfile.gettempdir(), "trustocr_temp")
26
- os.makedirs(temp_dir, exist_ok=True)
27
- # جای "temp_files" استفاده کن
28
 
 
 
29
 
30
- # اطمینان از اینکه Streamlit همه فایل‌ها را اینجا می‌نویسد
31
- os.environ["STREAMLIT_CONFIG_DIR"] = cfg_dir
32
-
33
- # اگر دوست داری همین‌جا config.toml بسازی و usage stats را خاموش کنی:
34
- conf_path = os.path.join(cfg_dir, "config.toml")
35
  if not os.path.exists(conf_path):
36
  with open(conf_path, "w", encoding="utf-8") as f:
37
- f.write("browser.gatherUsageStats = false\n")
38
-
39
- # runtime dir امن
40
- runtime_dir = os.path.join(tempfile.gettempdir(), ".streamlit")
41
- os.environ["STREAMLIT_RUNTIME_DIR"] = runtime_dir
42
- os.makedirs(runtime_dir, exist_ok=True)
 
 
43
 
44
  import streamlit as st
45
 
46
-
47
- # ===== Safe runtime dir for Streamlit/HF cache =====
48
- # runtime_dir = os.path.join(tempfile.gettempdir(), ".streamlit")
49
- # os.environ["STREAMLIT_RUNTIME_DIR"] = runtime_dir
50
- # os.makedirs(runtime_dir, exist_ok=True)
51
-
52
-
53
-
54
- # ===== Try to import Surya APIs =====
55
  DET_AVAILABLE = True
56
  try:
57
  from surya.detection import batch_text_detection
58
  except Exception:
59
  DET_AVAILABLE = False
60
 
61
- from surya.layout import batch_layout_detection # may still import; well gate usage by DET_AVAILABLE
62
 
63
- # Detection model loaders: segformer (newer) vs model (older)
64
  try:
65
  from surya.model.detection.segformer import load_model as load_det_model, load_processor as load_det_processor
66
  except Exception:
@@ -150,7 +499,7 @@ st.set_page_config(page_title="TRUST OCR DEMO", layout="wide")
150
  st.markdown("# TRUST OCR DEMO")
151
 
152
  if not DET_AVAILABLE:
153
- st.warning("⚠️ ماژول تشخیص متن Surya در این محیط در دسترس نیست. OCR کامل کار می‌کند، اما دکمه‌های Detection/Layout/Order غیرفعال شده‌اند. برای فعال‌سازی آن‌ها، Surya را به نسخهٔ سازگار پین کنید (راهنما پایین صفحه).")
154
 
155
  # Sidebar controls
156
  in_file = st.sidebar.file_uploader("فایل PDF یا عکس :", type=["pdf", "png", "jpg", "jpeg", "gif", "webp"])
@@ -184,19 +533,12 @@ col2, col1 = st.columns([.5, .5])
184
  def load_det_cached():
185
  return load_det_model(checkpoint="vikp/surya_det2"), load_det_processor(checkpoint="vikp/surya_det2")
186
 
187
- # from huggingface_hub import HfFolder
188
- # HF_TOKEN = os.environ.get("HF_TOKEN")
189
-
190
- # @st.cache_resource(show_spinner=True)
191
- # def load_rec_cached():
192
- # return load_rec_model(checkpoint="MohammadReza-Halakoo/TrustOCR", token=HF_TOKEN), \
193
- # load_rec_processor(checkpoint="MohammadReza-Halakoo/TrustOCR", token=HF_TOKEN)
194
-
195
  @st.cache_resource(show_spinner=True)
196
  def load_rec_cached():
 
197
  checkpoints = [
198
- "MohammadReza-Halakoo/TrustOCR", # خصوصی
199
- "vikp/surya_rec2", # عمومی (fallback)
200
  ]
201
  last_err = None
202
  for ckpt in checkpoints:
@@ -208,10 +550,6 @@ def load_rec_cached():
208
  last_err = e
209
  st.error(f"Loading recognition checkpoint failed: {last_err}")
210
  raise last_err
211
- # @st.cache_resource(show_spinner=True)
212
- # def load_rec_cached():
213
- # return load_rec_model(checkpoint="MohammadReza-Halakoo/TrustOCR"), \
214
- # load_rec_processor(checkpoint="MohammadReza-Halakoo/TrustOCR")
215
 
216
  @st.cache_resource(show_spinner=True)
217
  def load_layout_cached():
@@ -279,8 +617,7 @@ def ocr_page(pil_img: Image.Image, langs: List[str]):
279
  """Full-page OCR using Surya run_ocr — works without detection import."""
280
  langs = list(langs) if langs else ["Persian"]
281
  replace_lang_with_code(langs) # in-place
282
- # If detection models are loaded, pass them; else, let run_ocr use its internal defaults
283
- args = [pil_img], [langs]
284
  if det_model and det_processor and rec_model and rec_processor:
285
  img_pred: OCRResult = run_ocr([pil_img], [langs], det_model, det_processor, rec_model, rec_processor)[0]
286
  else:
@@ -303,7 +640,8 @@ if "pdf" in filetype:
303
  pil_image = get_page_image(in_file, page_number)
304
  else:
305
  bytes_data = in_file.getvalue()
306
- temp_dir = "temp_files"
 
307
  os.makedirs(temp_dir, exist_ok=True)
308
  file_path = os.path.join(temp_dir, in_file.name)
309
  with open(file_path, "wb") as f:
@@ -359,3 +697,4 @@ with col1:
359
 
360
  with col2:
361
  st.image(pil_image, caption="تصویر ورودی | Input Preview", use_column_width=True)
 
 
1
+ # # app.py — TRUST OCR DEMO (Streamlit) — works even if batch_text_detection is missing
2
+
3
+ # import os
4
+ # import io
5
+ # import tempfile
6
+ # from typing import List
7
+
8
+ # import numpy as np
9
+ # import cv2
10
+ # from PIL import Image
11
+ # import pypdfium2
12
+ # import pytesseract
13
+ # # --- set safe dirs before importing streamlit ---
14
+ # safe_home = os.environ.get("HOME") or "/app"
15
+ # os.environ["HOME"] = safe_home
16
+ # cfg_dir = os.path.join(safe_home, ".streamlit")
17
+ # os.makedirs(cfg_dir, exist_ok=True)
18
+
19
+
20
+ # # --- قبل از import streamlit، احیاناً مسیر کش قابل‌نوشتن:
21
+ # import os, tempfile
22
+ # os.environ.setdefault("HF_HOME", "/tmp/hf_home")
23
+ # os.makedirs(os.environ["HF_HOME"], exist_ok=True)
24
+ # import tempfile, os
25
+ # temp_dir = os.path.join(tempfile.gettempdir(), "trustocr_temp")
26
+ # os.makedirs(temp_dir, exist_ok=True)
27
+ # # جای "temp_files" استفاده کن
28
+
29
+
30
+ # # اطمینان از اینکه Streamlit همه فایل‌ها را اینجا می‌نویسد
31
+ # os.environ["STREAMLIT_CONFIG_DIR"] = cfg_dir
32
+
33
+ # # اگر دوست داری همین‌جا config.toml بسازی و usage stats را خاموش کنی:
34
+ # conf_path = os.path.join(cfg_dir, "config.toml")
35
+ # if not os.path.exists(conf_path):
36
+ # with open(conf_path, "w", encoding="utf-8") as f:
37
+ # f.write("browser.gatherUsageStats = false\n")
38
+
39
+ # # runtime dir امن
40
+ # runtime_dir = os.path.join(tempfile.gettempdir(), ".streamlit")
41
+ # os.environ["STREAMLIT_RUNTIME_DIR"] = runtime_dir
42
+ # os.makedirs(runtime_dir, exist_ok=True)
43
+
44
+ # import streamlit as st
45
+
46
+
47
+ # # ===== Safe runtime dir for Streamlit/HF cache =====
48
+ # # runtime_dir = os.path.join(tempfile.gettempdir(), ".streamlit")
49
+ # # os.environ["STREAMLIT_RUNTIME_DIR"] = runtime_dir
50
+ # # os.makedirs(runtime_dir, exist_ok=True)
51
+
52
+
53
+
54
+ # # ===== Try to import Surya APIs =====
55
+ # DET_AVAILABLE = True
56
+ # try:
57
+ # from surya.detection import batch_text_detection
58
+ # except Exception:
59
+ # DET_AVAILABLE = False
60
+
61
+ # from surya.layout import batch_layout_detection # may still import; we’ll gate usage by DET_AVAILABLE
62
+
63
+ # # Detection model loaders: segformer (newer) vs model (older)
64
+ # try:
65
+ # from surya.model.detection.segformer import load_model as load_det_model, load_processor as load_det_processor
66
+ # except Exception:
67
+ # from surya.model.detection.model import load_model as load_det_model, load_processor as load_det_processor
68
+
69
+ # from surya.model.recognition.model import load_model as load_rec_model
70
+ # from surya.model.recognition.processor import load_processor as load_rec_processor
71
+
72
+ # from surya.model.ordering.model import load_model as load_order_model
73
+ # from surya.model.ordering.processor import load_processor as load_order_processor
74
+ # from surya.ordering import batch_ordering
75
+
76
+ # from surya.ocr import run_ocr
77
+ # from surya.postprocessing.heatmap import draw_polys_on_image
78
+ # from surya.postprocessing.text import draw_text_on_image
79
+ # from surya.languages import CODE_TO_LANGUAGE
80
+ # from surya.input.langs import replace_lang_with_code
81
+ # from surya.schema import OCRResult, TextDetectionResult, LayoutResult, OrderResult
82
+
83
+
84
+ # # ===================== Helper Functions =====================
85
+
86
+ # def remove_border(image_path: str, output_path: str) -> np.ndarray:
87
+ # """Remove outer border & deskew (perspective) if a rectangular contour is found."""
88
+ # image = cv2.imread(image_path)
89
+ # if image is None:
90
+ # raise ValueError(f"Cannot read image: {image_path}")
91
+ # gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
92
+ # _, binary = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
93
+ # contours, _ = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
94
+ # if not contours:
95
+ # cv2.imwrite(output_path, image)
96
+ # return image
97
+ # max_contour = max(contours, key=cv2.contourArea)
98
+ # epsilon = 0.02 * cv2.arcLength(max_contour, True)
99
+ # approx = cv2.approxPolyDP(max_contour, epsilon, True)
100
+ # if len(approx) == 4:
101
+ # pts = approx.reshape(4, 2).astype("float32")
102
+ # rect = np.zeros((4, 2), dtype="float32")
103
+ # s = pts.sum(axis=1)
104
+ # rect[0] = pts[np.argmin(s)] # tl
105
+ # rect[2] = pts[np.argmax(s)] # br
106
+ # diff = np.diff(pts, axis=1)
107
+ # rect[1] = pts[np.argmin(diff)] # tr
108
+ # rect[3] = pts[np.argmax(diff)] # bl
109
+ # (tl, tr, br, bl) = rect
110
+ # widthA = np.linalg.norm(br - bl)
111
+ # widthB = np.linalg.norm(tr - tl)
112
+ # maxWidth = max(int(widthA), int(widthB))
113
+ # heightA = np.linalg.norm(tr - br)
114
+ # heightB = np.linalg.norm(tl - bl)
115
+ # maxHeight = max(int(heightA), int(heightB))
116
+ # dst = np.array([[0, 0], [maxWidth - 1, 0],
117
+ # [maxWidth - 1, maxHeight - 1],
118
+ # [0, maxHeight - 1]], dtype="float32")
119
+ # M = cv2.getPerspectiveTransform(rect, dst)
120
+ # cropped = cv2.warpPerspective(image, M, (maxWidth, maxHeight))
121
+ # cv2.imwrite(output_path, cropped)
122
+ # return cropped
123
+ # else:
124
+ # cv2.imwrite(output_path, image)
125
+ # return image
126
+
127
+
128
+ # def open_pdf(pdf_file) -> pypdfium2.PdfDocument:
129
+ # stream = io.BytesIO(pdf_file.getvalue())
130
+ # return pypdfium2.PdfDocument(stream)
131
+
132
+
133
+ # @st.cache_data(show_spinner=False)
134
+ # def get_page_image(pdf_file, page_num: int, dpi: int = 96) -> Image.Image:
135
+ # doc = open_pdf(pdf_file)
136
+ # renderer = doc.render(pypdfium2.PdfBitmap.to_pil, page_indices=[page_num - 1], scale=dpi / 72)
137
+ # png = list(renderer)[0]
138
+ # return png.convert("RGB")
139
+
140
+
141
+ # @st.cache_data(show_spinner=False)
142
+ # def page_count(pdf_file) -> int:
143
+ # doc = open_pdf(pdf_file)
144
+ # return len(doc)
145
+
146
+
147
+ # # ===================== Streamlit UI =====================
148
+
149
+ # st.set_page_config(page_title="TRUST OCR DEMO", layout="wide")
150
+ # st.markdown("# TRUST OCR DEMO")
151
+
152
+ # if not DET_AVAILABLE:
153
+ # st.warning("⚠️ ماژول تشخیص متن Surya در این محیط در دسترس نیست. OCR کامل کار می‌کند، اما دکمه‌های Detection/Layout/Order غیرفعال شده‌اند. برای فعال‌سازی آن‌ها، Surya را به نسخهٔ سازگار پین کنید (راهنما پایین صفحه).")
154
+
155
+ # # Sidebar controls
156
+ # in_file = st.sidebar.file_uploader("فایل PDF یا عکس :", type=["pdf", "png", "jpg", "jpeg", "gif", "webp"])
157
+ # languages = st.sidebar.multiselect(
158
+ # "زبان‌ها (Languages)",
159
+ # sorted(list(CODE_TO_LANGUAGE.values())),
160
+ # default=["Persian"],
161
+ # max_selections=4
162
+ # )
163
+ # auto_rotate = st.sidebar.toggle("چرخش خودکار (Tesseract OSD)", value=True)
164
+ # auto_border = st.sidebar.toggle("حذف قاب/کادر تصویر ورودی", value=True)
165
+
166
+ # # Buttons (disable some if detection missing)
167
+ # text_det_btn = st.sidebar.button("تشخیص متن (Detection)", disabled=not DET_AVAILABLE)
168
+ # layout_det_btn = st.sidebar.button("آنالیز صفحه (Layout)", disabled=not DET_AVAILABLE)
169
+ # order_det_btn = st.sidebar.button("ترتیب خوانش (Reading Order)", disabled=not DET_AVAILABLE)
170
+ # text_rec_btn = st.sidebar.button("تبدیل به متن (Recognition)")
171
+
172
+ # if in_file is None:
173
+ # st.info("یک فایل PDF/عکس از سایدبار انتخاب کنید. | Please upload a file to begin.")
174
+ # st.stop()
175
+
176
+ # filetype = in_file.type
177
+
178
+ # # Two-column layout (left: outputs / right: input image)
179
+ # col2, col1 = st.columns([.5, .5])
180
+
181
+ # # ===================== Load Models (cached) =====================
182
+
183
+ # @st.cache_resource(show_spinner=True)
184
+ # def load_det_cached():
185
+ # return load_det_model(checkpoint="vikp/surya_det2"), load_det_processor(checkpoint="vikp/surya_det2")
186
+
187
+ # # from huggingface_hub import HfFolder
188
+ # # HF_TOKEN = os.environ.get("HF_TOKEN")
189
+
190
+ # # @st.cache_resource(show_spinner=True)
191
+ # # def load_rec_cached():
192
+ # # return load_rec_model(checkpoint="MohammadReza-Halakoo/TrustOCR", token=HF_TOKEN), \
193
+ # # load_rec_processor(checkpoint="MohammadReza-Halakoo/TrustOCR", token=HF_TOKEN)
194
+
195
+ # @st.cache_resource(show_spinner=True)
196
+ # def load_rec_cached():
197
+ # checkpoints = [
198
+ # "MohammadReza-Halakoo/TrustOCR", # خصوصی
199
+ # "vikp/surya_rec2", # عمومی (fallback)
200
+ # ]
201
+ # last_err = None
202
+ # for ckpt in checkpoints:
203
+ # try:
204
+ # m = load_rec_model(checkpoint=ckpt)
205
+ # p = load_rec_processor(checkpoint=ckpt)
206
+ # return m, p
207
+ # except Exception as e:
208
+ # last_err = e
209
+ # st.error(f"Loading recognition checkpoint failed: {last_err}")
210
+ # raise last_err
211
+ # # @st.cache_resource(show_spinner=True)
212
+ # # def load_rec_cached():
213
+ # # return load_rec_model(checkpoint="MohammadReza-Halakoo/TrustOCR"), \
214
+ # # load_rec_processor(checkpoint="MohammadReza-Halakoo/TrustOCR")
215
+
216
+ # @st.cache_resource(show_spinner=True)
217
+ # def load_layout_cached():
218
+ # return load_det_model(checkpoint="vikp/surya_layout2"), load_det_processor(checkpoint="vikp/surya_layout2")
219
+
220
+ # @st.cache_resource(show_spinner=True)
221
+ # def load_order_cached():
222
+ # return load_order_model(checkpoint="vikp/surya_order"), load_order_processor(checkpoint="vikp/surya_order")
223
+
224
+
225
+ # # recognition models are enough for run_ocr; detection/layout/order models used only if DET_AVAILABLE
226
+ # rec_model, rec_processor = load_rec_cached()
227
+ # if DET_AVAILABLE:
228
+ # det_model, det_processor = load_det_cached()
229
+ # layout_model, layout_processor = load_layout_cached()
230
+ # order_model, order_processor = load_order_cached()
231
+ # else:
232
+ # det_model = det_processor = layout_model = layout_processor = order_model = order_processor = None
233
+
234
+
235
+ # # ===================== High-level Ops =====================
236
+
237
+ # def _apply_auto_rotate(pil_img: Image.Image) -> Image.Image:
238
+ # """Auto-rotate using Tesseract OSD if enabled."""
239
+ # if not auto_rotate:
240
+ # return pil_img
241
+ # try:
242
+ # osd = pytesseract.image_to_osd(pil_img, output_type=pytesseract.Output.DICT)
243
+ # angle = int(osd.get("rotate", 0)) # 0/90/180/270
244
+ # if angle and angle % 360 != 0:
245
+ # return pil_img.rotate(-angle, expand=True)
246
+ # return pil_img
247
+ # except Exception as e:
248
+ # st.warning(f"OSD rotation failed, continuing without rotation. Error: {e}")
249
+ # return pil_img
250
+
251
+
252
+ # def text_detection(pil_img: Image.Image):
253
+ # pred: TextDetectionResult = batch_text_detection([pil_img], det_model, det_processor)[0]
254
+ # polygons = [p.polygon for p in pred.bboxes]
255
+ # det_img = draw_polys_on_image(polygons, pil_img.copy())
256
+ # return det_img, pred
257
+
258
+
259
+ # def layout_detection(pil_img: Image.Image):
260
+ # _, det_pred = text_detection(pil_img)
261
+ # pred: LayoutResult = batch_layout_detection([pil_img], layout_model, layout_processor, [det_pred])[0]
262
+ # polygons = [p.polygon for p in pred.bboxes]
263
+ # labels = [p.label for p in pred.bboxes]
264
+ # layout_img = draw_polys_on_image(polygons, pil_img.copy(), labels=labels, label_font_size=40)
265
+ # return layout_img, pred
266
+
267
+
268
+ # def order_detection(pil_img: Image.Image):
269
+ # _, layout_pred = layout_detection(pil_img)
270
+ # bboxes = [l.bbox for l in layout_pred.bboxes]
271
+ # pred: OrderResult = batch_ordering([pil_img], [bboxes], order_model, order_processor)[0]
272
+ # polys = [l.polygon for l in pred.bboxes]
273
+ # positions = [str(l.position) for l in pred.bboxes]
274
+ # order_img = draw_polys_on_image(polys, pil_img.copy(), labels=positions, label_font_size=40)
275
+ # return order_img, pred
276
+
277
+
278
+ # def ocr_page(pil_img: Image.Image, langs: List[str]):
279
+ # """Full-page OCR using Surya run_ocr — works without detection import."""
280
+ # langs = list(langs) if langs else ["Persian"]
281
+ # replace_lang_with_code(langs) # in-place
282
+ # # If detection models are loaded, pass them; else, let run_ocr use its internal defaults
283
+ # args = [pil_img], [langs]
284
+ # if det_model and det_processor and rec_model and rec_processor:
285
+ # img_pred: OCRResult = run_ocr([pil_img], [langs], det_model, det_processor, rec_model, rec_processor)[0]
286
+ # else:
287
+ # img_pred: OCRResult = run_ocr([pil_img], [langs])[0]
288
+ # bboxes = [l.bbox for l in img_pred.text_lines]
289
+ # text = [l.text for l in img_pred.text_lines]
290
+ # rec_img = draw_text_on_image(bboxes, text, pil_img.size, langs, has_math="_math" in langs)
291
+ # return rec_img, img_pred
292
+
293
+
294
+ # # ===================== Input Handling =====================
295
+
296
+ # if "pdf" in filetype:
297
+ # try:
298
+ # pg_cnt = page_count(in_file)
299
+ # except Exception as e:
300
+ # st.error(f"خواندن PDF ناموفق بود: {e}")
301
+ # st.stop()
302
+ # page_number = st.sidebar.number_input("صفحه:", min_value=1, value=1, max_value=pg_cnt)
303
+ # pil_image = get_page_image(in_file, page_number)
304
+ # else:
305
+ # bytes_data = in_file.getvalue()
306
+ # temp_dir = "temp_files"
307
+ # os.makedirs(temp_dir, exist_ok=True)
308
+ # file_path = os.path.join(temp_dir, in_file.name)
309
+ # with open(file_path, "wb") as f:
310
+ # f.write(bytes_data)
311
+ # out_file = os.path.splitext(file_path)[0] + "-1.JPG"
312
+ # try:
313
+ # if auto_border:
314
+ # _ = remove_border(file_path, out_file)
315
+ # pil_image = Image.open(out_file).convert("RGB")
316
+ # else:
317
+ # pil_image = Image.open(file_path).convert("RGB")
318
+ # except Exception as e:
319
+ # st.warning(f"حذف قاب/بازخوانی تصویر با خطا مواجه شد؛ تصویر اصلی استفاده می‌شود. Error: {e}")
320
+ # pil_image = Image.open(file_path).convert("RGB")
321
+
322
+ # # Auto-rotate if enabled
323
+ # pil_image = _apply_auto_rotate(pil_image)
324
+
325
+ # # ===================== Buttons Logic =====================
326
+
327
+ # with col1:
328
+ # if text_det_btn and DET_AVAILABLE:
329
+ # try:
330
+ # det_img, det_pred = text_detection(pil_image)
331
+ # st.image(det_img, caption="تشخیص متن (Detection)", use_column_width=True)
332
+ # except Exception as e:
333
+ # st.error(f"خطا در تشخیص متن: {e}")
334
+
335
+ # if layout_det_btn and DET_AVAILABLE:
336
+ # try:
337
+ # layout_img, layout_pred = layout_detection(pil_image)
338
+ # st.image(layout_img, caption="آنالیز صفحه (Layout)", use_column_width=True)
339
+ # except Exception as e:
340
+ # st.error(f"خطا در آنالیز صفحه: {e}")
341
+
342
+ # if order_det_btn and DET_AVAILABLE:
343
+ # try:
344
+ # order_img, order_pred = order_detection(pil_image)
345
+ # st.image(order_img, caption="ترتیب خوانش (Reading Order)", use_column_width=True)
346
+ # except Exception as e:
347
+ # st.error(f"خطا در ترتیب خوانش: {e}")
348
+
349
+ # if text_rec_btn:
350
+ # try:
351
+ # rec_img, ocr_pred = ocr_page(pil_image, languages)
352
+ # text_tab, json_tab = st.tabs(["متن صفحه | Page Text", "JSON"])
353
+ # with text_tab:
354
+ # st.text("\n".join([p.text for p in ocr_pred.text_lines]))
355
+ # with json_tab:
356
+ # st.json(ocr_pred.model_dump(), expanded=False)
357
+ # except Exception as e:
358
+ # st.error(f"خطا در بازشناسی متن (Recognition): {e}")
359
+
360
+ # with col2:
361
+ # st.image(pil_image, caption="تصویر ورودی | Input Preview", use_column_width=True)
362
+
363
+
364
+ # app.py — TRUST OCR DEMO (Streamlit)
365
+ # Works on Hugging Face Spaces (no permission/XSRF issues)
366
 
367
  import os
368
  import io
 
374
  from PIL import Image
375
  import pypdfium2
376
  import pytesseract
 
 
 
 
 
 
377
 
378
+ # -------------------- Safe, writable dirs & config (BEFORE importing streamlit) --------------------
379
+ # Put everything under /tmp (world-writable on Spaces)
380
+ os.environ.setdefault("HOME", "/tmp")
381
+ os.environ.setdefault("STREAMLIT_CONFIG_DIR", "/tmp/.streamlit")
382
+ os.environ.setdefault("STREAMLIT_RUNTIME_DIR", "/tmp/.streamlit")
383
  os.environ.setdefault("HF_HOME", "/tmp/hf_home")
 
 
 
 
 
384
 
385
+ for d in (os.environ["STREAMLIT_CONFIG_DIR"], os.environ["STREAMLIT_RUNTIME_DIR"], os.environ["HF_HOME"]):
386
+ os.makedirs(d, exist_ok=True)
387
 
388
+ # Create a minimal config.toml to avoid 403 on uploads and reduce telemetry writes
389
+ conf_path = os.path.join(os.environ["STREAMLIT_CONFIG_DIR"], "config.toml")
 
 
 
390
  if not os.path.exists(conf_path):
391
  with open(conf_path, "w", encoding="utf-8") as f:
392
+ f.write(
393
+ "[server]\n"
394
+ "enableXsrfProtection = false\n"
395
+ "enableCORS = false\n"
396
+ "maxUploadSize = 200\n"
397
+ "\n[browser]\n"
398
+ "gatherUsageStats = false\n"
399
+ )
400
 
401
  import streamlit as st
402
 
403
+ # -------------------- Surya imports (gated) --------------------
 
 
 
 
 
 
 
 
404
  DET_AVAILABLE = True
405
  try:
406
  from surya.detection import batch_text_detection
407
  except Exception:
408
  DET_AVAILABLE = False
409
 
410
+ from surya.layout import batch_layout_detection # we'll gate usage using DET_AVAILABLE
411
 
412
+ # Detection model loaders: try newer segformer, fall back to older
413
  try:
414
  from surya.model.detection.segformer import load_model as load_det_model, load_processor as load_det_processor
415
  except Exception:
 
499
  st.markdown("# TRUST OCR DEMO")
500
 
501
  if not DET_AVAILABLE:
502
+ st.warning("⚠️ ماژول تشخیص متن Surya در این محیط در دسترس نیست. OCR کامل کار می‌کند، اما دکمه‌های Detection/Layout/Order غیرفعال شده‌اند.")
503
 
504
  # Sidebar controls
505
  in_file = st.sidebar.file_uploader("فایل PDF یا عکس :", type=["pdf", "png", "jpg", "jpeg", "gif", "webp"])
 
533
  def load_det_cached():
534
  return load_det_model(checkpoint="vikp/surya_det2"), load_det_processor(checkpoint="vikp/surya_det2")
535
 
 
 
 
 
 
 
 
 
536
  @st.cache_resource(show_spinner=True)
537
  def load_rec_cached():
538
+ """Try private checkpoint first, then fall back to public."""
539
  checkpoints = [
540
+ "MohammadReza-Halakoo/TrustOCR", # private (requires HUGGINGFACE_HUB_TOKEN if private)
541
+ "vikp/surya_rec2", # public fallback
542
  ]
543
  last_err = None
544
  for ckpt in checkpoints:
 
550
  last_err = e
551
  st.error(f"Loading recognition checkpoint failed: {last_err}")
552
  raise last_err
 
 
 
 
553
 
554
  @st.cache_resource(show_spinner=True)
555
  def load_layout_cached():
 
617
  """Full-page OCR using Surya run_ocr — works without detection import."""
618
  langs = list(langs) if langs else ["Persian"]
619
  replace_lang_with_code(langs) # in-place
620
+ # If detection/recognition models are loaded, pass them; else rely on Surya defaults
 
621
  if det_model and det_processor and rec_model and rec_processor:
622
  img_pred: OCRResult = run_ocr([pil_img], [langs], det_model, det_processor, rec_model, rec_processor)[0]
623
  else:
 
640
  pil_image = get_page_image(in_file, page_number)
641
  else:
642
  bytes_data = in_file.getvalue()
643
+ # use /tmp for writes
644
+ temp_dir = os.path.join(tempfile.gettempdir(), "trustocr_temp")
645
  os.makedirs(temp_dir, exist_ok=True)
646
  file_path = os.path.join(temp_dir, in_file.name)
647
  with open(file_path, "wb") as f:
 
697
 
698
  with col2:
699
  st.image(pil_image, caption="تصویر ورودی | Input Preview", use_column_width=True)
700
+