Skip to content

better support for radiometric panels - #2030

Open
maurerle wants to merge 4 commits into
OpenDroneMap:masterfrom
idt-fhac:radiometric_panels
Open

better support for radiometric panels#2030
maurerle wants to merge 4 commits into
OpenDroneMap:masterfrom
idt-fhac:radiometric_panels

Conversation

@maurerle

@maurerle maurerle commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

Summary

For usage with a Micasense RedEdge-P, we are currently using the DLS reflectance from camera+sun, however it is more accurate when preprocessing the Data with the micasense library: https://github.com/brainergylab/micasense_imageprocessing and using the QR panel adjustment from there.
This PR adds the capability to ODM as well.

This PR is assisted by opus-4.8 and is currently untested.
I would like some early feedback and improve based on it, after testing that it works as intended.
I am aware of https://github.com/OpenDroneMap/documents/blob/master/CONTRIBUTING.md - which is why this is a draft.

Adds --radiometric-calibration camera+panel, deriving per-band reflectance
from in-field MicaSense (and compatible) calibration reflectance panel captures.
This closes the long-standing # TODO: support for calibration panels in
opendm/multispectral.py.
The implementation is fully native: panel metadata is parsed directly from
XMP as Micasense already handles QR Code panel reading and exposes results in MetaData.

How it works

  1. Metadata parsing (opendm/photo.py): reads the panel XMP tags
    CalibrationPicture, Albedo, ReflectArea, PanelSerial and exposes
    get_panel_albedo(), get_panel_region(), get_panel_serial(),
    is_calibration_picture(), and is_panel_image().
  2. Detection & irradiance (stages/dataset.py + opendm/multispectral.py):
    panel captures are auto-detected, per-band irradiance is computed once
    (irradiance = mean_panel_radiance * π / albedo, robust median across panel
    captures), persisted to panels.json, and the panel frames are excluded from
    the reconstruction (they are ground shots that would pollute SfM).
  3. Application (stages/run_opensfm.py): the per-band irradiance is loaded
    and passed into dn_to_reflectance. compute_irradiance now checks panel
    irradiance before the stored HorizontalIrradiance, so it is actually used
    on DLS cameras.
    Fallback order per band: panel → stored HorizontalIrradiance → DLS estimate → 1.0.

New CLI options

  • --radiometric-calibration camera+panel
  • --panel-reflectance — override panel albedo when metadata is missing;
    accepts a single float (e.g. 0.49) or per-band JSON
    (e.g. '{"Red": 0.49, "Green": 0.49}').

Robustness

  • Per-band graceful fallback if a panel isn't detected for some bands.
  • Saturation guard rejects panels with too many saturated pixels.
  • LWIR/thermal bands are skipped (no panel).
  • No new dependencies; behavior is unchanged unless camera+panel is selected.

Test plan

  • Add unit tests for panel XMP parsing and parse_panel_reflectance
    (tests/test_photo.py).
  • RedEdge-P dataset with panel captures: panel region reflectance ≈ catalog
    albedo within tolerance.
  • Dataset without panels still processes (DLS/stored irradiance path).
  • --panel-reflectance 0.49 override path.
  • Confirm panel frames are excluded from the reconstruction.

Notes / limitations

  • Panel detection/exclusion happens in the dataset stage; rerun with
    --rerun-from dataset if enabling camera+panel on an already-loaded dataset.
  • Exclusion is metadata-driven (camera-flagged calibration pictures).
  • Docs (OpenDroneMap/docs) to be updated separately.
@maurerle
maurerle force-pushed the radiometric_panels branch from 59902b1 to 756eb9f Compare August 13, 2026 10:15
@maurerle
maurerle marked this pull request as ready for review August 17, 2026 09:23
Parse CalibrationPicture/Albedo/ReflectArea/PanelSerial directly from XMP
(no exiftool dependency) and expose panel getters plus is_calibration_picture()
and is_panel_image() helpers on ODM_Photo.

Assisted-by: Cursor:opus-4.8
New opendm/panel.py provides polygon region statistics (mean/std and saturated
pixel fraction) over a panel active area, used to derive panel irradiance.

Assisted-by: Cursor:opus-4.8
Add compute_irradiance_from_panels() (median across panel captures, saturation
rejection, graceful per-band fallback) and parse_panel_reflectance() for the
--panel-reflectance override. Thread panel_irradiance through dn_to_reflectance
and check it in compute_irradiance before the stored HorizontalIrradiance early
return, so panel irradiance is actually used for DLS cameras.

Assisted-by: Cursor:opus-4.8
Add the camera+panel choice and --panel-reflectance flag. The dataset stage
detects calibration panel captures, computes and persists per-band irradiance
to panels.json, and excludes panel frames from the reconstruction. run_opensfm
loads that irradiance and passes it per band into dn_to_reflectance.

Assisted-by: Cursor:opus-4.8
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

1 participant