feat: 大幅优化题目UI设计和图片关联算法
主要改进: 🎨 UI优化 - 实现学而思风格的题目卡片设计 - 优化数学公式和坐标点显示格式 - 添加专业的选项和答案展示 - 改进响应式设计和交互体验 🔧 技术优化 - 修复图片关联算法,避免重复分配 - 智能图片与题目匹配算法 - 添加admin用户便捷登录功能 - 优化题目文本格式化 📊 功能增强 - 可折叠的答案解析区域 - 动态分值提取和显示 - 专业的数学符号高亮 - 子题清晰分隔显示 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
This commit is contained in:
@@ -0,0 +1,207 @@
|
||||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
## Project Overview
|
||||
|
||||
This is **学习助手 Pro** (Learning Assistant Pro), an intelligent exam paper management and learning tracking system built with FastAPI (backend) and Vue 3 (frontend). The system specializes in PDF exam paper processing, automatic question extraction using AI/OCR, mistake management, and personalized learning with spaced repetition algorithms.
|
||||
|
||||
### Core Features
|
||||
- **PDF Processing**: High-speed PDF text extraction (0.01s/page) with 95%+ accuracy using hybrid text layer + OCR approach
|
||||
- **Question Extraction**: AI-powered automatic question identification and extraction from exam papers
|
||||
- **Mistake Management**: Ebbinghaus forgetting curve-based review system for mistake tracking
|
||||
- **Word Learning**: SM-2 spaced repetition algorithm for English vocabulary learning
|
||||
- **Analytics**: Comprehensive learning statistics and progress visualization
|
||||
|
||||
## Development Commands
|
||||
|
||||
### Quick Start
|
||||
```bash
|
||||
# Start both backend and frontend with provided script
|
||||
./start.sh
|
||||
|
||||
# Check system status
|
||||
./status.sh
|
||||
|
||||
# Stop all services
|
||||
./stop.sh
|
||||
```
|
||||
|
||||
### Backend Development (Python/FastAPI)
|
||||
```bash
|
||||
cd backend
|
||||
|
||||
# Install dependencies
|
||||
pip install -r requirements.txt
|
||||
|
||||
# Start development server (port 8888)
|
||||
python main.py
|
||||
# OR
|
||||
uvicorn main:app --host 0.0.0.0 --port 8888 --reload
|
||||
|
||||
# View API documentation
|
||||
# Open http://localhost:8888/docs in browser
|
||||
```
|
||||
|
||||
### Frontend Development (Vue 3/Vite)
|
||||
```bash
|
||||
cd frontend
|
||||
|
||||
# Install dependencies
|
||||
npm install
|
||||
|
||||
# Start development server (auto-selects port, typically 3000-3002)
|
||||
npm run dev
|
||||
|
||||
# Build for production
|
||||
npm run build
|
||||
|
||||
# Preview production build
|
||||
npm run preview
|
||||
```
|
||||
|
||||
### Database Operations
|
||||
```bash
|
||||
cd backend
|
||||
|
||||
# Initialize database with default data
|
||||
python main.py # Database auto-creates on first run
|
||||
|
||||
# Migrate database (if schema changes)
|
||||
python migrate_db.py
|
||||
```
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
### Backend Architecture (FastAPI)
|
||||
- **Main Application**: `backend/main.py` - Single-file FastAPI application with all API endpoints
|
||||
- **Database**: SQLite with SQLAlchemy ORM (easily upgradeable to PostgreSQL)
|
||||
- **Authentication**: JWT-based with bcrypt password hashing
|
||||
- **File Processing**: Specialized PDF processing modules in `backend/pdf_splitter_*.py`
|
||||
- **Upload Storage**: Local file storage in `backend/uploads/` directory
|
||||
|
||||
### Key Backend Components
|
||||
|
||||
#### PDF Processing Pipeline
|
||||
The system uses a hybrid approach for PDF processing:
|
||||
- **Primary**: Text layer extraction for maximum speed (0.01s/page)
|
||||
- **Fallback**: OCR processing for scanned PDFs using EasyOCR
|
||||
- **Main Module**: `backend/pdf_splitter_hybrid.py` - Intelligent PDF processor
|
||||
|
||||
#### Database Models
|
||||
All models are defined in `backend/main.py`:
|
||||
- **User Management**: `User`, `UserWordProgress`
|
||||
- **Word Learning**: `Word`, spaced repetition with SM-2 algorithm
|
||||
- **Paper Management**: `Paper` with metadata and file references
|
||||
- **Question Extraction**: `Question`, `QuestionCollection` with auto-extraction support
|
||||
- **Mistake Tracking**: `Mistake`, `ReviewLog` with Leitner box system
|
||||
|
||||
#### API Structure
|
||||
- **Authentication**: `/api/auth/*` - JWT-based login/register
|
||||
- **Word Management**: `/api/words/*` - CRUD and spaced repetition learning
|
||||
- **Paper Management**: `/api/papers/*` - PDF upload and management
|
||||
- **Question Processing**: `/api/questions/*`, `/api/papers/{id}/split` - AI extraction
|
||||
- **Mistake Review**: `/api/mistakes/*` - Leitner-based review system
|
||||
- **Analytics**: `/api/dashboard`, `/api/stats` - Learning statistics
|
||||
|
||||
### Frontend Architecture (Vue 3)
|
||||
- **Framework**: Vue 3 with Composition API
|
||||
- **UI Library**: Element Plus for consistent design
|
||||
- **State Management**: Pinia stores for auth and word data
|
||||
- **Routing**: Vue Router with authentication guards
|
||||
- **Build Tool**: Vite for fast development and building
|
||||
|
||||
#### Key Frontend Components
|
||||
- **Authentication**: `frontend/src/stores/auth.js` - JWT token management
|
||||
- **Word Learning**: `frontend/src/stores/word.js` - Spaced repetition logic
|
||||
- **Views**: Major application pages in `frontend/src/views/`
|
||||
- `MainDashboard.vue` - Main learning hub
|
||||
- `PaperViewer.vue` - PDF viewing and interaction
|
||||
- `Learning.vue` - Word learning interface
|
||||
- `MistakeReview.vue` - Review session interface
|
||||
|
||||
#### Routing Structure
|
||||
- `/` - Main dashboard (requires auth)
|
||||
- `/subjects/:subject` - Subject-specific paper listing
|
||||
- `/papers/:id` - Paper detail view
|
||||
- `/papers/:id/view` - PDF viewer with annotation support
|
||||
- `/words` - Vocabulary learning interface
|
||||
- `/review` - Mistake review sessions
|
||||
|
||||
## Development Guidelines
|
||||
|
||||
### Backend Development
|
||||
- **Database Changes**: Add new models to `backend/main.py`, run `python migrate_db.py` if needed
|
||||
- **New API Endpoints**: Add to `backend/main.py`, follow existing patterns
|
||||
- **PDF Processing**: Extend `backend/pdf_splitter_hybrid.py` for new extraction features
|
||||
- **Authentication**: Use `get_current_user()` dependency for protected routes
|
||||
|
||||
### Frontend Development
|
||||
- **New Views**: Add to `frontend/src/views/` and register in `frontend/src/router/index.js`
|
||||
- **State Management**: Add new stores in `frontend/src/stores/` following existing patterns
|
||||
- **API Calls**: Use the existing API service pattern in stores
|
||||
- **Authentication**: Route guards implemented in router, check `authStore.isAuthenticated`
|
||||
|
||||
### File Upload and Storage
|
||||
- **Backend**: Files stored in `backend/uploads/{user_id}/`
|
||||
- **PDF Processing**: Temporary files cleaned up automatically
|
||||
- **Frontend**: File upload through drag-and-drop or click-to-upload interfaces
|
||||
|
||||
### Testing and Development
|
||||
- **Backend Testing**: Use FastAPI's automatic docs at `/docs` for API testing
|
||||
- **Frontend Testing**: Development server supports hot reload
|
||||
- **PDF Processing**: Test files can be added to `backend/test_*.py` modules
|
||||
|
||||
## Performance Considerations
|
||||
|
||||
### PDF Processing
|
||||
- The system achieves 2900x performance improvement through hybrid text/OCR approach
|
||||
- Text layer extraction: 0.01s per page
|
||||
- OCR fallback: Used only when text layer extraction fails
|
||||
- Image extraction: Automatic correlation with question content
|
||||
|
||||
### Database Optimization
|
||||
- SQLite for development/small deployments
|
||||
- Easy migration path to PostgreSQL for production
|
||||
- Indexes on frequently queried fields (user_id, subject, etc.)
|
||||
|
||||
### Frontend Performance
|
||||
- Lazy loading of route components
|
||||
- Efficient state management with Pinia
|
||||
- Responsive design with mobile-first approach
|
||||
|
||||
## Deployment
|
||||
|
||||
### Development Environment
|
||||
- Use `./start.sh` for easy development setup
|
||||
- Backend runs on port 8888, frontend auto-selects port (3000-3002)
|
||||
- Hot reload enabled for both frontend and backend
|
||||
|
||||
### Production Considerations
|
||||
- Change `SECRET_KEY` in backend production
|
||||
- Configure CORS origins for specific domains
|
||||
- Set up proper file storage with backup
|
||||
- Consider PostgreSQL for production database
|
||||
- Configure HTTPS/reverse proxy with nginx
|
||||
|
||||
## Key Files to Understand
|
||||
|
||||
### Backend Core Files
|
||||
- `backend/main.py` - Complete FastAPI application (1659 lines)
|
||||
- `backend/pdf_splitter_hybrid.py` - PDF processing engine
|
||||
- `backend/requirements.txt` - Python dependencies
|
||||
- `backend/initial_words.csv` - Default English vocabulary data
|
||||
|
||||
### Frontend Core Files
|
||||
- `frontend/src/App.vue` - Main application component with global styles
|
||||
- `frontend/src/main.js` - Application bootstrap
|
||||
- `frontend/src/router/index.js` - Route definitions and auth guards
|
||||
- `frontend/src/stores/auth.js` - Authentication state management
|
||||
- `frontend/src/views/MainDashboard.vue` - Primary user interface
|
||||
|
||||
### Utility Scripts
|
||||
- `start.sh` - Development server startup script
|
||||
- `status.sh` - System status checking script
|
||||
- `stop.sh` - Service shutdown script
|
||||
|
||||
This system is designed for rapid development and deployment while maintaining high performance for PDF processing and excellent user experience for learning management.
|
||||
@@ -1,6 +1,38 @@
|
||||
# WordMaster Pro 部署指南
|
||||
# 学习助手 Pro 部署指南
|
||||
|
||||
这是一个基于 FastAPI + Vue3 的智能单词学习系统,支持远程访问和初中英语单词导入。
|
||||
这是一个基于 FastAPI + Vue3 的智能试卷管理与学习跟踪系统,支持PDF试卷智能解析、题目自动提取、错题管理和学习统计等功能。
|
||||
|
||||
## ✨ 核心功能
|
||||
|
||||
### 📄 智能试卷管理
|
||||
- PDF试卷上传和预览
|
||||
- 多学科试卷分类管理(数学、语文、英语等)
|
||||
- 试卷信息自动识别和标注
|
||||
|
||||
### 🔍 题目智能提取
|
||||
- AI驱动的PDF文本层解析,速度快准确率高(95%+)
|
||||
- OCR备份解析,处理扫描版PDF
|
||||
- 自动识别题目类型(选择题、填空题、解答题等)
|
||||
- 智能提取题目图片(几何图形、函数图像等)
|
||||
- 数学符号自动修复和优化
|
||||
|
||||
### 📊 错题管理系统
|
||||
- 智能错题收集和分类
|
||||
- 艾宾浩斯遗忘曲线复习提醒
|
||||
- 个性化学习计划生成
|
||||
- 错题复习统计和进度跟踪
|
||||
|
||||
### 📈 学习数据分析
|
||||
- 详细的学习进度统计
|
||||
- 多维度数据可视化
|
||||
- 学科强弱项分析
|
||||
- 学习效率评估
|
||||
|
||||
### 🚀 性能特点
|
||||
- **超快解析速度**: 0.01秒完成一页试卷解析
|
||||
- **高准确率**: 文本识别准确率95%+,性能提升2900倍
|
||||
- **智能图片提取**: 自动关联题目与图片,完美显示数学图形
|
||||
- **热加载开发**: 前后端完整热加载支持
|
||||
|
||||
## 🚀 快速启动
|
||||
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
创建管理员用户脚本
|
||||
用于方便测试,创建用户名admin,密码admin的账户
|
||||
"""
|
||||
import sys
|
||||
import os
|
||||
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
import bcrypt
|
||||
|
||||
# 数据库配置
|
||||
SQLALCHEMY_DATABASE_URL = "sqlite:///./wordmaster.db"
|
||||
engine = create_engine(SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False})
|
||||
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
||||
|
||||
# 导入模型(这里直接定义,避免循环导入)
|
||||
from sqlalchemy import Column, Integer, String, DateTime
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
from datetime import datetime
|
||||
|
||||
Base = declarative_base()
|
||||
|
||||
class User(Base):
|
||||
__tablename__ = "users"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
username = Column(String, unique=True, index=True)
|
||||
email = Column(String, unique=True, index=True)
|
||||
hashed_password = Column(String)
|
||||
created_at = Column(DateTime, default=datetime.utcnow)
|
||||
|
||||
def get_password_hash(password: str) -> str:
|
||||
"""生成密码哈希"""
|
||||
return bcrypt.hashpw(password.encode('utf-8'), bcrypt.gensalt()).decode('utf-8')
|
||||
|
||||
def create_admin_user():
|
||||
"""创建管理员用户"""
|
||||
# 创建数据库会话
|
||||
db = SessionLocal()
|
||||
|
||||
try:
|
||||
# 检查admin用户是否已存在
|
||||
existing_admin = db.query(User).filter(User.username == "admin").first()
|
||||
if existing_admin:
|
||||
print("❌ admin用户已存在!")
|
||||
# 更新密码为admin
|
||||
existing_admin.hashed_password = get_password_hash("admin")
|
||||
db.commit()
|
||||
print("✅ 已更新admin用户密码为'admin'")
|
||||
return
|
||||
|
||||
# 检查[email protected]邮箱是否已存在
|
||||
existing_email = db.query(User).filter(User.email == "[email protected]").first()
|
||||
if existing_email:
|
||||
print("❌ [email protected]邮箱已被使用!")
|
||||
return
|
||||
|
||||
# 创建admin用户
|
||||
hashed_password = get_password_hash("admin")
|
||||
admin_user = User(
|
||||
username="admin",
|
||||
email="[email protected]",
|
||||
hashed_password=hashed_password
|
||||
)
|
||||
|
||||
db.add(admin_user)
|
||||
db.commit()
|
||||
db.refresh(admin_user)
|
||||
|
||||
print("✅ 成功创建admin用户!")
|
||||
print(" 用户名: admin")
|
||||
print(" 密码: admin")
|
||||
print(" 邮箱: [email protected]")
|
||||
print(" ID:", admin_user.id)
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ 创建admin用户失败: {e}")
|
||||
db.rollback()
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("🚀 开始创建admin用户...")
|
||||
create_admin_user()
|
||||
print("✨ 完成!")
|
||||
+25
-10
@@ -332,6 +332,7 @@ class QuestionResponse(BaseModel):
|
||||
correct_answer: str
|
||||
explanation: Optional[str]
|
||||
image_key: Optional[str]
|
||||
images: Optional[List[dict]] # 题目关联的图片信息
|
||||
tags: Optional[List[str]]
|
||||
created_at: datetime
|
||||
|
||||
@@ -379,8 +380,14 @@ class SplitResponse(BaseModel):
|
||||
stats: dict
|
||||
error: Optional[str] = None
|
||||
|
||||
# 导入图片API路由
|
||||
from api_images import router as images_router
|
||||
|
||||
# FastAPI应用
|
||||
app = FastAPI(title="WordMaster Pro API", version="1.0.0", description="智能学习管理系统API")
|
||||
app = FastAPI(title="学习助手 Pro API", version="1.0.0", description="智能试卷管理与学习跟踪系统API")
|
||||
|
||||
# 注册图片API路由
|
||||
app.include_router(images_router)
|
||||
|
||||
# CORS中间件
|
||||
app.add_middleware(
|
||||
@@ -1287,6 +1294,7 @@ async def get_questions(
|
||||
correct_answer=q.correct_answer,
|
||||
explanation=q.explanation,
|
||||
image_key=q.image_key,
|
||||
images=q.images,
|
||||
tags=q.tags,
|
||||
created_at=q.created_at
|
||||
) for q in questions]
|
||||
@@ -1320,6 +1328,7 @@ async def get_question(
|
||||
correct_answer=question.correct_answer,
|
||||
explanation=question.explanation,
|
||||
image_key=question.image_key,
|
||||
images=question.images,
|
||||
tags=question.tags,
|
||||
created_at=question.created_at
|
||||
)
|
||||
@@ -1567,22 +1576,32 @@ async def split_paper(
|
||||
# 保存提取的题目到数据库
|
||||
saved_questions = []
|
||||
for i, question_data in enumerate(questions):
|
||||
# 移动题目图片到uploads目录
|
||||
# 从question_data中获取已关联的图片信息
|
||||
question_images = question_data.get('images', [])
|
||||
|
||||
# 如果没有图片但有image_path,移动图片到uploads目录
|
||||
image_key = None
|
||||
if question_data.get('image_path') and os.path.exists(question_data['image_path']):
|
||||
if not question_images and question_data.get('image_path') and os.path.exists(question_data['image_path']):
|
||||
# 生成新的文件名
|
||||
file_extension = os.path.splitext(question_data['image_filename'])[1]
|
||||
new_filename = f"{uuid.uuid4()}{file_extension}"
|
||||
dest_dir = os.path.join("uploads", str(current_user.id))
|
||||
os.makedirs(dest_dir, exist_ok=True)
|
||||
dest_path = os.path.join(dest_dir, new_filename)
|
||||
|
||||
|
||||
try:
|
||||
shutil.copy2(question_data['image_path'], dest_path)
|
||||
image_key = dest_path
|
||||
|
||||
# 创建图片对象
|
||||
question_images = [{
|
||||
'page': question_data.get('page', 1),
|
||||
'bbox': question_data.get('bbox', []),
|
||||
'key': os.path.basename(dest_path) # 只保存文件名
|
||||
}]
|
||||
except Exception as e:
|
||||
print(f"移动题目图片失败: {e}")
|
||||
|
||||
|
||||
# 创建题目记录
|
||||
db_question = Question(
|
||||
user_id=current_user.id,
|
||||
@@ -1600,11 +1619,7 @@ async def split_paper(
|
||||
# 拆题相关字段
|
||||
page_spans=question_data.get('page_spans', [{'page': question_data.get('page', 1), 'bbox': question_data.get('bbox', [])}]),
|
||||
source_text=question_data.get('text', ''),
|
||||
images=[{
|
||||
'page': question_data.get('page', 1),
|
||||
'bbox': question_data.get('bbox', []),
|
||||
'key': image_key
|
||||
}] if image_key else [],
|
||||
images=question_images, # 使用从PDFSplitterHybrid获取的图片信息
|
||||
formula=[], # 后续可以添加公式识别
|
||||
score=None, # 需要手动填写
|
||||
question_number=question_data.get('number', str(i+1)),
|
||||
|
||||
+114
-26
@@ -127,36 +127,124 @@ class PDFImageExtractor:
|
||||
def associate_images_with_questions(self, images: List[Dict], questions: List[Dict]) -> List[Dict]:
|
||||
"""
|
||||
将图片与题目关联
|
||||
基于页面位置和题目范围进行智能匹配
|
||||
基于页面位置和题目边界框进行智能匹配
|
||||
"""
|
||||
print(f"🔗 开始关联图片与题目: {len(images)}张图片, {len(questions)}道题目")
|
||||
|
||||
# 为题目添加图片信息
|
||||
for question in questions:
|
||||
question_images = []
|
||||
question_page = question.get("page", 1)
|
||||
|
||||
# 查找同一页面的图片
|
||||
page_images = [img for img in images if img["page"] == question_page]
|
||||
|
||||
for image in page_images:
|
||||
# 简单策略:同一页面的图片都关联到题目
|
||||
# 更复杂的策略可以基于位置、内容等进一步判断
|
||||
question_images.append({
|
||||
"filename": image["filename"],
|
||||
"path": image["relative_path"],
|
||||
"width": image["width"],
|
||||
"height": image["height"],
|
||||
"format": image["format"]
|
||||
})
|
||||
|
||||
question["images"] = question_images
|
||||
|
||||
if question_images:
|
||||
print(f"📝 题目{question.get('number', '?')}关联了{len(question_images)}张图片")
|
||||
|
||||
|
||||
# 用于跟踪已分配的图片
|
||||
used_images = set()
|
||||
|
||||
# 按页码组织题目
|
||||
questions_by_page = {}
|
||||
for i, question in enumerate(questions):
|
||||
page = question.get("page", 1)
|
||||
if page not in questions_by_page:
|
||||
questions_by_page[page] = []
|
||||
questions_by_page[page].append((i, question))
|
||||
|
||||
# 为每页进行图片关联
|
||||
for page_num, page_questions in questions_by_page.items():
|
||||
# 获取当前页的图片
|
||||
page_images = [img for img in images if img["page"] == page_num]
|
||||
|
||||
if not page_images:
|
||||
continue
|
||||
|
||||
# 按图片在页面中的垂直位置排序(从上到下)
|
||||
page_images_sorted = sorted(page_images, key=lambda x: min(rect["y0"] for rect in x["rects"]) if x["rects"] else 0)
|
||||
|
||||
# 为每个题目进行图片分配
|
||||
for question_idx, question in page_questions:
|
||||
question_images = []
|
||||
|
||||
# 获取题目的边界框信息
|
||||
question_bbox = self._get_question_bbox(question)
|
||||
|
||||
# 遍历页面中的图片,寻找合适的匹配
|
||||
for img_idx, image in enumerate(page_images_sorted):
|
||||
img_filename = image["filename"]
|
||||
|
||||
# 跳过已分配的图片
|
||||
if img_filename in used_images:
|
||||
continue
|
||||
|
||||
# 检查图片是否适合当前题目
|
||||
if self._should_associate_image(question, image, question_bbox):
|
||||
question_images.append({
|
||||
"filename": image["filename"],
|
||||
"path": image["relative_path"],
|
||||
"width": image["width"],
|
||||
"height": image["height"],
|
||||
"format": image["format"]
|
||||
})
|
||||
|
||||
used_images.add(img_filename)
|
||||
print(f"📝 题目{question.get('number', '?')}关联图片: {img_filename}")
|
||||
break # 每个题目最多关联一张图片,除非明确需要多张
|
||||
|
||||
question["images"] = question_images
|
||||
|
||||
if not question_images:
|
||||
print(f"📝 题目{question.get('number', '?')}未关联图片")
|
||||
|
||||
return questions
|
||||
|
||||
def _get_question_bbox(self, question: Dict) -> Dict:
|
||||
"""
|
||||
获取题目的边界框信息
|
||||
"""
|
||||
# 尝试从不同字段获取边界框
|
||||
bbox = question.get("bbox")
|
||||
if bbox and isinstance(bbox, dict):
|
||||
return bbox
|
||||
|
||||
# 尝试从page_spans获取
|
||||
page_spans = question.get("page_spans")
|
||||
if page_spans and len(page_spans) > 0:
|
||||
span = page_spans[0]
|
||||
if isinstance(span, dict) and "bbox" in span:
|
||||
return span["bbox"]
|
||||
|
||||
# 默认边界框(整个页面)
|
||||
return {"x0": 0, "y0": 0, "x1": 1000, "y1": 1000}
|
||||
|
||||
def _should_associate_image(self, question: Dict, image: Dict, question_bbox: Dict) -> bool:
|
||||
"""
|
||||
判断图片是否应该关联到当前题目
|
||||
"""
|
||||
# 策略1: 基于几何位置匹配
|
||||
if image.get("rects"):
|
||||
for rect in image["rects"]:
|
||||
# 检查图片是否在题目的合理范围内
|
||||
# 考虑页面布局和图片通常的放置位置
|
||||
img_center_y = (rect["y0"] + rect["y1"]) / 2
|
||||
question_center_y = (question_bbox.get("y0", 0) + question_bbox.get("y1", 1000)) / 2
|
||||
|
||||
# 如果图片中心在题目中心的合理范围内(上下200点)
|
||||
if abs(img_center_y - question_center_y) < 200:
|
||||
return True
|
||||
|
||||
# 如果图片在题目下方(通常图片紧跟题目)
|
||||
if rect["y0"] > question_bbox.get("y1", 0) and rect["y0"] - question_bbox.get("y1", 0) < 300:
|
||||
return True
|
||||
|
||||
# 策略2: 基于题目内容判断
|
||||
question_text = question.get("text", "").lower()
|
||||
question_type = question.get("type", "")
|
||||
|
||||
# 选择题通常较少有图片
|
||||
if question_type in ["single_choice", "multiple_choice"]:
|
||||
# 只有明确提到图形时才关联图片
|
||||
if any(keyword in question_text for keyword in ["如图", "下图", "图形", "图像", "坐标", "函数图像"]):
|
||||
return True
|
||||
|
||||
# 解答题、证明题等更可能有图片
|
||||
elif question_type in ["essay", "calculation", "proof"]:
|
||||
return True
|
||||
|
||||
# 默认不关联(更保守的策略)
|
||||
return False
|
||||
|
||||
def extract_and_associate(self, pdf_path: str, questions: List[Dict]) -> Tuple[List[Dict], List[Dict]]:
|
||||
"""
|
||||
提取PDF图片并与题目关联
|
||||
|
||||
+1087
-59
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user