# Project export: TreeSats

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: TreeHacks 2026
- Tagline: Satellites that protect themselves: end-to-end autonomous satellite collision avoidance in communication-denied environments
- Devpost: https://devpost.com/software/treesats
- GitHub: https://github.com/lundeen06/treesats/
- Demo: https://docs.google.com/presentation/d/1h7nnk5VF-ATjgIedNr6wvBTDB2vPyhHVhBHOyp4VM6I/edit?usp=sharing
- Video: https://www.youtube.com/embed/745r3VhbIOQ?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Team: 3 GitHub contributor(s) — lundeen06 (36 commits), sidanantha (4 commits), rayanam2021 (3 commits)

## Devpost submission (written by the team)

### Overview

The Problem Tens of thousands of satellites orbit Earth today. SpaceX alone plans 1 million by 2030. At that scale, ground-based collision avoidance doesn't work, especially when communications and GPS jamming are expanding across Ukraine, Taiwan, Iran, and Myanmar, now affecting commercial satellites. The constraints: Satellites can't wait for ground commands when collisions are imminent Communication windows are limited or denied in contested regions GPS is unavailable or jammed Collision windows are measured in minutes, not hours Current satellite operators rely on ground stations for collision warnings and maneuver commands. This doesn't scale to mega-constellations in communication-denied environments. The Solution StarGuard provides autonomous collision avoidance and threat assessment using only onboard star tracker cameras—no ground link or GPS required. Three autonomous capabilities: Collision Detection - YOLOv8 detects satellites/debris in star tracker imagery, BoT-SORT tracks objects across frames, UKF estimates trajectories from angles-only measurements Collision Avoidance - Convex optimization computes fuel-optimal evasive maneuvers Threat Assessment - NVIDIA Cosmos VLM classifies satellite types and assesses maneuver intent All processing happens onboard via edge compute using existing spacecraft hardware (star trackers). How It Works 1. Orbital Simulation (Training Data Generation) Used Tensorgator (GPU-accelerated Keplerian propagator) to simulate 10,000+ satellite constellations and generate synthetic training data for sensors. Built a pinhole camera model to render 256×256 star tracker images from the satellite's frame, auto-labeled via brightness thresholding—satellites appear as bright spots against space. 2. Computer Vision Pipeline (Detection & Tracking) Fine-tuned YOLOv8 for tiny satellite detection (3-5 pixel objects), integrated BoT-SORT for persistent tracking across frames. Extracts pixel coordinates → bearing angles (elevation/azimuth). 3. Angles-Only Navigation (State Reconstruction) Reconstructs full 6-DOF state from sequential images: 1 photo → bearing angles 2 photos → bearing + depth → position vector 3 photos → finite difference → position + velocity 4. State Estimation (Trajectory Prediction) Unscented Kalman Filter fuses orbital dynamics (Keplerian propagation) with star tracker measurements for noise-resistant trajectory estimates and collision prediction. 5. Collision Avoidance (Maneuver Planning) Convex optimization solver (CVXPY) computes minimum-ΔV maneuvers subject to collision constraints, fuel budgets, and thrust limits. 6. Threat Assessment (Object Classification) NVIDIA Cosmos Reason VLM via vLLM provides real-time satellite classification from proximity operations imagery—identifies satellite types and assesses maneuver intent. Technical Challenges & Solutions Angles-Only Navigation: Reconstructing 3D trajectories from 2D bearing angles is ill-posed. Solved by leveraging temporal information across multiple frames and constraining solutions with orbital dynamics. GPU Orbital Mechanics: Kepler's equation (M = E - e sin E) requires iterative numerical methods. Optimized Newton-Raphson for CUDA with vectorized operations across 10k+ satellites—reducing simulation time from hours to seconds. Tiny Object Detection: Satellites appear as 3-5 pixel bright spots in star tracker imagery. Required careful YOLOv8 hyperparameter tuning and augmentation strategies for robust detection. Real-Time Onboard Performance: Star tracker processing, detection, tracking, and maneuver planning must happen in real-time onboard with limited compute. Achieved through efficient model selection, GPU acceleration, and edge compute optimization. Key Results End-to-end autonomy: Raw pixels → executed safety-constrained maneuvers with no human in the loop Fully automated pipeline: Zero manual labeling—synthetic data generation with auto-labeling Scalability: Handles mega-constellations (10,000+ satellites) on consumer GPUs Production-ready: Uses only existing spacecraft hardware (star trackers) Communication-independent: Operates without GPS or ground contact Impact StarGuard enables satellites to operate autonomously in communication-denied environments—critical for mega-constellations, commercial satellites over contested regions, and national security missions where ground contact is limited or unavailable. Technical Insights GPU parallelization transforms orbital simulation from hours to seconds Sequential observations + dynamics constraints enable angles-only navigation Star tracker imagery is ideal for object detection—high contrast, known background Convex optimization guarantees globally optimal collision avoidance solutions VLMs can run efficiently enough for real-time onboard inference and complex threat assessments What's Next Embedded deployment on radiation-hardened flight computers Multi-sensor fusion (star tracker + IMU + magnetometer) Cooperative avoidance protocols for inter-satellite coordination Hardware-in-the-loop testing on satellite testbeds

