Files
rfcp/RFCP-Dependencies-Installer.md

18 KiB
Raw Blame History

RFCP Dependencies & Installer Specification

Overview

All dependencies needed for RFCP to work out of the box, including GPU acceleration. The installer must handle everything — user should NOT need to run pip manually.


Python Dependencies

Core (MUST have)

# requirements.txt

# Web framework
fastapi>=0.104.0
uvicorn[standard]>=0.24.0
websockets>=12.0

# Scientific computing
numpy>=1.24.0
scipy>=1.11.0

# Geospatial
pyproj>=3.6.0           # coordinate transformations
shapely>=2.0.0          # geometry operations (boundary contours)

# Terrain data
rasterio>=1.3.0         # GeoTIFF reading (optional, for custom terrain)
# Note: SRTM .hgt files read with numpy directly

# OSM data
requests>=2.31.0        # HTTP client for OSM Overpass API
geopy>=2.4.0            # distance calculations

# Database
# sqlite3 is built-in Python — no install needed

# Utilities
orjson>=3.9.0           # fast JSON (optional, faster API responses)
pydantic>=2.0.0         # data validation (FastAPI dependency)

GPU Acceleration (OPTIONAL — auto-detected)

# requirements-gpu-nvidia.txt
cupy-cuda12x>=12.0.0    # For CUDA 12.x (RTX 30xx, 40xx)
# OR
cupy-cuda11x>=11.0.0    # For CUDA 11.x (older cards)

# requirements-gpu-opencl.txt  
pyopencl>=2023.1        # For ANY GPU (Intel, AMD, NVIDIA)

Development / Testing

# requirements-dev.txt
pytest>=7.0.0
pytest-asyncio>=0.21.0
httpx>=0.25.0           # async test client

System Dependencies

NVIDIA GPU Support

REQUIRED: NVIDIA Driver (comes with GPU)
REQUIRED: CUDA Toolkit 12.x (for CuPy)

Check if installed:
  nvidia-smi              → shows driver version
  nvcc --version          → shows CUDA toolkit version

If missing CUDA toolkit:
  Download from: https://developer.nvidia.com/cuda-downloads
  Select: Windows > x86_64 > 11/10 > exe (local)
  Size: ~3 GB
  
Alternative: cupy auto-installs CUDA runtime!
  pip install cupy-cuda12x 
  This bundles CUDA runtime (~700 MB) — no separate install needed

Intel GPU Support (OpenCL)

REQUIRED: Intel GPU Driver (usually pre-installed)
REQUIRED: Intel OpenCL Runtime

Check if installed:
  Open Device Manager → Display Adapters → Intel UHD/Iris

For OpenCL:
  Download Intel GPU Computing Runtime:
  https://github.com/intel/compute-runtime/releases
  
  Or: Intel oneAPI Base Toolkit (includes OpenCL)
  https://www.intel.com/content/www/us/en/developer/tools/oneapi/base-toolkit-download.html

AMD GPU Support (OpenCL)

REQUIRED: AMD Adrenalin Driver (includes OpenCL)
Download from: https://www.amd.com/en/support

Node.js / Frontend Dependencies

System Requirements

Node.js >= 18.0.0 (LTS recommended)
npm >= 9.0.0

Check:
  node --version
  npm --version

Frontend packages (managed by npm)

// package.json — key dependencies
{
  "dependencies": {
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "leaflet": "^1.9.4",
    "react-leaflet": "^4.2.0",
    "recharts": "^2.8.0",
    "zustand": "^4.4.0",
    "lucide-react": "^0.294.0"
  },
  "devDependencies": {
    "vite": "^5.0.0",
    "typescript": "^5.3.0",
    "tailwindcss": "^3.4.0",
    "@types/leaflet": "^1.9.0"
  }
}

Installer Script

Windows Installer (NSIS or Electron-Builder)

# install_rfcp.py — Python-based installer/setup script

import subprocess
import sys
import platform
import os
import shutil
import json

def check_python():
    """Verify Python 3.10+ is available."""
    version = sys.version_info
    if version.major < 3 or version.minor < 10:
        print(f"❌ Python 3.10+ required, found {version.major}.{version.minor}")
        return False
    print(f"✅ Python {version.major}.{version.minor}.{version.micro}")
    return True

def check_node():
    """Verify Node.js 18+ is available."""
    try:
        result = subprocess.run(["node", "--version"], capture_output=True, text=True)
        version = result.stdout.strip().lstrip('v')
        major = int(version.split('.')[0])
        if major < 18:
            print(f"❌ Node.js 18+ required, found {version}")
            return False
        print(f"✅ Node.js {version}")
        return True
    except FileNotFoundError:
        print("❌ Node.js not found")
        return False

def detect_gpu():
    """Detect available GPU hardware."""
    gpus = {
        "nvidia": False,
        "nvidia_name": "",
        "intel": False,
        "intel_name": "",
        "amd": False,
        "amd_name": ""
    }
    
    # Check NVIDIA
    try:
        result = subprocess.run(
            ["nvidia-smi", "--query-gpu=name,driver_version,memory.total", 
             "--format=csv,noheader"],
            capture_output=True, text=True, timeout=5
        )
        if result.returncode == 0:
            info = result.stdout.strip()
            gpus["nvidia"] = True
            gpus["nvidia_name"] = info.split(",")[0].strip()
            print(f"✅ NVIDIA GPU: {info}")
    except (FileNotFoundError, subprocess.TimeoutExpired):
        print("  No NVIDIA GPU detected")
    
    # Check Intel/AMD via WMI (Windows)
    if platform.system() == "Windows":
        try:
            result = subprocess.run(
                ["wmic", "path", "win32_videocontroller", "get", 
                 "name,adapterram,driverversion", "/format:csv"],
                capture_output=True, text=True, timeout=5
            )
            for line in result.stdout.strip().split('\n'):
                if 'Intel' in line:
                    gpus["intel"] = True
                    gpus["intel_name"] = [x for x in line.split(',') if 'Intel' in x][0]
                    print(f"✅ Intel GPU: {gpus['intel_name']}")
                elif 'AMD' in line or 'Radeon' in line:
                    gpus["amd"] = True
                    gpus["amd_name"] = [x for x in line.split(',') if 'AMD' in x or 'Radeon' in x][0]
                    print(f"✅ AMD GPU: {gpus['amd_name']}")
        except Exception:
            pass
    
    return gpus

def install_core_dependencies():
    """Install core Python dependencies."""
    print("\n📦 Installing core dependencies...")
    subprocess.run([
        sys.executable, "-m", "pip", "install", "-r", "requirements.txt",
        "--quiet", "--no-warn-script-location"
    ], check=True)
    print("✅ Core dependencies installed")

def install_gpu_dependencies(gpus: dict):
    """Install GPU-specific dependencies based on detected hardware."""
    print("\n🎮 Setting up GPU acceleration...")
    
    gpu_installed = False
    
    # NVIDIA — install CuPy (includes CUDA runtime)
    if gpus["nvidia"]:
        print(f"  Installing CuPy for {gpus['nvidia_name']}...")
        try:
            # Try CUDA 12 first (newer cards)
            subprocess.run([
                sys.executable, "-m", "pip", "install", "cupy-cuda12x",
                "--quiet", "--no-warn-script-location"
            ], check=True, timeout=300)
            print(f"  ✅ CuPy (CUDA 12) installed for {gpus['nvidia_name']}")
            gpu_installed = True
        except (subprocess.CalledProcessError, subprocess.TimeoutExpired):
            try:
                # Fallback to CUDA 11
                subprocess.run([
                    sys.executable, "-m", "pip", "install", "cupy-cuda11x",
                    "--quiet", "--no-warn-script-location"
                ], check=True, timeout=300)
                print(f"  ✅ CuPy (CUDA 11) installed for {gpus['nvidia_name']}")
                gpu_installed = True
            except Exception as e:
                print(f"  ⚠️  CuPy installation failed: {e}")
                print(f"  💡 Manual install: pip install cupy-cuda12x")
    
    # Intel/AMD — install PyOpenCL
    if gpus["intel"] or gpus["amd"]:
        gpu_name = gpus["intel_name"] or gpus["amd_name"]
        print(f"  Installing PyOpenCL for {gpu_name}...")
        try:
            subprocess.run([
                sys.executable, "-m", "pip", "install", "pyopencl",
                "--quiet", "--no-warn-script-location"
            ], check=True, timeout=120)
            print(f"  ✅ PyOpenCL installed for {gpu_name}")
            gpu_installed = True
        except Exception as e:
            print(f"  ⚠️  PyOpenCL installation failed: {e}")
            print(f"  💡 Manual install: pip install pyopencl")
    
    if not gpu_installed:
        print("    No GPU acceleration available — using CPU (NumPy)")
        print("  💡 This is fine! GPU just makes large calculations faster.")
    
    return gpu_installed

def install_frontend():
    """Install frontend dependencies and build."""
    print("\n🌐 Setting up frontend...")
    frontend_dir = os.path.join(os.path.dirname(__file__), "frontend")
    
    if os.path.exists(os.path.join(frontend_dir, "package.json")):
        subprocess.run(["npm", "install"], cwd=frontend_dir, check=True)
        subprocess.run(["npm", "run", "build"], cwd=frontend_dir, check=True)
        print("✅ Frontend built")
    else:
        print("⚠️  Frontend directory not found")

