Skip to content

fix(dicom-image-loader): use Float32Array for non-integer rescale slopes to prevent pixel data corruption#2707

Open
eugenest2557 wants to merge 3 commits into
cornerstonejs:mainfrom
eugenest2557:fix/float-rescale-slope-pixel-data-corruption
Open

fix(dicom-image-loader): use Float32Array for non-integer rescale slopes to prevent pixel data corruption#2707
eugenest2557 wants to merge 3 commits into
cornerstonejs:mainfrom
eugenest2557:fix/float-rescale-slope-pixel-data-corruption

Conversation

@eugenest2557
Copy link
Copy Markdown

@eugenest2557 eugenest2557 commented Apr 21, 2026

Context

Fixes #2706

DICOM images with non-integer RescaleSlope (e.g. DTI FA maps with RescaleSlope=0.001) render as completely black in StackViewport. These images display correctly in other viewers such as RadiAnt and OsiriX.

The legacy Cornerstone had a similar issue (cornerstonejs/cornerstone#302, fixed in PR #303).

Two independent bugs are involved:

  1. getPixelDataTypeFromMinMax(0.0, 1.0) selects Uint8Array because Number.isInteger(1.0) === true in JavaScript. All fractional scaled values (0.013, 0.5, 0.999) are truncated to 0. This function is called twice (web worker + main thread setPixelDataType), destroying the data at both stages.

  2. toLowHighRange with WindowWidth=1 produces lower === upper === 0 via the LINEAR formula (WW-1) = 0, causing division by zero in the VOI shader and a binary black/white image.

Changes & Results

  1. _handlePreScaleSetup in decodeImageFrameWorker.js: Detect non-integer scaling parameters and force Float32Array before getPixelDataTypeFromMinMax is called.

  2. setPixelDataType in setPixelDataType.ts: Skip re-typing if pixelData is already Float32Array, preventing the main thread from downgrading it to Uint8Array.

  3. toLowHighRange in windowLevel.ts: When WindowWidth <= 1 with LINEAR function, fall back to LINEAR_EXACT (lower = WC - WW/2, upper = WC + WW/2) to produce a valid range.

All three fixes are needed — removing any one results in either a black screen or a binary black/white image.

Testing

  • Load a DTI FA DICOM with RescaleSlope=0.001 in StackViewport — should display grayscale instead of black
  • Load a standard CT DICOM (integer RescaleSlope=1) — should be unaffected
  • Load a DICOM with WindowWidth > 1toLowHighRange should use existing LINEAR formula unchanged

Checklist

PR

  • My Pull Request title is descriptive, accurate and follows the
    semantic-release format and guidelines.

Code

  • My code has been well-documented (function documentation, inline comments,
    etc.)

Public Documentation Updates

  • The documentation page has been updated as necessary for any public API
    additions or removals.

Tested Environment

  • OS: macOS
  • Node version: 20
  • Browser: Chrome

I may be wrong about some of the details — please correct me if I've misunderstood anything. Any feedback would be greatly appreciated. Thank you!

…pes to prevent pixel data corruption

DICOM images with non-integer RescaleSlope (e.g. DTI FA maps with
RescaleSlope=0.001) have their pixel data destroyed during preScale
because getPixelDataTypeFromMinMax incorrectly selects Uint8Array.

This happens because Number.isInteger(1.0) === true in JavaScript,
so scaled min/max of 0.0 and 1.0 are treated as integers, leading to
Uint8Array selection. All fractional values (0.013, 0.5, 0.999) are
then truncated to 0.

Fix:
1. In _handlePreScaleSetup: detect non-integer scaling parameters and
   force Float32Array before scaling is applied.
2. In setPixelDataType: skip re-typing if pixelData is already
   Float32Array, preventing the main thread from downgrading it back
   to Uint8Array after web worker transfer.

Fixes: cornerstonejs#2706

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
eugene25507 and others added 2 commits April 21, 2026 21:40
… division by zero

The DICOM LINEAR VOI formula uses (WW-1) which becomes 0 when WW=1,
producing lower === upper (degenerate range). This causes division by
zero in the VOI LUT shader, resulting in a binary black/white image.

Fall back to LINEAR_EXACT (lower = WC - WW/2, upper = WC + WW/2)
which correctly handles WW=1 (e.g. DTI FA with WC=0.5, WW=1 gives
lower=0, upper=1).

Fixes: cornerstonejs#2706

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@eugenest2557 eugenest2557 reopened this Apr 21, 2026
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.

[Bug] DICOM images with float RescaleSlope (e.g. DTI FA maps) render as all black in StackViewport

2 participants