Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Introduction

Welcome to the Camera Optics Calculator documentation!

This application helps you calculate and visualize camera system performance, including field of view (FOV), spatial resolution, and depth of field calculations.

What You Can Do

  • Calculate Field of View: Determine the angular and linear FOV for any camera/lens combination
  • Spatial Resolution: Calculate pixels per millimeter (PPM) and ground sample distance (GSD)
  • Depth of Field: Calculate near/far limits and total DOF for given aperture settings
  • Compare Systems: Overlay and compare multiple camera configurations
  • CLI or GUI: Use the tool via command-line or graphical interface

Built With

  • Rust - Fast, safe backend calculations via Tauri
  • TypeScript - Type-safe frontend UI
  • Vite - Modern build tooling
  • Canvas API - Real-time visualization

Get Started

Head to the Quick Start guide to begin!

Quick Start

Installation

GUI Application

# Clone the repository
git clone https://github.com/cramke/camera-optics.git
cd camera-optics

# Install frontend dependencies
pnpm install

# Run in development mode
pnpm tauri:dev

# Build for production
pnpm tauri:build

CLI Tool

The CLI is included with the application:

cd src-tauri
cargo build --release --bin camera-optics-cli

# The binary will be at:
# target/release/camera-optics-cli

First Calculation

Using the GUI

  1. Launch the app: pnpm tauri:dev
  2. Enter sensor dimensions (e.g., 36×24mm for full frame)
  3. Enter pixel resolution (e.g., 6000×4000)
  4. Enter focal length (e.g., 50mm)
  5. Enter working distance (e.g., 5000mm = 5 meters)
  6. Click "Calculate FOV"

Using the CLI

Calculate FOV for a full-frame camera with 50mm lens at 5m distance:

cargo run --bin camera-optics-cli -- fov \
  -w 36 -H 24 \
  -x 6000 -y 4000 \
  -f 50 \
  -d 5000

Next Steps

GUI Application

The graphical interface provides an intuitive way to calculate and compare camera systems.

Main Interface

The GUI is divided into several sections:

Camera System Input

Enter your camera and lens specifications:

  • Sensor Width/Height (mm): Physical sensor dimensions
  • Pixel Width/Height: Sensor resolution in pixels
  • Focal Length (mm): Lens focal length
  • Working Distance (mm): Distance to subject
  • System Name: Optional identifier for comparison

Calculation Buttons

  • Calculate FOV: Compute field of view for current system
  • Auto-Calculate: Automatically recalculates when you change focal length (with 300ms debounce)

Results Display

After calculation, you'll see:

  • Field of View: Horizontal and vertical (angular and linear)
  • Spatial Resolution: Pixels per mm (PPM) and ground sample distance (GSD)
  • Coverage Area: Total area covered by the sensor

System Comparison

  • Add System: Saves current configuration for comparison
  • Compare Mode: Overlays multiple systems on visualization
  • Edit System: Modify saved configurations
  • Delete System: Remove from comparison

Visualization

Real-time canvas visualization shows:

  • FOV rectangle with dimensions
  • Sensor aspect ratio
  • Multiple systems overlaid for comparison
  • Color-coded systems

Tips

  • Use Auto-Calculate for quick focal length exploration
  • Save systems before changing values to compare configurations
  • Hover over visualizations to see system details
  • Values are validated with realistic min/max constraints

CLI Tool

The command-line interface provides scriptable access to all calculations.

Installation

cd src-tauri
cargo build --release --bin camera-optics-cli

The binary will be at target/release/camera-optics-cli.

Commands

FOV Calculation

camera-optics-cli fov \
  --sensor-width 36 \
  --sensor-height 24 \
  --pixel-width 6000 \
  --pixel-height 4000 \
  --focal-length 50 \
  --distance 5000 \
  --name "Full Frame 50mm"

Short flags:

camera-optics-cli fov -w 36 -H 24 -x 6000 -y 4000 -f 50 -d 5000

Hyperfocal Distance

Find the focus distance where everything from half that distance to infinity is acceptably sharp:

camera-optics-cli hyperfocal \
  --focal-length 50 \
  --aperture 8

Depth of Field

