Commit 89e60e48 authored by wanglch's avatar wanglch
Browse files

Initial commit

parents
Pipeline #2484 canceled with stages
3.4 EXERCISES
For the following exercises, the given functions represent the position of a particle traveling along a horizontal line.
a. Find the velocity and acceleration functions.
b. Determine the time intervals when the object is slowing down or speeding up.
150. \[ s(t) = 2t^3 - 3t^2 - 12t + 8 \]
151. \[ s(t) = 2t^3 - 15t^2 + 36t - 10 \]
152. \[ s(t) = \frac{t}{1 + t^2} \]
153. A rocket is fired vertically upward from the ground. The distance \( s \) in feet that the rocket travels from the ground after \( t \) seconds is given by \( s(t) = -16t^2 + 560t. \)
a. Find the velocity of the rocket 3 seconds after being fired.
b. Find the acceleration of the rocket 3 seconds after being fired.
154. A ball is thrown downward with a speed of 8 ft/s from the top of a 64-foot-tall building. After \( t \) seconds, its height above the ground is given by \( s(t) = -16t^2 - 8t + 64. \)
a. Determine how long it takes for the ball to hit the ground.
b. Determine the velocity of the ball when it hits the ground.
155. The position function \( s(t) = t^2 - 3t - 4 \) represents the position of the back of a car backing out of a driveway and then driving in a straight line, where \( s \) is in feet and \( t \) is in seconds. In this case, \( s(t) = 0 \) represents the time at which the back of the car is at the garage door, so \( s(0) = -4 \) is the starting position of the car, 4 feet inside the garage.
a. Determine the velocity of the car when \( s(t) = 0. \)
b. Determine the velocity of the car when \( s(t) = 14. \)
156. The position of a hummingbird flying along a straight line in \( t \) seconds is given by \( s(t) = 3t^3 - 7t \) meters.
a. Determine the velocity of the bird at \( t = 1 \) sec.
b. Determine the acceleration of the bird at \( t = 1 \) sec.
c. Determine the acceleration of the bird when the velocity equals 0.
157. A potato is launched vertically upward with an initial velocity of 100 ft/s from a potato gun at the top of an 85-foot-tall building. The distance in feet that the potato travels from the ground after \( t \) seconds is given by \( s(t) = -16t^2 + 100t + 85. \)
a. Find the velocity of the potato after 0.5 s and 5.75 s.
b. Find the speed of the potato at 0.5 s and 5.75 s.
c. Determine when the potato reaches its maximum height.
d. Find the acceleration of the potato at 0.5 s and 1.5 s.
e. Determine how long the potato is in the air.
f. Determine the velocity of the potato upon hitting the ground.
158. The position function \( s(t) = t^3 - 8t \) gives the position in miles of a freight train where east is the positive direction and \( t \) is measured in hours.
a. Determine the direction the train is traveling when \( s(t) = 0. \)
b. Determine the direction the train is traveling when \( s(t) = 0. \)
c. Determine the time intervals when the train is slowing down or speeding up.
159. The following graph shows the position \( y = s(t) \) of an object moving along a straight line.
![Graph of the position function](image)
a. Use the graph of the position function to determine the time intervals when the velocity is positive, negative, or zero.
b. Sketch the graph of the velocity function.
c. Use the graph of the velocity function to determine the time intervals when the acceleration is positive, negative, or zero.
d. Determine the time intervals when the object is speeding up or slowing down.
\ No newline at end of file
3.4 EXERCISES
For the following exercises, the given functions represent the position of a particle traveling along a horizontal line.
a. Find the velocity and acceleration functions.
b. Determine the time intervals when the object is slowing down or speeding up.
150. \( s(t) = 2t^3 - 3t^2 - 12t + 8 \)
151. \( s(t) = 2t^3 - 15t^2 + 36t - 10 \)
152. \( s(t) = \frac{t}{1 + t^2} \)
153. A rocket is fired vertically upward from the ground. The distance \( s \) in feet that the rocket travels from the ground after \( t \) seconds is given by \( s(t) = -16t^2 + 560t + 155 \).
a. Find the velocity of the rocket 3 seconds after being fired.
b. Find the acceleration of the rocket 3 seconds after being fired.
154. A ball is thrown downward with a speed of 8 \( \text{ft/s} \) from the top of a 64-foot-tall building. After \( t \) seconds, its height above the ground is given by \( s(t) = -16t^2 - 8t + 64 \).
a. Determine how long it takes for the ball to hit the ground.
b. Determine the velocity of the ball when it hits the ground.
155. The position function \( s(t) = t^2 - 3t - 4 \) represents the position of the back of a car backing out of a driveway and then driving in a straight line, where \( s \) is in feet and \( t \) is in seconds. In this case, \( s(t) = 0 \) represents the time at which the back of the car is at the garage door, so \( s(0) = -4 \) is the starting position of the car, 4 feet inside the garage.
a. Determine the velocity of the car when \( s(t) = 0 \).
b. Determine the velocity of the car when \( s(t) = 14 \).
156. The position of a hummingbird flying along a straight line in \( t \) seconds is given by \( s(t) = 3t^3 - 7t \) meters.
a. Determine the velocity of the bird at \( t = 1 \) sec.
b. Determine the acceleration of the bird at \( t = 1 \) sec.
c. Determine the acceleration of the bird when the velocity equals 0.
157. A potato is launched vertically upward with an initial velocity of 100 \( \text{ft/s} \) from a potato gun at the top of an 85-foot-tall building. The distance in feet that the potato travels from the ground after \( t \) seconds is given by \( s(t) = -16t^2 + 100t + 85 \).
a. Find the velocity of the potato after 0.5 s and 5.75 s.
b. Find the speed of the potato at 0.5 s and 5.75 s.
c. Determine when the potato reaches its maximum height.
d. Find the acceleration of the potato at 0.5 s and 1.5 s.
e. Determine how long the potato is in the air.
f. Determine the velocity of the potato upon hitting the ground.
158. The position function \( s(t) = t^3 - 8t \) gives the position in miles of a freight train where east is the positive direction and \( t \) is measured in hours.
a. Determine the direction the train is traveling when \( s(t) = 0 \).
b. Determine the direction the train is traveling when \( s(t) = 0 \).
c. Determine the time intervals when the train is slowing down or speeding up.
159. The following graph shows the position \( y = s(t) \) of an object moving along a straight line.
![Graph](image)
a. Use the graph of the position function to determine the time intervals when the velocity is positive, negative, or zero.
b. Sketch the graph of the velocity function.
c. Use the graph of the velocity function to determine the time intervals when the acceleration is positive, negative, or zero.
d. Determine the time intervals when the object is speeding up or slowing down.
\ No newline at end of file
3.4 EXERCISES
For the following exercises, the given functions represent the position of a particle traveling along a horizontal line.
a. Find the velocity and acceleration functions.
b. Determine the time intervals when the object is slowing down or speeding up.
150. \( s(t) = 2t^3 - 3t^2 - 12t + 8 \)
151. \( s(t) = 2t^3 - 15t^2 + 36t - 10 \)
152. \( s(t) = \frac{t}{1 + t^2} \)
153. A rocket is fired vertically upward from the ground. The distance \( s \) in feet that the rocket travels from the ground after \( t \) seconds is given by \( s(t) = -16t^2 + 560t \).
a. Find the velocity of the rocket 3 seconds after being fired.
b. Find the acceleration of the rocket 3 seconds after being fired.
154. A ball is thrown downward with a speed of 8 ft/ s from the top of a 64-foot-tall building. After \( t \) seconds, its height above the ground is given by \( s(t) = -16t^2 - 8t + 64 \).
a. Determine how long it takes for the ball to hit the ground.
b. Determine the velocity of the ball when it hits the ground.
155. The position function \( s(t) = t^2 - 3t - 4 \) represents the position of the back of a car backing out of a driveway and then driving in a straight line, where \( s \) is in feet and \( t \) is in seconds. In this case, \( s(t) = 0 \) represents the time at which the back of the car is at the garage door, so \( s(0) = -4 \) is the starting position of the car, 4 feet inside the garage.
a. Determine the velocity of the car when \( s(t) = 0 \).
b. Determine the velocity of the car when \( s(t) = 14 \).
156. The position of a hummingbird flying along a straight line in \( t \) seconds is given by \( s(t) = 3t^3 - 7t \) meters.
a. Determine the velocity of the bird at \( t = 1 \) sec.
b. Determine the acceleration of the bird at \( t = 1 \) sec.
c. Determine the acceleration of the bird when the velocity equals 0.
157. A potato is launched vertically upward with an initial velocity of 100 ft/s from a potato gun at the top of an 85-foot-tall building. The distance in feet that the potato travels from the ground after \( t \) seconds is given by \( s(t) = -16t^2 + 100t + 85 \).
a. Find the velocity of the potato after 0.5 s and 5.75 s.
b. Find the speed of the potato at 0.5 s and 5.75 s.
c. Determine when the potato reaches its maximum height.
d. Find the acceleration of the potato at 0.5 s and 1.5 s.
e. Determine how long the potato is in the air.
f. Determine the velocity of the potato upon hitting the ground.
158. The position function \( s(t) = t^3 - 8t \) gives the position in miles of a freight train where east is the positive direction and \( t \) is measured in hours.
a. Determine the direction the train is traveling when \( s(t) = 0 \).
b. Determine the direction the train is traveling when \( s(t) = 0 \).
c. Determine the time intervals when the train is slowing down or speeding up.
159. The following graph shows the position \( y = s(t) \) of an object moving along a straight line.
![Graph of position function](image)
a. Use the graph of the position function to determine the time intervals when the velocity is positive, negative, or zero.
b. Sketch the graph of the velocity function.
Use the graph of the velocity function to determine the time intervals when the acceleration is positive, negative, or zero.
d. Determine the time intervals when the object is speeding up or slowing down.
\ No newline at end of file
#!/bin/bash
# Exit on error but allow the trap to execute
set -e
# Global variable to track server PID
SERVER_PID=""
# Trap function to handle Ctrl+C (SIGINT)
cleanup() {
echo -e "\n[INFO] Received interrupt signal. Cleaning up..."
# Find and kill any Python processes started by this script
echo "[INFO] Stopping any running Python processes"
pkill -P $$ python || true
# Stop server if running
if [ -n "$SERVER_PID" ] && kill -0 "$SERVER_PID" 2>/dev/null; then
echo "[INFO] Stopping server (PID: $SERVER_PID)"
kill -TERM "$SERVER_PID" 2>/dev/null || true
wait "$SERVER_PID" 2>/dev/null || true
fi
pkill vllm
echo "[INFO] Cleanup complete. Exiting."
exit 1
}
# Set the trap for SIGINT (Ctrl+C)
trap cleanup SIGINT
# Function to check if port 30000 is in use
check_port() {
port=30000
echo "[INFO] Checking if port $port is available..."
if command -v lsof >/dev/null 2>&1; then
# Linux/macOS
if lsof -i :$port >/dev/null 2>&1; then
echo "[ERROR] Port $port is already in use. Process details:"
lsof -i :$port
echo "[ERROR] Please stop the process using this port and try again."
echo " You can use: kill -9 <PID>"
return 1
fi
elif command -v netstat >/dev/null 2>&1; then
# Windows/other systems with netstat
if netstat -an | grep -q ":$port "; then
echo "[ERROR] Port $port is already in use. Process details:"
if command -v findstr >/dev/null 2>&1; then
# Windows
netstat -ano | findstr ":$port"
echo "[ERROR] Please stop the process using this port and try again."
echo " You can use: taskkill /F /PID <PID>"
else
netstat -an | grep ":$port "
echo "[ERROR] Please stop the process using this port and try again."
fi
return 1
fi
else
# Fallback method using nc if available
if command -v nc >/dev/null 2>&1; then
nc -z localhost $port >/dev/null 2>&1
if [ $? -eq 0 ]; then
echo "[ERROR] Port $port is already in use, but cannot determine which process."
echo "[ERROR] Please ensure port $port is available before continuing."
return 1
fi
else
echo "[WARNING] Cannot check if port $port is in use (neither lsof, netstat, nor nc available)."
echo "[WARNING] Continuing anyway, but this might fail if the port is already in use."
return 0
fi
fi
echo "[INFO] Port $port is available."
return 0
}
# Function to create conda environment if it doesn't exist
create_conda_env() {
env_name=$1
python_version=$2
# Check if environment exists
if conda info --envs | grep -q "^$env_name "; then
echo "Environment $env_name already exists, using it."
else
echo "Creating conda environment: $env_name"
conda create -y -n $env_name python=$python_version
fi
}
# Generic function to start a server (either vllm or sglang)
start_server() {
server_type=$1
model_name=$2
shift 2 # Remove server type and model name from the argument list
echo "Starting $server_type server for model: $model_name"
echo "Additional arguments: $@"
if [ "$server_type" = "sglang" ]; then
python -m sglang.launch_server --port 30000 --model "$model_name" "$@" &
elif [ "$server_type" = "vllm" ]; then
vllm serve $model_name --port 30000 "$@" &
else
echo "Unsupported server type: $server_type"
exit 1
fi
SERVER_PID=$!
# Check if the server process is running
if ! kill -0 $SERVER_PID 2>/dev/null; then
echo "Failed to start server process. Exiting."
exit 1
fi
# Wait for the server to be ready by checking the models endpoint
echo "Waiting for server to be ready..."
max_attempts=300
attempt=0
while [ $attempt -lt $max_attempts ]; do
if curl -s "http://localhost:30000/v1/models" -o /dev/null -w "%{http_code}" | grep -q "200"; then
echo "Server is ready!"
return 0
fi
attempt=$((attempt + 1))
echo "Waiting for server... attempt $attempt/$max_attempts"
sleep 2
done
echo "Server failed to become ready after multiple attempts. Exiting."
kill $SERVER_PID
SERVER_PID=""
exit 1
}
# Function to stop the server
stop_server() {
echo "Stopping server with PID: $SERVER_PID"
if [ -n "$SERVER_PID" ] && kill -0 "$SERVER_PID" 2>/dev/null; then
kill $SERVER_PID
wait $SERVER_PID 2>/dev/null || true
echo "Server stopped."
else
echo "No server to stop."
fi
SERVER_PID=""
}
# Create and activate olmocr environment
create_conda_env "olmocr" "3.11"
source $(conda info --base)/etc/profile.d/conda.sh
source activate olmocr
# Run olmocr benchmarks, exactly as the pipeline.py does it
echo "Running olmocr benchmarks..."
python -m olmocr.bench.convert olmocr_pipeline --repeats 5
# Install marker-pdf and run benchmarks
echo "Installing marker-pdf and running benchmarks..."
pip install marker-pdf==1.6.1
python -m olmocr.bench.convert marker
# Install verovio and run benchmarks
# echo "Installing verovio and running benchmarks..."
# pip install verovio
# python -m olmocr.bench.convert gotocr
# Run chatgpt benchmarks
echo "Running chatgpt benchmarks..."
python -m olmocr.bench.convert chatgpt
#python -m olmocr.bench.convert chatgpt:name=chatgpt45:model=gpt-4.5-preview-2025-02-27
# Run gemini benchmarks
echo "Running gemini benchmarks..."
python -m olmocr.bench.convert gemini:name=gemini_flash2:model=gemini-2.0-flash --parallel 4
echo "Running mistral..."
pip install mistralai
python -m olmocr.bench.convert mistral
# Run raw server benchmarks with generic server function
# For each model, start server, run benchmark, then stop server
# Check port availability at script start
check_port || exit 1
# olmocr_base_temp0_1 using sglang server
start_server sglang "allenai/olmOCR-7B-0225-preview" --chat-template qwen2-vl --mem-fraction-static 0.7
python -m olmocr.bench.convert server:name=olmocr_base_temp0_1:model=allenai/olmOCR-7B-0225-preview:temperature=0.1:prompt_template=fine_tune:response_template=json --repeats 5 --parallel 20
python -m olmocr.bench.convert server:name=olmocr_base_temp0_8:model=allenai/olmOCR-7B-0225-preview:temperature=0.8:prompt_template=fine_tune:response_template=json --repeats 5 --parallel 20
stop_server
start_server vllm "allenai/olmOCR-7B-0225-preview"
python -m olmocr.bench.convert server:name=olmocr_base_vllm_temp0_1:model=allenai/olmOCR-7B-0225-preview:temperature=0.1:prompt_template=fine_tune:response_template=json --repeats 5 --parallel 20
python -m olmocr.bench.convert server:name=olmocr_base_vllm_temp0_8:model=allenai/olmOCR-7B-0225-preview:temperature=0.8:prompt_template=fine_tune:response_template=json --repeats 5 --parallel 20
stop_server
# Feel free to enable if you want
# qwen2_vl_7b using sglang server
# start_server sglang "Qwen/Qwen2-VL-7B-Instruct" --chat-template qwen2-vl --mem-fraction-static 0.7
# python -m olmocr.bench.convert server:name=qwen2_vl_7b:model=Qwen/Qwen2-VL-7B-Instruct:temperature=0.1:prompt_template=full:response_template=plain --repeats 5 --parallel 20
# stop_server
# TODO: qwen2.5 Not working right now in sglang
# qwen25_vl_7b
# create_conda_env "qwen25" "3.11"
# source activate qwen25
# pip install olmocr
# pip install "sglang[all]>=0.4.3.post2" --find-links https://flashinfer.ai/whl/cu124/torch2.5/flashinfer-python transformers==4.48.3
# start_server sglang "Qwen/Qwen2.5-VL-7B-Instruct" --chat-template qwen2-vl --mem-fraction-static 0.7
# python -m olmocr.bench.convert server:name=qwen25_vl_7b:model=Qwen/Qwen2.5-VL-7B-Instruct:temperature=0.1:prompt_template=full:response_template=plain --repeats 5 --parallel 20
# stop_server
# TODO: Fix this, I was not able to get it to all install successfully
# Create and activate mineru environment
# create_conda_env "mineru" "3.11"
# source activate mineru
# Install magic-pdf and run benchmarks
# echo "Installing magic-pdf and running mineru benchmarks..."
# pip install -U "magic-pdf[full]==1.2.2" --extra-index-url https://wheels.myhloli.com
# python -m pip install paddlepaddle==3.0.0rc1 -i https://www.paddlepaddle.org.cn/packages/stable/cpu/
# pip install huggingface_hub Pillow paddleocr ultralytics doclayout-yolo pycocotools
# wget https://github.com/opendatalab/MinerU/raw/master/scripts/download_models_hf.py -O download_models_hf.py
# python download_models_hf.py
# python -m olmocr.bench.convert mineru
# Final cleanup
if [ -n "$SERVER_PID" ] && kill -0 $SERVER_PID 2>/dev/null; then
stop_server
fi
echo "All benchmarks completed successfully."
#!/usr/bin/env python3
import argparse
import glob
import json
import os
import sys
from collections import defaultdict
from runners.run_chatgpt import run_chatgpt
from runners.run_gemini import run_gemini
from olmocr.data.renderpdf import render_pdf_to_base64png
def parse_rules_file(file_path):
"""Parse the rules file and organize rules by PDF."""
pdf_rules = defaultdict(list)
with open(file_path, "r") as f:
for line in f:
line = line.strip()
if not line:
continue
try:
rule = json.loads(line)
if "pdf" in rule:
pdf_rules[rule["pdf"]].append(rule)
except json.JSONDecodeError:
print(f"Warning: Could not parse line as JSON: {line}")
return pdf_rules
def get_model_outputs(pdf_path):
"""Get outputs from both models for a given PDF."""
try:
print(f"Attempting to process PDF: {pdf_path}")
print(f"File exists: {os.path.exists(pdf_path)}")
chatgpt_output = run_chatgpt(pdf_path)
gemini_output = run_gemini(pdf_path)
return chatgpt_output, gemini_output
except Exception as e:
print(f"Error getting model outputs for {pdf_path}: {str(e)}")
return f"Error: {str(e)}", f"Error: {str(e)}"
def find_pdfs_in_directory(directory):
"""Find all PDF files in the given directory."""
if not os.path.isdir(directory):
print(f"Warning: {directory} is not a directory.")
return []
pdf_files = []
for ext in ["pdf", "PDF"]:
pattern = os.path.join(directory, f"*.{ext}")
pdf_files.extend(glob.glob(pattern))
print(f"Found {len(pdf_files)} PDF files in {directory}")
for pdf in pdf_files:
print(f" - {pdf}")
return pdf_files
def generate_html(pdf_rules, rules_file_path, pdfs_to_process=None):
"""Generate the HTML page with PDF renderings and model outputs."""
pdf_paths = []
if pdfs_to_process:
for pdf_item in pdfs_to_process:
if os.path.isdir(pdf_item):
pdf_paths.extend(find_pdfs_in_directory(pdf_item))
elif os.path.isfile(pdf_item):
pdf_paths.append(pdf_item)
else:
print(f"Warning: {pdf_item} is neither a valid file nor directory")
else:
pdf_base_dir = os.path.join(os.path.dirname(rules_file_path), "pdfs")
pdf_paths = [os.path.join(pdf_base_dir, pdf_name) for pdf_name in list(pdf_rules.keys())[:10]]
pdf_paths = list(set(pdf_paths))
print("Processing the following PDFs:")
for path in pdf_paths:
print(f" - {path} (exists: {os.path.exists(path)})")
html = """
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>PDF Model Comparison</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 20px;
background-color: #f5f5f5;
}
.container {
max-width: 1800px;
margin: 0 auto;
}
h1 {
color: #333;
text-align: center;
margin-bottom: 30px;
}
.pdf-container {
background-color: white;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
margin-bottom: 30px;
overflow: hidden;
}
.pdf-header {
background-color: #4a6fa5;
color: white;
padding: 15px;
font-size: 18px;
font-weight: bold;
display: flex;
justify-content: space-between;
align-items: center;
}
.pdf-content {
display: flex;
flex-direction: row;
padding: 20px;
}
@media (max-width: 1200px) {
.pdf-content {
flex-direction: column;
}
}
.pdf-image {
flex: 0 0 30%;
max-width: 500px;
text-align: center;
padding-right: 20px;
}
.pdf-image img {
max-width: 100%;
height: auto;
border: 1px solid #ddd;
}
.models-container {
flex: 1;
display: flex;
flex-direction: column;
}
.model-outputs {
display: flex;
flex-direction: row;
margin-bottom: 20px;
}
.model-output {
flex: 1;
margin: 0 10px;
border: 1px solid #ddd;
border-radius: 5px;
overflow: hidden;
}
.model-header {
background-color: #4a6fa5;
color: white;
padding: 10px;
font-weight: bold;
text-align: center;
}
.model-content {
padding: 15px;
height: 400px;
overflow-y: auto;
white-space: pre-wrap;
font-family: monospace;
font-size: 14px;
background-color: #f8f9fa;
}
.difference-content {
padding: 15px;
height: 200px;
overflow-y: auto;
white-space: pre-wrap;
font-family: monospace;
font-size: 14px;
background-color: #f8f9fa;
display: none;
margin-top: 20px;
border: 1px solid #ddd;
border-radius: 5px;
}
.controls {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
padding: 10px;
background-color: #e9ecef;
border-radius: 5px;
}
.rating-controls {
display: flex;
flex-wrap: wrap;
gap: 10px;
}
button {
padding: 8px 15px;
border: none;
border-radius: 4px;
cursor: pointer;
font-weight: bold;
transition: background-color 0.2s;
}
button:hover {
opacity: 0.9;
}
.rating-btn {
flex: 1;
min-width: 120px;
}
.chatgpt-better {
background-color: #28a745;
color: white;
}
.gemini-better {
background-color: #dc3545;
color: white;
}
.both-good {
background-color: #17a2b8;
color: white;
}
.both-bad {
background-color: #6c757d;
color: white;
}
.invalid-pdf {
background-color: #343a40;
color: white;
}
.show-diff-btn {
background-color: #fd7e14;
color: white;
}
.highlight {
background-color: #ffff99;
}
.rating-indicator {
display: inline-block;
padding: 5px 10px;
border-radius: 4px;
color: white;
font-weight: bold;
margin-left: 10px;
}
.error {
color: #dc3545;
padding: 20px;
text-align: center;
border: 1px solid #dc3545;
border-radius: 5px;
margin: 20px;
}
</style>
</head>
<body>
<div class="container">
<h1>PDF Model Comparison</h1>
"""
for pdf_path in pdf_paths:
if not os.path.exists(pdf_path) or not os.path.isfile(pdf_path):
print(f"Skipping non-existent or non-file path: {pdf_path}")
continue
pdf_name = os.path.basename(pdf_path)
pdf_id = pdf_name.replace(".", "-")
try:
print(f"Rendering PDF: {pdf_path}")
base64_img = render_pdf_to_base64png(pdf_path, 0)
img_html = f'<img src="data:image/png;base64,{base64_img}" alt="{pdf_name}">'
except Exception as e:
print(f"Error rendering PDF: {str(e)}")
img_html = f'<div class="error">Error rendering PDF: {str(e)}</div>'
print(f"Getting model outputs for: {pdf_path}")
chatgpt_output, gemini_output = get_model_outputs(pdf_path)
html += f"""
<div class="pdf-container" id="pdf-{pdf_id}">
<div class="pdf-header">
<span>{pdf_name}</span>
<span class="rating-indicator" id="rating-{pdf_id}"></span>
</div>
<div class="pdf-content">
<div class="pdf-image">
{img_html}
</div>
<div class="models-container">
<div class="controls">
<div class="rating-controls">
<button class="rating-btn chatgpt-better" onclick="rateModel('{pdf_id}', 'chatgpt')">ChatGPT Better</button>
<button class="rating-btn gemini-better" onclick="rateModel('{pdf_id}', 'gemini')">Gemini Better</button>
<button class="rating-btn both-good" onclick="rateModel('{pdf_id}', 'both-good')">Both Good</button>
<button class="rating-btn both-bad" onclick="rateModel('{pdf_id}', 'both-bad')">Both Bad</button>
<button class="rating-btn invalid-pdf" onclick="rateModel('{pdf_id}', 'invalid')">Invalid PDF</button>
</div>
<button class="show-diff-btn" onclick="toggleDifference('{pdf_id}')">Show Differences</button>
</div>
<div class="model-outputs">
<div class="model-output">
<div class="model-header">ChatGPT Output</div>
<div class="model-content" id="chatgpt-{pdf_id}">{chatgpt_output}</div>
</div>
<div class="model-output">
<div class="model-header">Gemini Output</div>
<div class="model-content" id="gemini-{pdf_id}">{gemini_output}</div>
</div>
</div>
<div class="difference-content" id="diff-{pdf_id}">
<h3>Differences</h3>
<p>Loading differences...</p>
</div>
</div>
</div>
</div>
"""
html += """
</div>
<script>
// Store ratings
const ratings = {};
function rateModel(pdfId, rating) {
// Save rating
ratings[pdfId] = rating;
// Update visual indicator
const indicator = document.getElementById(`rating-${pdfId}`);
indicator.textContent = rating.replace('-', ' ').toUpperCase();
// Apply class matching the rating
indicator.className = 'rating-indicator ' + rating;
// Save ratings to localStorage
localStorage.setItem('pdf-model-ratings', JSON.stringify(ratings));
}
function toggleDifference(pdfId) {
const diffElement = document.getElementById(`diff-${pdfId}`);
const chatgptContent = document.getElementById(`chatgpt-${pdfId}`).textContent;
const geminiContent = document.getElementById(`gemini-${pdfId}`).textContent;
if (diffElement.style.display === 'none' || !diffElement.style.display) {
diffElement.style.display = 'block';
// If content is "Loading differences...", calculate differences
if (diffElement.textContent.includes("Loading differences...")) {
// Simple difference highlighting
const diffResult = findDifferences(chatgptContent, geminiContent);
diffElement.innerHTML = diffResult;
}
} else {
diffElement.style.display = 'none';
}
}
function findDifferences(text1, text2) {
// Split into sentences for comparison
const sentences1 = text1.split(/(?<=[.!?])\\s+/);
const sentences2 = text2.split(/(?<=[.!?])\\s+/);
let result = "<h4>ChatGPT unique content:</h4><ul>";
// Find sentences in text1 that aren't in text2
sentences1.forEach(sentence => {
if (sentence.trim().length > 10 && !text2.includes(sentence)) {
result += `<li><span class="highlight">${sentence}</span></li>`;
}
});
result += "</ul><h4>Gemini unique content:</h4><ul>";
// Find sentences in text2 that aren't in text1
sentences2.forEach(sentence => {
if (sentence.trim().length > 10 && !text1.includes(sentence)) {
result += `<li><span class="highlight">${sentence}</span></li>`;
}
});
result += "</ul>";
return result;
}
// Load saved ratings on page load
window.onload = function() {
const savedRatings = localStorage.getItem('pdf-model-ratings');
if (savedRatings) {
const parsedRatings = JSON.parse(savedRatings);
for (const pdfId in parsedRatings) {
rateModel(pdfId, parsedRatings[pdfId]);
}
}
};
</script>
</body>
</html>
"""
return html
def main():
parser = argparse.ArgumentParser(description="Generate an HTML visualization for comparing AI model outputs on PDFs.")
parser.add_argument("-r", "--rules_file", help="Rules file path", default="./sample_data/dataset.jsonl")
parser.add_argument("-o", "--output", help="Output HTML file path", default="pdf_model_comparison.html")
parser.add_argument("-p", "--pdfs", nargs="+", help="Specific PDF files or directories to process")
parser.add_argument("-l", "--limit", type=int, help="Limit the number of PDFs to process", default=10)
args = parser.parse_args()
if not os.path.exists(args.rules_file):
print(f"Error: Rules file not found: {args.rules_file}")
sys.exit(1)
if args.pdfs:
for pdf_path in args.pdfs:
if not os.path.exists(pdf_path):
print(f"WARNING: Path not found: {pdf_path}")
pdf_rules = parse_rules_file(args.rules_file)
html = generate_html(pdf_rules, args.rules_file, args.pdfs)
with open(args.output, "w") as f:
f.write(html)
print(f"HTML visualization created: {args.output}")
print("Open this file in a web browser to view and rate model outputs.")
if __name__ == "__main__":
main()
import os
from openai import OpenAI
from prompts import build_find_difference_prompt
from runners.run_chatgpt import run_chatgpt
from runners.run_gemini import run_gemini
from olmocr.data.renderpdf import render_pdf_to_base64png
def combined_output(pdf_path: str) -> str:
chatgpt_output = run_chatgpt(pdf_path)
gemini_output = run_gemini(pdf_path)
return f"ChatGPT OUTPUT: \n" f"{chatgpt_output}\n\n" f"Gemini OUTPUT: \n" f"{gemini_output}"
def run_difference(pdf_path: str, page_num: int = 1, model: str = "gpt-4o-2024-08-06", temperature: float = 0.1) -> str:
"""
Convert page of a PDF file to markdown using GPT.
This function renders the first page of the PDF to an image, runs OCR on that image,
and returns the OCR result as a markdown-formatted string.
Args:
pdf_path (str): The local path to the PDF file.
page_num (int): Which page from document to pass.
model (str): Model used to process.
Temperature (float): Temperature used while utilizing the model.
Returns:
str: The result in markdown format.
"""
# Convert the first page of the PDF to a base64-encoded PNG image.
image_base64 = render_pdf_to_base64png(pdf_path, page_num=page_num, target_longest_image_dim=2048)
anchor_text = combined_output(pdf_path)
client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
response = client.chat.completions.create(
model=model,
messages=[
{
"role": "user",
"content": [
{"type": "text", "text": build_find_difference_prompt(anchor_text)},
{"type": "image_url", "image_url": {"url": f"data:image/png;base64,{image_base64}"}},
],
}
],
temperature=temperature,
max_tokens=3000,
)
raw_response = response.choices[0].message.content
return raw_response
#!/usr/bin/env python3
import os
import asyncio
from pathlib import Path
from playwright.async_api import async_playwright
# Simple configuration
CONFIG = {
"input_file": os.path.join(os.path.dirname(__file__), "templates", "listpage.js"), # React component file
"output_pdf": "book-page.pdf", # Output PDF filename
"temp_html": "temp-render.html", # Temporary HTML file
"wait_time": 1500, # Time to wait for rendering (ms)
"device_scale": 2, # Resolution multiplier
"debug": True # Keep temp files for debugging
}
async def create_html_file():
"""Create a temporary HTML file that loads the React component from a file."""
try:
# Check if input file exists
input_path = Path(CONFIG["input_file"])
if not input_path.exists():
print(f"Error: Input file '{input_path}' not found")
return False
# Read the component file
with open(input_path, 'r', encoding='utf-8') as f:
component_code = f.read()
# Create HTML that will load our component
html_content = """
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Book Page Template</title>
<script src="https://unpkg.com/react@17/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<style>
* {
box-sizing: border-box;
}
html, body {
margin: 0;
padding: 0;
width: 8.5in;
height: 11in;
overflow: hidden;
}
#root {
width: 100%;
height: 100%;
padding: 0.25in;
overflow: hidden;
}
@media print {
body {
-webkit-print-color-adjust: exact;
print-color-adjust: exact;
}
}
</style>
</head>
<body>
<div id="root"></div>
<script type="text/babel">
// The React component code loaded from external file
""" + component_code + """
// Render only the book page part, not the controls
ReactDOM.render(
<BookPageTemplate />,
document.getElementById('root')
);
</script>
</body>
</html>
"""
with open(CONFIG["temp_html"], 'w', encoding='utf-8') as f:
f.write(html_content)
print(f"Created HTML file: {CONFIG['temp_html']}")
print(f"Using React component from: {CONFIG['input_file']}")
return True
except Exception as e:
print(f"Error creating HTML file: {e}")
print(f"Exception details: {str(e)}")
import traceback
traceback.print_exc()
return False
async def render_to_pdf():
"""Render the React component to PDF using Playwright."""
try:
# Create the HTML file first
html_created = await create_html_file()
if not html_created:
print("Failed to create HTML file")
return
print("Launching browser...")
async with async_playwright() as p:
# Launch the browser with more debugging options
browser = await p.chromium.launch(
headless=True, # True for production, False for debugging
)
# Create a new page for letter size paper
page = await browser.new_page(
viewport={"width": 816, "height": 1056}, # 8.5in x 11in at 96dpi
device_scale_factor=CONFIG["device_scale"]
)
# Get absolute path to HTML file
html_path = Path(CONFIG["temp_html"]).absolute()
html_uri = f"file://{html_path}"
print(f"Navigating to: {html_uri}")
# Add event listeners for console messages and errors
page.on("console", lambda msg: print(f"Browser console: {msg.text}"))
page.on("pageerror", lambda err: print(f"Browser page error: {err}"))
# Navigate with longer timeout and wait for network idle
await page.goto(html_uri, wait_until="networkidle", timeout=30000)
# Wait for React to render
await page.wait_for_timeout(CONFIG["wait_time"])
# Add a check to ensure the component rendered
element_count = await page.evaluate("""() => {
const root = document.getElementById('root');
return root.childElementCount;
}""")
if element_count == 0:
print("Warning: No elements found in root. Component may not have rendered.")
else:
print(f"Found {element_count} elements in root. Component rendered successfully.")
# Save debug screenshot
if CONFIG["debug"]:
await page.screenshot(path="debug-screenshot.png")
print("Debug screenshot saved")
# Generate PDF
print("Generating PDF...")
await page.pdf(
path=CONFIG["output_pdf"],
format="Letter",
print_background=True,
margin={"top": "0", "right": "0", "bottom": "0", "left": "0"}
)
print(f"PDF generated successfully: {CONFIG['output_pdf']}")
# Close the browser
await browser.close()
# Cleanup temp files if not in debug mode
if not CONFIG["debug"] and Path(CONFIG["temp_html"]).exists():
Path(CONFIG["temp_html"]).unlink()
print("Temporary HTML file removed")
except Exception as e:
print(f"Error generating PDF: {e}")
if __name__ == "__main__":
# Run the async function
try:
asyncio.run(render_to_pdf())
except Exception as e:
print(f"Fatal error: {e}")
import traceback
traceback.print_exc()
\ No newline at end of file
//import React from 'react';
const BookPageTemplate = () => {
// Only three state variables as requested
const [title, setTitle] = React.useState("ADVENTURES OF DON QUIXOTE");
const [pageNumber, setPageNumber] = React.useState("289");
const [text, setText] = React.useState(
"deed,\" said Don Quixote, \"thou hast hit the point, Sancho, which can alone shake my resolution; I neither can, nor ought to, draw my sword, as I have often told thee, against those who are not dubbed knights. To thee which I had premeditated, thy share of the booty would have been at least the emperor's crown of gold and Cupid's painted wings; for I would have plucked them off perforce, and delivered them into thy hands.\" \"The"
);
// Styles for heavily degraded scan effect
const heavilyDegradedStyles = {
filter: 'grayscale(30%) contrast(120%) brightness(85%) sepia(20%)',
position: 'relative',
backgroundColor: '#e6ddc6', // More yellowed aged paper
backgroundImage: 'url("data:image/svg+xml,%3Csvg viewBox=\'0 0 200 200\' xmlns=\'http://www.w3.org/2000/svg\'%3E%3Cfilter id=\'noiseFilter\'%3E%3CfeTurbulence type=\'fractalNoise\' baseFrequency=\'0.85\' numOctaves=\'3\' stitchTiles=\'stitch\'/%3E%3C/filter%3E%3Crect width=\'100%25\' height=\'100%25\' filter=\'url(%23noiseFilter)\' opacity=\'0.25\'/%3E%3C/svg%3E")',
boxShadow: 'inset 0 0 70px rgba(0, 0, 0, 0.3), 0 0 5px rgba(0,0,0,0.1)',
padding: '32px',
borderRadius: '2px',
overflow: 'hidden',
transform: 'rotate(0.3deg)', // Slightly askew scan
};
// Heavily degraded text
const badScanTextStyle = {
fontFamily: '"Times New Roman", serif',
letterSpacing: '-0.01em',
wordSpacing: '0.02em',
fontWeight: '500',
color: '#222222',
textShadow: '0 0 1px rgba(0, 0, 0, 0.5)',
transform: 'scale(1.01, 0.99) rotate(-0.4deg)', // Distorted proportions
};
// Random coffee stain effect
const coffeeStain = {
position: 'absolute',
width: '100px',
height: '80px',
top: '25%',
right: '15%',
borderRadius: '50%',
background: 'radial-gradient(ellipse at center, rgba(139,69,19,0.15) 0%, rgba(139,69,19,0.1) 50%, rgba(139,69,19,0.05) 70%, rgba(139,69,19,0) 100%)',
transform: 'rotate(30deg) scale(1.5, 1)',
pointerEvents: 'none',
zIndex: 1,
};
// Water damage effect
const waterDamage = {
position: 'absolute',
width: '70%',
height: '40%',
bottom: '10%',
left: '5%',
opacity: 0.07,
background: 'radial-gradient(ellipse at center, rgba(0,0,0,0.2) 0%, rgba(0,0,0,0.1) 40%, rgba(0,0,0,0) 70%)',
borderRadius: '40% 60% 70% 30% / 40% 50% 60% 50%',
pointerEvents: 'none',
zIndex: 1,
};
// Add fold lines
const foldLine = {
position: 'absolute',
width: '100%',
height: '3px',
top: '30%',
left: 0,
background: 'linear-gradient(to right, rgba(0,0,0,0) 0%, rgba(0,0,0,0.03) 20%, rgba(0,0,0,0.08) 50%, rgba(0,0,0,0.03) 80%, rgba(0,0,0,0) 100%)',
boxShadow: '0 1px 3px rgba(255,255,255,0.2)',
pointerEvents: 'none',
zIndex: 2,
};
// Torn edge effect
const tornEdge = {
position: 'absolute',
top: 0,
right: 0,
width: '100%',
height: '100%',
background: 'linear-gradient(135deg, transparent 97%, #e6ddc6 97%, #e6ddc6 100%)',
pointerEvents: 'none',
};
return (
<div style={{
maxWidth: '800px',
margin: '0 auto',
padding: '16px',
}}>
{/* Heavily degraded scan container */}
<div style={heavilyDegradedStyles}>
{/* Noise overlay */}
<div style={{
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
background: 'url("data:image/png;base64,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") repeat',
opacity: 0.15,
pointerEvents: 'none',
}}></div>
{/* Scan lines effect */}
<div style={{
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
background: 'linear-gradient(to bottom, rgba(0,0,0,0.03) 1px, transparent 1px)',
backgroundSize: '100% 2px',
opacity: 0.5,
pointerEvents: 'none',
}}></div>
{/* Add coffee stain */}
<div style={coffeeStain}></div>
{/* Add water damage */}
<div style={waterDamage}></div>
{/* Add fold line */}
<div style={foldLine}></div>
{/* Add torn edge */}
<div style={tornEdge}></div>
{/* Header with skewed alignment */}
<div style={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
borderBottom: '2px solid #000',
paddingBottom: '4px',
marginBottom: '24px',
position: 'relative',
opacity: 0.8,
transform: 'skew(-0.5deg, 0.3deg)',
}}>
<div style={{width: '48px'}}></div>
<h1 style={{
...badScanTextStyle,
fontSize: '20px',
fontWeight: 'bold',
textAlign: 'center',
textTransform: 'uppercase',
letterSpacing: '1px',
opacity: 0.8,
}}>{title}</h1>
<div style={{
...badScanTextStyle,
fontSize: '20px',
fontWeight: 'bold',
opacity: 0.85,
}}>{pageNumber}</div>
</div>
{/* Horizontal divider with uneven quality */}
<div style={{
borderBottom: '1px solid #444',
marginBottom: '24px',
opacity: 0.6,
filter: 'blur(0.3px)',
transform: 'scaleY(1.5) skew(0.7deg)',
}}></div>
{/* Text content with severely degraded appearance */}
<div style={{
columnCount: 2,
columnGap: '20px',
columnRule: '1px solid rgba(0,0,0,0.1)',
textAlign: 'justify',
...badScanTextStyle,
fontSize: '16px',
lineHeight: '1.5',
opacity: 0.78,
// Very uneven ink distribution with blurry and faded parts
WebkitMaskImage: 'linear-gradient(to bottom, rgba(0,0,0,0.9), rgba(0,0,0,0.75) 50%, rgba(0,0,0,0.85))',
// Text distortion
filter: 'blur(0.2px)',
}}>
{/* Bad scan text with random character fading */}
<p>{text.split('').map((char, index) => {
const opacity = Math.random() > 0.8 ? 0.4 + Math.random() * 0.5 : 0.9 + Math.random() * 0.1;
const blur = Math.random() > 0.95 ? 1 : 0;
return <span key={index} style={{opacity, filter: `blur(${blur}px)`}}>{char}</span>;
})}</p>
</div>
{/* Extra random ink spill */}
<div style={{
position: 'absolute',
width: '10px',
height: '20px',
top: '60%',
left: '25%',
background: 'rgba(0,0,0,0.3)',
borderRadius: '50%',
transform: 'rotate(30deg)',
filter: 'blur(1px)',
zIndex: 3,
}}></div>
</div>
</div>
);
};
//export default BookPageTemplate;
window.BookPageTemplate = BookPageTemplate;
\ No newline at end of file
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment