Modules

Planet Ruler API modules organized by functionality.

planet_ruler

Core Modules

planet_ruler.geometry

planet_ruler.image

planet_ruler.observation

planet_ruler.annotate

Manual Limb Annotation Tool for Planet Ruler

planet_ruler.camera

Automatic extraction of camera parameters from smartphone images.

planet_ruler.fit

planet_ruler.uncertainty

planet_ruler.plot

planet_ruler.validation

Validation functions for planet_ruler.

planet_ruler.cli

Command-line interface for planet_ruler

planet_ruler.demo

Geometry Module

Mathematical functions for planetary geometry calculations.

planet_ruler.geometry.horizon_distance(r, h)[source]

Estimate the distance to the horizon (limb) given a height and radius.

Parameters:
  • r (float) – Radius of the body in question.

  • h (float) – Height above surface (units should match radius).

Returns:

Distance in same units as inputs.

Return type:

d (float)

planet_ruler.geometry.limb_camera_angle(r, h)[source]

The angle the camera must tilt in theta_x or theta_y to center the limb. Complement of theta (angle of limb down from the x-y plane).

Parameters:
  • r (float) – Radius of the body in question.

  • h (float) – Height above surface (units should match radius).

Returns:

Angle of camera (radians).

Return type:

theta_c (float)

planet_ruler.geometry.focal_length(w, fov)[source]

The size of the CCD (inferred) based on focal length and field of view.

Parameters:
  • w (float) – detector size (float): Width of CCD (m).

  • fov (float) – Field of view, assuming square (degrees).

Returns:

Focal length of the camera (m).

Return type:

f (float)

planet_ruler.geometry.detector_size(f, fov)[source]

The size of the CCD (inferred) based on focal length and field of view.

Parameters:
  • f (float) – Focal length of the camera (m).

  • radians (# todo really need to pick either degrees or)

  • fov (float) – Field of view, assuming square (degrees).

Returns:

Width of CCD (m).

Return type:

detector size (float)

planet_ruler.geometry.field_of_view(f, w)[source]

The size of the CCD (inferred) based on focal length and field of view.

Parameters:
  • f (float) – Focal length of the camera (m).

  • w (float) – Width of detector (m).

Returns:

Field of view, assuming square (degrees).

Return type:

fov (float)

planet_ruler.geometry.intrinsic_transform(camera_coords, f=1, px=1, py=1, x0=0, y0=0)[source]

Transform from camera coordinates into image coordinates.

Parameters:
  • camera_coords (np.ndarray) – Coordinates of the limb in camera space. Array has Nx4 shape where N is the number of x-axis pixels in the image.

  • f (float) – Focal length of the camera (m).

  • px (float) – The scale of x pixels.

  • py (float) – The scale of y pixels.

  • x0 (float) – The x-axis principle point (should be center of image in pixel coordinates).

  • y0 (float) – The y-axis principle point. (should be center of image in pixel coordinates).

Returns:

Coordinates in image space.

Return type:

pixel_coords (np.ndarray)

planet_ruler.geometry.extrinsic_transform(world_coords, theta_x=0, theta_y=0, theta_z=0, origin_x=0, origin_y=0, origin_z=0)[source]

Transform from world coordinates into camera coordinates. Note that for a limb calculation we will define origin_x/y/z as the camera position – these should all be set to zero.

Parameters:
  • world_coords (np.ndarray) – Coordinates of the limb in the world. Array has Nx4 shape where N is the number of x-axis pixels in the image.

  • theta_x (float) – Rotation around the x (horizontal) axis, AKA pitch. (radians)

  • theta_y (float) – Rotation around the y (toward the limb) axis, AKA roll. (radians)

  • theta_z (float) – Rotation around the z (vertical) axis, AKA yaw. (radians)

  • origin_x (float) – Horizontal offset from the object in question to the camera (m).

  • origin_y (float) – Distance from the object in question to the camera (m).

  • origin_z (float) – Height difference from the object in question to the camera (m).

Returns:

Coordinates in camera space.

Return type:

camera_coords (np.ndarray)

planet_ruler.geometry.limb_arc_sample(r, n_pix_x, n_pix_y, h=1, f=None, fov=None, w=None, x0=0, y0=0, theta_x=0, theta_y=0, theta_z=0, origin_x=0, origin_y=0, origin_z=0, return_full=False, num_sample=5000)[source]

Calculate the limb orientation in an image given the physical parameters of the system.

Parameters:
  • n_pix_x (int) – Width of image (pixels).

  • n_pix_y (int) – Height of image (pixels).

  • r (float) – Radius of the body in question.

  • h (float) – Height above surface (units should match radius).

  • f (float) – Focal length of the camera (m).

  • fov (float) – Field of view, assuming square (degrees).

  • w (float) – detector size (float): Width of CCD (m).

  • x0 (float) – The x-axis principle point.

  • y0 (float) – The y-axis principle point.

  • theta_x (float) – Rotation around the x (horizontal) axis, AKA pitch. (radians)

  • theta_y (float) – Rotation around the y (toward the limb) axis, AKA roll. (radians)

  • theta_z (float) – Rotation around the z (vertical) axis, AKA yaw. (radians)

  • origin_x (float) – Horizontal offset from the object in question to the camera (m).

  • origin_y (float) – Distance from the object in question to the camera (m).

  • origin_z (float) – Height difference from the object in question to the camera (m).

  • return_full (bool) – Return both the x and y coordinates of the limb in camera space. Note these will not be interpolated back on to the pixel grid.

  • num_sample (int) – The number of points sampled from the simulated limb – will be interpolated onto pixel grid. [default 1000]

Return type:

ndarray

planet_ruler.geometry.get_rotation_matrix(theta_x, theta_y, theta_z)[source]

Compute combined rotation matrix from Euler angles. Extracted from extrinsic_transform for reuse.

Returns:

3x3 rotation matrix

Return type:

R

planet_ruler.geometry.limb_arc(r, n_pix_x, n_pix_y, h=1, f=None, fov=None, w=None, x0=0, y0=0, theta_x=0, theta_y=0, theta_z=0, origin_x=0, origin_y=0, origin_z=0, return_full=False, **kwargs)[source]

Calculate limb position analytically at each pixel x-coordinate.

No sampling or interpolation - directly solves for phi at each column. This eliminates edge artifacts and is sometimes faster than sampling methods.

Mathematical approach: 1. Limb is parameterized by angle phi around circle 2. For each x_pixel, solve: x_pixel = f(phi) for phi 3. This reduces to: a·cos(phi) + b·sin(phi) = c 4. Standard analytical solution exists!

Args: (same as original limb_arc)

Returns:

Array of y-coordinates for each x-pixel column

Return type:

y_pixel

Image Processing Module

Computer vision and image analysis functions.

planet_ruler.image.load_image(filepath)[source]

Load a 3 or 4-channel image from filepath into an array.

Parameters:

filepath (str) – Path to image file.

Return type:

ndarray

Returns:

image array (np.ndarray)

planet_ruler.image.directional_gradient_blur(image, sigma_base=2.0, streak_length=20, decay_rate=0.2, normalize_gradients=True)[source]

Blur along gradient directions to create smooth ‘streaks’ toward edges.

For each pixel, follow its gradient direction outward, averaging pixels along that ray with exponential decay. This: - Smooths noisy/weak gradients (striations with inconsistent directions) - Strengthens coherent gradients (limb where all gradients align) - Creates smooth ‘valleys’ guiding optimizer toward strong edges

Parameters:
  • image (np.ndarray) – Input image (H x W or H x W x 3)

  • sigma_base (float) – Initial gradient smoothing for direction estimation

  • streak_length (int) – How far to follow gradient direction (pixels)

  • decay_rate (float) – Exponential decay rate (higher = faster decay)

  • normalize_gradients (bool) –

    • True: Use unit vectors (direction only) - RECOMMENDED All pixels sample same distance regardless of gradient strength. Consistent, predictable behavior.

    • False: Use full gradient magnitude as direction Strong gradients sample further. Can create artifacts.

Returns:

Blurred gradient magnitude field grad_angle (np.ndarray): Gradient angle field

Return type:

blurred_grad_mag (np.ndarray)

Note: This is UNI-directional (samples in one direction only).

Use bidirectional_gradient_blur() to preserve peak locations.

planet_ruler.image.bilinear_interpolate(array, y, x)[source]

Bilinear interpolation for 2D array at non-integer coordinates.

Parameters:
  • array – 2D array to sample from

  • y – Arrays of y and x coordinates (can be non-integer)

  • x – Arrays of y and x coordinates (can be non-integer)

Returns:

Interpolated values at (y, x) positions

planet_ruler.image.bidirectional_gradient_blur(image, sigma_base=2.0, streak_length=20, decay_rate=0.2, normalize_gradients=True)[source]

Blur in BOTH directions along gradient (forward and backward).

This creates symmetric streaks on both sides of edges, preserving the location of gradient maxima while smoothing the field.

Parameters:
  • image – Input image

  • sigma_base – Initial smoothing for gradient estimation

  • streak_length – How far to sample in each direction

  • decay_rate – Exponential decay (higher = faster falloff)

  • normalize_gradients – Use unit vectors (direction only) vs full magnitude

Returns:

Smoothed gradient magnitude field grad_angle: Original gradient angle field

Return type:

blurred_mag

planet_ruler.image.gradient_break(im_arr, log=False, y_min=0, y_max=-1, window_length=None, polyorder=1, deriv=0, delta=1)[source]

Scan each vertical line of an image for the maximum change in brightness gradient – usually corresponds to a horizon.

Parameters:
  • im_arr (np.ndarray) – Image array.

  • log (bool) – Use the log(gradient). Sometimes good for smoothing.

  • y_min (int) – Minimum y-position to consider.

  • y_max (int) – Maximum y-position to consider.

  • window_length (int) – Width of window to apply smoothing for each vertical. Larger means less noise but less sensitivity.

  • polyorder (int) – Polynomial order for smoothing.

  • deriv (int) – Derivative level for smoothing.

  • delta (int) – Delta for smoothing.

Returns:

image array (np.ndarray)

class planet_ruler.image.MaskSegmenter(image, method='sam', downsample_factor=1, interactive=True, **backend_kwargs)[source]

Bases: object

Method-agnostic mask-based segmentation.

Supports pluggable backends (SAM, custom algorithms, etc.) with optional downsampling and interactive classification.

__init__(image, method='sam', downsample_factor=1, interactive=True, **backend_kwargs)[source]

Initialize mask segmenter.

Parameters:
  • image (ndarray) – Input image (H x W x 3)

  • method (str) – Backend method (‘sam’ or ‘custom’)

  • downsample_factor (int) – Downsample factor for speed (1 = no downsampling)

  • interactive (bool) – Use interactive GUI for classification

  • **backend_kwargs – Backend-specific arguments

segment()[source]

Run segmentation pipeline.

Return type:

ndarray

Returns:

Limb coordinates (1D array of y-values)

class planet_ruler.image.SegmentationBackend[source]

Bases: object

Base class for segmentation backends.

segment(image)[source]

Segment image into masks.

Parameters:

image (ndarray) – Input image (H x W x 3)

Return type:

list

Returns:

List of mask objects (format can vary by backend)

class planet_ruler.image.SAMBackend(model_size='vit_b')[source]

Bases: SegmentationBackend

Segment Anything Model backend.

__init__(model_size='vit_b')[source]

Initialize SAM backend.

Parameters:

model_size (str) – SAM model variant (‘vit_b’, ‘vit_l’, ‘vit_h’)

segment(image)[source]

Run SAM on image.

Return type:

list

class planet_ruler.image.CustomBackend(segment_fn)[source]

Bases: SegmentationBackend

Custom user-provided segmentation backend.

__init__(segment_fn)[source]

Initialize with custom segmentation function.

Parameters:

segment_fn – Function that takes image and returns list of masks

segment(image)[source]

Run custom segmentation function.

Return type:

list

planet_ruler.image.smooth_limb(y, method='rolling-median', window_length=50, polyorder=1, deriv=0, delta=1)[source]

Smooth the limb position values.

Parameters:
  • y (np.ndarray) – Y-locations of the string for each column.

  • method (str) – Smoothing method. Must be one of [‘bin-interpolate’, ‘savgol’, ‘rolling-mean’, ‘rolling-median’].

  • window_length (int) – The length of the filter window (i.e., the number of coefficients). If mode is ‘interp’, window_length must be less than or equal to the size of x.

  • polyorder (float) – The order of the polynomial used to fit the samples. polyorder must be less than window_length.

  • deriv (int) – The order of the derivative to compute. This must be a non-negative integer. The default is 0, which means to filter the data without differentiating.

  • delta (int) – The spacing of the samples to which the filter will be applied. This is only used if deriv > 0. Default is 1.0.

Returns:

Y-locations of the smoothed string for each column.

Return type:

position (np.ndarray)

planet_ruler.image.fill_nans(limb)[source]

Fill NaNs for the limb position values.

Parameters:

limb (np.ndarray) – Y-locations of the limb on the image.

Returns:

Y-locations of the limb on the image.

Return type:

limb (np.ndarray)

planet_ruler.image.gradient_field(image, kernel_smoothing=5.0, directional_smoothing=50, directional_decay_rate=0.15)[source]

Pre-compute gradient field AND its derivatives for fast sub-pixel interpolation. Uses directional blur for enhanced edge detection.

Parameters:
  • image (ndarray) – Input image

  • kernel_smoothing (float) – Sigma for initial gradient direction estimation

  • directional_smoothing (int) – Distance to sample in each direction (±pixels). Set to 0 to bypass directional smoothing entirely.

  • directional_decay_rate (float) – Exponential decay rate for directional blur

Returns:

Dictionary containing gradient field components:
  • grad_mag: Gradient magnitude array

  • grad_angle: Gradient angle array

  • grad_sin: Sin of gradient angle (for interpolation)

  • grad_cos: Cos of gradient angle (for interpolation)

  • grad_mag_dy, grad_mag_dx: Derivatives of gradient magnitude

  • grad_sin_dy, grad_sin_dx: Derivatives of sine component

  • grad_cos_dy, grad_cos_dx: Derivatives of cosine component

  • image_height, image_width: Dimensions

Return type:

dict

Observation Module

High-level observation classes and workflows.

class planet_ruler.observation.PlanetObservation(image_filepath)[source]

Bases: object

Base class for planet observations.

Parameters:

image_filepath (str) – Path to image file.

__init__(image_filepath)[source]
plot(gradient=False, show=True)[source]

Display the observation and all current features.

Parameters:
  • gradient (bool) – Show the image gradient instead of the raw image.

  • show (bool) – Show – useful as False if intending to add more to the plot before showing.

Return type:

None

class planet_ruler.observation.LimbObservation(image_filepath, fit_config, limb_detection='manual', minimizer='differential-evolution')[source]

Bases: PlanetObservation

Observation of a planet’s limb (horizon).

Parameters:
  • image_filepath (str) – Path to image file.

  • fit_config (str) – Path to fit config file.

  • limb_detection (str) – Method to locate the limb in the image.

  • minimizer (str) – Choice of minimizer. Supports ‘differential-evolution’, ‘dual-annealing’, and ‘basinhopping’.

__init__(image_filepath, fit_config, limb_detection='manual', minimizer='differential-evolution')[source]
analyze(detect_limb_kwargs=None, fit_limb_kwargs=None)[source]

Perform complete limb analysis: detection + fitting in one call.

Parameters:
  • detect_limb_kwargs (dict) – Optional arguments for detect_limb()

  • fit_limb_kwargs (dict) – Optional arguments for fit_limb()

Returns:

For method chaining

Return type:

self

property radius_km: float

Get the fitted planetary radius in kilometers.

Returns:

Planetary radius in km, or 0 if not fitted

Return type:

float

property altitude_km: float

Get the observer altitude in kilometers.

Returns:

Observer altitude in km, or 0 if not fitted

Return type:

float

property focal_length_mm: float

Get the camera focal length in millimeters.

Returns:

Focal length in mm, or 0 if not fitted

Return type:

float

property radius_uncertainty: float

Get parameter uncertainty for radius.

Automatically selects best method based on minimizer: - differential_evolution: Uses population spread (fast, exact) - dual_annealing/basinhopping: Uses Hessian approximation (fast, approximate)

Returns:

Radius uncertainty in km (1-sigma), or 0 if not available

Return type:

float

parameter_uncertainty(parameter, method='auto', scale_factor=1.0, confidence_level=0.68, **kwargs)[source]

Get uncertainty for any fitted parameter.

Parameters:
  • parameter (str) – Parameter name (e.g., ‘r’, ‘h’, ‘f’, ‘theta_x’)

  • method (Literal['auto', 'hessian', 'profile', 'bootstrap']) – Uncertainty method - ‘auto’: Choose based on minimizer (recommended) - ‘hessian’: Fast Hessian approximation - ‘profile’: Slow but accurate profile likelihood - ‘bootstrap’: Multiple fits (very slow)

  • scale_factor (float) – Scale result (e.g., 1000.0 for m→km)

  • confidence_level (float) – Confidence level (0.68=1σ, 0.95=2σ)

  • **kwargs – Additional arguments passed to uncertainty calculator

Return type:

Dict

Returns:

dict with ‘uncertainty’, ‘method’, ‘confidence_level’, ‘additional_info’

Examples

# Radius uncertainty in km (1-sigma) obs.parameter_uncertainty(‘r’, scale_factor=1000.0)

# Altitude uncertainty in km (2-sigma / 95% CI) obs.parameter_uncertainty(‘h’, scale_factor=1000.0, confidence_level=0.95)

# Focal length uncertainty in mm (using profile likelihood) obs.parameter_uncertainty(‘f’, scale_factor=1000.0, method=’profile’)

plot_3d(**kwargs)[source]

Create 3D visualization of the planetary geometry.

Parameters:

**kwargs – Arguments passed to plot_3d_solution

Return type:

None

load_fit_config(fit_config)[source]

Load the fit configuration from file, setting all parameters to their initial values. Missing values are filled with defaults.

Parameters:

fit_config (str or dict) – Path to configuration file.

Return type:

None

register_limb(limb)[source]

Register a detected limb.

Parameters:

limb (np.ndarray) – Limb vector (y pixel coordinates).

Return type:

LimbObservation

detect_limb(detection_method=None, log=False, y_min=0, y_max=-1, window_length=501, polyorder=1, deriv=0, delta=1, segmentation_method='sam', downsample_factor=1, interactive=True, **segmentation_kwargs)[source]

Use the instance-defined method to find the limb in our observation. Kwargs are passed to the method.

Parameters:
  • detection_method (literal) –

    Detection method. Must be one of
    • manual

    • gradient-break

    • gradient-field

    • segmentation

    Default (None) uses the class attribute self.limb_detection.

  • log (bool) – Use the log(gradient). Sometimes good for smoothing.

  • y_min (int) – Minimum y-position to consider.

  • y_max (int) – Maximum y-position to consider.

  • window_length (int) – Width of window to apply smoothing for each vertical. Larger means less noise but less sensitivity.

  • polyorder (int) – Polynomial order for smoothing.

  • deriv (int) – Derivative level for smoothing.

  • delta (int) – Delta for smoothing.

  • segmentation_method (str) – Model used for segmentation. Must be one of [‘sam’].

  • downsample_factor (int) – Downsampling used for segmentation.

  • interactive (bool) – Prompts user to verify segmentation via annotation tool.

  • segmentation_kwargs (dict) – Kwargs passed to segmentation engine.

Return type:

LimbObservation

smooth_limb(fill_nan=True, **kwargs)[source]

Apply the smooth_limb function to current observation.

Parameters:

fill_nan (bool) – Fill any NaNs in the limb.

Return type:

None

fit_limb(loss_function='l2', max_iter=15000, resolution_stages=None, max_iter_per_stage=None, n_jobs=1, seed=0, image_smoothing=None, kernel_smoothing=5.0, directional_smoothing=50, directional_decay_rate=0.15, prefer_direction=None, minimizer=None, minimizer_preset='balanced', minimizer_kwargs=None, warm_start=False, dashboard=False, dashboard_kwargs=None, target_planet='earth', verbose=False)[source]

Fit the limb to determine planetary parameters.

Supports single-resolution or multi-resolution (coarse-to-fine) optimization. Multi-resolution is recommended for gradient_field loss to avoid local minima.

Parameters:
  • loss_function (Literal['l2', 'l1', 'log-l1', 'gradient_field']) – Loss function type - ‘l2’, ‘l1’, ‘log-l1’: Traditional (requires detected limb) - ‘gradient_field’: Direct gradient alignment (no detection needed)

  • max_iter (int) – Maximum iterations (for single-resolution or total if multires)

  • resolution_stages (Union[List[int], Literal['auto'], None]) –

    Resolution strategy - None: Single resolution (original behavior) - ‘auto’: Auto-determine stages based on image size - List[int]: Custom stages, e.g., [4, 2, 1] = 1/4 → 1/2 → full NOTE: Multi-resolution only works with gradient_field loss functions.

    Traditional loss functions (l2, l1, log-l1) require single resolution.

  • max_iter_per_stage (Optional[List[int]]) – Iterations per stage (auto if None)

  • n_jobs (int) – Number of parallel workers

  • seed (int) – Random seed for reproducibility

  • image_smoothing (Optional[float]) – For gradient_field - Gaussian blur sigma applied to image before gradient computation. Removes high-frequency artifacts (crater rims, striations) that could mislead optimization. Different from kernel_smoothing.

  • kernel_smoothing (float) – For gradient_field - initial blur for gradient direction estimation. Makes the gradient field smoother for directional sampling.

  • directional_smoothing (int) – For gradient_field - sampling distance along gradients

  • directional_decay_rate (float) – For gradient_field - exponential decay for samples

  • prefer_direction (Optional[Literal['up', 'down']]) – For gradient_field - prefer ‘up’ or ‘down’ gradients where ‘up’ means dark-sky/bright-planet and v.v. (None = no preference, choose best gradient regardless of direction)

  • minimizer (str) – Choice of minimizer. Supports ‘differential-evolution’, ‘dual-annealing’, and ‘basinhopping’.

  • minimizer_preset (Literal['fast', 'balanced', 'robust', 'scipy-default']) – Optimization strategy - ‘fast’: Quick convergence, may miss global minimum - ‘balanced’: Good trade-off (default) - ‘robust’: Thorough exploration, slower

  • minimizer_kwargs (Optional[Dict]) – Override specific minimizer parameters (advanced)

  • warm_start (bool) – If True, use previous fit’s results as starting point (useful for iterative refinement). If False (default), use original init_parameter_values. Note: Multi-resolution stages always warm-start from previous stages automatically. This parameter is for warm-starting across separate fit_limb() calls.

  • dashboard (bool) – Show live progress dashboard during optimization

  • dashboard_kwargs (Optional[Dict]) – Additional kwargs for FitDashboard - output_capture: OutputCapture instance for print/log display - show_output: Show output section (default True if capture provided) - max_output_lines: Number of output lines to show (default 3) - min_refresh_delay: Fixed refresh delay (0.0 for adaptive, default) - refresh_frequency: Refresh every N iterations (default 1)

  • target_planet (str) – Reference planet for dashboard comparisons (‘earth’, ‘mars’, ‘jupiter’, ‘saturn’, ‘moon’, ‘pluto’)

  • verbose (bool) – Print detailed progress

Returns:

For method chaining

Return type:

self

Examples

# Simple single-resolution fit obs.fit_limb()

# Auto multi-resolution for gradient field obs.fit_limb(loss_function=’gradient_field’, resolution_stages=’auto’)

# Remove image artifacts before optimization obs.fit_limb(

loss_function=’gradient_field’, resolution_stages=’auto’, image_smoothing=2.0, # Remove crater rims, striations kernel_smoothing=5.0 # Smooth gradient field

)

# Custom stages with robust optimization obs.fit_limb(

loss_function=’gradient_field’, resolution_stages=[8, 4, 2, 1], minimizer_preset=’robust’

)

# Override specific minimizer parameters obs.fit_limb(

loss_function=’gradient_field’, minimizer_kwargs={‘popsize’: 25, ‘atol’: 0.5}

)

# Dashboard with output capture from planet_ruler.dashboard import OutputCapture capture = OutputCapture() with capture:

obs.fit_limb(

loss_function=’gradient_field’, dashboard=True, dashboard_kwargs={‘output_capture’: capture}

)

# Iterative refinement with warm start obs.fit_limb(loss_function=’gradient_field’, resolution_stages=’auto’) obs.fit_limb(loss_function=’gradient_field’, warm_start=True,

minimizer_preset=’robust’) # Refine with more thorough search

save_limb(filepath)[source]

Save the detected limb position as a numpy array.

Parameters:

filepath (str) – Path to save file.

Return type:

None

load_limb(filepath)[source]

Load the detected limb position from a numpy array.

Parameters:

filepath (str) – Path to save file.

Return type:

None

planet_ruler.observation.package_results(observation)[source]

Consolidate the results of a fit to see final vs. initial values.

Parameters:

observation (object) – Instance of LimbObservation (must have used differential evolution minimizer).

Returns:

DataFrame of results including
  • fit value

  • initial value

  • parameter

Return type:

results (pd.DataFrame)

Annotation Module

Interactive manual limb detection and annotation.

Manual Limb Annotation Tool for Planet Ruler

Allows users to click points on a horizon image and generate a sparse target for fitting with the existing planet_ruler pipeline.

class planet_ruler.annotate.ToolTip(widget, text)[source]

Bases: object

Simple tooltip that appears on hover.

__init__(widget, text)[source]
show_tip(event=None)[source]
hide_tip(event=None)[source]
planet_ruler.annotate.create_tooltip(widget, text)[source]

Helper function to create tooltip for a widget.

class planet_ruler.annotate.TkLimbAnnotator(image_path, initial_stretch=1.0, initial_zoom=None)[source]

Bases: object

Tkinter-based interactive tool for manually annotating planet limbs.

Features: - Zoom with scroll wheel (fit large images in window) - Vertical stretch buttons (stretch pixels vertically for precision) - Scrollable canvas for navigating - Click to add points, right-click to undo - Save/load points to JSON - Generate sparse target array for CostFunction

__init__(image_path, initial_stretch=1.0, initial_zoom=None)[source]

Initialize the annotation tool.

Parameters:
  • image_path (str) – Path to the image to annotate

  • initial_stretch (float) – Initial vertical stretch factor

  • initial_zoom (float) – Initial zoom level (None = auto-fit)

create_widgets()[source]

Create all UI widgets.

auto_fit_zoom()[source]

Automatically set zoom to fit image in window.

set_zoom(zoom)[source]

Set absolute zoom level.

adjust_zoom(factor)[source]

Adjust zoom by a multiplicative factor.

on_scroll_zoom(event)[source]

Handle scroll wheel for zooming.

set_stretch(stretch)[source]

Set absolute stretch level.

adjust_stretch(delta)[source]

Adjust stretch by an additive amount.

update_stretched_image()[source]

Update the displayed image with current zoom and stretch.

redraw_points()[source]

Redraw all annotation points at current zoom and stretch.

on_left_click(event)[source]

Add a point at click location.

on_right_click(event)[source]

Undo last point.

clear_all()[source]

Clear all points.

update_status()[source]

Update status text.

get_status_text()[source]

Generate status text.

generate_target()[source]

Generate sparse target array.

save_points()[source]

Save points to JSON.

load_points()[source]

Load points from JSON.

get_target()[source]

Get the current sparse target array.

run()[source]

Start the application.

class planet_ruler.annotate.TkMaskSelector(image, masks, initial_zoom=None)[source]

Bases: object

Interactive mask classification tool for segmentation results.

Works with ANY segmentation method - completely backend-agnostic. Allows users to classify masks as ‘planet’, ‘sky’, or ‘exclude’ for horizon detection. Aligns with Planet Ruler’s educational philosophy of transparency over black-box automation.

Parameters:
  • image (ndarray) – Original image array (H x W x 3)

  • masks (list) – List of masks in any of these formats: - List of np.ndarray (boolean H x W arrays) - List of dicts with ‘mask’ or ‘segmentation’ key - Mixed formats are OK

  • initial_zoom (Optional[float]) – Initial zoom level (None = auto-fit to window)

__init__(image, masks, initial_zoom=None)[source]
update_canvas()[source]

Update canvas with current overlay at current zoom level.

update_mask_list()[source]

Update the mask listbox with text labels.

on_listbox_select(event)[source]

Handle mask selection from listbox.

on_canvas_click(event)[source]

Handle click on canvas to select mask.

on_key_press(event)[source]

Handle keyboard shortcuts.

start_pan(event)[source]

Start panning.

do_pan(event)[source]

Pan the canvas.

end_pan(event)[source]

End panning.

on_mousewheel(event)[source]

Zoom with mouse wheel.

reset_classifications()[source]

Reset all to exclude except first two.

finish()[source]

Close window - nuclear option for Jupyter compatibility.

run()[source]

Run the interactive selector with proper cleanup.

get_classified_masks()[source]

Return dictionary of classified masks with original mask objects.

Camera Module

Automatic camera parameter extraction from EXIF data.

Automatic extraction of camera parameters from smartphone images. Uses EXIF data and a phone camera database.

planet_ruler.camera.extract_exif(image_path)[source]

Extract EXIF data from image.

Return type:

Dict

planet_ruler.camera.get_camera_model(exif_data)[source]

Extract camera model from EXIF data.

Return type:

Optional[str]

planet_ruler.camera.get_focal_length_mm(exif_data)[source]

Extract focal length in mm from EXIF.

Return type:

Optional[float]

planet_ruler.camera.get_focal_length_35mm_equiv(exif_data)[source]

Get 35mm equivalent focal length (more reliable for phones).

Return type:

Optional[float]

planet_ruler.camera.get_image_dimensions(image_path)[source]

Get image width and height in pixels.

Return type:

Tuple[int, int]

planet_ruler.camera.calculate_sensor_dimensions(focal_length_mm, focal_length_35mm)[source]

Calculate sensor dimensions from focal length ratio. Uses the relationship: focal_length_mm / sensor_width = focal_length_35mm / 36mm

Return type:

Tuple[float, float]

planet_ruler.camera.get_sensor_statistics_by_type(camera_type)[source]

Calculate sensor dimension statistics for a given camera type. Returns median, min, and max for sensor width and height.

Return type:

Dict

planet_ruler.camera.infer_camera_type(exif_data)[source]

Try to infer camera type from EXIF data even if exact model unknown.

Return type:

Optional[str]

planet_ruler.camera.extract_camera_parameters(image_path)[source]

Automatically extract all camera parameters from any camera image. Handles phones, point-and-shoot cameras, DSLRs, mirrorless, etc.

Returns:

Camera parameters including:
  • focal_length_mm: focal length in millimeters

  • sensor_width_mm: sensor width in millimeters

  • sensor_height_mm: sensor height in millimeters

  • sensor_width_min: minimum sensor width (if using type statistics)

  • sensor_width_max: maximum sensor width (if using type statistics)

  • image_width_px: image width in pixels

  • image_height_px: image height in pixels

  • camera_model: detected camera model

  • camera_type: ‘phone’, ‘compact’, ‘dslr’, ‘mirrorless’, or ‘unknown’

  • confidence: ‘high’, ‘medium’, or ‘low’

Return type:

dict

planet_ruler.camera.get_gps_altitude(image_path)[source]

Extract GPS altitude from EXIF data if available.

Returns:

Altitude in meters, or None if not available

Return type:

float

planet_ruler.camera.get_initial_radius(planet='earth', perturbation_factor=0.5, seed=None)[source]

Get initial radius guess with perturbation.

Parameters:
  • planet (str) – Planet name

  • perturbation_factor (float) – Relative perturbation (default: 0.5 = ±50%)

  • seed (Optional[int]) – Random seed for reproducibility (default: None = unseeded)

Return type:

float

planet_ruler.camera.create_config_from_image(image_path, altitude_m=None, planet='earth', param_tolerance=0.1, perturbation_factor=0.5, seed=None)[source]

Create a complete planet_ruler configuration from an image. Works with any camera - phones, point-and-shoot, DSLRs, etc.

Parameters:
  • image_path (str) – Path to the image

  • altitude_m (Optional[float]) – Altitude in meters (REQUIRED if not in GPS data)

  • planet (str) – Planet name for initial radius guess (default: ‘earth’)

  • param_tolerance (float) – Fractional tolerance for parameter limits (default: 0.1 = ±10%)

  • perturbation_factor (float) – Initial radius perturbation (default: 0.5 = ±50%)

  • seed (Optional[int]) – Random seed for reproducibility (default: None = unseeded)

Returns:

Configuration ready for planet_ruler

Return type:

dict

Raises:

ValueError – If altitude cannot be determined from GPS and not provided manually

Notes

  • Initial radius is randomly perturbed to avoid local minima and prove data-driven results

  • For uncertainty quantification, consider running multiple times (multi-start optimization)

  • Theta parameters (orientation) have wide default limits to handle r-h coupling

Fitting Module

Parameter optimization and cost function handling.

planet_ruler.fit.unpack_parameters(params, template)[source]

Turn a list of parameters back into a dict.

Parameters:
  • params (list) – Values of dictionary elements in a list.

  • template (list) – Ordered list of target keys.

Returns:

Parameter dictionary.

Return type:

param_dict (dict)

planet_ruler.fit.pack_parameters(params, template)[source]

Turn a dict of parameters (or defaults) into a list.

Parameters:
  • params (dict) – Parameter dictionary (subset or full keys of template).

  • template (dict) – Template (full) parameter dictionary.

Returns:

List of parameter values.

Return type:

param_list (list)

class planet_ruler.fit.CostFunction(target, function, free_parameters, init_parameter_values, loss_function='l2', kernel_smoothing=5.0, directional_smoothing=30, directional_decay_rate=0.15, prefer_direction=None)[source]

Bases: object

Wrapper to simplify interface with the minimization at hand.

Parameters:
  • target (np.ndarray) – True value(s), e.g., the actual limb position. For gradient_field loss, this should be the image.

  • function (Callable) – Function mapping parameters to target of interest.

  • free_parameters (list) – List of free parameter names.

  • init_parameter_values (dict) – Initial values for named parameters.

  • loss_function (str) – Type of loss function, must be one of [‘l2’, ‘l1’, ‘log-l1’, ‘gradient_field’].

  • kernel_smoothing – For gradient_field - initial blur for gradient direction estimation. Makes the gradient field smoother for directional sampling.

  • directional_smoothing – For gradient_field - sampling distance along gradients

  • directional_decay_rate – For gradient_field - exponential decay for samples

  • prefer_direction (Optional[str]) – For gradient_field - prefer ‘up’ or ‘down’ gradients where ‘up’ means dark-sky/bright-planet and v.v. (None = no preference, choose best gradient regardless of direction)

__init__(target, function, free_parameters, init_parameter_values, loss_function='l2', kernel_smoothing=5.0, directional_smoothing=30, directional_decay_rate=0.15, prefer_direction=None)[source]
cost(params)[source]

Compute prediction and use desired metric to reduce difference from truth to a cost. AKA loss function.

Parameters:

params (np.ndarray | dict) – Parameter values, either packed into array or as dict.

Returns:

Cost given parameters.

Return type:

cost (float)

evaluate(params)[source]

Compute prediction given parameters.

Parameters:

params (np.ndarray | dict) – Parameter values, either packed into array or as dict.

Returns:

Prediction value(s).

Return type:

prediction (np.ndarray)

planet_ruler.fit.calculate_parameter_uncertainty(observation, parameter='r', method='auto', uncertainty_type='std', scale_factor=1.0)[source]

Calculate parameter uncertainty from fitting results.

Provides a flexible interface for uncertainty estimation that works with different optimization methods and uncertainty metrics.

Parameters:
  • observation – LimbObservation object with completed fit

  • parameter (str) – Parameter name to calculate uncertainty for (default: “r”)

  • method (str) – Uncertainty calculation method: - “auto”: Automatically detect method from fit results - “differential_evolution”: Use DE population posteriors - “bootstrap”: Use bootstrap resampling (future implementation) - “hessian”: Use Hessian-based uncertainty (future implementation)

  • uncertainty_type (str) – Type of uncertainty measure: - “std”: Standard deviation of parameter distribution - “ptp”: Peak-to-peak range (max - min) - “iqr”: Interquartile range (75th - 25th percentile) - “ci”: Confidence interval (returns dict with bounds)

  • scale_factor (float) – Scale factor to apply to results (e.g., 1000 for km units)

Returns:

Dictionary containing uncertainty information:
  • ”value”: Fitted parameter value (scaled)

  • ”uncertainty”: Uncertainty estimate (scaled)

  • ”method”: Method used for uncertainty calculation

  • ”type”: Type of uncertainty measure used

  • ”raw_data”: Raw parameter samples if available

Return type:

dict

Raises:
  • ValueError – If uncertainty method is not supported or data is insufficient

  • AttributeError – If observation doesn’t have required fit results

planet_ruler.fit.format_parameter_result(uncertainty_result, units='')[source]

Format parameter uncertainty results for display.

Parameters:
  • uncertainty_result (dict) – Result from calculate_parameter_uncertainty

  • units (str) – Units to display (e.g., “km”, “m”, “degrees”)

Returns:

Formatted string representation of result

Return type:

str

planet_ruler.fit.unpack_diff_evol_posteriors(observation)[source]

Extract the final state population of a differential evolution minimization and organize as a DataFrame.

Parameters:

observation (object) – Instance of LimbObservation (must have used differential evolution minimizer).

Returns:

Population (rows) and properties (columns).

Return type:

population (pd.DataFrame)

Uncertainty Module

Parameter uncertainty estimation using multiple methods.

planet_ruler.uncertainty.calculate_parameter_uncertainty(observation, parameter, method='auto', scale_factor=1.0, confidence_level=0.68, n_bootstrap=20, **kwargs)[source]

Calculate uncertainty for a fitted parameter using appropriate method.

Automatically selects best method based on minimizer used: - differential_evolution: Population spread (fast, exact) - dual_annealing/basinhopping: Hessian approximation (fast, approximate) - any: Profile likelihood or bootstrap (slow, accurate)

Parameters:
  • observation – LimbObservation instance with completed fit

  • parameter (str) – Parameter name (e.g., ‘r’, ‘h’, ‘f’)

  • method (Literal['auto', 'hessian', 'profile', 'bootstrap']) – Uncertainty estimation method - ‘auto’: Choose based on minimizer and available data - ‘hessian’: Inverse Hessian at optimum (fast) - ‘profile’: Profile likelihood (slow but accurate) - ‘bootstrap’: Multiple fits with different seeds (slow)

  • scale_factor (float) – Scale result (e.g., 1000.0 to convert m→km)

  • confidence_level (float) – Confidence level (0.68=1σ, 0.95=2σ)

  • n_bootstrap (int) – Number of bootstrap iterations

Returns:

  • ‘uncertainty’: The uncertainty value

  • ’method’: Method used

  • ’confidence_level’: Confidence level

  • ’additional_info’: Method-specific details

Return type:

dict with keys

Plotting Module

Visualization and plotting functions.

planet_ruler.plot.plot_image(im_arr, gradient=False, show=True)[source]

Display an image using matplotlib.pyplot.imshow.

Parameters:
  • im_arr (np.ndarray) – Array of image values.

  • gradient (bool) – Display as gradient (y-axis).

  • show (bool) – Display the image.

Return type:

None

planet_ruler.plot.plot_limb(y, show=True, c='y', s=10, alpha=0.2)[source]

Display the limb (usually on top of an image).

Parameters:
  • y (np.ndarray) – Array of image values.

  • show (bool) – Display the image.

  • c (str) – Color of the limb.

  • s (int) – Size of marker.

  • alpha (float) – Opacity of markers.

Return type:

None

planet_ruler.plot.plot_3d_solution(r, h=1, zoom=1, savefile=None, legend=True, vertical_axis='z', azim=None, roll=None, x_axis=False, y_axis=True, z_axis=False, **kwargs)[source]

Plot a limb solution in 3D.

Parameters:
  • r (float) – Radius of the body in question.

  • h (float) – Height above surface (units should match radius).

  • zoom (float) – Shrink the height according to a zoom factor to make viewing easier.

  • savefile (str) – Path to optionally save figure.

  • legend (bool) – Display the legend.

  • vertical_axis (str) – Which axis will be used as the vertical (x, y, or z).

  • azim (float) – Viewing azimuth.

  • roll (float) – Viewing roll.

  • x_axis (bool) – Plot the x-axis.

  • y_axis (bool) – Plot the y-axis.

  • z_axis (bool) – Plot the z-axis.

  • kwargs – Absorbs other solution kwargs that don’t matter for physical space.

Return type:

None

planet_ruler.plot.plot_topography(image)[source]

Display the full limb, including the section not seen in the image.

Parameters:

image (np.ndarray) – Image array.

Return type:

None

planet_ruler.plot.plot_gradient_field_at_limb(y_pixels, image, image_smoothing=None, kernel_smoothing=5.0, directional_smoothing=50, directional_decay_rate=0.15, sample_spacing=50, arrow_scale=20)[source]

Visualize gradient field along a proposed limb curve.

Parameters:
  • y_pixels (np.ndarray) – Y-coordinates of limb at each x-position

  • image (np.ndarray) – Input image (H x W x 3 or H x W)

  • image_smoothing (int) – For gradient_field - Gaussian blur sigma applied to image before gradient computation. Removes high-frequency artifacts (crater rims, striations) that could mislead optimization. Different from kernel_smoothing.

  • kernel_smoothing (float) – Initial smoothing for gradient direction estimation

  • directional_smoothing (int) – How far to sample along gradients

  • directional_decay_rate (float) – Exponential decay rate for sampling

  • sample_spacing (int) – Sample every N pixels along x-axis

  • arrow_scale (float) – Scale factor for arrow lengths

Returns:

Matplotlib figure and axis objects

Return type:

fig, ax

planet_ruler.plot.compare_blur_methods(image, y_pixels=None)[source]

Compare gradient magnitude with different blur methods.

Parameters:
  • image – Input image

  • y_pixels – Optional limb curve to overlay

planet_ruler.plot.compare_gradient_fields(y_pixels_list, labels, image, image_smoothing=None, kernel_smoothing=5.0, directional_smoothing=30, directional_decay_rate=0.15)[source]

Compare gradient alignment for multiple proposed limbs.

Parameters:
  • y_pixels_list (list) – List of y-coordinate arrays (different limb proposals)

  • labels (list) – Labels for each limb

  • image (np.ndarray) – Input image

  • kernel_smoothing (float) – Initial smoothing for gradient direction estimation

  • directional_smoothing (int) – How far to sample along gradients

  • directional_decay_rate (float) – Exponential decay rate for sampling

planet_ruler.plot.plot_diff_evol_posteriors(observation, show_points=False, log=True)[source]

Extract and display the final state population of a differential evolution minimization.

Parameters:
  • observation (object) – Instance of LimbObservation (must have used differential evolution minimizer).

  • show_points (bool) – Show the individual population members in addition to the contour.

  • log (bool) – Set the y-scale to log.

Returns:

None

planet_ruler.plot.plot_full_limb(observation, x_min=None, x_max=None, y_min=None, y_max=None)[source]

Display the full limb, including the section not seen in the image.

Parameters:
  • observation (object) – Instance of LimbObservation.

  • x_min (int) – Left edge in pixels.

  • x_max (int) – Right edge in pixels.

  • y_min (int) – Bottom edge in pixels.

  • y_max (int) – Top edge in pixels.

Return type:

None

planet_ruler.plot.plot_residuals(observation, show_sparse_markers=True, marker_size=10, alpha=0.6, figsize=(16, 6), show_image=False, image_alpha=0.4, band_size=30)[source]

Plot residuals between the fitted limb and the detected target limb.

Parameters:
  • observation – Instance of LimbObservation that has been fitted.

  • show_sparse_markers (bool) – Use larger markers for sparse data.

  • marker_size (int) – Size of markers for sparse data.

  • alpha (float) – Transparency of residual markers/line.

  • figsize (tuple) – Figure size (width, height).

  • show_image (bool) – Show straightened image strip as background.

  • image_alpha (float) – Transparency of background image.

  • band_size (int) – Size of band around residuals to plot (in pixels)

Return type:

None

planet_ruler.plot.plot_gradient_field_quiver(image, step=2, scale=0.15, limb_y=None, roi_height=None, image_smoothing=None, kernel_smoothing=5.0, directional_smoothing=50, directional_decay_rate=0.15, downsample_factor=32, figsize=(16, 10), cmap='hot')[source]

Plot gradient field as quiver (arrow) plot.

Parameters:
  • image (ndarray) – Input image array.

  • step (int) – Spacing between arrows (every Nth pixel).

  • scale (Optional[float]) – Arrow scale factor (None = auto).

  • limb_y (Optional[ndarray]) – Optional limb curve to overlay (y-coordinates).

  • roi_height (Optional[int]) – If provided, only show ±roi_height pixels around limb.

  • image_smoothing (Optional[int]) – For gradient_field - Gaussian blur sigma applied to image before gradient computation. Removes high-frequency artifacts (crater rims, striations) that could mislead optimization. Different from kernel_smoothing.

  • kernel_smoothing (float) – Sigma for initial gradient direction estimation.

  • directional_smoothing (int) – Distance for directional blur sampling.

  • directional_decay_rate (float) – Exponential decay for directional blur.

  • downsample_factor (int) – Downsample image by this factor before computing gradients. Values > 1 reduce resolution (e.g., 2 = half resolution, 4 = quarter). Useful for visualizing gradient field at different optimization stages.

  • figsize (tuple) – Figure size (width, height).

  • cmap (str) – Colormap for gradient magnitude.

Return type:

None

planet_ruler.plot.plot_segmentation_masks(observation)[source]

Display all the classes/masks generated by the segmentation.

Parameters:

observation (object) – Instance of LimbObservation.

Return type:

None

planet_ruler.plot.plot_sam_masks(masks, labels=None, colors=None, alpha=0.5, image=None, figsize=(16, 10), title=None, show=True)[source]

Plot multiple SAM segmentation masks as separate labeled objects with legend.

Parameters:
  • masks (list) – List of SAM mask dictionaries with ‘segmentation’ keys containing boolean arrays. Can also be a list of boolean arrays directly.

  • labels (list) – Labels for each mask (for legend). If None, uses “Mask 0”, “Mask 1”, etc.

  • colors (list) – Colors for each mask. If None, uses a default color cycle.

  • alpha (float) – Transparency of mask overlays (0=transparent, 1=opaque).

  • image (np.ndarray) – Optional background image to display under masks.

  • figsize (tuple) – Figure size in inches.

  • title (str) – Optional title for the plot.

  • show (bool) – Whether to display the plot immediately.

Returns:

(fig, ax) matplotlib figure and axis objects.

Return type:

tuple

Example

>>> masks = [{'segmentation': planet_mask}, {'segmentation': sky_mask}]
>>> fig, ax = plot_sam_masks(
...     masks,
...     labels=['Planet', 'Sky'],
...     colors=['yellow', 'cyan'],
...     image=observation.image
... )

Validation Module

Configuration validation and consistency checking.

Validation functions for planet_ruler.

planet_ruler.validation.validate_limb_config(config, strict=True)[source]

Validate that a planet_ruler configuration is internally consistent.

Checks: 1. Initial parameter values are within their specified limits 2. Theta parameter limits are wide enough (avoid r-h coupling issues) 3. Radius limits span reasonable range for robust optimization

Parameters:
  • config (dict) – Configuration dictionary with keys: - ‘init_parameter_values’: dict of parameter initial values - ‘parameter_limits’: dict of [lower, upper] limits

  • strict (bool) – If True, raise exceptions. If False, only log warnings.

Raises:

AssertionError – If strict=True and validation fails

Return type:

None

Example

>>> config = {
...     'init_parameter_values': {'r': 6371000, 'theta_x': 0.1},
...     'parameter_limits': {'r': [1e6, 1e8], 'theta_x': [-3.14, 3.14]}
... }
>>> validate_config(config)  # Passes

Command-Line Interface

CLI tools for command-line access to planet_ruler.

Command-line interface for planet_ruler

This module provides a simple CLI for measuring planetary radii from horizon photographs.

planet_ruler.cli.load_config(config_path)[source]

Load configuration from YAML or JSON file.

Return type:

Dict[str, Any]

planet_ruler.cli.main()[source]

Main CLI entry point.

planet_ruler.cli.measure_command(args)[source]

Handle the measure command.

planet_ruler.cli.demo_command(args)[source]

Handle the demo command.

planet_ruler.cli.list_command(args)[source]

Handle the list command.

Demo Module

Example scenarios and configuration management.

planet_ruler.demo.make_dropdown()[source]
planet_ruler.demo.load_demo_parameters(demo)[source]
planet_ruler.demo.display_text(filepath)[source]