## README (from the GitHub repository)

# TreeSats 🛰️🌲
## Defending Space Sovereignty

**Satellites that protect themselves: end-to-end autonomous satellite collision avoidance in communication-denied environments**

TreeSats enables satellites to detect collisions, execute evasive maneuvers, and assess threats—all without GPS or ground contact. Using only star tracker cameras (standard spacecraft hardware), TreeSats provides autonomous protection in contested space.

---

## The Problem

Thousands of satellites orbit Earth today. SpaceX alone plans to deploy **1 million** by 2030. Meanwhile, **GPS and communications jamming** is expanding across Eastern Europe, Southeast Asia, and Myanmar—affecting even commercial satellites. Ground station control isn't viable at this scale in contested regions.

Satellites are no longer passive—they maneuver unpredictably, creating collision risks with no margin for error.

## The Solution:
<p align="left">
  <img src="https://drive.google.com/uc?export=view&id=1lbUgbUyeI4kUeUHzc2t0AbJ29y34Fm_s" alt="Starguard System Diagram" width="250em"/>
</p>

TreeSats' **Starguard** system provides three autonomous capabilities:

1. **Collision Detection** - YOLOv8 + BoT-SORT identify satellites and debris in star tracker imagery, UKF estimates trajectories from angles-only measurements
   <p align="center">
     <img src="https://drive.google.com/uc?export=view&id=1xzDJ-i59cHXLY0aJ8GwoAdINu9SL0_rB" alt="Detection" width="40%"/>
     <img src="https://drive.google.com/thumbnail?id=1NW5rMCgtGMTp9HBoaMQg_mX1bjjVmRvC&sz=w1000" alt="Collision Avoidance" width="50%"/>
   </p>

2. **Collision Avoidance** - Convex optimization computes fuel-optimal evasive maneuvers for non-adversarial and adversarial satellites
<p align="center">
  <img src="https://drive.google.com/thumbnail?id=1DY_pNTBg9X7kO-Gb5fsW9xsS55nn07S-&sz=w1000" alt="Collision Avoidance" width="60%"/>
  <img src="https://drive.google.com/thumbnail?id=1s6pmjE2kwMOZi4Kas_SFDr-vOzMF5uRe&sz=w1000" alt="Threat Assessment" width="35%"/>
</p>

3. **Threat Assessment** - NVIDIA Cosmos VLM conducts a satellite threat assessment for satellites in close proximity orbits
<p align="center">
  <img src="https://drive.google.com/thumbnail?id=1VvNbrP6w-B9JxoqLWdP5gTR2MLfsx2OF&sz=w1000" alt="Demo 0" width="80%"/>
  <img src="https://drive.google.com/uc?export=view&id=1BDNoNxncAkacbsRdfgB7F8g53OMLWowd" alt="Demo 2" width="80%"/>
</p>

All processing happens onboard using existing star tracker cameras. No GPS. No ground link required.

---

## Quick Start

```bash
# Install dependencies
pip install -r requirements.txt

# Generate synthetic training data (orbital sim + star tracker images)
python main.py --mode train --duration 0.5 --visualize

# Auto-label the generated images
cd sat/computer_vision
python auto_label.py batch data/images/train/ data/labels/train/

# Train YOLOv8 satellite detector
python train.py --epochs 150 --batch 64

# Run full pipeline (simulate → detect → track → avoid)
cd ../..
python main.py --mode pipeline
```

---

## Key Features

- **Communication-Independent**: Operates without GPS or ground contact using only star tracker imagery
- **Autonomous**: Real-time collision detection, avoidance, and threat assessment without ground intervention
- **Scalable**: GPU acceleration handles 10,000+ satellites simultaneously
- **Mission-Aware**: Fuel-optimal maneuvers via convex optimization

---

## Tech Stack

