triflix commited on
Commit
4cfe185
·
verified ·
1 Parent(s): cb997d4

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +229 -274
app.py CHANGED
@@ -1,280 +1,244 @@
 
 
 
 
 
 
 
 
1
  from fastapi import FastAPI, File, UploadFile, HTTPException
2
  from fastapi.responses import JSONResponse
3
  from fastapi.middleware.cors import CORSMiddleware
4
- from typing import List
5
- import io
6
- import numpy as np
7
- from PIL import Image
8
- import pdf2image
9
- import cv2
10
  from paddleocr import PaddleOCR
11
- import gc
12
- import asyncio
13
- from concurrent.futures import ThreadPoolExecutor
14
- import logging
15
-
16
- # Configure logging
17
- logging.basicConfig(level=logging.INFO)
18
- logger = logging.getLogger(__name__)
19
-
20
- app = FastAPI(
21
- title="Marathi OCR API",
22
- description="OCR API for Marathi text extraction from images and PDFs",
23
- version="1.0.0"
24
- )
25
 
26
- # CORS middleware
27
- app.add_middleware(
28
- CORSMiddleware,
29
- allow_origins=["*"],
30
- allow_credentials=True,
31
- allow_methods=["*"],
32
- allow_headers=["*"],
33
- )
34
 
35
- # Global OCR instance (initialized once)
36
  ocr_instance = None
37
- executor = ThreadPoolExecutor(max_workers=2) # Limit concurrent processing
38
 
39
- # Constants
40
- MAX_FILE_SIZE = 10 * 1024 * 1024 # 10MB
41
- ALLOWED_IMAGE_EXTENSIONS = {".jpg", ".jpeg", ".png", ".bmp", ".tiff", ".webp"}
42
- ALLOWED_EXTENSIONS = ALLOWED_IMAGE_EXTENSIONS | {".pdf"}
43
- MAX_FILES_PER_REQUEST = 10
44
- PDF_DPI = 200 # Balance between quality and RAM usage
 
 
 
 
45
 
46
 
47
  def get_ocr():
48
- """Lazy load OCR instance"""
49
  global ocr_instance
50
  if ocr_instance is None:
51
- logger.info("Initializing PaddleOCR...")
52
  ocr_instance = PaddleOCR(
53
  lang="mr",
 
54
  use_doc_orientation_classify=False,
55
  use_doc_unwarping=False,
56
  use_textline_orientation=False,
57
- use_angle_cls=False, # Disable angle classification for speed
58
- show_log=False
59
  )
60
- logger.info("PaddleOCR initialized successfully")
61
  return ocr_instance
62
 
63
 
64
- def validate_file(file: UploadFile, file_size: int):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
65
  """Validate uploaded file"""
66
- # Check file size
67
- if file_size > MAX_FILE_SIZE:
 
68
  raise HTTPException(
69
- status_code=413,
70
- detail=f"File too large. Maximum size: {MAX_FILE_SIZE / 1024 / 1024}MB"
71
  )
72
 
73
- # Check extension
74
- file_ext = file.filename.lower().split('.')[-1]
75
- if f".{file_ext}" not in ALLOWED_EXTENSIONS:
 
 
 
76
  raise HTTPException(
77
  status_code=400,
78
- detail=f"Invalid file type. Allowed: {', '.join(ALLOWED_EXTENSIONS)}"
79
  )
80
-
81
- return f".{file_ext}"
82
 
83
 
84
- def process_image_bytes(image_bytes: bytes) -> np.ndarray:
85
- """Convert image bytes to numpy array"""
 
86
  try:
87
- image = Image.open(io.BytesIO(image_bytes))
 
 
 
88
 