Calculate near limit, far limit, and total DOF:

camera-optics-cli dof \
  --distance 3000 \
  --focal-length 50 \
  --aperture 2.8

System Comparison

Compare common sensor formats:

camera-optics-cli compare --distance 5000 --presets

Compare custom systems:

camera-optics-cli compare \
  --distance 5000 \
  --systems "36,24,6000,4000,50,Full Frame" \
           "23.5,15.6,6000,4000,35,APS-C"

Output Format

All commands output JSON by default for easy parsing:

{
  "camera": {
    "name": "Full Frame 50mm",
    "sensor_width_mm": 36.0,
    "sensor_height_mm": 24.0,
    "pixel_width": 6000,
    "pixel_height": 4000,
    "focal_length_mm": 50.0
  },
  "fov_horizontal_deg": 39.6,
  "fov_vertical_deg": 27.0,
  "fov_width_mm": 3600.0,
  "fov_height_mm": 2400.0,
  "ppm_horizontal": 1.67,
  "ppm_vertical": 1.67,
  "gsd_mm": 0.6
}

Scripting Examples

Batch calculations

#!/bin/bash
for focal in 24 35 50 85; do
  camera-optics-cli fov -w 36 -H 24 -x 6000 -y 4000 -f $focal -d 5000 \
    > results_${focal}mm.json
done

Parse with jq

camera-optics-cli fov -w 36 -H 24 -x 6000 -y 4000 -f 50 -d 5000 | \
  jq '.fov_width_mm'

System Comparison

Camera Optics Calculator - Usage Guide

Project Structure

src-tauri/
├── src/
│   ├── lib.rs              # Tauri GUI library (shared optics module)
│   ├── main.rs             # GUI binary entry point
│   ├── cli_commands.rs     # CLI binary entry point
│   ├── gui_commands.rs     # Tauri command wrappers
│   └── optics/             # Shared optical calculation library
│       ├── mod.rs          # Module exports
│       ├── types.rs        # Data structures (CameraSystem, FovResult)
│       └── calculations.rs # Pure calculation functions (FOV, DOF, etc.)

Building

Build both GUI and CLI:

cd src-tauri
cargo build --bins

Build release versions:

cargo build --bins --release

The binaries will be in:

  • GUI: target/debug/tauri-app (or target/release/tauri-app)
  • CLI: target/debug/camera-optics-cli (or target/release/camera-optics-cli)

CLI Usage

Calculate Field of View

Calculate FOV and spatial resolution for a camera system:

camera-optics-cli fov \
  -w 36 \              # Sensor width (mm)
  -H 24 \              # Sensor height (mm)
  -x 6000 \            # Horizontal pixels
  -y 4000 \            # Vertical pixels
  -f 50 \              # Focal length (mm)
  -d 5000 \            # Working distance (mm)
  -n "My Camera"       # Optional name

Example output:

Full Frame 50mm: 36x24 mm sensor, 6000x4000 px (6.00x6.00 µm), 50 mm lens

FOV: 39.60° × 26.99° (3600.00 × 2400.00 mm @ 5000 mm)
Resolution: 1.667 ppm, GSD: 0.600 mm/px

Compare Multiple Systems

Compare common sensor formats:

camera-optics-cli compare -d 5000 --presets

Compares Full Frame, APS-C, and Micro 4/3 sensors at 5000mm distance.

Calculate Hyperfocal Distance

camera-optics-cli hyperfocal -f 50 -a 8 -c 0.03

Arguments:

  • -f: Focal length (mm)
  • -a: F-number (aperture)
  • -c: Circle of confusion (mm) - optional, defaults to 0.03

Calculate Depth of Field

camera-optics-cli dof -d 3000 -f 50 -a 2.8

Arguments:

  • -d: Object distance (mm)
  • -f: Focal length (mm)
  • -a: F-number (aperture)
  • -c: Circle of confusion (mm) - optional, defaults to 0.03

GUI Usage (Tauri App)

Run in development mode:

pnpm tauri dev

Build for production:

pnpm tauri build

Available Tauri Commands (from TypeScript):

import { invoke } from '@tauri-apps/api/core';