| Component | Technology | Purpose |
|-----------|-----------|---------|
| Orbital Propagation | Tensorgator (GPU) | High-fidelity Keplerian mechanics for 10k+ satellites |
| Object Detection | YOLOv8 | Detect satellites/debris in star tracker images |
| Object Tracking | BoT-SORT | Persistent IDs across frames for trajectory extraction |
| State Estimation | Unscented Kalman Filter | Fuse measurements with orbital dynamics |
| Maneuver Planning | CVXPY | Convex optimization for collision avoidance |
| Classification | NVIDIA Cosmos VLM | Identify satellite types and assess threats |

---

## Repository Structure

```
treesats/
├── main.py                    # Entry point (train/pipeline modes)
├── sim/                       # GPU orbital simulation
│   ├── simulate.py           # Tensorgator propagation
│   └── star_tracker/         # Camera rendering
├── sat/
│   ├── computer_vision/      # YOLOv8 detection + tracking
│   │   ├── train.py
│   │   ├── auto_label.py
│   │   └── TRAINING.md       # Complete training guide
│   └── control/              # Collision avoidance + GNC
├── treesats_proximity/        # VLM threat assessment
└── flyby_data/               # Test imagery (Cassini, TIE, etc.)
```

---

## Documentation

- **[Training Guide](sat/computer_vision/TRAINING.md)** - YOLOv8 training for satellite detection
- **[Auto-Labeling Guide](sat/computer_vision/AUTO_LABELING.md)** - Automated dataset generation
- **[Presentation Slides](slides/treesats.pdf)** - Full system overview

---

<br>

# Detailed Technical Overview

## How Starguard Works

### 1. Detection Pipeline

**Star trackers** are attitude sensors present on virtually all spacecraft. TreeSats repurposes them for threat detection:

```
Star Tracker Image → YOLOv8 Detection → BoT-SORT Tracking → Bearing Angles
```

- Filters out background stars (known catalog)
- Detects moving objects as bright spots
- Assigns persistent track IDs
- Extracts pixel coordinates → elevation/azimuth angles

### 2. Angles-Only Navigation

From sequential star tracker images, TreeSats reconstructs full 6-DOF state:

| Images | Information Extracted |
|--------|---------------------|
| 1 photo | Bearing angles (el, az) |
| 2 photos | Bearing + depth → **Position vector** |
| 3 photos | Finite difference → **Position + Velocity** |

**Output**: State vector `x = [x, y, z, ẋ, ẏ, ż]` in ECI frame

### 3. State Estimation (UKF)

An **Unscented Kalman Filter** fuses two information sources:

- **Dynamics Model**: Keplerian orbital propagation (`M = E - e sin E`)
- **Measurements**: Position/velocity from star tracker angles

This provides filtered, noise-resistant state estimates for collision prediction.

### 4. Collision Detection

Forward-propagate filtered trajectories to compute:

- **Closest approach distance** (miss distance)
- **Time to closest approach**
- **Probability of collision** (from covariance)

If risk exceeds threshold → trigger autonomous maneuver.

### 5. Evasive Maneuvers

**Convex optimization** (CVXPY) solves for minimum ΔV subject to:

- Collision avoidance constraint (minimum safe distance)
- Fuel budget limit
- Thrust magnitude/direction limits
- Mission constraints (maintain orbit parameters)

**Output**: Time-optimal thrust profile for safe evasion.

---

## GPU Orbital Simulation

TreeSats uses **Tensorgator** (GPU-accelerated Keplerian propagator) to simulate 10,000+ satellites:

```python
# Solve Kepler's equation: M = E - e sin E
# Distribute across 10k satellites on CUDA cores
positions, velocities = propagate_constellation(
    n_sats=10000,
    duration_hours=24,
    dt_seconds=10
)
```

**Performance**: 10,000 satellites × 8,640 timesteps (24h @ 10s) in seconds on GPU vs. hours on CPU.

---

## Star Tracker Rendering

**Pinhole camera model** generates synthetic 256×256 images:

1. Project satellite positions into camera frame (RTN → body frame)
2. Apply perspective projection with field-of-view
3. Render as bright dots on black background
4. Save as training images

**Auto-labeling**: Brightness thresholding automatically generates YOLO bounding boxes from ground truth positions—no manual annotation required.

---

## Computer Vision Pipeline

### Training Workflow

```bash
# 1. Generate synthetic data
python main.py --mode train --duration 0.5 --visualize

# 2. Auto-label (brightness thresholding)
cd sat/computer_vision
python auto_label.py batch data/images/train/ data/labels/train/

# 3. Validate dataset
python prepare_data.py validate

# 4. Train YOLOv8
python train.py --epochs 150 --batch 64 --model yolov8m
```

