Update main.py
Browse files
main.py
CHANGED
|
@@ -183,101 +183,110 @@ def load_answer_key(pdf_bytes: bytes) -> dict:
|
|
| 183 |
# FastAPI Endpoints
|
| 184 |
##############################################################
|
| 185 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 186 |
@app.post("/process")
|
| 187 |
async def process_pdfs(
|
| 188 |
-
student_pdf: UploadFile = File(
|
| 189 |
-
|
| 190 |
-
|
| 191 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 192 |
):
|
| 193 |
try:
|
| 194 |
-
# Read
|
| 195 |
student_pdf_bytes = await student_pdf.read()
|
| 196 |
-
|
| 197 |
-
|
| 198 |
-
|
| 199 |
-
|
| 200 |
-
|
|
|
|
|
|
|
| 201 |
answer_keys = {
|
| 202 |
-
"A": load_answer_key(paper_a_bytes),
|
| 203 |
-
"B": load_answer_key(paper_b_bytes),
|
| 204 |
"K": load_answer_key(paper_k_bytes)
|
| 205 |
}
|
| 206 |
-
|
| 207 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 208 |
student_images = convert_from_bytes(student_pdf_bytes)
|
| 209 |
all_results = []
|
| 210 |
-
|
| 211 |
-
# Loop over all student pages
|
| 212 |
for idx, page in enumerate(student_images):
|
| 213 |
print(f"Processing student page {idx+1}...")
|
| 214 |
-
|
| 215 |
-
#
|
| 216 |
-
page_cv = np.array(page)
|
| 217 |
-
|
| 218 |
-
|
| 219 |
-
|
| 220 |
-
|
| 221 |
-
|
| 222 |
-
|
| 223 |
-
|
| 224 |
-
candidate_margin_top = int(height * 0.10)
|
| 225 |
-
candidate_margin_bottom = int(height * 0.75)
|
| 226 |
-
cv2.rectangle(candidate_mask, (0, candidate_margin_top), (width, height - candidate_margin_bottom), 255, -1)
|
| 227 |
-
masked_candidate = cv2.bitwise_and(page_cv, page_cv, mask=candidate_mask)
|
| 228 |
-
coords = cv2.findNonZero(candidate_mask)
|
| 229 |
if coords is None:
|
| 230 |
-
continue
|
| 231 |
-
x, y,
|
| 232 |
-
|
| 233 |
-
|
| 234 |
-
|
| 235 |
-
|
| 236 |
-
|
| 237 |
-
|
| 238 |
-
|
| 239 |
-
|
| 240 |
-
paper = ""
|
| 241 |
-
if candidate_info and "Candidate Info" in candidate_info:
|
| 242 |
-
paper = candidate_info["Candidate Info"].get("Paper", "").strip()
|
| 243 |
if not paper:
|
| 244 |
-
paper = parse_paper(
|
| 245 |
-
paper = paper.upper()
|
| 246 |
print(f"Student {idx+1} Paper: {paper}")
|
| 247 |
-
|
| 248 |
-
#
|
| 249 |
if paper not in answer_keys or answer_keys[paper] is None:
|
| 250 |
-
print(f"
|
| 251 |
continue
|
| 252 |
-
|
| 253 |
-
|
| 254 |
-
#
|
| 255 |
-
|
| 256 |
-
|
| 257 |
-
|
| 258 |
-
|
| 259 |
-
|
| 260 |
-
|
| 261 |
-
|
| 262 |
-
|
| 263 |
-
|
| 264 |
-
|
| 265 |
-
|
| 266 |
-
|
| 267 |
-
|
| 268 |
-
|
| 269 |
-
"Student Answers": student_answers,
|
| 270 |
-
"Correct Answer Key": correct_answer_key,
|
| 271 |
-
"Result": result
|
| 272 |
-
}
|
| 273 |
-
all_results.append(result_card)
|
| 274 |
-
|
| 275 |
-
# Write the results to a file in the temporary folder.
|
| 276 |
with open(RESULT_FILE, "w", encoding="utf-8") as f:
|
| 277 |
json.dump({"results": all_results}, f, indent=2)
|
| 278 |
-
|
| 279 |
return JSONResponse(content={"results": all_results})
|
| 280 |
-
|
| 281 |
except Exception as e:
|
| 282 |
raise HTTPException(status_code=500, detail=str(e))
|
| 283 |
|
|
|
|
| 183 |
# FastAPI Endpoints
|
| 184 |
##############################################################
|
| 185 |
|
| 186 |
+
from typing import Optional
|
| 187 |
+
from fastapi import FastAPI, UploadFile, File, HTTPException
|
| 188 |
+
from fastapi.responses import JSONResponse
|
| 189 |
+
import numpy as np
|
| 190 |
+
import cv2
|
| 191 |
+
import json
|
| 192 |
+
from PIL import Image
|
| 193 |
+
|
| 194 |
+
app = FastAPI()
|
| 195 |
+
|
| 196 |
@app.post("/process")
|
| 197 |
async def process_pdfs(
|
| 198 |
+
student_pdf: UploadFile = File(
|
| 199 |
+
...,
|
| 200 |
+
description="PDF with all student answer sheets (one page per student)"
|
| 201 |
+
),
|
| 202 |
+
paper_k_pdf: UploadFile = File(
|
| 203 |
+
...,
|
| 204 |
+
description="Answer key PDF for Paper K"
|
| 205 |
+
),
|
| 206 |
+
paper_a_pdf: Optional[UploadFile] = File(
|
| 207 |
+
None,
|
| 208 |
+
description="(Optional) Answer key PDF for Paper A"
|
| 209 |
+
),
|
| 210 |
+
paper_b_pdf: Optional[UploadFile] = File(
|
| 211 |
+
None,
|
| 212 |
+
description="(Optional) Answer key PDF for Paper B"
|
| 213 |
+
),
|
| 214 |
):
|
| 215 |
try:
|
| 216 |
+
# 1. Read the student and Paper K files (required)
|
| 217 |
student_pdf_bytes = await student_pdf.read()
|
| 218 |
+
paper_k_bytes = await paper_k_pdf.read()
|
| 219 |
+
|
| 220 |
+
# 2. Read optional answer keys if provided
|
| 221 |
+
paper_a_bytes = await paper_a_pdf.read() if paper_a_pdf else None
|
| 222 |
+
paper_b_bytes = await paper_b_pdf.read() if paper_b_pdf else None
|
| 223 |
+
|
| 224 |
+
# 3. Build the answer_keys dict dynamically
|
| 225 |
answer_keys = {
|
|
|
|
|
|
|
| 226 |
"K": load_answer_key(paper_k_bytes)
|
| 227 |
}
|
| 228 |
+
if paper_a_bytes is not None:
|
| 229 |
+
answer_keys["A"] = load_answer_key(paper_a_bytes)
|
| 230 |
+
if paper_b_bytes is not None:
|
| 231 |
+
answer_keys["B"] = load_answer_key(paper_b_bytes)
|
| 232 |
+
|
| 233 |
+
# 4. Convert the student PDF to images
|
| 234 |
student_images = convert_from_bytes(student_pdf_bytes)
|
| 235 |
all_results = []
|
| 236 |
+
|
|
|
|
| 237 |
for idx, page in enumerate(student_images):
|
| 238 |
print(f"Processing student page {idx+1}...")
|
| 239 |
+
|
| 240 |
+
# — Candidate Info Extraction (as before) —
|
| 241 |
+
page_cv = cv2.cvtColor(np.array(page), cv2.COLOR_RGB2BGR)
|
| 242 |
+
h, w = page_cv.shape[:2]
|
| 243 |
+
mask = np.zeros((h, w), dtype="uint8")
|
| 244 |
+
top = int(h * 0.10)
|
| 245 |
+
bottom = int(h * 0.75)
|
| 246 |
+
cv2.rectangle(mask, (0, top), (w, h - bottom), 255, -1)
|
| 247 |
+
masked = cv2.bitwise_and(page_cv, page_cv, mask=mask)
|
| 248 |
+
coords = cv2.findNonZero(mask)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 249 |
if coords is None:
|
| 250 |
+
continue
|
| 251 |
+
x, y, mw, mh = cv2.boundingRect(coords)
|
| 252 |
+
cand_pil = Image.fromarray(
|
| 253 |
+
cv2.cvtColor(masked[y:y+mh, x:x+mw], cv2.COLOR_BGR2RGB)
|
| 254 |
+
)
|
| 255 |
+
info_resp = parse_info(cand_pil)
|
| 256 |
+
cand_info = extract_json_from_output(info_resp)
|
| 257 |
+
|
| 258 |
+
# Determine which paper this student sat
|
| 259 |
+
paper = cand_info.get("Candidate Info", {}).get("Paper", "").strip().upper()
|
|
|
|
|
|
|
|
|
|
| 260 |
if not paper:
|
| 261 |
+
paper = parse_paper(info_resp).upper()
|
|
|
|
| 262 |
print(f"Student {idx+1} Paper: {paper}")
|
| 263 |
+
|
| 264 |
+
# Skip if we don't have a key for that paper
|
| 265 |
if paper not in answer_keys or answer_keys[paper] is None:
|
| 266 |
+
print(f"Skipping: no answer key for paper '{paper}'")
|
| 267 |
continue
|
| 268 |
+
correct_key = answer_keys[paper]
|
| 269 |
+
|
| 270 |
+
# — Student Answers Extraction —
|
| 271 |
+
ans_resp = parse_all_answers(page)
|
| 272 |
+
stud_answers = extract_json_from_output(ans_resp)
|
| 273 |
+
|
| 274 |
+
# — Scoring —
|
| 275 |
+
result = calculate_result(stud_answers, correct_key)
|
| 276 |
+
all_results.append({
|
| 277 |
+
"Student Index": idx + 1,
|
| 278 |
+
"Candidate Info": cand_info.get("Candidate Info", {}),
|
| 279 |
+
"Student Answers": stud_answers,
|
| 280 |
+
"Correct Answer Key": correct_key,
|
| 281 |
+
"Result": result
|
| 282 |
+
})
|
| 283 |
+
|
| 284 |
+
# 5. Save & return
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 285 |
with open(RESULT_FILE, "w", encoding="utf-8") as f:
|
| 286 |
json.dump({"results": all_results}, f, indent=2)
|
| 287 |
+
|
| 288 |
return JSONResponse(content={"results": all_results})
|
| 289 |
+
|
| 290 |
except Exception as e:
|
| 291 |
raise HTTPException(status_code=500, detail=str(e))
|
| 292 |
|