// Calculate single camera FOV
const result = await invoke('calculate_camera_fov', {
  camera: {
    sensor_width_mm: 36,
    sensor_height_mm: 24,
    pixel_width: 6000,
    pixel_height: 4000,
    focal_length_mm: 50,
    name: 'Full Frame',
  },
  distanceMm: 5000,
});

// Compare multiple cameras
const results = await invoke('compare_camera_systems', {
  cameras: [camera1, camera2, camera3],
  distanceMm: 5000,
});

// Calculate hyperfocal distance
const hyperfocal = await invoke('calculate_hyperfocal_distance', {
  focalLengthMm: 50,
  fNumber: 8,
  cocMm: 0.03,
});

// Calculate depth of field
const dof = await invoke('calculate_depth_of_field', {
  objectDistanceMm: 3000,
  focalLengthMm: 50,
  fNumber: 2.8,
  cocMm: 0.03,
});

Common Sensor Sizes (Reference)

FormatWidth × Height (mm)Common Pixel Counts
Full Frame36 × 246000×4000, 8000×5333
APS-C23.5 × 15.66000×4000, 5184×3456
Micro 4/317.3 × 13.05184×3888, 4608×3456
1"13.2 × 8.85472×3648
1/1.8"7.2 × 5.34000×3000

Optical Formulas Used

Field of View (Angular)

FOV = 2 × arctan(sensor_size / (2 × focal_length))

Field of View (Linear at distance)

FOV_linear = 2 × distance × tan(FOV_angular / 2)

Spatial Resolution

PPM (pixels per mm) = pixel_count / FOV_linear
GSD (ground sample distance) = FOV_linear / pixel_count

Hyperfocal Distance

H = (f² / (N × c)) + f

Where: f = focal length, N = f-number, c = circle of confusion

Depth of Field

Near limit: Dn = (H × s) / (H + (s - f))
Far limit:  Df = (H × s) / (H - (s - f))

Where: H = hyperfocal distance, s = subject distance, f = focal length

Next Steps

For the GUI:

  1. Design the HTML interface with input forms
  2. Create Canvas/SVG visualization for FOV overlay
  3. Add TypeScript to connect UI to Rust commands
  4. Implement interactive comparison with multiple cameras

For the CLI:

  1. Add more presets (industrial cameras, smartphones, etc.)
  2. Export results to JSON/CSV
  3. Batch processing from configuration files
  4. Add lens distortion calculations

Development Tips

  • All optical math goes in optics/calculations.rs
  • Keep functions pure (no side effects)
  • Add unit tests for formulas
  • Tauri commands are just thin wrappers in gui_commands.rs
  • CLI uses the same calculation functions, ensuring consistency

FOV Parameter Feature

Overview

The DORI Designer now supports Horizontal Field of View (FOV) as an input parameter constraint. This allows you to specify a desired FOV angle and see what camera configurations can achieve it while meeting your DORI distance requirements.

How It Works

FOV Relationship

FOV is mathematically related to focal length and sensor width through this formula:

FOV = 2 × arctan(sensor_width / (2 × focal_length))

When you specify a FOV value, it constrains the ratio between focal length and sensor width.

Usage in DORI Designer

  1. Enter your DORI target distances (Detection, Observation, Recognition, or Identification)
  2. Specify FOV constraint (optional):
    • Enter a value in degrees (e.g., 60° for a moderate wide angle, 90° for very wide)
    • The system will calculate valid focal length and sensor width ranges that maintain this FOV
  3. Combine with other constraints:
    • FOV + Pixel Width: Calculates focal and sensor ranges maintaining both constraints
    • FOV + Focal Length: Determines the exact sensor width needed
    • FOV + Sensor Width: Determines the exact focal length needed
  4. View calculated ranges for unconstrained parameters

Examples

Example 1: Wide-angle surveillance

  • Target: Identification at 10m
  • Constraint: 90° horizontal FOV
  • Result: Shows focal length range (2.6mm - 43.3mm) and matching sensor width range (3mm - 50mm)

Example 2: Narrow-angle monitoring

  • Target: Recognition at 50m
  • Constraints: 30° FOV + 1920 pixel width
  • Result: Calculates specific focal and sensor ranges that satisfy both the FOV and DORI requirements