### Inf

[README truncated for size]

## Detected evidence (automated analysis)

Indexed codebase: 35 recognized source files, 263 KB.
- Python (language) — detected in the code
- PyTorch (technology) — claimed on Devpost, not found in the code

## Codebase structure (from repository index)

### Files (43 of 43)

```
.gitattributes
.gitignore
angles_only_nav/__init__.py
angles_only_nav/bearing_angles.py
deliverables/devpost.txt
main.py
README.md
requirements.txt
sandbox.py
sat/__init__.py
sat/computer_vision/__init__.py
sat/computer_vision/auto_label.py
sat/computer_vision/AUTO_LABELING.md
sat/computer_vision/config/dataset.yaml
sat/computer_vision/extract_frames.py
sat/computer_vision/inference.py
sat/computer_vision/pipeline.py
sat/computer_vision/prepare_data.py
sat/computer_vision/setup_training_data.py
sat/computer_vision/test_pipeline.py
sat/computer_vision/train.py
sat/computer_vision/TRAINING.md
sat/computer_vision/visualize_autolabel.py
sat/computer_vision/weights/satellite_detector/args.yaml
sat/computer_vision/weights/satellite_detector/weights/best.pt
sat/computer_vision/weights/satellite_detector/weights/last.pt
sat/computer_vision/yolo26n.pt
sat/computer_vision/yolov8m.pt
sat/control/__init__.py
sat/control/collision_avoidance.py
sat/control/rtn_to_eci_propagate.py
sim/__init__.py
sim/params.py
sim/plots.py
sim/simulate.py
sim/star_tracker/__init__.py
sim/star_tracker/star_tracker.py
test/__init__.py
test/test_bearing_hcw.py
tests/__init__.py
treesats_proximity/assessment.py
treesats_proximity/start_vllm.sh
treesats_proximity/stop_vllm.sh
```

### Dependencies

- requirements.txt: cvxpy, dotenv, matplotlib, numpy, opencv-python, requests, rich, tensorgator, tqdm

### Recent commits (newest first)

- Update Lundeen Cahilly's affiliation in README
- Adjust image widths in README for Collision Avoidance
- Merge pull request #6 from lundeen06/lundeen06-patch-1
- Enhance README with updated system capabilities and images
- Revise team member details in README
- Update Starguard system diagram image width
- Increase width of Starguard System Diagram image
- Starguard
- Update devpost.txt
- Fix header formatting in README.md
- Add subtitle to TreeSats project description
- Revise README title and project description
- readme and devpost
- readme
- make rich
- rich
- fine tune!
- threat assessment on videos
- Merge branch 'main' of https://github.com/lundeen06/treesats into main
- Merge pull request #5 from lundeen06/proximity

## Key source files (fetched from GitHub, selected and truncated for size)

### sat/computer_vision/TRAINING.md

```markdown
# Satellite Detection Training Guide

Train a YOLOv8 model to detect satellites and debris in 250x250 space images.

---

## Prerequisites

### Hardware
- **Recommended:** NVIDIA DGX Spark (128GB memory) or similar GPU
- **Minimum:** Any CUDA-capable GPU with 8GB+ VRAM

### Software
```bash
pip install ultralytics torch torchvision pillow
```

---

## Step 1: Prepare Your Images

### Image Requirements
- **Size:** 250×250 pixels
- **Format:** JPG, PNG, or TIFF
- **Content:** Black space background with bright dot-like satellites/debris

### Directory Structure
Place your images in the following folders:

```
computer_vision/data/
├── images/
│   ├── train/          # ~70% of your images
│   │   ├── img_001.jpg
│   │   ├── img_002.jpg
│   │   └── ...
│   ├── val/            # ~15% of your images
│   │   └── ...
│   └── test/           # ~15% of your images (held out for final evaluation)
│       └── ...
└── labels/
    ├── train/
    ├── val/
    └── test/
```

---

## Step 2: Label Your Images

You need to create a `.txt` label file for each image with bounding boxes around satellites/debris.

### Option A: Use a Labeling Tool (Recommended)

Use one of these tools to annotate your images:

| Tool | URL | Notes |
|------|-----|-------|
| **Roboflow** | [roboflow.com](https://roboflow.com) | Easiest, exports YOLO format directly |
| **CVAT** | [cvat.ai](https://cvat.ai) | Free, open source, powerful |
| **Label Studio** | [labelstud.io](https://labelstud.io) | Flexible, self-hosted |

**Export settings:**
- Format: **YOLO** or **YOLOv8**
- Place exported images in `data/images/{split}/`
- Place exported labels in `data/labels/{split}/`

### Option B: Manual Labeling with Python

```python
from sat.computer_vision import create_yolo_label

# For each image, define bounding boxes as [x_min, y_min, x_max, y_max] in pixels
annotations = [
    {"class": "satellite", "bbox": [100, 80, 108, 88]},   # 8x8 pixel box
    {"class": "debris", "bbox": [200, 150, 205, 155]},    # 5x5 pixel box
]

create_yolo_label(
    image_path="data/images/train/space_001.jpg",
    annotations=annotations,
    output_dir="data/labels/train"
)
```

### YOLO Label Format

Each label file (`.txt`) contains one line per object:
```
<class_id> <x_center> <y_center> <width> <height>
```

All values normalized 0-1. Example for a 250×250 image:
```
0 0.416 0.336 0.032 0.032
1 0.810 0.610 0.020 0.020
```

### Classes
| ID | Name | Description |
|----|------|-------------|
| 0 | satellite | Active/intact satellites |
| 1 | debris | Space debris fragments |

---

## Step 3: Validate Your Dataset

Before training, verify your data is properly set up:

```bash
cd sat/computer_vision
python prepare_data.py validate
```

**Expected output:**
```
============================================================
DATASET VALIDATION REPORT
============================================================

TRAIN SPLIT:
  Images: 800
  Labels: 800

VAL SPLIT:
  Images: 100
  Labels: 100

TEST SPLIT:
  Imag
[truncated — 4253 more characters]
```

### sat/computer_vision/AUTO_LABELING.md

```markdown
# Auto-Labeling Guide

Automatically detect and label bright satellites/debris in dark space images using brightness thresholding.

---

## How It Works

```
1. Load 250x250 image
2. Convert to grayscale
3. Threshold: pixels > 200 brightness = "bright"
4. Find connected blobs (contours)
5. Filter by size (ignore noise and artifacts)
6. Create bounding box around each blob
7. Write YOLO format label file
```

This works because satellites appear as **bright white dots** on a **black background** — high contrast makes simple thresholding very effective.

---

## Quick Start

```bash
cd sat/computer_vision

# 1. Preview detections on a sample image first
python auto_label.py preview data/images/train/sample.jpg

# 2. If detections look good, batch label all images
python auto_label.py batch data/images/train/ data/labels/train/
python auto_label.py batch data/images/val/ data/labels/val/
python auto_label.py batch data/images/test/ data/labels/test/

# 3. Validate the dataset
python prepare_data.py validate
```

---

## Commands

### Preview Detections

Visualize what will be detected before creating labels:

```bash
python auto_label.py preview <image_path> [options]
```

**Examples:**
```bash
# Display preview window
python auto_label.py preview data/images/train/img001.jpg

# Save preview to file (no window)
python auto_label.py preview data/images/train/img001.jpg --save preview.png

# Test different threshold
python auto_label.py preview data/images/train/img001.jpg --threshold 150
```

### Label Single Image

```bash
python auto_label.py label <image_path> <output_dir> [options]
```

**Example:**
```bash
python auto_label.py label data/images/train/img001.jpg data/labels/train/
# Creates: data/labels/train/img001.txt
```

### Batch Label Directory

Label all images in a folder at once:

```bash
python auto_label.py batch <images_dir> <labels_dir> [options]
```

**Example:**
```bash
python auto_label.py batch data/images/train/ data/labels/train/
# Output:
# Processing 500 images...
# Settings: threshold=200, min_area=4, max_area=500
# --------------------------------------------------
# Created label: data/labels/train/img001.txt (3 detections)
# Created label: data/labels/train/img002.txt (1 detections)
# ...
# --------------------------------------------------
# Done! Processed 500 images, 1247 total detections
```

---

## Parameters

| Parameter | Default | Description |
|-----------|---------|-------------|
| `--threshold` | 200 | Brightness cutoff (0-255). Pixels above this are detected. |
| `--min-area` | 4 | Minimum blob size in pixels. Filters out noise. |
| `--max-area` | 500 | Maximum blob size in pixels. Filters out large artifacts. |
| `--class-id` | 0 | Class ID for detections (0=satellite, 1=debris) |

---

## Tuning the Threshold

The `--threshold` parameter is the most important setting. It determines what counts as "bright enough" to be a satellite.

