feat: [dsm-rgb-landcover-tirol] DSM geometry + orthophoto/land-cover albedo for the Tirol demo - #62
Open
JustCreature wants to merge 9 commits into
Open
feat: [dsm-rgb-landcover-tirol] DSM geometry + orthophoto/land-cover albedo for the Tirol demo#62JustCreature wants to merge 9 commits into
JustCreature wants to merge 9 commits into
Conversation
… sentinels in DEM readers The BEV ALS DSM declares NoData = +3.4028235e38 via the GDAL_NODATA ASCII tag (42113). The old predicate (NaN / < -1000 / == 0) let those cells through as valid 3.4e38 m elevations, poisoning max_terrain_h and the raymarch sky exit. extract_window, parse_geotiff_auto and the overview cache builder now share is_nodata_value(), which also honours the file's declared GDAL_NODATA value for finite custom sentinels. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…to dem_io composite_surface_over paints the valid cells of a DSM window onto the fine-tier DTM canvas with a short BFS feather at the ALS coverage and window boundaries. The DTM stays the canvas so fine-tier coverage never shrinks where the DSM window is clipped, and real canopy/building heights are never sanded down the way fill_nodata_from_base's 30 px outward blend would. Canvas sentinel cells take the overlay at full weight so nothing ever blends toward -9999. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ow reader to dem_io extract_color_window reads a camera-centred window from the BEV 20 cm orthophoto mosaic (ModernJPEG YCbCr chunks decoded via the tiff crate's zune path, BT.601-converted on the caller's thread) and nearest-resamples the land-cover raster onto the same grid, packing one RGBA8 buffer: RGB = albedo, A = material code ladder (water 255 / high veg 192 / med veg 128 / building 64 / other 0 — codes spaced >= 64 apart so bilinear boundary mixes degrade gracefully). The LC mosaic is Gray(4): 4-bit nibble-packed chunks are unpacked in extract_u8_window. ifd_overview_levels walks the IFD chain filtered by NewSubfileType so the RGB mosaic's interleaved transparency-mask IFDs don't shift the overview indices the way raw ifd_scales would. LC class codes pinned empirically with the new inspect_color example against single-class reference spots (Achensee = 100% class 5 water, Hintertux glacier = 100% class 2 ground/ice, forest 96% class 1, meadow 88% class 6, town 44% class 4 buildings). Gated integration tests run against the local multi-GB tiles and skip cleanly elsewhere. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…nd shader draping to render_gpu Two mipmapped Rgba8Unorm windows (fine + close) join the canonical bind group as entries 20-23, with their own origin/extent/rotation uniform blocks (CameraUniforms grows 224 -> 288 bytes; layout test pins the new offsets). upload_ortho_fine/close follow the established drop-first reload cycle (placeholder swap -> rebuild_bind_group -> poll(Wait) -> tracked alloc) and set_ortho_*_inactive eagerly frees the textures. gen_rgba_mip_bytes box-filters RGB but takes nearest alpha so the land-cover material codes survive the mip chain. Deliberately non-sRGB: the whole shader works in display-space 0-255. Shader: hit-point albedo = procedural ramp -> close ortho -> fine ortho with the same edge-smoothstep + per-window rotation as the height tiers and the existing distance-heuristic mip. Where ortho wins, the brightness floor is lifted (the photo carries baked sun shading; full diffuse on top double-shades north faces). Water material (alpha >= ~0.9) blends toward a lake tint and adds a mirror-direction sun glint. ortho_mode is threaded through dispatch_frame; the drape is a no-op while both extents are 0. GPU suites gain upload->inactive lifecycle and vram-accounting coverage for the new texture pair. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…albedo tiers in the viewer DemoViewConfig gains surface_tile_paths / ortho_tile_paths / landcover_tile_paths (serde-defaulted to the BEV Tirol mosaics, resolved against tiles_dir; missing files degrade gracefully via build_tile_index, so old configs keep working untouched). The fine worker extracts a DSM window with the same centre/radius as its DTM window and composites it on with composite_surface_over — canopy and buildings become fine-tier geometry with normals, DDA self-shadowing and bicubic smoothing for free. StreamingTier becomes generic over its payload (TierCentre trait) so two new ortho workers (fine ~0.8 m/px, close ~6.4 m/px, radii per the new ortho_radii(VramClass) presets) reuse all drift/trigger bookkeeping. They resolve mask-aware overview levels once per file, read RGBA windows via extract_color_window, pre-pack mips, and the frame loop uploads them like any other tier. Base reloads deactivate + invalidate both windows (their origins are base-relative); the OOM ladder gains step 0 = drop ortho before any height tier; T toggles the drape. Includes a gated offscreen smoke test that renders the full DSM + ortho pipeline against the real tiles through the production placement math and asserts the frame contains ortho-textured terrain (/tmp/offscreen_dsm_ortho.png for visual inspection). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…d, add rendering-approaches learning note CLAUDE.md gains the DSM + ortho/land-cover architecture section (bind group entries 20-23, material code ladder, empirically pinned LC classes, OOM step 0), the new dem_io modules, and the updated tiers.rs inventory. The learnings note compares polygons vs SDF raymarching vs this renderer's heightfield raymarch and maps the trade-offs onto shader_texture.wgsl. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
…t for readable texel sum cargo clippy --all-targets -D warnings flags the deliberate (0 + 40 + 80 + 120) / 4 in the mip-average assertion. Keep the four contributing texels visible instead of collapsing to a bare 60, matching the existing rationale'd allow on the f16 mip shape test. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…rtho black coverage Two rendering fixes for the DSM + ortho draping: 1. Buildings and trees rendered like smooth mountains. The fine tier now carries the DSM, but the Catmull-Rom bicubic normal smoothing (meant for bare-earth ridgelines within smooth_radius_m) rounded every building wall and canopy edge into a hill. Gate the smoothing by world-slope magnitude: blend back to the raw per-texel Sobel normal where the gradient is wall-steep (smoothstep 1.0->2.5 m/m) so man-made faces and canopy edges stay crisp while gentle terrain stays bicubic-smooth. 2. Big black spot where the ortho coverage ends. The BEV mosaic fills areas with no photo coverage with pure black (0,0,0), which draped onto the terrain. Gate each ortho sample by luminance (smoothstep 2->16) so near-black no-data fades out and the procedural terrain colour shows through; real ortho pixels stay well above the threshold. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…nt and IFD enumeration The color/ortho readers and placement math only ran against the multi-GB BEV mosaics, which CI never has, so they showed as uncovered and dropped patch coverage below the 60% gate. Add synthetic-fixture tests that exercise the same production paths without the real tiles: - color_synth.rs: tiny in-process RGB + single-band GeoTIFFs drive extract_color_window (RGB pass-through, land-cover resample, material packing, georef stub) and landcover_histogram (color.rs 18% -> 95%). - tiers.rs inline tests for cross_crs_world_origin[_and_extent]: same-CRS exact placement plus both cross-CRS branches (geographic base, projected base) through real proj4 transforms (tiers.rs 28% -> 69%). - synthetic.rs: ifd_overview_levels over a multi-IFD overview cache (geotiff.rs 40% -> 98%). Patch coverage 56% -> 81% under the CI condition (tiles absent). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
The Tirol demo view now renders a photo-real world: trees and buildings as actual geometry (BEV ALS DSM drives the fine tier), the 20 cm BEV orthophoto draped as albedo near/mid range, and the land-cover raster as a material layer (water gets a flat tint + sun glint). Everything is config-driven via
config.toml— no UI changes — and every new layer degrades gracefully when its file is missing, so the demo keeps working on machines without the mosaics.big_size/ALS_DSM_CRS3035RES50000mN2650000E4450000.tifcolor/2019470_Mosaik_RGB.tifcolor/2022470_Mosaik_LC.tifHow
dem_io
GDAL_NODATA = +3.4028235e38sailed through the oldNaN / < -1000 / == 0predicate and parsed as a valid 3.4e38 m elevation, poisoningmax_terrain_hand the march's sky exit.extract_window,parse_geotiff_autoand the overview-cache builder now shareis_nodata_value(), which reads the GDAL_NODATA ASCII tag (42113) and rejects the float-max family.composite_surface_over(canvas, overlay, feather_px): paints valid DSM cells onto the fine-tier DTM with a 6 px BFS feather at the ALS coverage edge. The DTM stays the canvas so fine-tier coverage never shrinks where the DSM window is clipped — deliberately notfill_nodata_from_basewith swapped roles, whose 30 px outward blend would sand real canopy down to bare earth along every coverage edge.extract_color_window(color.rs): camera-centred window from the JPEG-YCbCr ortho (the tiff crate's zune path returns raw YCbCr; BT.601→RGB happens on the worker) + land cover nearest-resampled onto the same grid. Output is one RGBA8 buffer: RGB = albedo, A = material code ladder (water 255 / high veg 192 / med veg 128 / building 64 / other 0 — codes ≥ 64 apart so bilinear boundary mixes degrade into the neighbouring band instead of aliasing to an unrelated material).ifd_overview_levels: NewSubfileType-filtered IFD walk. The RGB mosaic interleaves per-dataset transparency-mask IFDs with its overview chain, so a raw scale index (à laifd_scales) would mis-pair scales with IFDs.extract_u8_window. Class codes are undocumented; they were pinned empirically with the newinspect_colorexample against single-class spots: Achensee deep centre = 100 % class 5 (water), Hintertux glacier = 100 % class 2 (ground/ice), forest slope = 96 % class 1 (high veg), valley meadow = 88 % class 6 (low veg), Mayrhofen centre = 44 % class 4 (buildings).render_gpu
Rgba8Unormortho windows join the canonical bind group as entries 20–23, each with its own origin/extent/cos_rot/sin_rotuniform block (CameraUniforms224 → 288 bytes; the std140 layout test pins the new offsets). Deliberately non-sRGB — the whole shader works in display-space 0–255.upload_ortho_fine/closefollow the established drop-first reload cycle (placeholder swap →rebuild_bind_group→poll(Wait)→ tracked alloc) so reload peak staysmax(old, new);set_ortho_*_inactiveeagerly frees. All allocations go through the vram tracker.gen_rgba_mip_bytesbox-filters RGB but takes nearest alpha — averaging the discrete material codes would fabricate classes at every mip.mix(brightness, clamp(brightness, 0.55, 1.0), w·0.7)) — the photo carries the capture day's baked sun shading, and full diffuse on top double-shades north faces. Water (A ≥ ~0.9) blends toward a lake tint and adds a mirror-direction sun glint.viewer / launcher
DemoViewConfiggainssurface_tile_paths/ortho_tile_paths/landcover_tile_paths(serde-defaulted to the BEV mosaics, resolved againsttiles_dir; old configs parse untouched).StreamingTieris now generic over its payload (TierCentretrait), so two new ortho workers (fine ≈ 0.8 m/px, close ≈ 6.4 m/px; radii per the newortho_radii(VramClass)presets) reuse all drift/trigger bookkeeping. Steady-state ortho cost: ≈ 170 MB (Mid), ≈ 330 MB (High), ≈ 17 MB (Low).Ttoggles the ortho drape;R(debug reload) now also invalidates the ortho tiers.Post-review rendering fixes
smooth_radius_m) was rounding every building wall and canopy edge into a hill. The smoothing is now gated by world-slope magnitude — it blends back to the raw per-texel Sobel normal where the gradient is wall-steep (smoothstep(1.0, 2.5)m/m), so man-made faces and canopy edges stay crisp while gentle terrain stays smooth. (Note: true hollow geometry under a tree canopy isn't representable in a single-valued heightfield — this makes trees/buildings read as distinct sharp objects, which was the visible problem.)(0,0,0), which draped onto the terrain as a black hole. Each ortho sample is now gated by luminance (smoothstep(2, 16)) so near-black no-data fades out and the procedural terrain colour shows through; real ortho pixels stay well above the threshold, making the coverage edge a clean one-texel fade.Verification
clippy --all-targets -D warningsclean.write_texturecalls; zero wgpu validation errors.Coverage
The color/ortho readers and placement math only ran end-to-end against the multi-GB mosaics, which CI lacks, so they showed uncovered and dropped patch coverage below the 60 % gate. Synthetic-fixture tests now exercise the same production paths without the real files: patch coverage 56 % → 81 % (measured under the CI condition, tiles absent).
dem_io/src/color.rsdem_io/src/geotiff.rsviewer/tiers.rsviewer/mod.rsstays uncovered by design — it's the winit/GPU event-loop glue the project's testing policy excludes.Note: the default Hintertux camera sits ~200 m west of the DSM coverage edge (starts ~11.69°E) — fly east into the Tux/Ziller valleys (Mayrhofen is the showcase) to see buildings and forest canopy in relief.
🤖 Generated with Claude Code