Hammad712 commited on
Commit
da7faf8
·
verified ·
1 Parent(s): 0eecb97

Update main.py

Browse files
Files changed (1) hide show
  1. main.py +84 -75
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(..., description="PDF with all student answer sheets (one page per student)"),
189
- paper_a_pdf: UploadFile = File(..., description="Answer key PDF for Paper A"),
190
- paper_b_pdf: UploadFile = File(..., description="Answer key PDF for Paper B"),
191
- paper_k_pdf: UploadFile = File(..., description="Answer key PDF for Paper K")
 
 
 
 
 
 
 
 
 
 
 
 
192
  ):
193
  try:
194
- # Read file bytes
195
  student_pdf_bytes = await student_pdf.read()
196
- paper_a_bytes = await paper_a_pdf.read()
197
- paper_b_bytes = await paper_b_pdf.read()
198
- paper_k_bytes = await paper_k_pdf.read()
199
-
200
- # Preload answer keys from the three PDFs
 
 
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
- # Convert the student answer PDF to images (each page = one student)
 
 
 
 
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
- # Convert the PIL image to OpenCV format for masking
216
- page_cv = np.array(page)
217
- page_cv = cv2.cvtColor(page_cv, cv2.COLOR_RGB2BGR)
218
- height, width = page_cv.shape[:2]
219
-
220
- ###########################################################
221
- # 1. Extract Candidate Information Region
222
- ###########################################################
223
- candidate_mask = np.zeros((height, width), dtype="uint8")
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 # Skip page if no candidate region is found.
231
- x, y, w, h = cv2.boundingRect(coords)
232
- cropped_candidate = masked_candidate[y:y+h, x:x+w]
233
- candidate_pil = Image.fromarray(cv2.cvtColor(cropped_candidate, cv2.COLOR_BGR2RGB))
234
-
235
- # Extract candidate info using GenAI.
236
- candidate_info_response = parse_info(candidate_pil)
237
- candidate_info = extract_json_from_output(candidate_info_response)
238
-
239
- # Determine the candidate's paper.
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(candidate_info_response)
245
- paper = paper.upper()
246
  print(f"Student {idx+1} Paper: {paper}")
247
-
248
- # Retrieve the appropriate answer key.
249
  if paper not in answer_keys or answer_keys[paper] is None:
250
- print(f"Error: Invalid or missing answer key for paper '{paper}' for student {idx+1}. Skipping.")
251
  continue
252
- correct_answer_key = answer_keys[paper]
253
-
254
- ###########################################################
255
- # 2. Extract Student Answers from the Entire Page
256
- ###########################################################
257
- student_answers_response = parse_all_answers(page)
258
- student_answers = extract_json_from_output(student_answers_response)
259
-
260
- ###########################################################
261
- # 3. Calculate the Result for this Student
262
- ###########################################################
263
- result = calculate_result(student_answers, correct_answer_key)
264
-
265
- # Compile the result for this student.
266
- result_card = {
267
- "Student Index": idx + 1,
268
- "Candidate Info": candidate_info.get("Candidate Info", {}) if candidate_info else {},
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