### Finding the Right Threshold

1. **Start with preview:**
   ```bash
   py
[truncated — 4771 more characters]
```

### requirements.txt

```
tensorgator
dotenv
numpy
matplotlib
cvxpy
tqdm
opencv-python
requests
rich
```

### main.py

```python
"""
Main entry point for TreeSats satellite simulation and navigation system.

Modes:
- train: Generate training data (simulation + star tracker images)
- pipeline: Run full system (simulation → autolabel → angles-only nav)
"""

import numpy as np
import argparse
import os
import sys

# Import from sim module
from sim.simulate import run_simulation
from sim.params import SATELLITE, CONSTELLATION, SIMULATION, STAR_TRACKER, VISUALIZATION, CONSTANTS, PATHS


def generate_training_data(args):
    """
    Generate training data: run full simulation and save star tracker images.

    Parameters:
    -----------
    args : argparse.Namespace
        Command line arguments
    """
    # Run simulation
    positions, times, constellation = run_simulation(
        n_sats=CONSTELLATION['n_satellites'],
        duration_hours=args.duration,
        dt_seconds=args.dt,
        sat=SATELLITE
    )

    # Print detailed position evolution for satellite 0
    print(f"\n{'='*80}")
    print(f"Position Evolution for Satellite 0 (ECI frame)")
    print(f"{'='*80}")
    print(f"{'Time (min)':>12} {'X (km)':>12} {'Y (km)':>12} {'Z (km)':>12} {'R (km)':>12} {'V (km/s)':>12}")
    print(f"{'-'*80}")

    # Expected orbital velocity for satellite 0
    mu = CONSTANTS['mu']
    a0 = constellation[0, 0]  # semi-major axis in meters
    expected_velocity = np.sqrt(mu / a0) / 1000  # km/s
    print(f"Expected orbital velocity: {expected_velocity:.3f} km/s\n")

    # Print every 1 minute (based on dt)
    interval = max(1, int(60 / args.dt))  # Print every ~1 minute
    for i in range(0, len(times), interval):
        t_min = times[i] / 60
        x, y, z = positions[i, 0, :] / 1000  # Satellite 0, convert to km
        r = np.linalg.norm(positions[i, 0, :]) / 1000  # Distance from Earth center

        # Calculate velocity by finite difference
        if i < len(times) - 1:
            dt = times[i+1] - times[i]
            vel = (positions[i+1, 0, :] - positions[i, 0, :]) / dt  # m/s
            v_mag = np.linalg.norm(vel) / 1000  # km/s
        else:
            v_mag = 0.0

        print(f"{t_min:12.2f} {x:12.2f} {y:12.2f} {z:12.2f} {r:12.2f} {v_mag:12.3f}")

    # Generate star tracker images
    print(f"\nGenerating star tracker images...")
    from sim.star_tracker import render_star_tracker_sequence
    from sat.control.rtn_to_eci_propagate import eci_to_rtn_basis
    import matplotlib.pyplot as plt
    from PIL import Image

    # Calculate observer velocity for RTN frame (using finite difference)
    # Positions are in meters, convert to km for RTN calculation
    observer_pos_km = positions[:, 0, :] / 1000  # Shape: (n_timesteps, 3)

    # Calculate velocity using central differences
    dt = times[1] - times[0]  # timestep in seconds
    observer_vel_km_s = np.zeros_like(observer_pos_km)
    observer_vel_km_s[0] = (observer_pos_km[1] - observer_pos_km[0]) / dt
    observer_vel_km_s[-1] = (observer_pos_km[-1] - observer_pos_km[-2]) / dt
    observer_vel_km_s[1:-1] = (observer_pos_km[2:] - observer_pos_km[:-2]) / (2 * dt)

    # Get RTN basis at first timestep to determine T direction
    basis_rtn = eci_to_rtn_basis(observer_pos_km[0], observer_vel_km_s[0])
    # basis_rtn has rows [R, T, N], so T direction is row 1
    t_direction_eci = basis_rtn[1, :]  # Tangential (along-track) direction in ECI

    print(f"Star tracker pointing in T (tangential) direction: {t_direction_eci}")

    # Render image sequence with T-direction pointing
    images, visible_sats_list, pixel_coords_list = render_star_tracker_sequence(
        positions,
        observer_index=STAR_TRACKER['observer_index'],
        fov_deg=STAR_TRACKER['fov_deg'],
        image_size=STAR_TRACKER['image_size'],
        pointing_direction=t_direction_eci
    )

    print(f"Star tracker images rendered:")
    print(f"  Total timesteps: {len(images)}")
    print(f"  Visible satellites per timestep: {[len(v) for v in visible_sats_list]}")

    # Create output directory
    output_dir = PATHS['data_dir']
    os.makedirs(output_dir, exist_ok=True)

    # Save raw 256x256 star tracker images
    print(f"\nSaving raw star tracker images to {output_dir}/...")
    for i, img_array in enumerate(images):
        # Convert to 8-bit grayscale (0-255)
        img_uint8 = (img_array * 255).astype(np.uint8)
        img = Image.fromarray(img_uint8, mode='L')
        img_path = os.path.join(output_dir, f'star_tracker_{i:04d}.png')
        img.save(img_path)

    print(f"Saved {len(images)} raw star tracker images (256x256 pixels)")

    # Generate MP4 video from star tracker images
    print(f"\nGenerating MP4 video from star tracker sequence...")
    try:
        import cv2

        # Video parameters - 30 fps by default for smooth playback
        video_fps = 30

        video_path = os.path.join(output_dir, 'star_tracker_sequence.mp4')
        fourcc = cv2.VideoWriter_fourcc(*'mp4v')
        video_writer = cv2.VideoWriter(video_path, fourcc, video_fps, (256, 256), isColor=False)

        for img_array in images:
            # Convert to 8-bit grayscale (0-255)
            img_uint8 = (img_array * 255).astype(np.uint8)
            video_writer.write(img_uint8)

        video_writer.release()
        print(f"Star tracker MP4 saved to: {video_path}")
        print(f"  Duration: {len(images)/video_fps:.1f} seconds at {video_fps} fps")
    except ImportError:
        print("Warning: opencv-python not installed. Skipping MP4 generation.")
        print("Install with: pip install opencv-python")

    # Visualize if requested
    if args.visualize:
        # 1. Visualize 3D orbits around Earth
        print(f"\nGenerating 3D orbit visualization...")
        from sim.plots import plot_orbits_static, animate_orbits_fast

        # Create plots output directory
        plots_dir = PATHS['plots_dir']
        os.makedirs(plots_dir, exist_ok=True)

        # Earth texture path
        earth_texture_path = PATHS['earth_texture']

        # Save orbit visualization in sim/plo
[truncated — 8090 more characters]
```