89
- # Convert to RGB if necessary
90
- if image.mode != 'RGB':
91
- image = image.convert('RGB')
92
-
93
- # Convert to numpy array
94
- img_array = np.array(image)
95
-
96
- # Optional: Resize if image is too large to save RAM
97
- max_dimension = 4096
98
- h, w = img_array.shape[:2]
99
- if max(h, w) > max_dimension:
100
- scale = max_dimension / max(h, w)
101
- new_w, new_h = int(w * scale), int(h * scale)
102
- img_array = cv2.resize(img_array, (new_w, new_h))
103
- logger.info(f"Resized image from {w}x{h} to {new_w}x{new_h}")
104
-
105
- return img_array
106
- except Exception as e:
107
- logger.error(f"Error processing image: {e}")
108
- raise HTTPException(status_code=400, detail=f"Invalid image format: {str(e)}")
109
-
110
-
111
- def pdf_to_images(pdf_bytes: bytes) -> List[np.ndarray]:
112
- """Convert PDF to list of image arrays without saving to disk"""
113
- try:
114
- # Convert PDF bytes to images in memory
115
- images = pdf2image.convert_from_bytes(
116
- pdf_bytes,
117
- dpi=PDF_DPI,
118
- fmt='RGB',
119
- thread_count=1 # Limit threads to control RAM
120
- )
121
-
122
- # Convert PIL images to numpy arrays
123
- img_arrays = []
124
- for img in images:
125
- img_array = np.array(img)
126
- img_arrays.append(img_array)
127
-
128
- logger.info(f"Converted PDF to {len(img_arrays)} images")
129
- return img_arrays
130
-
131
- except Exception as e:
132
- logger.error(f"Error converting PDF: {e}")
133
- raise HTTPException(status_code=400, detail=f"Invalid PDF format: {str(e)}")
134
-
135
-
136
- def run_ocr(img_array: np.ndarray) -> dict:
137
- """Run OCR on image array"""
138
- try:
139
  ocr = get_ocr()
140
- result = ocr.ocr(img_array, cls=False)
141
 
142
- if not result or not result[0]:
 
 
 
 
143
  return {
144
- "texts": [],
145
- "scores": [],
146
- "details": []
 
 
 
 
 
 
 
 
 
 
 
 
 
 
147
  }
148
-
149
- # Extract data
150
- texts = []
151
- scores = []
152
- details = []
153
-
154
- for line in result[0]:
155
- bbox = line[0] # Bounding box coordinates
156
- text = line[1][0] # Recognized text
157
- score = line[1][1] # Confidence score
158
 
159
- texts.append(text)
160
- scores.append(float(score))
161
- details.append({
162
- "text": text,
163
- "confidence": float(score),
164
- "bbox": [[int(point[0]), int(point[1])] for point in bbox]
165
- })
166
-
167
  return {
168
- "texts": texts,
169
- "scores": scores,
170
- "details": details
171
  }
172
-
173
- except Exception as e:
174
- logger.error(f"OCR processing error: {e}")
175
- raise HTTPException(status_code=500, detail=f"OCR failed: {str(e)}")
 
 
 
 
 
176
 
177
 
178
- async def process_single_file(file: UploadFile) -> dict:
179
- """Process a single file (image or PDF)"""
 
 
 
 
180
  try:
181
- # Read file into memory
182
- file_bytes = await file.read()
183
- file_size = len(file_bytes)
 
184
 
185
- # Validate
186
- file_ext = validate_file(file, file_size)
187
 
188
- logger.info(f"Processing file: {file.filename} ({file_size / 1024:.2f}KB)")
 
 
 
 
189
 
190
- results = []
191
-
192
- if file_ext == ".pdf":
193
- # Process PDF
194
- img_arrays = pdf_to_images(file_bytes)
195
-
196
- # Process each page
197
- for page_num, img_array in enumerate(img_arrays, 1):
198
- logger.info(f"Processing PDF page {page_num}/{len(img_arrays)}")
199
-
200
- # Run OCR in thread pool to avoid blocking
201
- loop = asyncio.get_event_loop()
202
- ocr_result = await loop.run_in_executor(executor, run_ocr, img_array)
203
-
204
- results.append({
205
- "page": page_num,
206
- **ocr_result
207
- })
208
-
209
- # Clean up
210
- del img_array
211
- gc.collect()
212
-
213
- else:
214
- # Process single image
215
- img_array = process_image_bytes(file_bytes)
216
-
217
- # Run OCR in thread pool
218
- loop = asyncio.get_event_loop()
219
- ocr_result = await loop.run_in_executor(executor, run_ocr, img_array)
220
 
