@mytec: iter3.10 start, baseline rc ready
This commit is contained in:
504
docs/devlog/gpu_supp/RFCP-Iteration-3.5.2-Native-GPU-Polish.md
Normal file
504
docs/devlog/gpu_supp/RFCP-Iteration-3.5.2-Native-GPU-Polish.md
Normal file
@@ -0,0 +1,504 @@
|
||||
# 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 && (
|
||||
<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:**
|
||||
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 <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:
|
||||
|
||||
```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
|
||||
Reference in New Issue
Block a user