def download_terrain_data():
    """Pre-download SRTM terrain tiles for Ukraine."""
    print("\n🏔️  Checking terrain data...")
    cache_dir = os.path.expanduser("~/.rfcp/terrain")
    os.makedirs(cache_dir, exist_ok=True)
    
    # Ukraine bounding box: lat 44-53, lon 22-41
    # SRTM tiles needed for typical use
    required_tiles = [
        # Lviv oblast area (common test area)
        "N49E025", "N49E024", "N49E026",
        "N50E025", "N50E024", "N50E026",
        # Dnipro area
        "N48E034", "N48E035",
        "N49E034", "N49E035",
    ]
    
    existing = [f.replace(".hgt", "") for f in os.listdir(cache_dir) if f.endswith(".hgt")]
    missing = [t for t in required_tiles if t not in existing]
    
    if missing:
        print(f"  {len(missing)} terrain tiles needed (auto-download on first use)")
    else:
        print(f"  ✅ {len(existing)} terrain tiles cached")

def create_launcher():
    """Create desktop shortcut / launcher script."""
    print("\n🚀 Creating launcher...")
    
    if platform.system() == "Windows":
        # Create .bat launcher
        launcher = os.path.join(os.path.dirname(__file__), "RFCP.bat")
        with open(launcher, 'w') as f:
            f.write('@echo off\n')
            f.write('title RFCP - RF Coverage Planner\n')
            f.write('echo Starting RFCP...\n')
            f.write(f'cd /d "{os.path.dirname(__file__)}"\n')
            f.write(f'"{sys.executable}" -m uvicorn backend.app.main:app --host 0.0.0.0 --port 8888\n')
        print(f"  ✅ Launcher created: {launcher}")
    
    return True

def verify_installation():
    """Run quick verification tests."""
    print("\n🔍 Verifying installation...")
    
    checks = []
    
    # Check core imports
    try:
        import numpy as np
        checks.append(f"✅ NumPy {np.__version__}")
    except ImportError:
        checks.append("❌ NumPy missing")
    
    try:
        import scipy
        checks.append(f"✅ SciPy {scipy.__version__}")
    except ImportError:
        checks.append("❌ SciPy missing")
    
    try:
        import fastapi
        checks.append(f"✅ FastAPI {fastapi.__version__}")
    except ImportError:
        checks.append("❌ FastAPI missing")
    
    try:
        import shapely
        checks.append(f"✅ Shapely {shapely.__version__}")
    except ImportError:
        checks.append("⚠️  Shapely missing (boundary features disabled)")
    
    # Check GPU
    try:
        import cupy as cp
        device = cp.cuda.Device(0)
        checks.append(f"✅ CuPy → {device.name} ({device.mem_info[1]//1024//1024} MB)")
    except ImportError:
        checks.append("  CuPy not available")
    except Exception as e:
        checks.append(f"⚠️  CuPy error: {e}")
    
    try:
        import pyopencl as cl
        devices = []
        for p in cl.get_platforms():
            for d in p.get_devices():
                devices.append(d.name)
        checks.append(f"✅ PyOpenCL → {', '.join(devices)}")
    except ImportError:
        checks.append("  PyOpenCL not available")
    except Exception as e:
        checks.append(f"⚠️  PyOpenCL error: {e}")
    
    for check in checks:
        print(f"  {check}")
    
    return all("❌" not in c for c in checks)

def main():
    """Main installer entry point."""
    print("=" * 60)
    print("  RFCP — RF Coverage Planner — Installer")
    print("=" * 60)
    print()
    
    # Step 1: Check prerequisites
    print("📋 Checking prerequisites...")
    if not check_python():
        sys.exit(1)
    check_node()
    
    # Step 2: Detect GPU
    gpus = detect_gpu()
    
    # Step 3: Install dependencies
    install_core_dependencies()
    install_gpu_dependencies(gpus)
    
    # Step 4: Frontend
    install_frontend()
    
    # Step 5: Terrain data
    download_terrain_data()
    
    # Step 6: Launcher
    create_launcher()
    
    # Step 7: Verify
    print()
    success = verify_installation()
    
    # Summary
    print()
    print("=" * 60)
    if success:
        print("  ✅ RFCP installed successfully!")
        print()
        print("  To start RFCP:")
        print("    python -m uvicorn backend.app.main:app --port 8888")
        print("    Then open: http://localhost:8888")
        print()
        if gpus["nvidia"]:
            print(f"  🎮 GPU: {gpus['nvidia_name']} (CUDA)")
        elif gpus["intel"] or gpus["amd"]:
            gpu_name = gpus["intel_name"] or gpus["amd_name"]
            print(f"  🎮 GPU: {gpu_name} (OpenCL)")
        else:
            print("  💻 Mode: CPU only")
    else:
        print("  ⚠️  Installation completed with warnings")
        print("  Some features may be limited")
    print("=" * 60)