221
- results.append({
222
- "page": 1,
223
- **ocr_result
224
- })
 
 
 
225
 
226
  # Clean up
227
- del img_array
228
  gc.collect()
229
 
230
- # Clean up file bytes
231
- del file_bytes
232
- gc.collect()
233
-
234
  return {
235
- "filename": file.filename,
236
- "file_type": file_ext,
237
- "total_pages": len(results),
238
- "results": results,
239
- "status": "success"
 
 
 
240
  }
241
 
242
- except HTTPException:
243
- raise
244
  except Exception as e:
245
- logger.error(f"Error processing file {file.filename}: {e}")
246
  return {
247
- "filename": file.filename,
248
- "status": "error",
249
  "error": str(e)
250
  }
251
-
252
-
253
- @app.on_event("startup")
254
- async def startup_event():
255
- """Initialize on startup"""
256
- logger.info("Starting OCR API...")
257
- # Pre-load OCR model
258
- get_ocr()
259
- logger.info("API ready!")
260
-
261
-
262
- @app.on_event("shutdown")
263
- async def shutdown_event():
264
- """Cleanup on shutdown"""
265
- logger.info("Shutting down...")
266
- executor.shutdown(wait=True)
267
 
268
 
269
  @app.get("/")
270
  async def root():
271
- """Health check endpoint"""
272
  return {
273
- "status": "healthy",
274
- "message": "Marathi OCR API is running",
 
 
 
 
275
  "endpoints": {
276
- "single_file": "/ocr/",
277
- "multiple_files": "/ocr/batch/",
278
  "health": "/health"
279
  }
280
  }
@@ -282,58 +246,81 @@ async def root():
282
 
283
  @app.get("/health")
284
  async def health():
285
- """Detailed health check"""
286
- return {
287
- "status": "healthy",
288
- "ocr_loaded": ocr_instance is not None,
289
- "max_file_size_mb": MAX_FILE_SIZE / 1024 / 1024,
290
- "max_files_per_request": MAX_FILES_PER_REQUEST,
291
- "supported_formats": list(ALLOWED_EXTENSIONS)
292
- }
293
 
294
 
295
- @app.post("/ocr/")
296
  async def ocr_single_file(file: UploadFile = File(...)):
297
  """
298
- OCR for a single image or PDF file
299
 
300
  - **file**: Image (JPG, PNG, etc.) or PDF file
301
-
302
- Returns OCR results with text, confidence scores, and bounding boxes
303
  """
304
- result = await process_single_file(file)
 
 
 
 
 
 
 
 
 
 
305
 
306
- if result["status"] == "error":
307
- raise HTTPException(status_code=500, detail=result["error"])
 
308
 
309
  return JSONResponse(content=result)
310
 
311
 
312
- @app.post("/ocr/batch/")
313
  async def ocr_batch_files(files: List[UploadFile] = File(...)):
314
  """
315
- OCR for multiple images or PDF files
316
 
317
  - **files**: List of image or PDF files (max 10)
318
-
319
- Returns OCR results for each file
320
  """
321
- if len(files) > MAX_FILES_PER_REQUEST:
322
  raise HTTPException(
323
  status_code=400,
324
- detail=f"Too many files. Maximum: {MAX_FILES_PER_REQUEST}"
325
  )
326
 
327
- logger.info(f"Processing batch of {len(files)} files")
328
-
329
- # Process files sequentially to manage RAM
330
  results = []
 
331
  for file in files:
332
- result = await process_single_file(file)
333
- results.append(result)
334
-
335
- # Force garbage collection between files
336
- gc.collect()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
337
 
338
  return JSONResponse(content={
339
  "total_files": len(files),
@@ -341,38 +328,6 @@ async def ocr_batch_files(files: List[UploadFile] = File(...)):
341
  })
342
 
343
 
