正如所评论的,缩放最大值可能还不够。
我们可以尝试“线性拉伸”,其中一些低百分位数变为 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)
输出样本: