Sunaina792's picture
Update main.py
768c1da verified
# main.py β€” AI Interview Confidence Analyzer (Gradio / HuggingFace Spaces)
import cv2
import sys
import os
import time
import numpy as np
from collections import deque
import gradio as gr
import tempfile
sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "modules"))
from modules.face_landmarks import FaceLandmarkExtractor
from modules.expression_detection import ExpressionDetector
from modules.eye_contact import EyeContactDetector
from modules.head_pose import HeadPoseEstimator
from modules.llm import (
generate_questions,
evaluate_answer,
generate_final_summary,
)
from modules.resume_parser import resume_to_profile, get_resume_context_for_llm
# ── Weights ──────────────────────────────────────────────────────────────────
WEIGHTS = {
"eye_contact": 30,
"expression": 25,
"head_stability": 25,
"nervousness": 20,
}
SCORE_HISTORY_LEN = 45
# ── Helpers ───────────────────────────────────────────────────────────────────
def compute_confidence_score(expr_result, eye_result, head_result):
eye_score = eye_result.get("score", 0)
expr_score = expr_result.get("expression_score", 0)
head_score = head_result.get("stability_score", 0)
nerv_score = expr_result.get("nervousness_score", 0)
nerv_contribution = max(0, 100 - nerv_score)
final = (
eye_score * WEIGHTS["eye_contact"] / 100 +
expr_score * WEIGHTS["expression"] / 100 +
head_score * WEIGHTS["head_stability"] / 100 +
nerv_contribution * WEIGHTS["nervousness"] / 100
)
return min(100, max(0, int(final)))
def confidence_label(score):
if score >= 80: return "High", "#00dc00"
if score >= 60: return "Moderate", "#00c896"
if score >= 40: return "Low", "#00a5ff"
return "Very Low", "#003cff"
def analyze_frame(frame):
"""Run all detectors on a single frame. Returns annotated frame + metrics dict."""
landmark_extractor = FaceLandmarkExtractor()
expr_detector = ExpressionDetector(fps=30)
eye_detector = EyeContactDetector()
head_estimator = HeadPoseEstimator()
lm_result = landmark_extractor.extract(frame)
if not lm_result["face_detected"]:
landmark_extractor.release()
return frame, None
kp = lm_result["key_points"]
landmarks = lm_result["landmarks"]
expr_result = expr_detector.detect(kp, frame.shape)
eye_result = eye_detector.detect(kp, frame.shape)
head_result = head_estimator.detect(landmarks, frame.shape)
confidence = compute_confidence_score(expr_result, eye_result, head_result)
landmark_extractor.release()
metrics = {
"confidence": confidence,
"expression": expr_result.get("expression", "N/A"),
"nervousness": expr_result.get("nervousness_score", 0),
"blink_rate": expr_result.get("blink_rate", 0),
"gaze": eye_result.get("gaze_direction", "N/A"),
"eye_pct": eye_result.get("eye_contact_pct", 0),
"direction": head_result.get("direction", "N/A"),
"stability": head_result.get("stability_score", 0),
"pitch": head_result.get("pitch", 0.0),
"yaw": head_result.get("yaw", 0.0),
}
return lm_result["annotated_frame"], metrics
def metrics_to_markdown(metrics, question=""):
if metrics is None:
return "## No face detected\nPosition yourself properly in the frame."
score = metrics["confidence"]
label, color = confidence_label(score)
bar_filled = "β–ˆ" * (score // 5)
bar_empty = "β–‘" * (20 - score // 5)
md = f"""
## Confidence Score: {score}/100 β€” {label}
`{bar_filled}{bar_empty}` {score}%
| Metric | Value |
|---|---|
| Expression | {metrics['expression']} |
| Nervousness | {metrics['nervousness']}/100 |
| Blink Rate | {metrics['blink_rate']}/min |
| Gaze | {metrics['gaze']} |
| Eye Contact | {metrics['eye_pct']}% |
| Head Direction | {metrics['direction']} |
| Head Stability | {metrics['stability']}/100 |
| Pitch / Yaw | {metrics['pitch']:.1f} / {metrics['yaw']:.1f} |
### Tips
"""
if metrics["gaze"] != "Center":
md += "- Look directly at the camera\n"
if metrics["nervousness"] > 50:
md += "- Breathe slowly, you got this\n"
if metrics["direction"] != "Forward":
md += "- Face forward\n"
if metrics["gaze"] == "Center" and metrics["nervousness"] <= 50 and metrics["direction"] == "Forward":
md += "- Great posture! Keep it up\n"
if question:
md += f"\n---\n**Current Question:** {question}"
return md
# ── Tab 1: Image Analysis ─────────────────────────────────────────────────────
def analyze_image(image):
if image is None:
return None, "Please upload an image."
frame = cv2.cvtColor(np.array(image), cv2.COLOR_RGB2BGR)
annotated, metrics = analyze_frame(frame)
if metrics is None:
out_img = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
return out_img, "No face detected in image."
out_img = cv2.cvtColor(annotated, cv2.COLOR_BGR2RGB)
return out_img, metrics_to_markdown(metrics)
# ── Tab 2: Video Analysis ─────────────────────────────────────────────────────
def analyze_video(video_path):
if video_path is None:
return None, "Please upload a video."
cap = cv2.VideoCapture(video_path)
if not cap.isOpened():
return None, "Could not open video."
all_scores = []
expr_counts = {}
frame_count = 0
sample_every = 10 # analyze every 10th frame for speed
last_annotated = None
while True:
ret, frame = cap.read()
if not ret:
break
frame_count += 1
if frame_count % sample_every != 0:
continue
annotated, metrics = analyze_frame(frame)
if metrics:
all_scores.append(metrics["confidence"])
expr = metrics["expression"]
expr_counts[expr] = expr_counts.get(expr, 0) + 1
last_annotated = annotated
cap.release()
if not all_scores:
return None, "No face detected in video."
avg = int(np.mean(all_scores))
peak = int(np.max(all_scores))
low = int(np.min(all_scores))
label, _ = confidence_label(avg)
expr_breakdown = "\n".join(
f"- {e}: {int(c / sum(expr_counts.values()) * 100)}%"
for e, c in sorted(expr_counts.items(), key=lambda x: -x[1])
)
summary_md = f"""
## Video Analysis Summary
| Metric | Value |
|---|---|
| Avg Confidence | {avg}/100 [{label}] |
| Peak Score | {peak}/100 |
| Lowest Score | {low}/100 |
| Frames Analyzed | {len(all_scores)} |
### Expression Breakdown
{expr_breakdown}
"""
out_img = cv2.cvtColor(last_annotated, cv2.COLOR_BGR2RGB) if last_annotated is not None else None
return out_img, summary_md
# ── Tab 3: AI Mock Interview ──────────────────────────────────────────────────
def setup_interview(name, job_role, experience, skills, resume_file):
"""Generate questions from profile or resume."""
if not name or not job_role:
return "Please fill Name and Job Role.", gr.update(visible=False), []
profile = {
'name': name, 'job_role': job_role,
'experience': experience, 'skills': skills,
'projects': [], 'education': '', 'summary': '', 'resume_text': ''
}
if resume_file is not None:
try:
parsed = resume_to_profile(resume_file.name)
profile.update({k: v for k, v in parsed.items() if v})
except Exception as e:
print(f"[WARN] Resume parse failed: {e}")
resume_ctx = get_resume_context_for_llm(profile)
try:
questions = generate_questions(
profile['name'], profile['job_role'],
profile['experience'], profile['skills'],
resume_text=resume_ctx,
num_questions=3,
)
except Exception as e:
return f"Question generation failed: {e}", gr.update(visible=False), []
q_display = "\n\n".join([f"**Q{i+1}:** {q}" for i, q in enumerate(questions)])
return (
f"Interview ready! {len(questions)} questions generated.\n\n{q_display}",
gr.update(visible=True),
questions
)
def evaluate_single_answer(question, answer, job_role):
"""Evaluate one Q&A pair with LLM."""
if not question or not answer:
return "Please provide both question and answer."
try:
feedback = evaluate_answer(
question=question,
answer=answer,
job_role=job_role,
followup='',
followup_answer='',
)
score_str = feedback.get('score_str', '?')
strength = feedback.get('strength', '')
improve = feedback.get('improvement', '')
detail = feedback.get('detail', '')
return f"""
## AI Evaluation
**Score:** {score_str}
**Strength:** {strength}
**Improvement:** {improve}
**Details:** {detail}
"""
except Exception as e:
return f"Evaluation failed: {e}"
# ── Build Gradio UI ───────────────────────────────────────────────────────────
with gr.Blocks(
title="AI Interview Confidence Analyzer",
theme=gr.themes.Base(primary_hue="purple"),
css="""
.score-box { font-size: 1.4em; font-weight: bold; }
footer { display: none !important; }
"""
) as demo:
gr.Markdown("""
# πŸŽ™οΈ AI Interview Confidence Analyzer
Multimodal confidence analysis β€” Face | Expression | Eye Contact | Head Pose | LLM Evaluation
""")
with gr.Tabs():
# ── Tab 1: Image ──────────────────────────────────────────────────────
with gr.Tab("πŸ“Έ Analyze Image"):
gr.Markdown("Upload a photo from your interview or practice session.")
with gr.Row():
img_input = gr.Image(label="Upload Image", type="pil")
img_output = gr.Image(label="Annotated Result")
img_metrics = gr.Markdown(label="Metrics")
img_btn = gr.Button("Analyze", variant="primary")
img_btn.click(
fn=analyze_image,
inputs=img_input,
outputs=[img_output, img_metrics]
)
# ── Tab 2: Video ──────────────────────────────────────────────────────
with gr.Tab("🎬 Analyze Video"):
gr.Markdown("Upload a recorded interview video for frame-by-frame analysis.")
with gr.Row():
vid_input = gr.Video(label="Upload Video")
vid_output = gr.Image(label="Last Analyzed Frame")
vid_metrics = gr.Markdown(label="Summary")
vid_btn = gr.Button("Analyze Video", variant="primary")
vid_btn.click(
fn=analyze_video,
inputs=vid_input,
outputs=[vid_output, vid_metrics]
)
# ── Tab 3: AI Mock Interview ──────────────────────────────────────────
with gr.Tab("πŸ€– AI Mock Interview"):
gr.Markdown("""
Enter your profile, generate personalized interview questions,
then type your answers to get AI feedback.
""")
with gr.Row():
with gr.Column():
name_input = gr.Textbox(label="Your Name", placeholder="Sunaina")
role_input = gr.Textbox(label="Job Role", placeholder="AI/ML Engineer")
exp_input = gr.Textbox(label="Experience", placeholder="1 year intern, built RAG systems")
skill_input = gr.Textbox(label="Skills", placeholder="Python, LangChain, FastAPI, FAISS")
resume_file = gr.File(label="Resume (PDF/DOCX/TXT) β€” optional", file_types=[".pdf", ".docx", ".txt"])
setup_btn = gr.Button("Generate Questions", variant="primary")
with gr.Column():
setup_output = gr.Markdown(label="Questions")
questions_state = gr.State([])
setup_btn.click(
fn=setup_interview,
inputs=[name_input, role_input, exp_input, skill_input, resume_file],
outputs=[setup_output, gr.Column(visible=False), questions_state]
)
gr.Markdown("---")
gr.Markdown("### Evaluate Your Answer")
gr.Markdown("Copy a question from above, paste it, write your answer, and get AI feedback.")
with gr.Row():
eval_question = gr.Textbox(label="Question", lines=2, placeholder="Paste the question here")
eval_role = gr.Textbox(label="Job Role", placeholder="AI/ML Engineer")
eval_answer = gr.Textbox(label="Your Answer", lines=5, placeholder="Type your answer here...")
eval_btn = gr.Button("Get AI Feedback", variant="primary")
eval_output = gr.Markdown(label="AI Feedback")
eval_btn.click(
fn=evaluate_single_answer,
inputs=[eval_question, eval_answer, eval_role],
outputs=eval_output
)
gr.Markdown("""
---
Built by **Sunaina** | AI/ML Engineer Intern @ Indux Technology
| [GitHub](https://github.com/Sunaina792/AI-Interview-system)
""")
if __name__ == "__main__":
demo.launch()