344
- @app.post("/ocr/extract-text/")
345
- async def extract_text_only(file: UploadFile = File(...)):
346
- """
347
- Extract only text from image/PDF (simplified response)
348
-
349
- - **file**: Image or PDF file
350
-
351
- Returns only extracted text without bounding boxes
352
- """
353
- result = await process_single_file(file)
354
-
355
- if result["status"] == "error":
356
- raise HTTPException(status_code=500, detail=result["error"])
357
-
358
- # Simplify response
359
- simplified = {
360
- "filename": result["filename"],
361
- "file_type": result["file_type"],
362
- "pages": []
363
- }
364
-
365
- for page_result in result["results"]:
366
- simplified["pages"].append({
367
- "page": page_result["page"],
368
- "text": " ".join(page_result["texts"]),
369
- "word_count": len(page_result["texts"]),
370
- "average_confidence": sum(page_result["scores"]) / len(page_result["scores"]) if page_result["scores"] else 0
371
- })
372
-
373
- return JSONResponse(content=simplified)
374
-
375
-
376
  if __name__ == "__main__":
377
  import uvicorn
378
  uvicorn.run(app, host="0.0.0.0", port=7860)
 
1
+ import os
2
+ import tempfile
3
+ import asyncio
4
+ from pathlib import Path
5
+ from typing import List, Optional
6
+ import gc
7
+ from contextlib import asynccontextmanager
8
+
9
  from fastapi import FastAPI, File, UploadFile, HTTPException
10
  from fastapi.responses import JSONResponse
11
  from fastapi.middleware.cors import CORSMiddleware
 
 
 
 
 
 
12
  from paddleocr import PaddleOCR
13
+ from PIL import Image
14
+ import io
 
 
 
 
 
 
 
 
 
 
 
 
15
 
16
+ # PDF support
17
+ try:
18
+ from pdf2image import convert_from_path
19
+ PDF_SUPPORT = True
20
+ except ImportError:
21
+ PDF_SUPPORT = False
 
 
22
 
23
+ # Global OCR instance (singleton pattern for memory efficiency)
24
  ocr_instance = None
 
25
 
26
+ # Supported formats
27
+ SUPPORTED_IMAGE_FORMATS = {'.jpg', '.jpeg', '.png', '.bmp', '.tiff', '.webp'}
28
+ SUPPORTED_FORMATS = SUPPORTED_IMAGE_FORMATS.copy()
29
+ if PDF_SUPPORT:
30
+ SUPPORTED_FORMATS.add('.pdf')
31
+
32
+ # Configuration
33
+ MAX_FILE_SIZE = 10 * 1024 * 1024 # 10MB per file
34
+ MAX_FILES = 10 # Maximum files per request
35
+ MAX_PDF_PAGES = 20 # Maximum PDF pages to process
36
 
37
 
38
  def get_ocr():
39
+ """Singleton OCR instance - initialized once"""
40
  global ocr_instance
41
  if ocr_instance is None:
 
42
  ocr_instance = PaddleOCR(
43
  lang="mr",
44
+ text_recognition_model_name="devanagari_PP-OCRv5_mobile_rec",
45
  use_doc_orientation_classify=False,
46
  use_doc_unwarping=False,
47
  use_textline_orientation=False,
48
+ show_log=False, # Reduce console noise
49
+ use_gpu=False # HuggingFace Spaces usually don't have GPU
50
  )
 
51
  return ocr_instance
52
 
53
 
54
+ @asynccontextmanager
55
+ async def lifespan(app: FastAPI):
56
+ """Initialize OCR on startup, cleanup on shutdown"""
57
+ # Startup
58
+ print("🚀 Initializing PaddleOCR...")
59
+ get_ocr()
60
+ print("✅ PaddleOCR ready!")
61
+ yield
62
+ # Shutdown
63
+ print("🧹 Cleaning up...")
64
+ gc.collect()
65
+
66
+
67
+ app = FastAPI(
68
+ title="Marathi OCR API",
69
+ description="PaddleOCR API for Marathi/Devanagari text recognition",
70
+ version="1.0.0",
71
+ lifespan=lifespan
72
+ )
73
+
74
+ # CORS middleware
75
+ app.add_middleware(
76
+ CORSMiddleware,
77
+ allow_origins=["*"],
78
+ allow_credentials=True,
79
+ allow_methods=["*"],
80
+ allow_headers=["*"],
81
+ )
82
+
83
+
84
+ def validate_file(file: UploadFile) -> None:
85
  """Validate uploaded file"""
