r.vif: preserve active MASK when read_data() exits with an error#1715
r.vif: preserve active MASK when read_data() exits with an error#1715Valyrian-Code wants to merge 1 commit into
Conversation
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
This PR improves how r.vif handles an existing GRASS MASK by backing it up, attempting best-effort restoration, and integrating restoration into cleanup() to reduce the chance of leaving users with a broken mask state after errors.
Changes:
- Introduces a tracked
MASKbackup (mask_backup_name) and arestore_mask_backup()helper. - Updates
cleanup()to restore the originalMASKbefore removing temporary rasters. - Wraps sampling (
r.stats/np.loadtxt) withtry/finallyto remove internal masks and restore the backup when possible.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Track the backup mask name in a module-level variable instead of CLEAN_RAST, and restore it from cleanup() before the temp-map removal loop so MASK survives crashes during r.stats / numpy processing. If a MASK already exists at restore time (left over from the internal r.mask call), remove it first; at cleanup time any MASK present is one r.vif created itself. Also wrap the r.stats/numpy block in try/finally so the internal mask is removed and the backup restored on the normal-exception path. Fixes OSGeo#1702
3bc6793 to
70cab44
Compare
|
Thanks for the review. Addressed two of the three:
Re-tested manually in GRASS 8.5.0 — normal run completes with MASK preserved, crash-injection test (raise after r.mask) leaves MASK intact via atexit cleanup. |
|
In 8.5, we have MaskManager for this. I would consider duplicatating the code here for compatibility with 8.4. |
Good point. Just to confirm before refactoring: the r.vif use case is "replace MASK with a different one" (random-sample), which is what MaskManager handles cleanly via manager.mask_name and GRASS_MASK. (This is different from r.boxplot's "disable the existing MASK" case where MaskManager alone is not sufficient see #1701.) Plan:
Does that match what you had in mind? |
Summary
Fixes #1702.
When
r.vifis run withn=(random sampling),read_data()renames the activeMASKto a temporary backup so a new random-sampling mask can be applied. The backup name was previously added toCLEAN_RASTviatmpname(), so theatexitcleanup()deleted it on any exit — including exceptions raised duringr.statsor the numpy parse. The user's original mask was permanently lost, and the random-sample reclass left behind byr.mask raster=new_maskwas left as the activeMASK.This is the same antipattern fixed in r.boxplot (#1696, PR #1701), adapted to r.vif's flow.
Fix
Track the backup mask name in a module-level
mask_backup_namevariable instead of adding it toCLEAN_RAST. Set the variable only after a successful rename.Add a
restore_mask_backup()helper that:MASKfirst (at cleanup time any MASK present is one r.vif created internally, so this is safe).try/except, emitsgs.warning()with the backup name and a manual restore command on failure, and never raises.Call
restore_mask_backup()fromcleanup()before the temp-map removal loop, so the user's MASK is restored on any exit path.Wrap the
r.stats/np.loadtxtblock intry/finallyso on the normal-exception path the internal mask is removed and the backup is restored locally.Test plan
Verified manually in GRASS 8.5.0 with an active MASK and three correlated rasters. Controlled failure injected after
r.mask raster=new_maskto simulate a crash in the protected window.Before the fix:
g.list pattern="rvifoldmask*"after crash → backup raster remained butg.list pattern=MASKafter crash → MASK was the random-sample reclass, not the user's mask. Original MASK was deleted bycleanup().After the fix:
g.list pattern=MASK→ MASK presentr.info MASK→ "Reclass of rvif_mask" — the user's original maskg.list pattern="rvifoldmask*"→ empty (backup was renamed back, not orphaned)cc @ecodiv @wenzeslaus