### sandbox.py

```python
#!/usr/bin/env python3
"""
Sandbox script for testing the simulation and CV pipeline independently.

Commands:
    python sandbox.py simulate   - Run simulation, generate video
    python sandbox.py track      - Run CV tracking, print matrices
    python sandbox.py visualize  - Generate annotated output video
    python sandbox.py all        - Run all steps
"""

import argparse
import subprocess
import sys
from pathlib import Path

# Paths
PROJECT_ROOT = Path(__file__).parent
SIMULATION_VIDEO = PROJECT_ROOT / "sat" / "data" / "star_tracker_sequence.mp4"
OUTPUT_DIR = PROJECT_ROOT / "output" / "sandbox"


def run_simulate():
    """Run the simulation to generate star tracker video."""
    print("=" * 60)
    print("STEP 1: Running Simulation")
    print("=" * 60)
    
    # Run main.py --mode train
    cmd = [sys.executable, "main.py", "--mode", "train"]
    print(f"Running: {' '.join(cmd)}\n")
    
    result = subprocess.run(cmd, cwd=PROJECT_ROOT)
    
    if result.returncode != 0:
        print("\n❌ Simulation failed!")
        return False
    
    # Check output
    if SIMULATION_VIDEO.exists():
        print(f"\n✓ Video generated: {SIMULATION_VIDEO}")
        print(f"  Size: {SIMULATION_VIDEO.stat().st_size / 1024:.1f} KB")
    else:
        print(f"\n❌ Video not found at: {SIMULATION_VIDEO}")
        return False
    
    return True


def run_track(max_frames: int = 10):
    """Run CV tracking and print the matrices."""
    print("=" * 60)
    print("STEP 2: Running CV Pipeline (Tracking)")
    print("=" * 60)
    
    if not SIMULATION_VIDEO.exists():
        print(f"❌ Video not found: {SIMULATION_VIDEO}")
        print("   Run 'python sandbox.py simulate' first.")
        return False
    
    print(f"Input video: {SIMULATION_VIDEO}\n")
    
    # Import the pipeline
    from sat.computer_vision.pipeline import SatelliteTracker
    
    tracker = SatelliteTracker()
    
    print(f"Processing video (showing first {max_frames} frames)...\n")
    print("-" * 60)
    
    for frame in tracker.stream_video(str(SIMULATION_VIDEO)):
        if frame.frame_id >= max_frames:
            print(f"... (stopped at {max_frames} frames, video has more)")
            break
        
        print(f"Frame {frame.frame_id}: {frame.num_satellites} satellites detected")
        
        if frame.num_satellites > 0:
            print("  Normalized matrix [sat_id, x, y]:")
            for row in frame.normalized:
                sat_id = int(row[0])
                x, y = row[1], row[2]
                print(f"    sat_id:{sat_id:2d}  x:{x:+.3f}  y:{y:+.3f}")
        print()
    
    print("-" * 60)
    print("✓ Tracking complete")
    return True


def run_visualize():
    """Generate annotated output video with tracking overlays."""
    print("=" * 60)
    print("STEP 3: Generating Tracked Video")
    print("=" * 60)
    
    if not SIMULATION_VIDEO.exists():
        print(f"❌ Video not found: {SIMULATION_VIDEO}")
        print("   Run 'python sandbox.py simulate' first.")
        return False
    
    # Create output directory
    OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
    output_video = OUTPUT_DIR / "tracked_output.mp4"
    
    print(f"Input:  {SIMULATION_VIDEO}")
    print(f"Output: {output_video}\n")
    
    # Import and run
    from sat.computer_vision.inference import track_video_custom
    
    result_path = track_video_custom(
        video_path=str(SIMULATION_VIDEO),
        output_path=str(output_video),
        conf_threshold=0.25,
    )
    
    print(f"\n✓ Tracked video saved: {result_path}")
    
    # Also save tracking data to JSON
    from sat.computer_vision.inference import track_satellites_to_file
    
    json_output = OUTPUT_DIR / "tracking_data.json"
    track_satellites_to_file(
        video_path=str(SIMULATION_VIDEO),
        output_path=str(json_output),
    )
    print(f"✓ Tracking data saved: {json_output}")
    
    return True


def run_all():
    """Run all steps in sequence."""
    print("\n" + "=" * 60)
    print("SANDBOX: Running Full Pipeline")
    print("=" * 60 + "\n")
    
    # Step 1: Simulate
    if not run_simulate():
        return False
    print("\n")
    
    # Step 2: Track
    if not run_track():
        return False
    print("\n")
    
    # Step 3: Visualize
    if not run_visualize():
        return False
    
    print("\n" + "=" * 60)
    print("ALL STEPS COMPLETE")
    print("=" * 60)
    print(f"\nOutputs:")
    print(f"  Simulation video: {SIMULATION_VIDEO}")
    print(f"  Tracked video:    {OUTPUT_DIR / 'tracked_output.mp4'}")
    print(f"  Tracking JSON:    {OUTPUT_DIR / 'tracking_data.json'}")
    
    return True


def main():
    parser = argparse.ArgumentParser(
        description="Sandbox for testing simulation and CV pipeline",
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog="""
Examples:
    python sandbox.py simulate   # Run simulation, generate video
    python sandbox.py track      # Run CV tracking, print matrices  
    python sandbox.py visualize  # Generate annotated output video
    python sandbox.py all        # Run all steps
        """
    )
    
    parser.add_argument(
        "command",
        choices=["simulate", "track", "visualize", "all"],
        help="Command to run"
    )
    
    parser.add_argument(
        "--max-frames",
        type=int,
        default=10,
        help="Max frames to display in track mode (default: 10)"
    )
    
    args = parser.parse_args()
    
    if args.command == "simulate":
        success = run_simulate()
    elif args.command == "track":
        success = run_track(max_frames=args.max_frames)
    elif args.command == "visualize":
        success = run_visualize()
    elif args.command == "all":
        success = run_all()
    
    sys.exit(0 if success else 1)


if __name__ == "__main__":
    main()

```

### test/__init__.py

```python
# Tests for treesats

```

### tests/__init__.py

```python
"""
Test suite for treesats.
"""

```

### sat/__init__.py

```python
"""
Satellite onboard systems module.
"""

```

### sim/__init__.py

```python
"""
Simulation module for satellite constellation propagation using Tensorgator.
"""

```

### treesats_proximity/stop_vllm.sh

```shell
#!/bin/bash

echo "Stopping Cosmos Reason 2 vLLM Server..."
docker stop cosmos-server
docker rm cosmos-server
echo "✓ Server stopped"

```

[25 more indexed source files omitted to keep this export small. The full file list is in the Codebase structure section above.]