86
+ # Check file extension
87
+ file_ext = Path(file.filename).suffix.lower()
88
+ if file_ext not in SUPPORTED_FORMATS:
89
  raise HTTPException(
90
+ status_code=400,
91
+ detail=f"Unsupported file format. Supported: {', '.join(SUPPORTED_FORMATS)}"
92
  )
93
 
94
+ # Check file size (read first chunk to estimate)
95
+ file.file.seek(0, 2) # Seek to end
96
+ file_size = file.file.tell()
97
+ file.file.seek(0) # Reset
98
+
99
+ if file_size > MAX_FILE_SIZE:
100
  raise HTTPException(
101
  status_code=400,
102
+ detail=f"File too large. Maximum size: {MAX_FILE_SIZE // 1024 // 1024}MB"
103
  )
 
 
104
 
105
 
106
+ async def process_image_bytes(image_bytes: bytes, filename: str) -> dict:
107
+ """Process image bytes with OCR"""
108
+ temp_path = None
109
  try:
110
+ # Create temporary file
111
+ with tempfile.NamedTemporaryFile(delete=False, suffix=Path(filename).suffix) as tmp:
112
+ tmp.write(image_bytes)
113
+ temp_path = tmp.name
114
 
115
+ # Run OCR
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
116
  ocr = get_ocr()
117
+ result = ocr.ocr(temp_path, cls=False)
118
 
119
+ # Extract results
120
+ if result and result[0]:
121
+ texts = [line[1][0] for line in result[0]]
122
+ scores = [float(line[1][1]) for line in result[0]]
123
+
124
  return {
125
+ "filename": filename,
126
+ "success": True,
127
+ "text_count": len(texts),
128
+ "results": [
129
+ {"text": text, "confidence": score}
130
+ for text, score in zip(texts, scores)
131
+ ],
132
+ "full_text": "\n".join(texts)
133
+ }
134
+ else:
135
+ return {
136
+ "filename": filename,
137
+ "success": True,
138
+ "text_count": 0,
139
+ "results": [],
140
+ "full_text": "",
141
+ "message": "No text detected"
142
  }
 
 
 
 
 
 
 
 
 
 
143
 
144
+ except Exception as e:
 
 
 
 
 
 
 
145
  return {
146
+ "filename": filename,
147
+ "success": False,
148
+ "error": str(e)
149
  }
150
+ finally:
151
+ # Clean up temporary file
152
+ if temp_path and os.path.exists(temp_path):
153
+ try:
154
+ os.unlink(temp_path)
155
+ except:
156
+ pass
157
+ # Force garbage collection
158
+ gc.collect()
159
 
160
 
161
+ async def process_pdf(file_bytes: bytes, filename: str) -> dict:
162
+ """Process PDF file page by page"""
163
+ if not PDF_SUPPORT:
164
+ raise HTTPException(status_code=400, detail="PDF support not available")
165
+
166
+ temp_pdf_path = None
167
  try:
168
+ # Save PDF temporarily
169
+ with tempfile.NamedTemporaryFile(delete=False, suffix='.pdf') as tmp:
170
+ tmp.write(file_bytes)
171
+ temp_pdf_path = tmp.name
172
 
173
+ # Convert PDF to images
174
+ images = convert_from_path(temp_pdf_path, dpi=200, fmt='jpeg')
175
 
176
+ if len(images) > MAX_PDF_PAGES:
177
+ raise HTTPException(
178
+ status_code=400,
179
+ detail=f"PDF has too many pages. Maximum: {MAX_PDF_PAGES}"
180
+ )
181
 
182
+ # Process each page
183
+ all_results = []
184
+ for page_num, image in enumerate(images, 1):
185
+ # Convert PIL Image to bytes
186
+ img_byte_arr = io.BytesIO()
187
+ image.save(img_byte_arr, format='JPEG')
188
+ img_bytes = img_byte_arr.getvalue()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
189
 
190
+ # Process page
191
+ result = await process_image_bytes(
192
+ img_bytes,
193
+ f"{filename}_page_{page_num}"
194
+ )
195
+ result["page_number"] = page_num
196
+ all_results.append(result)
197
 
198
  # Clean up
199
+ del image
200
  gc.collect()
