# RFCP — Iteration 3.5.2: Native Backend + GPU Fix + UI Polish ## Overview Fix critical architecture issues: GPU indicator dropdown broken, GPU acceleration not working (CuPy in wrong Python environment), and prepare path to remove WSL2 dependency for end users. Plus UI polish items carried over from 3.5.1. **Priority:** GPU fixes first, then UI polish, then native Windows exploration. --- ## CRITICAL CONTEXT ### Current Architecture Problem ``` RFCP.exe (Electron, Windows) └── launches backend via WSL2: python3 -m uvicorn app.main:app --host 0.0.0.0 --port 8090 └── /usr/bin/python3 (WSL2 system Python 3.12) └── NO venv, NO CuPy installed User installed CuPy in Windows Python → backend doesn't see it. User installed CuPy in WSL system Python → needs --break-system-packages ``` ### GPU Hardware (Confirmed Working) ``` nvidia-smi output (from WSL2): NVIDIA GeForce RTX 4060 Laptop GPU Driver: 581.42 (Windows) / 580.95.02 (WSL2) CUDA: 13.0 VRAM: 8188 MiB GPU passthrough: WORKING ✅ ``` ### Files to Reference ``` backend/app/services/gpu_backend.py — GPUManager class backend/app/api/routes/gpu.py — GPU API endpoints frontend/src/components/ui/GPUIndicator.tsx — GPU badge/dropdown desktop/ — Electron app source installer/ — Build scripts ``` --- ## Task 1: Fix GPU Indicator Dropdown Z-Index (Priority 1 — 10 min) ### Problem GPU dropdown WORKS (opens on click, shows diagnostics, install hints) but renders BEHIND the right sidebar panel. The sidebar (Sites, Coverage Settings) has higher z-index than the GPU dropdown, so the dropdown is invisible/hidden underneath. See screenshots: dropdown is partially visible only when sidebar is made very narrow. It shows: "COMPUTE DEVICES", "CPU (NumPy)", install hints, "Run Diagnostics", and even diagnostics JSON — all working but hidden behind sidebar. ### Root Cause GPUIndicator dropdown z-index is lower than the right sidebar panel z-index. ### Solution In `GPUIndicator.tsx` — find the dropdown container div and set z-index higher than the sidebar: ```tsx {isOpen && (
...
)} ``` **Key requirements:** 1. `z-index: 9999` (or at minimum higher than sidebar) 2. Position: dropdown should open to the LEFT (toward center of screen) to avoid being cut off by right edge 3. `right-0` on the absolute positioning (anchored to right edge of badge) **Alternative approach** — use Tailwind z-index: ```tsx className="absolute top-full right-0 mt-1 z-[9999] ..." ``` **Also check:** The parent container of GPUIndicator might need `position: relative` for absolute positioning to work correctly against the right sidebar. ### Testing - [ ] Click "CPU" badge → dropdown appears ABOVE the sidebar - [ ] Full dropdown visible: devices, install hints, diagnostics - [ ] Dropdown doesn't get cut off on right side - [ ] Click outside → dropdown closes - [ ] Dropdown works at any window width --- ## Task 2: Install CuPy in WSL Backend (Priority 1 — 10 min) ### Problem CuPy installed in Windows Python, but backend runs in WSL2 system Python. ### Solution Add a startup check in the backend that detects missing GPU packages and provides clear instructions. Also, the Electron app should try to install dependencies on first launch. **Step 1: Backend startup GPU check** In `backend/app/main.py`, add on startup: ```python @app.on_event("startup") async def check_gpu_availability(): """Log GPU status on startup for debugging.""" import logging logger = logging.getLogger("rfcp.gpu") # Check CuPy try: import cupy as cp device_count = cp.cuda.runtime.getDeviceCount() if device_count > 0: name = cp.cuda.Device(0).name mem = cp.cuda.Device(0).mem_info[1] // 1024 // 1024 logger.info(f"✅ GPU detected: {name} ({mem} MB VRAM)") logger.info(f" CuPy {cp.__version__}, CUDA devices: {device_count}") else: logger.warning("⚠️ CuPy installed but no CUDA devices found") except ImportError: logger.warning("⚠️ CuPy not installed — GPU acceleration disabled") logger.warning(" Install: pip install cupy-cuda12x --break-system-packages") except Exception as e: logger.warning(f"⚠️ CuPy error: {e}") # Check PyOpenCL try: import pyopencl as cl platforms = cl.get_platforms() for p in platforms: for d in p.get_devices(): logger.info(f"✅ OpenCL device: {d.name.strip()}") except ImportError: logger.info("ℹ️ PyOpenCL not installed (optional)") except Exception: pass ``` **Step 2: GPU diagnostics endpoint enhancement** Enhance `/api/gpu/diagnostics` to return install commands: ```python @router.get("/diagnostics") async def gpu_diagnostics(): import platform, sys diagnostics = { "python": sys.version, "platform": platform.platform(), "executable": sys.executable, "is_wsl": "microsoft" in platform.release().lower(), "cuda_available": False, "opencl_available": False, "install_hint": "", "devices": [] } # Check nvidia-smi try: import subprocess result = subprocess.run( ["nvidia-smi", "--query-gpu=name,memory.total", "--format=csv,noheader"], capture_output=True, text=True, timeout=5 ) if result.returncode == 0: diagnostics["nvidia_smi"] = result.stdout.strip() except: diagnostics["nvidia_smi"] = "not found" # Check CuPy try: import cupy diagnostics["cupy_version"] = cupy.__version__ diagnostics["cuda_available"] = True count = cupy.cuda.runtime.getDeviceCount() for i in range(count): d = cupy.cuda.Device(i) diagnostics["devices"].append({ "id": i, "name": d.name, "memory_mb": d.mem_info[1] // 1024 // 1024, "backend": "CUDA" }) except ImportError: if diagnostics.get("is_wsl"): diagnostics["install_hint"] = "pip3 install cupy-cuda12x --break-system-packages" else: diagnostics["install_hint"] = "pip install cupy-cuda12x" return diagnostics ``` **Step 3: Frontend shows diagnostics clearly** In GPUIndicator dropdown, show: ``` ⚠ No GPU detected Your system: WSL2 + NVIDIA RTX 4060 To enable GPU acceleration: ┌─────────────────────────────────────────────┐ │ pip3 install cupy-cuda12x │ │ --break-system-packages │ └─────────────────────────────────────────────┘ Then restart RFCP. [Copy Command] [Run Diagnostics] ``` ### Testing - [ ] Backend startup logs GPU status - [ ] /api/gpu/diagnostics returns WSL detection + install hint - [ ] Frontend shows clear install instructions - [ ] After installing CuPy in WSL + restart → GPU appears in list --- ## Task 3: Terrain Profile Click Fix (Priority 2 — 5 min) ### Problem Clicking "Terrain Profile" button in ruler measurement also adds a point on the map. ### Solution In the Terrain Profile button handler: ```tsx const handleTerrainProfile = (e: React.MouseEvent) => { e.stopPropagation(); e.preventDefault(); // ... open terrain profile }; ``` Also check if the button is rendered inside a map click handler area — may need `L.DomEvent.disableClickPropagation(container)` on the parent. ### Testing - [ ] Click "Terrain Profile" → opens profile, NO new ruler point added - [ ] Map click still works normally when not clicking the button --- ## Task 4: Coverage Boundary — Real Contour Shape (Priority 2 — 45 min) ### Problem Current boundary is a rough circle/ellipse. Should follow actual coverage contour. ### Approaches **Option A: Shapely Alpha Shape (recommended)** ```python # backend/app/services/boundary_service.py from shapely.geometry import MultiPoint from shapely.ops import unary_union import numpy as np def calculate_coverage_boundary(points: list, threshold_dbm: float = -100) -> list: """Calculate concave hull of coverage area above threshold.""" # Filter points above threshold valid = [(p['lon'], p['lat']) for p in points if p['rsrp'] >= threshold_dbm] if len(valid) < 3: return [] mp = MultiPoint(valid) # Use convex hull first, then try concave try: # Shapely 2.0+ has concave_hull from shapely import concave_hull hull = concave_hull(mp, ratio=0.3) except ImportError: # Fallback to convex hull hull = mp.convex_hull # Simplify to reduce points (0.001 deg ≈ 100m) simplified = hull.simplify(0.001, preserve_topology=True) # Extract coordinates if simplified.geom_type == 'Polygon': coords = list(simplified.exterior.coords) return [{'lat': c[1], 'lon': c[0]} for c in coords] return [] ``` **Option B: Grid-based contour (simpler)** ```python def grid_contour_boundary(points: list, threshold_dbm: float, resolution: float): """Find boundary by detecting edge cells in grid.""" # Create binary grid: 1 = above threshold, 0 = below # Find cells where 1 is adjacent to 0 = boundary # Convert cell coords back to lat/lon # Return ordered boundary points ``` ### API Endpoint ```python # Add to coverage calculation response @router.post("/coverage/calculate") async def calculate_coverage(...): result = coverage_service.calculate(...) # Calculate boundary if result.points: boundary = calculate_coverage_boundary( result.points, threshold_dbm=settings.min_signal ) result.boundary = boundary return result ``` ### Frontend ```tsx // CoverageBoundary.tsx — use returned boundary coords // Instead of calculating alpha shape on frontend const CoverageBoundary = ({ points, boundary }) => { // If server returned boundary, use it if (boundary && boundary.length > 0) { return [p.lat, p.lon])} />; } // Fallback to current convex hull implementation return ; }; ``` ### Dependencies Need `shapely` installed: ``` pip install shapely # or pip3 install shapely --break-system-packages ``` Check if already in requirements.txt. ### Testing - [ ] 5km calculation → boundary follows actual coverage shape - [ ] 10km calculation → boundary is irregular (terrain-dependent) - [ ] Toggle boundary on/off works - [ ] Boundary doesn't crash with < 3 points --- ## Task 5: Results Popup Enhancement (Priority 3 — 15 min) ### Problem Calculation complete toast/popup doesn't show which models were used. ### Solution Enhance the toast message after calculation: ```tsx // Current: toast.success(`Calculated ${points} points in ${time}s`); // Enhanced: const modelCount = result.modelsUsed?.length ?? 0; const freq = sites[0]?.frequency ?? 0; const presetName = settings.preset ?? 'custom'; toast.success( `${points} pts • ${time}s • ${presetName} • ${freq} MHz • ${modelCount} models`, { duration: 5000 } ); ``` ### Testing - [ ] After calculation, toast shows: points, time, preset, frequency, model count --- ## Task 6: Native Windows Backend (Priority 3 — Research/Plan) ### Problem Current setup REQUIRES WSL2. Users without WSL2 can't use RFCP at all. ### Current Flow ``` RFCP.exe (Electron) → detects WSL2 → launches: wsl python3 -m uvicorn ... → backend runs in WSL2 Linux ``` ### Target Flow ``` RFCP.exe (Electron) → Option A: embedded Python (Windows native) → Option B: detect system Python (Windows) → Option C: keep WSL2 but with fallback ``` ### Research Tasks (don't implement yet, just investigate) 1. **Check how Electron currently launches backend:** ```bash # Look at desktop/ directory cat desktop/src/main.ts # or main.js # Find where it spawns python/uvicorn ``` 2. **Check if Windows Python works for backend:** ```powershell # In Windows PowerShell: cd D:\root\rfcp\backend python -m uvicorn app.main:app --host 0.0.0.0 --port 8090 # Does it start? What errors? ``` 3. **Evaluate embedded Python options:** - python-embedded (official, ~30 MB) - PyInstaller (bundle backend as .exe) - cx_Freeze - Nuitka (compile Python to C) 4. **Document findings** — create a brief report: ``` RFCP-Native-Backend-Research.md - Current architecture (WSL2 dependency) - Windows Python compatibility test results - Recommended approach - Migration steps - Timeline estimate ``` ### Goal User downloads RFCP.exe → installs → clicks icon → everything works. No WSL2. No manual pip install. GPU auto-detected. --- ## Implementation Order ### Priority 1 (30 min total) 1. **Task 1:** Fix GPU dropdown — make it clickable again 2. **Task 2:** GPU diagnostics + install instructions in UI 3. **Task 3:** Terrain Profile click propagation fix ### Priority 2 (1 hour) 4. **Task 4:** Coverage boundary real contour (shapely) 5. **Task 5:** Results popup enhancement ### Priority 3 (Research only) 6. **Task 6:** Investigate native Windows backend — report only, no implementation --- ## Build & Deploy ```bash # After implementation: cd /mnt/d/root/rfcp/frontend npx tsc --noEmit # TypeScript check npm run build # Production build # Rebuild Electron: cd /mnt/d/root/rfcp/installer bash build-win.sh # Test: # Install new .exe and verify GPU indicator works ``` --- ## Success Criteria - [ ] GPU dropdown opens when clicking badge - [ ] Dropdown shows device list or install instructions - [ ] After `pip3 install cupy-cuda12x --break-system-packages` in WSL + restart → GPU visible - [ ] Terrain Profile click doesn't add ruler points - [ ] Coverage boundary follows actual signal contour - [ ] Results toast shows model count and frequency - [ ] Native Windows backend research document created