feat: propagate measurement and time uncertainty into the result band (closes #3, #4)#38
Open
steps-re wants to merge 2 commits into
Open
feat: propagate measurement and time uncertainty into the result band (closes #3, #4)#38steps-re wants to merge 2 commits into
steps-re wants to merge 2 commits into
Conversation
Closes bellingcat#3. Closes bellingcat#4. Previously the width of the possible-location band was a fixed constant. This lets users supply the uncertainty on their inputs so the band reflects the region actually consistent with the observation: - Measurement uncertainty (bellingcat#3): object_height_uncertainty / shadow_length_uncertainty (or sun_altitude_angle_uncertainty) widen the band by the propagated relative error. - Time uncertainty (bellingcat#4): time_uncertainty sweeps the observation time by +/- its value, evaluating the sun position at t +/- dt (two extra global computations, as suggested on the issue) rather than re-meshing the globe over many moments. Both combine into a per-cell "consistency" surface: the distance from a perfect match to the range of relative differences the observation could plausibly take. Using range containment (rather than dividing by a 1-sigma band) keeps near-terminator cells, where the shadow is hugely time-sensitive, correctly excluded instead of spuriously highlighted. Output is unchanged when no uncertainty is supplied. Adds CLI options and tests covering the measurement, time, and combined cases. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ctor Addresses review feedback: the measurement uncertainty of the relative difference r is (1 + r) * sqrt((dh/h)^2 + (ds/s)^2), not the constant sqrt(...) which is correct only at a perfect match (r = 0). Scaling the band by (1 + r) — the predicted-to-observed ratio — makes it exact to first order everywhere and correctly asymmetric (a long shadow tolerates more absolute error than a short one). Verified the consistent region now matches the analytic first-order bounds. Also fixes a stale comment and documents that the 3-sample time bracket is tight where the band is. Co-Authored-By: Claude Opus 4.8 (1M context) <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.
Closes #3. Closes #4.
Currently the width of the possible-location band is a fixed constant. This lets users supply the uncertainty on their measurements and on the observation time, so the band reflects the region actually consistent with the observation.
Model
Both uncertainty sources feed one per-cell consistency surface: the distance from a perfect match (relative difference = 0) to the range of relative differences the observation could plausibly take. A value of 0 means the cell is consistent with the observation; that region is what gets highlighted.
object_height_uncertainty/shadow_length_uncertainty(orsun_altitude_angle_uncertainty) widen the plausible range. The band on a relative differenceris(1 + r) * sqrt((dh/h)^2 + (ds/s)^2)— the first-order propagated error, which scales with the predicted-to-observed ratio(1 + r). This is exact to first order everywhere (not just at the match) and correctly asymmetric: a long shadow tolerates more absolute error than a short one. The radians conversion cancels for the sun-altitude form, so the same(1 + r)scaling applies there.time_uncertaintysweeps the observation time by ±dt and evaluates the sun position att ± dt— two extra global computations, following the approach suggested in the issue thread, rather than re-meshing the whole globe over many moments. The relative difference at each cell then spans a range instead of a single value. (The 3-sample min/max bracket is tight exactly where the candidate band is — near a cell's local solar noon the shadow barely moves — and only under-covers on fast-moving low-sun cells that are excluded regardless.)Why range containment rather than a 1-sigma divide
The obvious approach — normalise the residual by a 1-sigma band — breaks down near the day/night terminator, where the shadow length is astronomically sensitive to time (
1/tan(altitude) -> inf). There the band blows up, and dividing by it makes those cells look like strong candidates. Modelling the plausible range and asking whether it contains a perfect match keeps those cells correctly excluded (their predicted shadow is always far from the observed one).Verified on a worked example: with a 30-minute time uncertainty the consistent region stays centred on the true solution (median sun altitude 63.4° =
arctan(10/5)) with 0% of consistent cells near the terminator; and the measurement-only consistent region matches the analytic first-order bounds.Compatibility & tests
location_uncertaintystaysNone, same fixed band as before, base surface bit-identical).find/find_sunCLI options and README docs.blackclean.Note
This touches
find_shadows/cli.py, so it may need a trivial rebase if #36 or #37 merge first — happy to do that.