Example 3: No FOV constraint

  • Target: Observation at 25m
  • No FOV constraint
  • Result: Shows FOV range possible (e.g., 7° to 74°) based on other parameter ranges

Implementation Details

Backend (Rust)

  • Type: horizontal_fov_deg: Option<f64> in ParameterConstraint and DoriParameterRanges
  • Calculation: calculate_dori_parameter_ranges() handles FOV constraints
  • Formula: sensor = 2 × focal × tan(FOV/2) used to maintain relationship
  • Bounds: Focal length constrained so sensor stays within physical limits (3mm - 50mm)

Frontend (TypeScript/HTML)

  • Input field: "Horizontal FOV (°)" in DORI Designer tab
  • Clear button: Click × to make FOV a floating parameter
  • Range display: Shows FOV range when not constrained
  • Auto-update: Recalculates when FOV or other parameters change

Tests

Three comprehensive tests verify FOV constraint behavior:

  1. test_dori_ranges_with_fov_constraint - FOV only
  2. test_dori_ranges_fov_and_pixel_constraint - FOV + pixels
  3. test_dori_ranges_no_fov_constraint - FOV as output range

All tests verify that the FOV relationship is maintained across the calculated ranges.

Benefits

  • More intuitive: Specify desired viewing angle directly
  • Better design: Choose FOV based on scene coverage needs
  • Reduces trial-and-error: System calculates compatible camera specs automatically
  • Real-world workflow: Many users think in terms of FOV rather than focal length

Future Enhancements

  • FOV visualization showing coverage area
  • Common FOV presets (wide, normal, telephoto)
  • Diagonal FOV calculation
  • Vertical FOV display

API Reference

Tauri Commands

The frontend communicates with the Rust backend through Tauri commands.

calculate_fov

Calculate field of view for a camera system.

TypeScript:

import { invoke } from '@tauri-apps/api/core';

const result = await invoke('calculate_fov', {
  camera: {
    name: 'My Camera',
    sensor_width_mm: 36,
    sensor_height_mm: 24,
    pixel_width: 6000,
    pixel_height: 4000,
    focal_length_mm: 50,
  },
  distance_mm: 5000,
});

Response:

{
  camera: CameraSystem;
  fov_horizontal_deg: number;
  fov_vertical_deg: number;
  fov_width_mm: number;
  fov_height_mm: number;
  ppm_horizontal: number;
  ppm_vertical: number;
  gsd_mm: number;
}

calculate_hyperfocal

Calculate hyperfocal distance.

TypeScript:

const result = await invoke('calculate_hyperfocal', {
  focalLengthMm: 50,
  aperture: 8,
});

Response:

{
  hyperfocal_distance_mm: number;
}

calculate_dof

Calculate depth of field.

TypeScript:

const result = await invoke('calculate_dof', {
  distanceMm: 3000,
  focalLengthMm: 50,
  aperture: 2.8,
});

Response:

{
  near_limit_mm: number;
  far_limit_mm: number;
  total_dof_mm: number;
}

Data Types

CameraSystem

interface CameraSystem {
  name: string;
  sensor_width_mm: number;
  sensor_height_mm: number;
  pixel_width: number;
  pixel_height: number;
  focal_length_mm: number;
}

FovResult

interface FovResult {
  camera: CameraSystem;
  fov_horizontal_deg: number;
  fov_vertical_deg: number;
  fov_width_mm: number;
  fov_height_mm: number;
  ppm_horizontal: number;
  ppm_vertical: number;
  gsd_mm: number;
}

DofResult

interface DofResult {
  near_limit_mm: number;
  far_limit_mm: number;
  total_dof_mm: number;
}

HyperfocalResult

interface HyperfocalResult {
  hyperfocal_distance_mm: number;
}

Validation Constraints

All inputs are validated with these constraints:

const VALIDATION_CONSTRAINTS = {
  sensorWidth: { min: 0.1, max: 200 },      // mm
  sensorHeight: { min: 0.1, max: 200 },     // mm
  pixelWidth: { min: 10, max: 100000 },     // pixels
  pixelHeight: { min: 10, max: 100000 },    // pixels
  focalLength: { min: 0.1, max: 10000 },    // mm
  fov: { min: 0.1, max: 180 },              // degrees
  distance: { min: 0.01, max: 100000 },     // mm
};

