handyhome-ocr-api / analyze_document.py
takomattyy's picture
Upload 2 files
fd1f4fc verified
#!/usr/bin/env python3
"""
Document Authenticity Analysis Script
Purpose:
Performs forensic analysis on document images to detect tampering and verify authenticity.
Uses Error Level Analysis (ELA) and metadata examination techniques.
Why this script exists:
- Need to verify document authenticity in verification workflows
- Detect potential image tampering or editing
- Provide forensic evidence for document verification
- Support fraud prevention in document processing systems
Key Features:
- Error Level Analysis (ELA) for tampering detection
- EXIF metadata analysis for editing software detection
- Brightness ratio analysis for tampering assessment
- Support for single document analysis
Dependencies:
- Pillow (PIL): Image processing (https://pillow.readthedocs.io/)
- exifread: EXIF metadata extraction (https://pypi.org/project/ExifRead/)
- numpy: Numerical operations (https://numpy.org/doc/)
- requests: HTTP library (https://docs.python-requests.org/)
Usage:
python analyze_document.py "https://example.com/document.jpg"
Output:
JSON with tampering and metadata analysis results
"""
import sys, json, requests, exifread,numpy as np
from PIL import Image, ImageChops, ImageEnhance
from io import BytesIO
import uuid, os
# List of photo editing software to detect in metadata
# This helps identify if an image has been edited
software_list = ['photoshop', 'lightroom', 'gimp', 'paint', 'paint.net', 'paintshop pro', 'paintshop pro x', 'paintshop pro x2', 'paintshop pro x3', 'paintshop pro x4', 'paintshop pro x5', 'paintshop pro x6', 'paintshop pro x7', 'paintshop pro x8', 'paintshop pro x9', 'paintshop pro x10']
def download_image(url, output_path='temp_image.jpg'):
"""
Download image from URL and process it for analysis.
Args:
url (str): Image URL
output_path (str): Local save path
Returns:
str: Path to processed image or None if failed
Why this approach:
- Handles JSON-wrapped URLs (common in web applications)
- Converts RGBA to RGB for JPEG compatibility
- Provides detailed error handling and logging
- Uses high quality to preserve analysis accuracy
"""
print(f"DOCUMENT Starting download for URL: {url}", file=sys.stderr)
# Handle URL that might be a JSON string
if isinstance(url, str) and (url.startswith('{') or url.startswith('[')):
try:
url_data = json.loads(url)
print(f"DOCUMENT Parsed JSON URL data: {url_data}", file=sys.stderr)
if isinstance(url_data, dict) and 'url' in url_data:
url = url_data['url']
print(f"DOCUMENT Extracted URL from dict: {url}", file=sys.stderr)
elif isinstance(url_data, list) and len(url_data) > 0:
url = url_data[0] if isinstance(url_data[0], str) else url_data[0].get('url', '')
print(f"DOCUMENT Extracted URL from list: {url}", file=sys.stderr)
except json.JSONDecodeError as e:
print(f"DOCUMENT Error parsing URL JSON: {url}, Error: {str(e)}", file=sys.stderr)
return None
if not url or url == '':
print(f"DOCUMENT Empty URL after processing", file=sys.stderr)
return None
try:
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
}
response = requests.get(url, headers=headers, timeout=30)
response.raise_for_status()
image_data = response.content
print(f"DOCUMENT Downloaded image from {url}", file=sys.stderr)
print(f"DOCUMENT Image data size: {len(image_data)} bytes", file=sys.stderr)
# Process the image
image = Image.open(BytesIO(image_data))
print(f"DOCUMENT Original image format: {image.format}", file=sys.stderr)
print(f"DOCUMENT Original image mode: {image.mode}", file=sys.stderr)
# Convert to RGB if necessary (JPEG doesn't support alpha channel)
if image.mode == 'RGBA':
background = Image.new('RGB', image.size, (255, 255, 255))
background.paste(image, mask=image.split()[-1])
image = background
print(f"DOCUMENT Converted RGBA to RGB", file=sys.stderr)
elif image.mode != 'RGB':
image = image.convert('RGB')
print(f"DOCUMENT Converted {image.mode} to RGB", file=sys.stderr)
# Save as JPG without trying to preserve EXIF
image.save(output_path, 'JPEG', quality=95)
print(f"DOCUMENT Saved image to {output_path}", file=sys.stderr)
return output_path
except Exception as e:
print(f"DOCUMENT Error processing image: {str(e)}", file=sys.stderr)
return None
# Tampering Function
def perform_error_level_analysis(image_path, output_path="ELA.png", quality=90):
"""
Perform Error Level Analysis (ELA) on image.
Args:
image_path (str): Path to original image
output_path (str): Path to save ELA result
quality (int): JPEG quality for recompression
Returns:
str: Path to ELA result image
Why ELA:
- Detects areas of image that have been edited or tampered with
- Works by comparing original image with recompressed version
- Edited areas show different compression artifacts
- Standard forensic technique for image authenticity
"""
print(f"DOCUMENT TAMPERING Starting Error Level Analysis...", file=sys.stderr)
try:
original_image = Image.open(image_path)
# Convert RGBA to RGB if necessary (JPEG doesn't support alpha channel)
if original_image.mode == 'RGBA':
# Create a white background
background = Image.new('RGB', original_image.size, (255, 255, 255))
# Paste the image onto the background
background.paste(original_image, mask=original_image.split()[-1]) # Use alpha channel as mask
original_image = background
elif original_image.mode != 'RGB':
original_image = original_image.convert('RGB')
# Use a unique temp file name to avoid conflicts
temp_file = f"temp_ela_{str(uuid.uuid4())[:8]}.jpg"
original_image.save(temp_file, "JPEG", quality=quality)
resaved_image = Image.open(temp_file)
difference = ImageChops.difference(original_image, resaved_image)
difference = ImageEnhance.Brightness(difference).enhance(10)
# Save the difference image
difference.save(output_path, format='PNG')
# Clean up temp file
try:
os.remove(temp_file)
except:
pass
print(f"DOCUMENT TAMPERING ELA analysis completed, saved to {output_path}", file=sys.stderr)
return output_path
except Exception as e:
print(f"DOCUMENT TAMPERING Error during ELA: {str(e)}", file=sys.stderr)
return None
def detect_tampering(ela_path, threshold=100):
"""
Analyze ELA results to detect tampering.
Args:
ela_path (str): Path to ELA result image
threshold (int): Brightness threshold for tampering detection
Returns:
dict: Tampering analysis results
Why this approach:
- Analyzes brightness distribution in ELA image
- High brightness indicates areas of potential tampering
- Uses statistical thresholds for tampering assessment
- Provides confidence levels for tampering detection
"""
print(f"DOCUMENT TAMPERING Analyzing ELA results...", file=sys.stderr)
ela_image = Image.open(ela_path)
ela_array = np.array(ela_image)
bright_pixels = np.sum(ela_array > threshold)
total_pixels = ela_array.size
brightness_ratio = bright_pixels / total_pixels
print(f"DOCUMENT TAMPERING Brightness ratio: {brightness_ratio:.4f}", file=sys.stderr)
if brightness_ratio < 0.02:
return {
"tampered": "False",
"brightness_ratio": round(float(brightness_ratio), 4)
}
elif brightness_ratio > 0.02 and brightness_ratio <= 0.05:
return {
"tampered": "Investigation Required",
"brightness_ratio": round(float(brightness_ratio), 4)
}
elif brightness_ratio > 0.05:
return {
"tampered": "True",
"brightness_ratio": round(float(brightness_ratio), 4)
}
# Metadata Analysis Function (TO BE TESTED IN MOBILE)
def analyze_metadata(image_path):
"""
Analyze EXIF metadata for editing software and authenticity indicators.
Args:
image_path (str): Path to image file
Returns:
dict: Metadata analysis results
Why this approach:
- EXIF metadata contains information about image creation and editing
- Detects photo editing software used
- Identifies camera information and timestamps
- Provides forensic evidence for image authenticity
"""
print(f"DOCUMENT METADATA Starting metadata analysis...", file=sys.stderr)
try:
with open(image_path, 'rb') as f:
tags = exifread.process_file(f)
metadata = {tag: str(tags.get(tag)) for tag in tags.keys()}
# Debug: Print what we found
print(f"DOCUMENT METADATA Found {len(metadata)} metadata tags", file=sys.stderr)
if metadata:
print(f"DOCUMENT METADATA Metadata keys: {list(metadata.keys())}", file=sys.stderr)
except Exception as e:
print(f"DOCUMENT METADATA Error reading metadata: {str(e)}", file=sys.stderr)
return {
"result": "error",
"message": f"Error reading metadata: {str(e)}",
"metadata": {}
}
if not metadata:
print(f"DOCUMENT METADATA No metadata found in {image_path}", file=sys.stderr)
return {
"result": "no metadata",
"message": "No metadata found in image.",
"metadata": metadata
}
# Check for timestamp metadata
has_timestamp = any(key in metadata for key in ['EXIF DateTimeOriginal', 'Image DateTime', 'EXIF DateTime'])
# Check for editing software
software_used = metadata.get('Image Software', '').lower()
# Check for camera information
has_camera_info = any(key in metadata for key in ['Image Make', 'Image Model', 'EXIF Make', 'EXIF Model'])
# Provide more detailed analysis
analysis_summary = []
if has_timestamp:
analysis_summary.append("Timestamp found")
if has_camera_info:
analysis_summary.append("Camera information found")
if software_used:
analysis_summary.append(f"Software detected: {software_used}")
if not has_timestamp and not has_camera_info and not software_used:
return {
"result": "minimal metadata",
"message": "Image contains minimal metadata (no timestamp, camera info, or editing software detected)",
"metadata": metadata,
"analysis": "Image appears to be legitimate with no signs of editing"
}
if any(term in software_used for term in software_list):
return {
"result": "edited",
"message": f"Image appears to have been edited with software: {software_used}",
"metadata": metadata,
"analysis": "Suspicious editing software detected"
}
return {
"result": "success",
"message": f"Successfully analyzed metadata: {', '.join(analysis_summary)}",
"metadata": metadata,
"analysis": "Image appears to be legitimate with no signs of editing"
}
# Main execution
if __name__ == "__main__":
# Validate command line arguments
if len(sys.argv) < 2:
print(json.dumps({"error": "No image URL provided"}))
sys.exit(1)
image_url = sys.argv[1]
try:
# Download and process the image
image_path = download_image(image_url)
if not image_path:
raise Exception("Failed to download image")
# Perform metadata analysis first (before any file gets deleted)
metadata_results = analyze_metadata(image_path)
# Perform ELA and tampering detection
ela_path = perform_error_level_analysis(image_path)
if not ela_path:
raise Exception("Failed to perform error level analysis")
tampering_results = detect_tampering(ela_path)
# Clean up files after all analysis is done
try:
if ela_path:
os.remove(ela_path)
if image_path:
os.remove(image_path)
except:
pass
# Return combined results
print(json.dumps({
"success": True,
"tampering_results": tampering_results,
"metadata_results": metadata_results
}))
except Exception as e:
print(json.dumps({
"success": False,
"error": str(e),
"context": {
"url": image_url
}
}))
sys.exit(1)