Skip to content

feat(12bit-support): add support to 12-bits containers for Little Endian, Big Endian, JPEG, gzip and RLE file formats#2661

Open
TaylorHo wants to merge 9 commits into
cornerstonejs:mainfrom
TaylorHo:main
Open

feat(12bit-support): add support to 12-bits containers for Little Endian, Big Endian, JPEG, gzip and RLE file formats#2661
TaylorHo wants to merge 9 commits into
cornerstonejs:mainfrom
TaylorHo:main

Conversation

@TaylorHo

@TaylorHo TaylorHo commented Mar 15, 2026

Copy link
Copy Markdown

Context

In the DICOM format, we can define Bits Allocated (0028,0100) and Bits Stored (0028,0101). In many CT DICOM images, there are 12 bits stored (Bits Stored) into 16-bit containers (Bits Allocated).

This PR adds support also for loading and rendering into Cornerstone3D DICOM images of 12 bits stored (Bits Stored) into 12-bit containers (Bits Allocated). This approach reduces ~25% of the file sizes, due to the removal of the extra empty bits. We know this can be useful only for some cases (for example, not all CT images can fit in 12 bits).

We also developed a tool for converting DICOM files that use 16-bit but have 12-bit stored values into this "12-bit allocated and 12-bit stored" format. We called it dicom-12bit.

We also applied this conversion and compression support to GDCM (a PR will be opened soon for the origin repo) in this GDCM fork.

All this is part of research conducted over the past months. This research will be sent for paper evaluation soon.

We have a working example of this 12-bit allocated and 12-bit stored into this demo repo: TaylorHo/dicom-12-bit-example-viewer. Here, the same changes that this PR does are applied as git patches to add this support. 12-bit images are provided as examples in this same repo.

We describe more useful results of this change in the paper; we are just opening this PR before submitting to keep the history.

Changes & Results

The changes try to be minimal, adding support to the uncompressed 12-bit formats: Little Endian and Big Endian. This also supports the JPEG family of compression algorithms, including JPEG-2000, JPEG-LS, and JPEG lossy, and files compressed with gzip and RLE.

If you, for example, convert a DICOM that is 12-bit stored into 16-bit containers, and use our tool to convert to 12-bit stored into 12-bit containers, you can also use our modified version of GDCM to compress this new format.

This way, using the demo application provided, you can see that all the cited formats are rendering correctly.

To facilitate, the demo application has a folder with all the possible compressed formats supported by Cornerstone3D, but with 12 bits allocated.

Testing

We developed a demo application that applies our changes using a git patch to the Cornerstone3D library and that can be used to visualize the 12-bit images.

You can use a tool like gdcmdump to see that all the example files in the repo are 12-bit stored into 12 bits.

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: Windows 11, macOS 26.3.1, and Ubuntu 24.04
  • Node version: v22.16.0
  • Browser: Chrome 146.0.76380.119

Summary by CodeRabbit

  • New Features
    • Added comprehensive support for 12-bit DICOM images across multiple formats: uncompressed, JPEG Baseline, JPEG Lossless, and RLE-compressed frames.
    • Implemented proper endianness conversion and data type handling for 12-bit image processing.

@TaylorHo

Copy link
Copy Markdown
Author

The PR to GDCM was opened in its repo.

@coderabbitai

coderabbitai Bot commented Jun 13, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR extends DICOM image decoding to support 12-bit pixel data. A case 12: label in the volume property generator routes 12-bit data through 16-bit type handling. Uncompressed 12-bit frames are unpacked using bitwise operations, while endian-aware and JPEG decoders treat 12-bit identically to 16-bit. RLE decoding introduces a new decode12 function that mirrors 16-bit segment unpacking logic.

Changes

12-bit DICOM Image Support

Layer / File(s) Summary
12-bit volume data type handling
packages/core/src/utilities/generateVolumePropsFromImageIds.ts
Data type determination in _determineDataType now routes 12-bit images through the same float-scaling and sign-handling logic used for 16-bit frames.
Uncompressed 12-bit frame extraction
packages/dicomImageLoader/src/imageLoader/wadouri/getUncompressedImageFrame.ts
New bitsAllocated === 12 branch unpacks packed 12-bit samples from byte streams: calculates aligned frame size, validates bounds, extracts samples using bitwise operations into a Uint16Array, and returns as Uint8Array view.
Endian-aware 12-bit decoding
packages/dicomImageLoader/src/shared/decoders/decodeBigEndian.ts, decodeLittleEndian.ts
Both big-endian and little-endian decoders now route 12-bit frames through the same 16-bit typed-array and endianness-swap paths, treating 12-bit identically to 16-bit in the decoding flow.
JPEG-encoded 12-bit decoding
packages/dicomImageLoader/src/shared/decoders/decodeJPEGBaseline12Bit-js.ts, decodeJPEGLossless.ts
JPEG Baseline and Lossless decoders updated to recognize bitsAllocated === 12: both now route through 16-bit pixel-data extraction paths, matching the existing handling of 16-bit JPEG samples.
RLE-encoded 12-bit decoding
packages/dicomImageLoader/src/shared/decoders/decodeRLE.ts
RLE decoder now recognizes 12-bit frames and dispatches to new decode12 function, which unpacks RLE-compressed segments using the same run-length expansion logic as decode16 but writes signed/unsigned 16-bit output according to pixelRepresentation.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 Twelve bits now hop through the decoders bright,
From RLE runs to JPEG light,
Endian swaps embrace the middle way,
Packed samples dance in 16-bit display,
Cornerstone sees more, bit by bit!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 22.22% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: adding 12-bit container support across multiple file formats (Little Endian, Big Endian, JPEG, gzip, RLE). It follows semantic-release format with 'feat' prefix and clear scope.
Description check ✅ Passed The description provides comprehensive context, detailed changes across compression formats, testing methodology with example repository and tool references, and marks all checklist items as complete.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/dicomImageLoader/src/shared/decoders/decodeBigEndian.ts (1)

