【问题标题】:Converting hand radiograph DICOM to PNG returns white/bright image将手部 X 光片 DICOM 转换为 PNG 返回白色/明亮图像
【发布时间】:2022-10-13 06:15:34
【问题描述】:

我正在将 DICOM 格式的手部 X 射线转换为 PNG 格式。下面的代码执行此操作:

import os
import cv2
import pydicom
import numpy as np
from PIL import Image

inputdir = "P:/BoneDataset/DICOM-File/0-RefinedDICOM/"
outdir = 'P:/BoneDataset/DICOM-File/1-ConvertedPics/'

test_list = [f for f in os.listdir(inputdir)]

for f in test_list[:10]:
    ds = pydicom.read_file(inputdir + f) # read dicom image
    img = ds.pixel_array # get image array
    scaled_img = (np.maximum(img,0) / img.max()) * 255.0
    img = scaled_img.astype(np.uint8)
    cv2.imwrite(outdir + f.replace('.dcm','.png'),img)

下图显示了一些结果(输入 (DICOM) --> 输出 (PNG)):

Input 1 --> Output 1

Input 2 --> Output 2

Input 3 --> Output 3

如你所见,我希望输出图像看起来与输入 X 射线相同,但是,我得到黑白输出。这是因为图像阈值问题还是文件有问题?

编辑1:

我尝试了下面建议的代码,但是它返回与以前相同的输出,而不是与输入相同的图像。

我已经匿名化了 DICOM 文件,因此您可以找到 DICOM 数据集文件HERE。请使用这些 DICOM 文件。

【问题讨论】:

  • 很难说,当你不附加文件时,文件是否有问题......除此之外,你缩放到 0..255(即 uint8),然后设置顶部 uint16。为什么?您将 dicom 图像的负值设置为 0。为什么?
  • 我无法附上文件的原因是出于我研究的道德原因。所以我将它编辑为 uint8 但是,它返回相同的输出。
  • 您当然应该使用匿名版本进行上传。
  • 我认为你的问题可能是你只是规模最大。您还需要缩放最小值。尝试使用 skimage.rescale_intensity。使用“输入”选项自动获取输入范围或调整输入范围以获得所需的结果。您还应该删除标签,以便它们的亮度不会影响最大值。

标签: python image opencv pydicom medical-imaging


【解决方案1】:

正如所评论的,缩放最大值可能还不够。
我们可以尝试“线性拉伸”,其中一些低百分位数变为 0,高百分位数变为 255,并且其间的水平被线性变换。

附加选项:
由于许多 DICOM 图像具有黑色边距和白色标签,因此在计算百分位数时,我们可能希望忽略最小值和最大值。

无法保证结果与“输入 X 射线”相同,但可能不会是黑白的。

为了测试,我从here 下载了 CASE 1 DICOM 示例。


代码示例:

import cv2
import pydicom
import numpy as np

def lin_stretch_img(img, low_prc, high_prc, do_ignore_minmax=True):
    """ 
    Apply linear "stretch" - low_prc percentile goes to 0, 
    and high_prc percentile goes to 255.
    The result is clipped to [0, 255] and converted to np.uint8

    Additional feature:
    When computing high and low percentiles, ignore the minimum and maximum intensities (assumed to be outliers).
    """
    # For ignoring the outliers, replace them with the median value
    if do_ignore_minmax:
        tmp_img = img.copy()
        med = np.median(img)  # Compute median
        tmp_img[img == img.min()] = med
        tmp_img[img == img.max()] = med
    else:
        tmp_img = img

    lo, hi = np.percentile(tmp_img, (low_prc, high_prc))  # Example: 1% - Low percentile, 99% - High percentile

    if lo == hi:
        return np.full(img.shape, 128, np.uint8)  # Protection: return gray image if lo = hi.

    stretch_img = (img.astype(float) - lo) * (255/(hi-lo))  # Linear stretch: lo goes to 0, hi to 255.
    stretch_img = stretch_img.clip(0, 255).astype(np.uint8)  # Clip range to [0, 255] and convert to uint8
    return stretch_img


# https://www.visus.com/fileadmin/content/pictures/Downloads/JiveX_DICOME_Viewer/case1.zip
ds = pydicom.read_file('case1_008.dcm') # read dicom image
img = ds.pixel_array # get image array

img = lin_stretch_img(img, 1, 99)  # Apply "linear stretching" (lower percentile 1 goes to 0, and percentile 99 to 255).

