qgis-tem-loader

qgis plugin for loading TEM geophysical inversion XYZ files as 3D objects
git clone git://src.adamsgaard.dk/qgis-tem-loader # fast
git clone https://src.adamsgaard.dk/qgis-tem-loader.git # slow
Log | Files | Refs | README | LICENSE Back to index

commit fcdf07cef585a42061e60de5ff2b2b5c5eb2f722
parent f689cd6bd1d37813ce21b1d777a145cdc498dcea
Author: Anders Damsgaard <anders@adamsgaard.dk>
Date:   Thu, 22 Oct 2026 09:09:07 +0000

docs: document mean resistivity maps

Diffstat:
MAGENTS.md | 6+++++-
MCHANGELOG.md | 5+++++
MREADME.md | 20+++++++++++++++++++-
3 files changed, 29 insertions(+), 2 deletions(-)

diff --git a/AGENTS.md b/AGENTS.md @@ -34,8 +34,10 @@ CI runs `make test` on Python 3.12; the GitLab pipeline also produces `tem_loade - `tem_loader/core.py` — XYZ parsing, CSV writing, resistivity color mapping. No QGIS dependency; fully unit-testable. - `tem_loader/interpolate.py` — Vectorized (numpy) IDW interpolation core: `GridSpec`, `axis_count`/`axis_cell_size`, `samples_from_layers`, `idw`, `interpolate_voxels`. Depends on numpy but has no QGIS dependency; fully unit-testable (numpy must be installed for tests). +- `tem_loader/mean_resistivity.py` — Mean resistivity maps per elevation interval: per-sounding thickness-weighted log10 interval mean (`interval_mean`), vectorized 2D IDW (`idw_grid`), and `mean_resistivity_maps`. numpy only; no QGIS dependency. +- `tem_loader/geotiff.py` — GeoTIFF writer using `osgeo.gdal` (ships with QGIS). `test/test_geotiff.py` skips when GDAL is not installed, as in CI. - `tem_loader/tem_loader.py` — QGIS plugin glue: dialogs, layer creation, CRS resolution, styling, group organization. -- `tem_loader/styles/*.qml` — QGIS layer style files for points, DOI, layers, and voxels (supports 3D renderer with dynamic color). +- `tem_loader/styles/*.qml` — QGIS layer style files for points, DOI, layers, and voxels (supports 3D renderer with dynamic color), and the raster style for mean resistivity maps (mirrors `core.RESISTIVITY_COLOR_RAMP`). - `test/data/` — fixture XYZ files for the three supported export families: TEMImage, Aarhus Workbench, and SCI Workbench. ## Conventions @@ -55,3 +57,5 @@ CI runs `make test` on Python 3.12; the GitLab pipeline also produces `tem_loade - `core.py` must remain free of QGIS imports — it is the only module tested without a running QGIS instance. - The SCI Workbench format aggregates per-layer rows into soundings at parse time; its code path diverges from the other two formats. - XYZ files with an extra leading index column (pandas-style) must be handled by dropping the first value before zipping. +- `tem_loader.py` imports `geotiff.py` (and thus `osgeo`) at module level; the plugin tests in `test_core.py` stub `osgeo` alongside `qgis` so they run in CI without GDAL. +- `test_fixture_doi_values_fit_fixed_scale` parses every `test/data/*.xyz`, including untracked files; keep unsupported local samples in the project root. diff --git a/CHANGELOG.md b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## [Unreleased] + +### Added +- "Mean resistivity maps…" menu item: mean resistivity maps (middelmodstandskort) for fixed elevation intervals, written as one styled GeoTIFF per interval. For each sounding, the thickness-weighted log10 mean resistivity above the DOI is computed within the interval, and the values are gridded with IDW in log10 space, leaving cells beyond the search radius blank. The dialog sets the elevation range, interval thickness, cell size, search radius, IDW power, minimum neighbors, and minimum interval coverage. + ## [0.3.0] - 2026-06-27 ### Added diff --git a/README.md b/README.md @@ -49,6 +49,24 @@ After loading one or more TEM datasets, the plugin can interpolate their resisti The interpolation is pure Python (no external dependencies). For large grids a progress bar is shown and can be cancelled. The grid is capped at 5 000 000 cells to prevent accidental out-of-memory runs; a warning is shown when the cap is exceeded. +### Mean resistivity maps + +Mean resistivity maps (Danish: middelmodstandskort) show the mean resistivity of the TEM models within fixed elevation intervals, one GeoTIFF per interval. + +1. Load at least one dataset with **Load TEM XYZ files…** so that `layers` (LineStringZ) and optionally `doi` (PointZ) layers are present in the project. +2. Go to **Plugins > TEM Loader > Mean resistivity maps…**. +3. Select the input `layers` and any `doi` layers (layers named `doi` are preselected). +4. Set the top and bottom elevation and the interval thickness (default 10 m), or use **Use elevation range of selected input layers** to fill the range from the data, snapped to the interval thickness. +5. Set the cell size (default 50 m), the search radius (**Auto** derives it as three times the median sounding spacing), the IDW power, the minimum number of neighbors, and the minimum interval coverage (default 50 %). +6. Click **OK**. One GeoTIFF per interval is written to `<name>_mean_resistivity/` next to the input GeoPackage, as `mean_resistivity_<top>_<bottom>.tif`, and added to a **Mean resistivity** group, top interval first. Only the top map is visible initially. + +Method: + +- For each sounding, the mean resistivity in an interval is the thickness-weighted mean of log10(resistivity) over the parts of the model layers inside the interval. Only the model above the DOI is used: layers are clipped to the DOI at import by default, and selected `doi` layers clip them again. +- A sounding contributes to an interval only when its model covers at least the minimum interval coverage of the interval thickness. +- The per-sounding values are gridded with inverse-distance weighting in log10 space. Grid cells with fewer than the minimum number of soundings within the search radius are left blank (nodata). +- Rasters are single-band Float32 GeoTIFFs in ohm-m with nodata `-9999`, in the CRS of the first input layer, styled with the same resistivity color classes as the `layers` layer. Intervals without data produce no map. + ## XYZ File Format Input files are whitespace-delimited with optional `/`-prefixed metadata lines. The loader currently supports these export variants: @@ -87,7 +105,7 @@ make package # build tem_loader.zip for installation make clean # remove the zip ``` -`core.py` (XYZ parsing and CSV helper output) has no QGIS dependency and is fully unit-testable. `tem_loader.py` (GeoPackage writing and layer loading) requires a running QGIS instance and is verified with mocked unit tests plus manual QGIS checks. +`core.py` (XYZ parsing and CSV helper output) has no QGIS dependency and is fully unit-testable. `interpolate.py` and `mean_resistivity.py` depend only on numpy. `geotiff.py` uses the GDAL Python bindings that ship with QGIS; its tests are skipped when GDAL is not installed. `tem_loader.py` (GeoPackage writing and layer loading) requires a running QGIS instance and is verified with mocked unit tests plus manual QGIS checks. ## References