13-34: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Avoid byte-swapping unpacked 12-bit samples in big-endian decode.

The 12-bit uncompressed path already unpacks into 16-bit sample values before this decoder runs. Including bitsAllocated === 12 in the swap16 branch causes an extra byte inversion and corrupts pixel values.

💡 Proposed fix
-  if (imageFrame.bitsAllocated === 16 || imageFrame.bitsAllocated === 12) {
+  if (imageFrame.bitsAllocated === 16) {
     let arrayBuffer = pixelData.buffer;

     let offset = pixelData.byteOffset;
     const length = pixelData.length;
@@
     // Do the byte swap
     for (let i = 0; i < imageFrame.pixelData.length; i++) {
       imageFrame.pixelData[i] = swap16(imageFrame.pixelData[i]);
     }
+  } else if (imageFrame.bitsAllocated === 12) {
+    let arrayBuffer = pixelData.buffer;
+    let offset = pixelData.byteOffset;
+    const length = pixelData.length;
+
+    if (offset % 2) {
+      arrayBuffer = arrayBuffer.slice(offset);
+      offset = 0;
+    }
+
+    imageFrame.pixelData =
+      imageFrame.pixelRepresentation === 0
+        ? new Uint16Array(arrayBuffer, offset, length / 2)
+        : new Int16Array(arrayBuffer, offset, length / 2);
   } else if (imageFrame.bitsAllocated === 8) {
     imageFrame.pixelData = pixelData;
   }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/dicomImageLoader/src/shared/decoders/decodeBigEndian.ts` around
lines 13 - 34, The decoder currently performs a 16-bit byte-swap for both 16-bit
and 12-bit paths which corrupts already-unpacked 12-bit samples; update the
condition in decodeBigEndian (file: decodeBigEndian.ts) so that the swap loop
running swap16(...) only executes when imageFrame.bitsAllocated === 16 (leave
the 12-bit path untouched), i.e. ensure imageFrame.pixelData for bitsAllocated
=== 12 is not passed through swap16; keep all other logic (alignment/Uint16Array
vs Int16Array selection) unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@packages/dicomImageLoader/src/shared/decoders/decodeBigEndian.ts`:
- Around line 13-34: The decoder currently performs a 16-bit byte-swap for both
16-bit and 12-bit paths which corrupts already-unpacked 12-bit samples; update
the condition in decodeBigEndian (file: decodeBigEndian.ts) so that the swap
loop running swap16(...) only executes when imageFrame.bitsAllocated === 16
(leave the 12-bit path untouched), i.e. ensure imageFrame.pixelData for
bitsAllocated === 12 is not passed through swap16; keep all other logic
(alignment/Uint16Array vs Int16Array selection) unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: b79a06fd-53fe-4cd2-ac04-a6abc8c030e4

📥 Commits

Reviewing files that changed from the base of the PR and between 2769fcf and fb63ea7.

📒 Files selected for processing (7)
  • packages/core/src/utilities/generateVolumePropsFromImageIds.ts
  • packages/dicomImageLoader/src/imageLoader/wadouri/getUncompressedImageFrame.ts
  • packages/dicomImageLoader/src/shared/decoders/decodeBigEndian.ts
  • packages/dicomImageLoader/src/shared/decoders/decodeJPEGBaseline12Bit-js.ts
  • packages/dicomImageLoader/src/shared/decoders/decodeJPEGLossless.ts
  • packages/dicomImageLoader/src/shared/decoders/decodeLittleEndian.ts
  • packages/dicomImageLoader/src/shared/decoders/decodeRLE.ts

@TaylorHo

Copy link
Copy Markdown
Author

Can someone please take a look here? @JamesAPetts, @wayfarer3130, @swederik, @sedghi

Also, regarding this AI Caution warning, in this specific part of the code, there's no need for different logic to read the file when bitsAllocated === 12.

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.

1 participant