201
 
 
 
 
 
202
  return {
203
+ "filename": filename,
204
+ "success": True,
205
+ "total_pages": len(images),
206
+ "pages": all_results,
207
+ "combined_text": "\n\n".join([
208
+ f"=== Page {r['page_number']} ===\n{r.get('full_text', '')}"
209
+ for r in all_results
210
+ ])
211
  }
212
 
 
 
213
  except Exception as e:
 
214
  return {
215
+ "filename": filename,
216
+ "success": False,
217
  "error": str(e)
218
  }
219
+ finally:
220
+ # Clean up
221
+ if temp_pdf_path and os.path.exists(temp_pdf_path):
222
+ try:
223
+ os.unlink(temp_pdf_path)
224
+ except:
225
+ pass
226
+ gc.collect()
 
 
 
 
 
 
 
 
227
 
228
 
229
  @app.get("/")
230
  async def root():
231
+ """API information"""
232
  return {
233
+ "name": "Marathi OCR API",
234
+ "status": "running",
235
+ "supported_formats": list(SUPPORTED_FORMATS),
236
+ "pdf_support": PDF_SUPPORT,
237
+ "max_file_size_mb": MAX_FILE_SIZE // 1024 // 1024,
238
+ "max_files_per_request": MAX_FILES,
239
  "endpoints": {
240
+ "single_file": "/ocr",
241
+ "multiple_files": "/ocr/batch",
242
  "health": "/health"
243
  }
244
  }
 
246
 
247
  @app.get("/health")
248
  async def health():
249
+ """Health check"""
250
+ return {"status": "healthy", "ocr_loaded": ocr_instance is not None}
 
 
 
 
 
 
251
 
252
 
253
+ @app.post("/ocr")
254
  async def ocr_single_file(file: UploadFile = File(...)):
255
  """
256
+ Process a single image or PDF file
257
 
258
  - **file**: Image (JPG, PNG, etc.) or PDF file
 
 
259
  """
260
+ validate_file(file)
261
+
262
+ # Read file
263
+ file_bytes = await file.read()
264
+ file_ext = Path(file.filename).suffix.lower()
265
+
266
+ # Process based on file type
267
+ if file_ext == '.pdf':
268
+ result = await process_pdf(file_bytes, file.filename)
269
+ else:
270
+ result = await process_image_bytes(file_bytes, file.filename)
271
 
272
+ # Clean up
273
+ del file_bytes
274
+ gc.collect()
275
 
276
  return JSONResponse(content=result)
277
 
278
 
279
+ @app.post("/ocr/batch")
280
  async def ocr_batch_files(files: List[UploadFile] = File(...)):
281
  """
282
+ Process multiple image/PDF files
283
 
284
  - **files**: List of image or PDF files (max 10)
 
 
285
  """
286
+ if len(files) > MAX_FILES:
287
  raise HTTPException(
288
  status_code=400,
289
+ detail=f"Too many files. Maximum: {MAX_FILES}"
290
  )
291
 
 
 
 
292
  results = []
293
+
294
  for file in files:
295
+ try:
296
+ validate_file(file)
297
+ file_bytes = await file.read()
298
+ file_ext = Path(file.filename).suffix.lower()
299
+
300
+ # Process based on file type
301
+ if file_ext == '.pdf':
302
+ result = await process_pdf(file_bytes, file.filename)
303
+ else:
304
+ result = await process_image_bytes(file_bytes, file.filename)
305
+
306
+ results.append(result)
307
+
308
+ # Clean up after each file
309
+ del file_bytes
310
+ gc.collect()
311
+
312
+ except HTTPException as he:
313
+ results.append({
314
+ "filename": file.filename,
315
+ "success": False,
316
+ "error": he.detail
317
+ })
318
+ except Exception as e:
319
+ results.append({
320
+ "filename": file.filename,
321
+ "success": False,
322
+ "error": str(e)
323
+ })
324
 
325
  return JSONResponse(content={
326
  "total_files": len(files),
 
328
  })
329
 
330
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
331
  if __name__ == "__main__":
332
  import uvicorn
333
  uvicorn.run(app, host="0.0.0.0", port=7860)