crabbly commited on
Commit
56cb810
·
verified ·
1 Parent(s): 44b6c66

Update main.py

Browse files
Files changed (1) hide show
  1. main.py +39 -20
main.py CHANGED
@@ -13,9 +13,11 @@ from scipy.optimize import curve_fit, minimize
13
  from ultralytics import YOLO
14
  from fastapi import FastAPI, UploadFile, File, Query, Form
15
  from fastapi.middleware.cors import CORSMiddleware
 
16
  import uvicorn
17
  from skimage import color
18
  import hashlib
 
19
 
20
  # OOM PREVENTION
21
  torch.set_num_threads(1)
@@ -796,36 +798,41 @@ app.add_middleware(
796
 
797
  processor = WatermelonProcessor(MODEL_PATH)
798
 
 
 
 
 
 
 
799
  @app.get("/")
800
- def read_root(): return {"status": "Phenotyping API is awake and running!"}
 
 
 
 
801
 
802
  @app.post("/process_single")
803
  async def process_single(
804
  file: UploadFile = File(...),
805
  include_image: bool = Query(True),
806
- password: str = Form("")
 
807
  ):
 
808
  request_t = time.perf_counter()
809
 
810
- # --- SECURE PASSWORD CHECK ---
811
  expected_hash = "9139eb3676d5dfafced7613f044d86d9e7c84f40a04c83ddce062878621315d0"
812
  if hashlib.sha256(password.encode('utf-8')).hexdigest() != expected_hash:
813
- return ProcessResult(success=False, message="Unauthorized: Incorrect password.", filename=file.filename, processing_ms=int(round((time.perf_counter() - request_t) * 1000))).__dict__
814
 
815
  contents, img = None, None
816
 
817
  try:
818
  contents = await file.read()
819
- if not contents:
820
- res = ProcessResult(success=False, message="Empty upload.", filename=file.filename)
821
- res.processing_ms = int(round((time.perf_counter() - request_t) * 1000))
822
- return res.__dict__
823
 
824
  img = cv2.imdecode(np.frombuffer(contents, np.uint8), cv2.IMREAD_COLOR)
825
- if img is None:
826
- res = ProcessResult(success=False, message="Could not decode image.", filename=file.filename)
827
- res.processing_ms = int(round((time.perf_counter() - request_t) * 1000))
828
- return res.__dict__
829
 
830
  scale_ratio = 1.0
831
  h, w = img.shape[:2]
@@ -833,19 +840,31 @@ async def process_single(
833
  scale_ratio = MAX_IMAGE_SIZE / float(max(h, w))
834
  img = cv2.resize(img, (int(w * scale_ratio), int(h * scale_ratio)), interpolation=cv2.INTER_AREA)
835
 
836
- res = processor.process_image(img, file.filename, scale_ratio, include_image=include_image)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
837
  res.processing_ms = int(round((time.perf_counter() - request_t) * 1000))
838
  return res.__dict__
839
 
840
  except Exception as exc:
841
  traceback.print_exc()
842
- res = ProcessResult(
843
- success=False,
844
- message=f"Server error: {type(exc).__name__}: {exc}",
845
- filename=file.filename,
846
- processing_ms=int(round((time.perf_counter() - request_t) * 1000)),
847
- )
848
- return res.__dict__
849
 
850
  finally:
851
  del img, contents
 
13
  from ultralytics import YOLO
14
  from fastapi import FastAPI, UploadFile, File, Query, Form
15
  from fastapi.middleware.cors import CORSMiddleware
16
+ from fastapi.concurrency import run_in_threadpool
17
  import uvicorn
18
  from skimage import color
19
  import hashlib
20
+ import asyncio
21
 
22
  # OOM PREVENTION
23
  torch.set_num_threads(1)
 
798
 
799
  processor = WatermelonProcessor(MODEL_PATH)
800
 
801
+ # --- CONCURRENCY & QUEUE MANAGEMENT ---
802
+ dev_lock = asyncio.Lock()
803
+ gen_lock = asyncio.Lock()
804
+ dev_queue_count = 0
805
+ gen_queue_count = 0
806
+
807
  @app.get("/")
808
+ def read_root(): return {"status": "Watermelon API is awake and running!"}
809
+
810
+ @app.get("/queue_status")
811
+ def get_queue_status():
812
+ return {"dev_queue": dev_queue_count, "gen_queue": gen_queue_count}
813
 
814
  @app.post("/process_single")
815
  async def process_single(
816
  file: UploadFile = File(...),
817
  include_image: bool = Query(True),
818
+ password: str = Form(""),
819
+ username: str = Form("")
820
  ):
821
+ global dev_queue_count, gen_queue_count
822
  request_t = time.perf_counter()
823
 
 
824
  expected_hash = "9139eb3676d5dfafced7613f044d86d9e7c84f40a04c83ddce062878621315d0"
825
  if hashlib.sha256(password.encode('utf-8')).hexdigest() != expected_hash:
826
+ return ProcessResult(success=False, message="Unauthorized.", filename=file.filename).__dict__
827
 
828
  contents, img = None, None
829
 
830
  try:
831
  contents = await file.read()
832
+ if not contents: return ProcessResult(success=False, message="Empty.", filename=file.filename).__dict__
 
 
 
833
 
834
  img = cv2.imdecode(np.frombuffer(contents, np.uint8), cv2.IMREAD_COLOR)
835
+ if img is None: return ProcessResult(success=False, message="Decode error.", filename=file.filename).__dict__
 
 
 
836
 
837
  scale_ratio = 1.0
838
  h, w = img.shape[:2]
 
840
  scale_ratio = MAX_IMAGE_SIZE / float(max(h, w))
841
  img = cv2.resize(img, (int(w * scale_ratio), int(h * scale_ratio)), interpolation=cv2.INTER_AREA)
842
 
843
+ # --- CPU CORE ROUTING & QUEUE LOGIC ---
844
+ is_dev = (username.strip().lower() == 'devtest')
845
+
846
+ # run_in_threadpool prevents OpenCV/YOLO from freezing the API so /queue_status can still answer
847
+ if is_dev:
848
+ dev_queue_count += 1
849
+ try:
850
+ async with dev_lock:
851
+ res = await run_in_threadpool(processor.process_image, img, file.filename, scale_ratio, include_image)
852
+ finally:
853
+ dev_queue_count -= 1
854
+ else:
855
+ gen_queue_count += 1
856
+ try:
857
+ async with gen_lock:
858
+ res = await run_in_threadpool(processor.process_image, img, file.filename, scale_ratio, include_image)
859
+ finally:
860
+ gen_queue_count -= 1
861
+
862
  res.processing_ms = int(round((time.perf_counter() - request_t) * 1000))
863
  return res.__dict__
864
 
865
  except Exception as exc:
866
  traceback.print_exc()
867
+ return ProcessResult(success=False, message=str(exc), filename=file.filename).__dict__
 
 
 
 
 
 
868
 
869
  finally:
870
  del img, contents