if __name__ == "__main__":
    main()

Electron-Builder / NSIS Packaging

For .exe Installer

# electron-builder.yml

appId: com.rfcp.coverage-planner
productName: "RFCP - RF Coverage Planner"
copyright: "RFCP 2026"

directories:
  output: dist
  buildResources: build

files:
  - "backend/**/*"
  - "frontend/dist/**/*"
  - "requirements.txt"
  - "install_rfcp.py"
  - "!**/*.pyc"
  - "!**/node_modules/**"
  - "!**/venv/**"

extraResources:
  - from: "python-embedded/"
    to: "python/"
  - from: "terrain-data/"
    to: "terrain/"

win:
  target:
    - target: nsis
      arch: [x64]
  icon: "build/icon.ico"

nsis:
  oneClick: false
  allowToChangeInstallationDirectory: true
  installerIcon: "build/icon.ico"
  license: "LICENSE.md"
  
  # Custom NSIS script for GPU detection
  include: "build/gpu-detect.nsh"
  
  # Install steps:
  # 1. Extract files
  # 2. Run install_rfcp.py (detects GPU, installs deps)
  # 3. Create Start Menu shortcuts
  # 4. Create Desktop shortcut

Portable Version (.zip)

RFCP-Portable/
├── RFCP.bat                    # Main launcher
├── install.bat                 # First-time setup
├── backend/
│   ├── app/
│   │   ├── main.py
│   │   ├── api/
│   │   ├── services/
│   │   └── models/
│   └── requirements.txt
├── frontend/
│   └── dist/                   # Pre-built frontend
├── python/                     # Embedded Python (optional)
│   ├── python.exe
│   └── Lib/
├── terrain/                    # Pre-cached .hgt files
│   ├── N49E025.hgt
│   └── ...
├── data/
│   ├── osm_cache.db           # SQLite cache (created on first run)
│   └── config.json            # User settings
└── README.md

install.bat (First-Time Setup)

@echo off
title RFCP - First Time Setup
echo ============================================
echo   RFCP - RF Coverage Planner - Setup
echo ============================================
echo.

REM Check if Python exists
python --version >nul 2>&1
if errorlevel 1 (
    echo ERROR: Python not found!
    echo Please install Python 3.10+ from python.org
    pause
    exit /b 1
)

REM Run installer
python install_rfcp.py

echo.
echo Setup complete! Run RFCP.bat to start.
pause

RFCP.bat (Launcher)

@echo off
title RFCP - RF Coverage Planner
cd /d "%~dp0"

REM Check if installed
if not exist "backend\app\main.py" (
    echo ERROR: RFCP not found. Run install.bat first.
    pause
    exit /b 1
)

echo Starting RFCP...
echo Open http://localhost:8888 in your browser
echo Press Ctrl+C to stop
echo.

python -m uvicorn backend.app.main:app --host 0.0.0.0 --port 8888

Dependency Size Estimates

Component Size
Python (embedded) ~30 MB
Core pip packages ~80 MB
CuPy + CUDA runtime ~700 MB
PyOpenCL ~15 MB
Frontend (built) ~5 MB
SRTM terrain (Ukraine) ~300 MB
Total (with CUDA) ~1.1 GB
Total (CPU only) ~415 MB

Runtime Requirements

Resource Minimum Recommended
RAM 4 GB 8+ GB
Disk 500 MB 2 GB (with terrain cache)
CPU 4 cores 8+ cores
GPU - NVIDIA GTX 1060+ / Intel UHD 630+
OS Windows 10 Windows 10/11 64-bit
Python 3.10 3.11+
Node.js 18 20 LTS

Auto-Update Mechanism (Future)

# Check for updates on startup
async def check_for_updates():
    try:
        response = await httpx.get(
            "https://api.github.com/repos/user/rfcp/releases/latest",
            timeout=5
        )
        latest = response.json()["tag_name"]
        current = get_current_version()
        
        if latest != current:
            return {
                "update_available": True,
                "current": current,
                "latest": latest,
                "download_url": response.json()["assets"][0]["browser_download_url"]
            }
    except:
        pass
    return {"update_available": False}