cv2.imwrite('case1_008.png', img)

您的代码的输出:

上述示例代码的输出:

img = lin_stretch_img(img, 0.01, 99.99) 的输出(可能会给出更好的结果):


更新

样本 DICOM 图像的极性被反转。
最小值旨在显示为白色,最大值显示为黑色。

为了纠正极性,我们可以执行img = 255-img(转换为uint8后)。

检查极性是否反转:
根据documentation,如果光度解释等于“MONOCHROME1”,则极性反转(“MONOCHROME2”不反转)。

单色1
像素数据表示单个单色图像平面。最小样本
在执行任何 VOI 灰度转换后,值将显示为白色。见 PS3.4。仅当每像素样本数 (0028,0002) 的值为 1 时才可使用此值。可用于本机(未压缩)或封装(压缩)格式的像素数据。

如果光度解释为“MONOCHROME1”,则反转极性:

if ds[0x0028, 0x0004].value == 'MONOCHROME1':
    img = 255-img

文档还说我们必须“在 VOI 灰度转换之后”应用它。

here 描述了应用“VOI 灰度转换”:

img = apply_voi_lut(img, ds, index=0)

更新的代码示例:

import cv2
import pydicom
from pydicom.pixel_data_handlers.util import apply_voi_lut
import numpy as np

def lin_stretch_img(img, low_prc, high_prc, do_ignore_minmax=True):
    """ 
    Apply linear "stretch" - low_prc percentile goes to 0, 
    and high_prc percentile goes to 255.
    The result is clipped to [0, 255] and converted to np.uint8

    Additional feature:
    When computing high and low percentiles, ignore the minimum and maximum intensities (assumed to be outliers).
    """
    # For ignoring the outliers, replace them with the median value
    if do_ignore_minmax:
        tmp_img = img.copy()
        med = np.median(img)  # Compute median
        tmp_img[img == img.min()] = med
        tmp_img[img == img.max()] = med
    else:
        tmp_img = img

    lo, hi = np.percentile(tmp_img, (low_prc, high_prc))  # Example: 1% - Low percentile, 99% - High percentile

    if lo == hi:
        return np.full(img.shape, 128, np.uint8)  # Protection: return gray image if lo = hi.

    stretch_img = (img.astype(float) - lo) * (255/(hi-lo))  # Linear stretch: lo goes to 0, hi to 255.
    stretch_img = stretch_img.clip(0, 255).astype(np.uint8)  # Clip range to [0, 255] and convert to uint8
    return stretch_img


# https://www.visus.com/fileadmin/content/pictures/Downloads/JiveX_DICOME_Viewer/case1.zip
ds = pydicom.read_file('1.2.392.200036.9125.9.0.152034855.3288075520.2287343482.dcm') # read dicom image
img = ds.pixel_array # get image array

# https://pydicom.github.io/pydicom/stable/old/working_with_pixel_data.html#voi-lut-or-windowing-operation
# Apply "VOI gray scale transformations":
img = apply_voi_lut(img, ds, index=0)

img = lin_stretch_img(img, 0.1, 99.9)  # Apply "linear stretching" (lower percentile 0.1 goes to 0, and percentile 99.9 to 255).

# https://dicom.innolitics.com/ciods/rt-dose/image-pixel/00280004
if ds[0x0028, 0x0004].value == 'MONOCHROME1':
    img = 255-img  # Invert polarity if Photometric Interpretation is 'MONOCHROME1'

cv2.imwrite('1.2.392.200036.9125.9.0.152034855.3288075520.2287343482.png', img)

输出样本:

【讨论】:

  • 非常感谢你的代码。我尝试了 CASE 1 DICOM 样本,它就像一个魅力。但是,当我尝试手部 X 光片时,它仍然返回与帖子中提到的相同的输出(白色和黑色)。我已经匿名化了 DICOM 文件,所以请看一下上面编辑过的帖子!
  • DICOM 文件在哪里?我只能看到JPEG图像。
  • 刚刚更新了帖子
  • 用我的匿名 DICOM 数据集的链接更新了帖子。如果可以,请尝试一下?
  • 看起来图像极性反转了。在img = ds.pixel_array 之后添加img = 65535 - img。现在,我没有时间研究原因。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-04-18
  • 2018-07-18
  • 1970-01-01
相关资源
最近更新 更多