Frontend Architecture

Pattern: Layered Architecture with strict separation of concerns

src/
├── core/        # Domain layer (types, constants)
├── services/    # Service layer (API, state)
├── ui/          # Presentation layer (components)
└── main.ts      # Entry point & orchestration

Structure

📦 core/ - Domain Layer (117 lines)

Pure domain logic with zero dependencies.

Files:

  • types.ts (37) - CameraSystem, FovResult, CameraWithResult, ReferenceObject
  • constants.ts (74) - Reference objects, camera presets, system colors
  • index.ts (6) - Module exports

Rule: No imports from other layers


🔌 services/ - Service Layer (110 lines)

Backend communication and state management.

Files:

  • api.ts (32) - Tauri IPC wrapper (calculateCameraFov, calculateFocalLengthFromFov)
  • store.ts (72) - Observable state store for camera systems
  • index.ts (6) - Module exports

Dependencies: core/ only


🎨 ui/ - Presentation Layer (406 lines)

DOM manipulation, rendering, and user interactions.

Files:

  • form.ts (118) - Form I/O, presets, validation
  • results.ts (46) - Results page rendering
  • visualization.ts (235) - Canvas FOV visualization
  • index.ts (7) - Module exports

Dependencies: core/ + services/


🚀 main.ts - Entry Point (170 lines)

Application bootstrap, event handlers, workflow orchestration.

Dependencies: All layers

Dependency Flow

main.ts
  ↓
┌─────────┬───────────┬────────┐
│  core/  │ services/ │   ui/  │
│ (types) │ (api,     │ (form, │
│         │  store)   │  viz)  │
└─────────┴───────────┴────────┘

Rules:

  • core/ → No dependencies
  • services/core/ only
  • ui/core/ + services/
  • main.ts → All layers
  • ❌ No circular dependencies

Import Examples

✅ Correct:

// main.ts
import { calculateCameraFov } from "./services";
import { drawVisualization } from "./ui";

// ui/form.ts
import type { CameraSystem } from "../core/types";
import { store } from "../services/store";

// services/api.ts
import type { FovResult } from "../core/types";

❌ Wrong:

// core/types.ts
import { store } from "../services/store"; // ❌ Core can't depend on services

// services/api.ts
import { displayResult } from "../ui/results"; // ❌ Service can't depend on UI

Adding Features

New UI Component:

  1. Create in ui/
  2. Import from core/ and services/
  3. Export from ui/index.ts

New Service:

  1. Create in services/
  2. Import from core/ only
  3. Export from services/index.ts

New Type:

  • Add to core/types.ts (available everywhere)

Building

Prerequisites

System Dependencies (Linux)

sudo apt-get update
sudo apt-get install -y \
  libwebkit2gtk-4.1-dev \
  libappindicator3-dev \
  librsvg2-dev \
  patchelf

Development Build

# Install frontend dependencies
pnpm install

# Run in development mode (hot reload)
pnpm tauri:dev

This starts:

  • Vite dev server on http://localhost:1420
  • Tauri window with the app
  • Hot reload for frontend changes
  • Rust recompilation on backend changes

Production Build

# Build optimized release
pnpm tauri:build

Output locations:

  • Linux: src-tauri/target/release/bundle/deb/
  • Windows: src-tauri/target/release/bundle/msi/
  • macOS: src-tauri/target/release/bundle/dmg/

CLI Only

cd src-tauri
cargo build --release --bin camera-optics-cli
# Binary: target/release/camera-optics-cli

Debug vs Release

Debug build (faster compile, slower runtime):

cargo build

Release build (slower compile, optimized):

cargo build --release

Build Flags

Disable warnings-as-errors

cargo build  # No RUSTFLAGS set

Enable all warnings

RUSTFLAGS="-D warnings" cargo build

Troubleshooting

WebKit errors on Linux

Install missing system dependencies:

sudo apt-get install libwebkit2gtk-4.1-dev

pnpm not found

npm install -g pnpm

Tauri build fails

