Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Difficulty Converting DICOM Image to Viewable JPEG (uint8) - Inconsistent Pixel Value Ranges #1823

Open
alevangel opened this issue Feb 13, 2025 · 6 comments · May be fixed by #1834
Open

Difficulty Converting DICOM Image to Viewable JPEG (uint8) - Inconsistent Pixel Value Ranges #1823

alevangel opened this issue Feb 13, 2025 · 6 comments · May be fixed by #1834

Comments

@alevangel
Copy link

alevangel commented Feb 13, 2025

Description

I'm encountering an issue when trying to read and display a specific DICOM image (int16). Specifically, I'm having trouble converting a region of the image to a JPEG (uint8) format suitable for easy viewing.

The DICOM metadata includes 0028,1050 Window Center and 0028,1051 Window Width values, suggesting a recommended intensity window for display. I initially assumed that clipping the pixel values within this range would produce a viewable image. However, this approach doesn't work correctly. There are areas within the image (at different magnifications) where useful pixel data falls significantly outside the specified window, leading to an incorrect visualization.

The provided code example demonstrates the issue. When extracting a 512x512 region at a low magnificatio, the resulting NumPy array contains values clustered around -15708. Using this value as a baseline for normalization doesn't produce consistent results across all regions at that magnification. The dynamic range of values is not consistent. How should such case be handled?

Is also note that QuPath is able to handle this image correctly at all zoom levels, indicating that a proper display solution exists.

Download file: 1.3 1.dcm.zip

Code example:

import large_image

slide = large_image.open("data/input/1.3 1.dcm")
nparray, _ = slide.getRegion(region=dict(left=0, top=0, right=512, bottom=512, units='pixels'),
                             tile_size=dict(width=512, height=512),
                             format=large_image.tilesource.TILE_FORMAT_NUMPY,
                             encoding='JPEG',
                             mag=0.1532848953064165)

Environment

  • large-image version: 1.31.2
  • Python Version: 3.9

References

@manthey
Copy link
Member

manthey commented Feb 20, 2025

This is almost certainly this bug in bioformats: ome/bioformats#3620

@manthey
Copy link
Member

manthey commented Feb 20, 2025

I think the solutions would be to (a) modify bioformats, (b) when the inverted flag is set, undo what bioformats does, or (c) have a different tile source that would read non-slide modality dicoms.

@manthey
Copy link
Member

manthey commented Feb 20, 2025

Looking further into this, I think the correct solution for bioformats would be to NOT invert the values when rescale=false (and then the bug they have would be avoided). The window width and center are for the actual values. Bioformats does an inversion when the photometric interpretation is MONOCHROME1 (and then, only for 16 bit samples, incorrectly applied window width and center).

manthey added a commit that referenced this issue Feb 20, 2025
Bioformats ignores the rescale=false option with MONOCHROME1 dicom data.
Further, not only does it invert the pixel value direction, but it
offsets 16 bit data incorrectly.  This undoes these effects so that the
raw pixel data is returned.

Fixes #1823.
manthey added a commit that referenced this issue Feb 20, 2025
Bioformats ignores the rescale=false option with MONOCHROME1 dicom data.
Further, not only does it invert the pixel value direction, but it
offsets 16 bit data incorrectly.  This undoes these effects so that the
raw pixel data is returned.

Fixes #1823.
@manthey manthey linked a pull request Feb 20, 2025 that will close this issue
@manthey
Copy link
Member

manthey commented Feb 20, 2025

manthey added a commit that referenced this issue Feb 20, 2025
Bioformats ignores the rescale=false option with MONOCHROME1 dicom data.
Further, not only does it invert the pixel value direction, but it
offsets 16 bit data incorrectly.  This undoes these effects so that the
raw pixel data is returned.

Fixes #1823.
@alevangel
Copy link
Author

@manthey In the proposed solution, I observed that the image contrast varies significantly depending on the zoom levels. This leads to an inconsistent color display, where the image appears different at different zoom levels. In other words, the image does not maintain uniform and consistent coloring during zooming.

My current hotfix addresses this issue by adjusting the bit depth of the image tiles before normalization. Specifically:

bits_stored = int(self._metadata['seriesMetadata']['0028,0101 Bits Stored'])
high_bit = int(self._metadata['seriesMetadata']['0028,0102 High Bit'])
if high_bit < bits_stored - 1:
    shift = (bits_stored - 1) - high_bit
    tile = tile << shift
    mask = (1 << bits_stored) - 1
    tile = tile & mask

Subsequently, I normalize the image using the min and max values obtained from the thumbnail image:

min_max_range=(np.min(thumbnail_image), np.max(thumbnail_image))
image = (image - min_max_range[0]) / (min_max_range[1] - min_max_range[0]) * 255

@manthey
Copy link
Member

manthey commented Feb 21, 2025

In the sample image provided, I'm not seeing a different based on zoom. That is, the data looks like it is all on the same scale from 0 to 16383, with the interesting contrast range identified by the window width and center. The default as jpegs all end up with pixel values between 128 and 191 (because of how int16 data is scaled to uint8). Can you share more specifically how you are seeing the data vary by zoom?

manthey added a commit that referenced this issue Feb 21, 2025
Bioformats ignores the rescale=false option with MONOCHROME1 dicom data.
Further, not only does it invert the pixel value direction, but it
offsets 16 bit data incorrectly.  This undoes these effects so that the
raw pixel data is returned.

Fixes #1823.
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 a pull request may close this issue.

2 participants