14 KiB
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:
{isOpen && (
<div
className="absolute top-full mt-1 bg-dark-surface border border-dark-border
rounded-lg shadow-2xl p-3 min-w-[300px]"
style={{ zIndex: 9999 }} // MUST be above sidebar (which is ~z-50 or z-auto)
>
...
</div>
)}
Key requirements:
z-index: 9999(or at minimum higher than sidebar)- Position: dropdown should open to the LEFT (toward center of screen) to avoid being cut off by right edge
right-0on the absolute positioning (anchored to right edge of badge)
Alternative approach — use Tailwind z-index:
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:
@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:
@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:
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)
# 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)
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
# 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
// 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 <Polygon positions={boundary.map(p => [p.lat, p.lon])} />;
}
// Fallback to current convex hull implementation
return <CurrentImplementation points={points} />;
};
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:
// 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)
- Check how Electron currently launches backend:
# Look at desktop/ directory
cat desktop/src/main.ts # or main.js
# Find where it spawns python/uvicorn
- Check if Windows Python works for backend:
# 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?
-
Evaluate embedded Python options:
- python-embedded (official, ~30 MB)
- PyInstaller (bundle backend as .exe)
- cx_Freeze
- Nuitka (compile Python to C)
-
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)
- Task 1: Fix GPU dropdown — make it clickable again
- Task 2: GPU diagnostics + install instructions in UI
- Task 3: Terrain Profile click propagation fix
Priority 2 (1 hour)
- Task 4: Coverage boundary real contour (shapely)
- Task 5: Results popup enhancement
Priority 3 (Research only)
- Task 6: Investigate native Windows backend — report only, no implementation
Build & Deploy
# 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-packagesin 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