Clear cache and rebuild:

rm -rf node_modules target
pnpm install
pnpm tauri build

Testing

Running Tests

Frontend Tests (Vitest)

# Run all tests
pnpm test

# Run with UI
pnpm test:ui

# Generate coverage
pnpm test:coverage

Rust Tests

cd src-tauri
cargo test

Rust Tests with Coverage

cd src-tauri
cargo install cargo-llvm-cov
cargo llvm-cov test

Test Structure

Frontend

Tests are located in src/**/*.test.ts:

  • src/services/store.test.ts - Store CRUD operations (22 tests)

Example test:

import { describe, it, expect, beforeEach } from 'vitest';
import { Store } from './store';

describe('Store', () => {
  it('should add new system', () => {
    const store = new Store();
    const system = {
      /* ... */
    };
    const id = store.add(system);
    expect(store.get(id)).toBeDefined();
  });
});

Rust

Tests are embedded in source files with #[cfg(test)]:

#![allow(unused)]
fn main() {
#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_calculate_fov() {
        let camera = CameraSystem { /* ... */ };
        let result = calculate_fov(&camera, 5000.0);
        assert!(result.fov_horizontal_deg > 0.0);
    }
}
}

CI Tests

Tests run automatically in GitHub Actions:

  • Format Check: cargo fmt --check, prettier --check
  • Lint: cargo clippy, (TypeScript via tsc)
  • Build: cargo build --release, pnpm build
  • Test: cargo test, pnpm test
  • Coverage: Combined Rust + TypeScript

Writing Tests

Frontend Test Guidelines

  1. Unit tests for utilities and services
  2. Integration tests for Tauri commands (mock backend)
  3. Use happy-dom for DOM testing
  4. Mock Tauri APIs with vi.mock()

Rust Test Guidelines

  1. Unit tests in same file as implementation
  2. Integration tests in tests/ directory
  3. Test edge cases (zero, negative, infinity)
  4. Use assert_eq! for exact matches
  5. Use assert! with tolerance for floats

Coverage Goals

  • Minimum: 70% overall
  • Target: 85%+ for critical paths
  • Excluded: UI interaction code, error handling

View coverage:

  • Frontend: coverage/index.html
  • Rust: Terminal output from cargo llvm-cov

Contributing

Thank you for considering contributing to Camera Optics Calculator!

Getting Started

  1. Fork the repository
  2. Clone your fork:
    git clone https://github.com/YOUR_USERNAME/camera-optics.git
    cd camera-optics
    
  3. Create a branch:
    git checkout -b feature/your-feature-name
    

Development Workflow

  1. Make your changes
  2. Format code:
    cargo fmt
    pnpm prettier --write .
    
  3. Lint:
    cargo clippy
    pnpm build  # TypeScript checking
    
  4. Test:
    cargo test
    pnpm test
    
  5. Commit:
    git add .
    git commit -m "feat: add new feature"
    

Commit Convention

Use Conventional Commits:

  • feat: New feature
  • fix: Bug fix
  • docs: Documentation
  • style: Formatting (no code change)
  • refactor: Code restructuring
  • test: Adding tests
  • chore: Maintenance

Examples:

  • feat: add hyperfocal distance calculation
  • fix: correct FOV calculation for wide angle lenses
  • docs: update API reference

Pull Request Process

  1. Update documentation if needed
  2. Add tests for new features
  3. Ensure CI passes (all checks must pass)
  4. Request review from maintainers

Code Style

TypeScript

  • Use TypeScript strict mode
  • Prefer const over let
  • Use single quotes for strings
  • 2-space indentation
  • Max line length: 100 characters

Rust

  • Follow rustfmt defaults
  • Use clippy recommendations
  • Document public APIs with /// comments
  • Prefer ? operator for error handling

Testing Requirements

  • New features must include tests
  • Bug fixes should include regression tests
  • Maintain or improve coverage percentage

Security

If you discover a security vulnerability:

  1. Do not open a public issue
  2. Email the maintainer directly
  3. Provide detailed reproduction steps

Questions?

  • Open an issue for discussion
  • Check existing issues/PRs first
  • Be respectful and constructive

We appreciate your contributions! 🎉