fix: DEM elevation overcounting and add hysteresis-only recalculation button

- dem.py: apply 45s median filter before hysteresis to suppress SRTM
  tile-boundary steps that were accumulating through the 5m threshold;
  raise DEM hysteresis threshold from 5m to 10m
- dem.py: back up elevation_m as elevation_m_original in timeseries
  before the first DEM overwrite, so original sensor data is preserved
- dem.py: add recalculate_elevation_hysteresis() — recomputes gain/loss
  from original recorded elevation (reads elevation_m_original if a DEM
  run already replaced elevation_m) using source-aware thresholds
  (5m barometric, 10m GPS/unknown); does not touch the elevation array
- edit/server.py, serve/server.py: split /recalculate-elevation into
  two endpoints: /recalculate-elevation/dem and
  /recalculate-elevation/hysteresis
- EditDrawer.svelte: replace single DEM button with two side-by-side
  buttons — "Recalculate (hysteresis)" (fast, offline) and
  "Recalculate (DEM)" (SRTM lookup)
This commit is contained in:
Davide Scaini
2026-04-20 21:41:23 +02:00
parent 2b7a37ed41
commit ebac3f50f4
4 changed files with 215 additions and 42 deletions
+19 -2
View File
@@ -428,8 +428,8 @@ async def save_activity(activity_id: str, payload: dict[str, Any]) -> JSONRespon
return JSONResponse({"ok": True, "sidecar": str(sidecar_path)})
@app.post("/api/activity/{activity_id}/recalculate-elevation")
async def recalculate_elevation_endpoint(activity_id: str) -> JSONResponse:
@app.post("/api/activity/{activity_id}/recalculate-elevation/dem")
async def recalculate_elevation_dem_endpoint(activity_id: str) -> JSONResponse:
"""Replace GPS altitude with DEM terrain elevation and recompute gain/loss.
Requires --dem-url to be set when starting bincio edit.
@@ -450,6 +450,23 @@ async def recalculate_elevation_endpoint(activity_id: str) -> JSONResponse:
raise HTTPException(422, str(e))
@app.post("/api/activity/{activity_id}/recalculate-elevation/hysteresis")
async def recalculate_elevation_hysteresis_endpoint(activity_id: str) -> JSONResponse:
"""Recompute gain/loss from original recorded elevation using source-aware hysteresis."""
dd = _get_data_dir()
_check_id(activity_id)
try:
from bincio.extract.dem import recalculate_elevation_hysteresis
from bincio.render.merge import merge_one
result = recalculate_elevation_hysteresis(dd, activity_id)
merge_one(dd, activity_id)
return JSONResponse(result)
except FileNotFoundError as e:
raise HTTPException(404, str(e))
except ValueError as e:
raise HTTPException(422, str(e))
@app.post("/api/activity/{activity_id}/images")
async def upload_image(activity_id: str, file: UploadFile = File(...)) -> JSONResponse:
dd = _get_data_dir()