Measure planetary radii from horizon photographs using computer vision and geometry
Project description
PLANET RULER
Measure planetary radii with nothing but a camera and science!
Got a horizon photo? Measure your planet in 3 lines of code!
import planet_ruler as pr
obs = pr.LimbObservation("horizon_photo.jpg", "config/camera.yaml")
obs.detect_limb().fit_limb() # → Planet radius: 6,234 km
|
Ground Level ~100 ft Horizon appears flat |
Commerical Aircraft ~35,000 ft Perceptible curvature |
International Space Station ~250 miles Dramatic spherical curvature |
Quick Start
Installation
From PyPI (recommended):
pip install planet-ruler
The package name is planet-ruler (with hyphen), but you import it with an underscore:
import planet_ruler as pr # Import uses underscore
Optional dependencies:
For ML segmentation:
pip install planet-ruler[ml]
For Jupyter notebooks:
pip install planet-ruler[jupyter]
For everything:
pip install planet-ruler[all]
From source (development):
git clone https://github.com/bogsdarking/planet_ruler.git
cd planet_ruler
pip install -e .
After installation, the command-line tool is available:
planet-ruler --help
Python API
import planet_ruler as pr
# Basic analysis
obs = pr.LimbObservation("photo.jpg", "camera_config.yaml")
# Choose detection method:
obs.detect_limb(method='manual') # Interactive GUI (default)
# obs.detect_limb(method='gradient-break') # Simple gradient-based detection
# obs.detect_limb(method='gradient-field') # Gradient flow analysis
# obs.detect_limb(method='segmentation') # AI-powered (requires PyTorch)
# OR skip detection and use gradient-field optimization directly:
# obs.fit_limb(loss_function='gradient_field') # Direct gradient optimization
obs.fit_limb() # Multi-resolution parameter optimization
obs.plot() # Visualize results
# Access results with uncertainty
print(f"Radius: {obs.best_parameters['r']/1000:.0f} ± {obs.radius_uncertainty:.0f} km")
Command Line
# Measure planetary radius using existing config file
planet-ruler measure photo.jpg --camera-config camera_config.yaml
# Auto-generate config from image EXIF data (requires altitude)
planet-ruler measure photo.jpg --auto-config --altitude 10668
# Auto-config with specific planet (affects initial radius guess)
planet-ruler measure photo.jpg --auto-config --altitude 10668 --planet mars
# Choose detection method (manual, gradient-break, gradient-field, or segmentation)
planet-ruler measure photo.jpg --auto-config --altitude 10668 --detection-method gradient-field
# Try built-in examples
planet-ruler demo --planet earth
The Science Behind It
The Problem: How big is your planet?
The Solution: Depending on your altitude, planetary curvature becomes visible in the horizon. By measuring this curvature and accounting for your camera, we can reverse-engineer the planet's size.
How It Works (Click to expand)
- Capture: Photograph showing horizon/limb from altitude
- Detect: Choose your detection method:
- Manual: Interactive GUI for precise point selection (default, no dependencies)
- Gradient-field: Automated detection using gradient flow analysis
- Segmentation: AI-powered detection (requires PyTorch + Segment Anything)
- Measure: Extract curvature from the detected horizon
- Model: Account for camera optics, altitude, and viewing geometry
- Optimize: Fit theoretical curves to observations using multi-resolution optimization
- Uncertainty: Quantify measurement precision using population spread, Hessian approximation, or profile likelihood
Mathematical Foundation:
- Spherical geometry and horizon distance calculations
- Camera intrinsic/extrinsic parameter modeling
- Gradient-field analysis with directional blur and flux integration
- Multi-resolution optimization with coarse-to-fine refinement
- Non-linear optimization with robust error handling
Real Results
Validated on actual space mission data:
| Planet | Source | Estimated | True Value | Error |
|---|---|---|---|---|
| Earth | ISS Photo | 5,516 km | 6,371 km | 13.4% |
| Saturn | Cassini | 65,402 km | 58,232 km | 12.3% |
| Pluto | New Horizons | 1,432 km | 1,188 km | 20.6% |
Key Features
|
Automatic Camera Detection
Flexible Detection Methods
Advanced Camera Models
Multi-Planetary Support
|
Scientific Rigor
Live Progress Dashboard
Rich Visualizations
Multiple Interfaces
Works with Any Camera
|
Installation & Setup
Requirements
- Python 3.8+
- RAM: 4GB+ recommended (for AI models)
- Storage: ~2GB for full installation with models
Install Options
Quick Start (Recommended)
# Clone and install in one go
git clone https://github.com/bogsdarking/planet_ruler.git
cd planet_ruler
python -m pip install -e .
# Verify installation
planet-ruler --help
python -c "import planet_ruler; print('Ready to measure planets!')"
Minimal Install (Core features only)
git clone https://github.com/bogsdarking/planet_ruler.git
cd planet_ruler
# Install without heavy AI dependencies
python -m pip install -e . --no-deps
python -m pip install numpy scipy matplotlib pillow pyyaml pandas tqdm seaborn
# Note: Manual horizon detection required without segment-anything
Development Install
git clone https://github.com/bogsdarking/planet_ruler.git
cd planet_ruler
# Full development environment
python -m pip install -e .
python -m pip install -r requirements.txt
python -m pip install -r requirements-test.txt
# Run tests to verify
pytest tests/ -v
Troubleshooting
- Segment Anything issues? See installation guide
- M1 Mac problems? Use conda for better compatibility
- Memory errors? Try the minimal install option
Try It Now
Zero-Configuration Workflow
# Just need your photo and altitude - planet_ruler handles the rest!
from planet_ruler.camera import create_config_from_image
import planet_ruler as pr
# Step 1: Auto-detect camera parameters
config = create_config_from_image("my_photo.jpg", altitude_m=10668)
# Step 2: Measure the planet
obs = pr.LimbObservation("my_photo.jpg", config)
obs.detect_limb().fit_limb()
print(f"Your planet's radius: {obs.best_parameters['r']/1000:.0f} km")
Interactive Demo
# Launch interactive widget with examples (in Jupyter notebook)
from planet_ruler.demo import make_dropdown, load_demo_parameters
demo = make_dropdown() # Choose Earth, Saturn, or Pluto
params = load_demo_parameters(demo)
Use Your Own Photo
Option 1: Auto-Config
import planet_ruler as pr
from planet_ruler.camera import create_config_from_image
# Auto-generate config from image EXIF data
config = create_config_from_image("your_photo.jpg", altitude_m=10668, planet="earth")
# Use the auto-generated config
obs = pr.LimbObservation("your_photo.jpg", config)
obs.detect_limb()
obs.fit_limb()
print(f"Detected camera: {config['camera_info']['camera_model']}")
print(f"Fitted radius: {obs.best_parameters['r']/1000:.0f} km")
Option 2: Manual Config File
import planet_ruler as pr
# Requires camera configuration file
obs = pr.LimbObservation(
"your_photo.jpg",
"config/your_camera.yaml"
)
# Choose detection method: 'manual', 'gradient-break', 'gradient-field', or 'segmentation'
obs.detect_limb(method='manual') # Interactive GUI detection
# obs.detect_limb(method='gradient-break') # Simple gradient detection
# obs.detect_limb(method='gradient-field') # Gradient flow analysis
# OR use gradient-field optimization (skips traditional detection):
# obs.fit_limb(loss_function='gradient_field') # Direct gradient optimization
obs.detect_limb()
obs.fit_limb()
print(f"Fitted radius: {obs.best_parameters['r']/1000:.0f} km")
Camera Configuration Template
# config/your_camera.yaml
description: "Your camera setup"
free_parameters:
- r # planetary radius
- h # altitude
- f # focal length
- w # sensor width
init_parameter_values:
r: 6371000 # Earth radius in meters
h: 400000 # Altitude in meters
f: 0.05 # Focal length in meters
w: 0.035 # Sensor width in meters
parameter_limits:
r: [1000000, 20000000]
h: [100000, 1000000]
f: [0.01, 0.2]
w: [0.01, 0.1]
Usage Examples
Example 1: Smartphone Photo with Auto-Config
import planet_ruler as pr
from planet_ruler.camera import create_config_from_image
# Your iPhone/Android photo with horizon - just need altitude!
config = create_config_from_image(
"airplane_window_photo.jpg",
altitude_m=10668, # 35,000 feet in meters
planet="earth"
)
print(f"Detected: {config['camera_info']['camera_model']}")
# -> "iPhone 14 Pro" (or your camera model)
# Measure the planet
obs = pr.LimbObservation("airplane_window_photo.jpg", config)
obs.detect_limb()
obs.fit_limb()
obs.plot()
print(f"Earth radius: {obs.best_parameters['r']/1000:.0f} km")
Example 2: Command Line with Auto-Config
# Simple one-liner with any camera!
planet-ruler measure my_photo.jpg --auto-config --altitude 10668
# With specific planet and detection method
planet-ruler measure mars_photo.jpg --auto-config --altitude 4500 --planet mars --detection-method gradient-field
# Override auto-detected parameters if needed
planet-ruler measure photo.jpg --auto-config --altitude 10668 --focal-length 50
Example 3: Earth from ISS (Traditional Config)
import planet_ruler as pr
# Load ISS Earth photo with manual configuration
obs = pr.LimbObservation(
"demo/images/iss064e002941.jpg",
"config/earth_iss_1.yaml"
)
# Full analysis pipeline
obs.detect_limb(method='gradient-break') # Automated gradient-based detection
obs.fit_limb() # Multi-resolution parameter optimization
# OR use gradient-field optimization (more advanced):
# obs.fit_limb(loss_function='gradient_field', resolution_stages='auto')
obs.plot() # Visualize results
# Results
print(f"Best fit parameters: {obs.best_parameters}")
print(f"Planetary radius (r): {obs.best_parameters['r']/1000:.0f} km")
Example 4: Saturn from Cassini
# Analyze Saturn's limb from Cassini spacecraft
obs = pr.LimbObservation(
"demo/images/saturn_pia21341-1041.jpg",
"config/saturn-cassini-1.yaml"
)
# Two-step analysis
obs.detect_limb(method='gradient-break') # Automated detection
obs.fit_limb() # Multi-resolution fitting
# OR single-step gradient-field optimization:
# obs.fit_limb(loss_function='gradient_field', resolution_stages='auto')
# Rich visualization
from planet_ruler.plot import plot_3d_solution
plot_3d_solution(**obs.best_parameters) # 3D planetary geometry view
Documentation & Resources
Learning Resources
| Resource | Description | Best For |
|---|---|---|
| Interactive Tutorial | Complete walkthrough with examples | First-time users |
| API Documentation | Detailed function reference | Developers |
| Camera Setup Guide | Configuration examples | Custom setups |
| Example Gallery | Real space mission results | Inspiration |
Quick References
# Core classes and functions
pr.LimbObservation(image_path, fit_config) # Main analysis class
pr.geometry.horizon_distance(altitude, radius) # Theoretical calculations
pr.fit.optimize_parameters(obs, method='differential_evolution') # Optimization
pr.uncertainty.calculate_parameter_uncertainty(obs, 'r') # Uncertainty estimation
pr.plot.show_analysis(obs, style='comprehensive') # Visualization
# Key methods
obs.detect_limb(method='manual') # Interactive detection (default)
obs.fit_limb(resolution_stages='auto') # Multi-resolution optimization
obs.plot() # Show results with uncertainty
# Advanced gradient-field optimization:
# obs.fit_limb(loss_function='gradient_field', resolution_stages='auto')
Use Cases & Applications
- Astronomy courses: Demonstrate planetary geometry concepts
- Computer vision: Real-world optimization and AI applications
- Mathematics: Applied geometry and curve-fitting examples
- Physics: Observational techniques and measurement uncertainty
Limitations & Best Practices
Accuracy Expectations
- Typical accuracy: ~20%
- Best case: ~15% with optimal conditions and camera calibration (so far!)
- Factors affecting precision: Image quality, horizon clarity, altitude, camera specs
Technical Limitations
- Optimization challenges: Complex parameter space → potential local minima (mitigated by multi-resolution optimization)
- Detection method trade-offs: Manual (precise, time-intensive), gradient-field (automated, works for clear horizons), AI segmentation (most versatile, requires PyTorch)
- Computational cost: Multi-resolution optimization can be slow on older hardware
Best Practices
- Image quality: Sharp, high-resolution horizons work best
- Altitude: Higher = more curvature = better measurements
- Camera knowledge: Focal length and sensor specs improve results
- Horizon clarity: Mountains, clouds, or haze reduce accuracy
- Run multiple optimizations and compare results for consistency
Contributing
We welcome contributions from astronomers, developers, educators, and enthusiasts!
Planet Ruler is maintained by one developer in their spare time. Issue responses may take 3-7 days. Before opening an issue, please check the documentation and existing issues.
Quick Contribution Setup
# Fork the repo, then:
git clone https://github.com/YOUR_USERNAME/planet_ruler.git
cd planet_ruler
python -m pip install -e . && python -m pip install -r requirements.txt && python -m pip install -r requirements-test.txt
pytest tests/ -v # Verify everything works
Ways to Contribute
| Type | Examples | Good For |
|---|---|---|
| Bug Reports | Detection failures, optimization issues | Everyone |
| Features | New algorithms, UI improvements | Developers |
| Documentation | Tutorials, examples, API docs | Educators |
| Examples | New planetary bodies, camera setups | Researchers |
| Testing | Edge cases, performance tests | QA enthusiasts |
Contribution Guidelines
- Found a bug? → Create an issue
- Have an idea? → Start a discussion
- Ready to code? → See our CONTRIBUTING.md guide
First-time contributors welcome! Look for issues labeled
good first issue
Acknowledgments & References
Built With
- Segment Anything (Meta) - AI-powered horizon detection
- SciPy - Scientific optimization algorithms
- NumPy - High-performance numerical computing
- Matplotlib - Publication-quality visualizations
Scientific References
- Horizon geometry fundamentals - Basic theory
- Camera calibration techniques - Optics modeling
- Earth curvature visibility - Observational considerations
- Camera resectioning methods - Parameter estimation
- Intrinsic camera parameters - Mathematical foundations
Inspiration
If you've ever wondered about the size of your planet, you are not alone -- humanity has tried to measure this throughout the ages. Though Earth is large enough to defy the usual methods we have for measuring things, a creative mind can do it with surprisingly little. Eratosthenes, in ancient Greece, was able to do it to impressive accuracy using only a rod and the sun. How much better can we do today?
License
Licensed under the Apache License, Version 2.0 - see LICENSE file for details.
μεταξὺ δὲ τοῦ πυρὸς καὶ τῶν δεσμωτῶν
between the fire and the captives -- Plato
⭐ Star this repo • Report issues • Join discussions
Made with ❤️ for curious minds exploring our cosmic neighborhood
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file planet_ruler-1.7.0.tar.gz.
File metadata
- Download URL: planet_ruler-1.7.0.tar.gz
- Upload date:
- Size: 7.8 MB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
40f2061d9de063e2df1e197ca4263b38144c9ba63abd47c3517c926825d2570e
|
|
| MD5 |
436a5255f7219fe5c178f09ab208d665
|
|
| BLAKE2b-256 |
4ddb7d00d2a76447d6daa874f6a8995cbfe85db70dc8028f491b3a7717ef1f3a
|
File details
Details for the file planet_ruler-1.7.0-py3-none-any.whl.
File metadata
- Download URL: planet_ruler-1.7.0-py3-none-any.whl
- Upload date:
- Size: 98.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a23d703688649093c44c50becc30d0e69dae461199baf47ce9259c42b0315b89
|
|
| MD5 |
ae571097dfe106e01316543525568a1f
|
|
| BLAKE2b-256 |
7a60b706042c349a948c7bd0f32f61531cce237e93d76b0a1aa1729ec187fb63
|