Skip to content

Fix black previews of off-center masks and >4 GiB .nii.gz expansion - #2

Merged
marcoduering merged 1 commit into
mainfrom
claude/happy-goodall-6g2ijb
Sep 27, 2026
Merged

marcoduering merged 1 commit into
mainfrom
claude/happy-goodall-6g2ijb

Conversation

@marcoduering

Copy link
Copy Markdown
Owner

Summary

1. Masks whose foreground misses the center slices rendered black everywhere

The segmentation LUT and the intensity window were derived only from the three center slices. For a lesion or single-ROI mask that misses all three, the labels were empty (no LUT) and the window was Bounds(0,0), so every slice rendered black, and window/level drag was inert (_winRefSpan == 0).

  • MiqVolume.DetectSegmentationLut (shared by the initial load and post-expansion): when the center sample is all background, collect labels from all of volume 0. A single label gives the white mask LUT; several give a colour LUT.
  • MiqVolume.WindowFromSlices: a null or degenerate center-slice window falls back to VolumeWindow, a window over the whole volume at that timepoint (two passes, at most 1M strided samples), widened to include the background value. Used by CenterInteractiveState and the per-volume SharedWindow.
  • MiqTriPlanarControl: the W/L reference span falls back to a non-zero value, so right-drag works on a constant volume.

2. .nii.gz over 4 GiB uncompressed was offered an expansion that cannot succeed

ParsePartial decided ExpansionBlocked from the gzip ISIZE footer, which is the size mod 2^32. Example: a 91×109×91×1200 float32 series (4,332,619,552 B) reports ISIZE 37,652,256, so it was not blocked. The first scrub then decompressed toward 2 GB, threw, and the swallowed error left "loading…" on screen permanently.

  • Blocking now uses the header-derived size (MiqParser.NiftiFileExtent: vox_offset + every dim × bytes per voxel). ISIZE is no longer used in ParsePartial.
  • A stream that ends inside volume 0 goes to the full parse, which reports the truncation.

Behaviour changes

  • A volume whose center slices are all NaN, but which has finite data elsewhere, now shows an image instead of the "No finite voxel values" message.
  • A multi-volume .nii.gz that holds exactly one volume of data now loads as a partial preview instead of a full parse. Missing volumes render black, as they already did in the full parse.

Tests

  • SegmentationDetectionTests: off-center binary mask (white LUT), off-center multi-label (colour LUT), window fallback with the Off setting (window [0,1], mask pixels visible), all-zero volume stays degenerate.
  • GzipPartialBlockingTests (non-parallel collection, because it lowers MaxArrayBytes): wrapped ISIZE with too-large header is blocked, fitting header is expandable, few-volume too-large file stays blocked, truncated volume 0 throws, NiftiFileExtent.

Not yet built or run: the authoring environment had no .NET SDK (download blocked by network policy), and CI builds only the plugin, not MIQ.Tests. Please run dotnet build and dotnet test locally before merging.

🤖 Generated with Claude Code

https://claude.ai/code/session_01XqBwhnTRsqxk3SEvp9z4T9


Generated by Claude Code

Off-center masks: when the three center slices hold only background, the
segmentation LUT and intensity window were derived from zeros, so every
slice rendered black and window/level drag was inert. Now:
- label detection collects labels from all of volume 0 when the center
  sample is empty (single label -> white mask LUT, several -> colour LUT);
- a null or degenerate center-slice window falls back to a window over
  the whole volume at that timepoint (strided to at most 1M samples,
  widened to include the background value);
- the window/level drag reference span falls back to a non-zero value.

.nii.gz > 4 GiB: ParsePartial decided ExpansionBlocked from the gzip
ISIZE footer, which is the size mod 2^32. A >4 GiB series could report
a small ISIZE, be offered an expansion that cannot fit a byte[], and
end on a permanent "loading..." after a multi-GB decompression. The
decision now uses the header-derived file size (NiftiFileExtent), and a
stream that ends inside volume 0 goes to the full parse, which reports
the truncation.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XqBwhnTRsqxk3SEvp9z4T9
@marcoduering
marcoduering merged commit 823bce3 into main Sep 27, 2026
1 check passed
@marcoduering
marcoduering deleted the claude/happy-goodall-6g2ijb branch September 27, 2026 11:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants