from fastapi import FastAPI, UploadFile, File, HTTPException, Query from fastapi.responses import JSONResponse, StreamingResponse import uvicorn import io import json import os import tempfile import numpy as np import cv2 from PIL import Image from pdf2image import convert_from_bytes GENAI_API_KEY = os.getenv("GENAI_API_KEY") if not GENAI_API_KEY: raise Exception("GENAI_API_KEY not set in .env file.") # Import the Google GenAI client libraries. from google import genai from google.genai import types # Initialize the GenAI client with the API key from .env. client = genai.Client(api_key=GENAI_API_KEY) app = FastAPI(title="Student Result Card API") # Use the system temporary directory TEMP_FOLDER = tempfile.gettempdir() # ----------------------------- # Preprocessing Methods # ----------------------------- def preprocess_candidate_info(image_cv): """ Preprocess the image to extract the candidate information region. Region is defined by a mask covering the top-left portion. """ height, width = image_cv.shape[:2] mask = np.zeros((height, width), dtype="uint8") margin_top = int(height * 0.10) margin_bottom = int(height * 0.25) cv2.rectangle(mask, (0, margin_top), (width, height - margin_bottom), 255, -1) masked = cv2.bitwise_and(image_cv, image_cv, mask=mask) coords = cv2.findNonZero(mask) x, y, w, h = cv2.boundingRect(coords) cropped = masked[y:y+h, x:x+w] return Image.fromarray(cv2.cvtColor(cropped, cv2.COLOR_BGR2RGB)) def preprocess_mcq(image_cv): """ Preprocess the image to extract the MCQ answers region (questions 1 to 10). Region is defined by a mask on the left side of the page. """ height, width = image_cv.shape[:2] mask = np.zeros((height, width), dtype="uint8") margin_top = int(height * 0.27) margin_bottom = int(height * 0.23) right_boundary = int(width * 0.35) cv2.rectangle(mask, (0, margin_top), (right_boundary, height - margin_bottom), 255, -1) masked = cv2.bitwise_and(image_cv, image_cv, mask=mask) coords = cv2.findNonZero(mask) x, y, w, h = cv2.boundingRect(coords) cropped = masked[y:y+h, x:x+w] return Image.fromarray(cv2.cvtColor(cropped, cv2.COLOR_BGR2RGB)) def preprocess_free_response(image_cv): """ Preprocess the image to extract the free-response answers region (questions 11 to 15). Region is defined by a mask on the middle-right part of the page. """ height, width = image_cv.shape[:2] mask = np.zeros((height, width), dtype="uint8") margin_top = int(height * 0.27) margin_bottom = int(height * 0.38) left_boundary = int(width * 0.35) right_boundary = int(width * 0.68) cv2.rectangle(mask, (left_boundary, margin_top), (right_boundary, height - margin_bottom), 255, -1) masked = cv2.bitwise_and(image_cv, image_cv, mask=mask) coords = cv2.findNonZero(mask) x, y, w, h = cv2.boundingRect(coords) cropped = masked[y:y+h, x:x+w] return Image.fromarray(cv2.cvtColor(cropped, cv2.COLOR_BGR2RGB)) def preprocess_full_answers(image_cv): """ For extracting the correct answer key, we assume the entire page contains the answers. """ return Image.fromarray(cv2.cvtColor(image_cv, cv2.COLOR_BGR2RGB)) # ----------------------------- # Extraction Methods using Gemini # ----------------------------- def extract_json_from_output(output_str): """ Extracts a JSON object from a string containing extra text. """ start = output_str.find('{') end = output_str.rfind('}') if start == -1 or end == -1: return None json_str = output_str[start:end+1] try: return json.loads(json_str) except json.JSONDecodeError: return None def get_student_info(image_input): """ Extracts candidate information from an image. """ output_format = """ Answer in the following JSON format. Do not write anything else: { "Candidate Info": { "Name": "", "Number": "", "Country": "", "Level": "" } } """ prompt = f""" You are an assistant that extracts candidate information from an image. The image contains details including name, candidate number, country, and level. Extract the information accurately and provide the result in JSON using the format below: {output_format} """ response = client.models.generate_content(model="gemini-2.0-flash", contents=[prompt, image_input]) return extract_json_from_output(response.text) def get_mcq_answers(image_input): """ Extracts multiple-choice answers (questions 1 to 10) from an image. """ output_format = """ Answer in the following JSON format do not write anything else: { "Answers": { "1": "