在对DCM格式图像进行显示时,根据窗宽窗位进行归一化

本文介绍了一种CT图像处理方法,通过设置特定的窗宽(window_width)和窗位(window_center),实现对原始CT图像像素值的映射转换,从而达到增强或突出图像中某一组织或结构的效果。

window_center=1200 #窗位
window_width=400 #窗宽
win_min = (2window_center - window_width)/2.0 + 0.5
win_max = (2
window_center + window_width)/2.0 + 0.5
dFactor = 255.0/(win_max - win_min)
nNumPixels=rawData.shape[0]
for i in range(nNumPixels):
pixel_val = rawData[i]
if pixel_val < win_min:
disp_pixel_val = 0
continue;
if pixel_val > win_max:
disp_pixel_val = 255
continue;
nPixelVal = (pixel_val - win_min)*dFactor
if nPixelVal < 0:
disp_pixel_val = 0
elif nPixelVal > 255:
disp_pixel_val = 255
else:
disp_pixel_val = nPixelVal

<think> 我们已知在DICOM中,(Window Width)和(Window Center)用于调整图像显示对比度和亮度。通常,原始DICOM像素数据是12或16的,但在显示,为了适应8显示设备,会通过进行映射转换。现在,我们有一个8图像(例如从DICOM转换而来的JPEG或PNG),但我们希望根据原始的信息将其映射回16的范围(注意:这里并不是完全恢复原始16数据,而是模拟出一个16的数据范围,因为8数据已经丢失了部分信息)。 映射原理: (WW)和(WC)定义了一个线性变换,将原始像素值映射到显示灰度值。通常,显示灰度值范围是0到255(8)。我们可以将这个映射过程反过来,从8显示值推算出原始值的近似范围。 原始映射(从原始像素值到显示值): 设原始像素值为$P$,为$WW$,为$WC$,则显示值$D$(范围0-255)的计算公式为: 首先计算下限和上限: 下限 = $WC - WW/2$ 上限 = $WC + WW/2$ 然后: $D = \begin{cases} 0 & \text{if } P \leq WC - WW/2 \\ 255 & \text{if } P \geq WC + WW/2 \\ \left( \frac{P - (WC - WW/2)}{WW} \right) \times 255 & \text{otherwise} \end{cases}$ 现在,我们已知显示值$D$(8,0-255),要反推原始像素值$P$的近似值(16)。注意,由于8数据有量化损失,我们无法精确恢复原始值,但可以恢复到一个近似的范围。 反向映射公式: 由上式中的线性部分可得: $P = (WC - WW/2) + \frac{D \times WW}{255}$ 但需要注意的是,这个公式只适用于中间线性部分。对于$D=0$和$D=255$,我们只能知道$P$在下限以下或上限以上,具体值未知。通常,我们可以将$D=0$映射为$P_{min}$(例如0),$D=255$映射为$P_{max}$(例如65535),或者按照线性关系扩展到整个16范围。但更合理的做法是只使用定义的线性区间。 然而,这里有一个关键点:我们映射回16,通常希望覆盖整个原始数据的动态范围(比如0到65535),但原始数据的实际范围可能比定义的区间要大。因此,我们有两种选择: 1. 只恢复区间内的值,即$P$在$[WC-WW/2, WC+WW/2]$之间,并将这个区间线性映射到16(比如0到65535)或者保持原始单(HU值)。 2. 根据原始DICOM中可能提供的其他信息(如最小像素值和最大像素值)来恢复整个动态范围。 由于我们只有8图像信息,没有原始的最小/最大值,我们只能选择第一种方式:将区间映射到16范围。 具体步骤: 1. 确定区间:$[L, U]$,其中$L = WC - WW/2$,$U = WC + WW/2$。 2. 将8图像中的每个像素值$D$(0-255)线性映射到这个区间: $P = L + \frac{D}{255} \times (U - L)$ 3. 然后,我们可以将这个$P$值缩放到16(0-65535)的范围。注意,这里$P$的单是原始像素单(可能是HU)。如果我们需要16无符号整数,可以如下缩放: 首先将$P$归一化到[0,1]:但是注意,$P$的值可能为负(如果是HU值),也可能超过65535。因此,我们通常根据实际需要缩放。 然而,实际中,我们可能并不需要将$P$表示为0-65535之间的整数,而是保持其物理值(浮点数)。如果需要存储为16整数,则必须知道整个数据集的最小值和最大值,但我们没有这些信息。 因此,我们通常有两种输出形式: - 浮点型:保留计算得到的$P$(物理值) - 16整型:将区间$[L, U]$线性映射到16整数的范围[0, 65535](或[-32768, 32767])。这,我们可以定义: $P_{int} = \left( \frac{P - L}{U - L} \right) \times 65535$ 注意:这样映射后,原来的0-255的8图像就变成了16图像,并且区间被拉伸到整个16范围。 但是,请注意,原始DICOM可能有多个(如肺、骨等),但这里我们只使用一个(通常是默认的)。 示例代码: 假设我们有一张8灰度图像(numpy数组形式)`img_8bit`,`wc`和`ww`,我们希望得到映射后的16图像(0-65535的整数)。 步骤: 1. 计算下限L和上限U L = wc - ww/2.0 U = wc + ww/2.0 2. 将8图像归一化到0-1(浮点) normalized = img_8bit.astype(np.float32) / 255.0 3. 将归一化图像映射到[L, U]区间 p_values = L + normalized * (U - L) 4. 现在,如果我们要将[L, U]区间映射到[0, 65535]的整数: # 首先将p_values归一化到[0,1](相对于区间[L,U]) normalized_16 = (p_values - L) / (U - L) # 然后缩放到0-65535并转换为整数 img_16bit = (normalized_16 * 65535).astype(np.uint16) 注意:这种方法将区间拉伸到整个16动态范围,因此图像对比度与原始8图像相同(只是数增加)。如果原始8图像已经使用了,那么这样映射回去的图像在16下使用相同的显示,将和8图像看起来一样。 但是,如果我们希望恢复到原始DICOM文件的像素值范围,我们需要知道原始的最小值和最大值。在没有这些信息的情况下,我们只能做到这样。 另一种方法:如果我们知道原始DICOM的像素值范围(例如通过DICOM标签(0028,0106)和(0028,0107)获得最小和最大像素值),那么我们可以先将8图像映射回原始范围。 步骤(如果知道原始最小最大值): 设原始最小值为min_original,最大值为max_original。 那么原始8图像是通过以下线性映射得到的: img_8bit = ((p_values_original - min_original) / (max_original - min_original) * 255).astype(np.uint8) 反向映射: p_values_original = min_original + (img_8bit.astype(np.float32) / 255.0) * (max_original - min_original) 然后我们可以转换为16整数,但要注意原始数据可能有符号,所以需要根据情况选择有符号16还是无符号16。 由于问题中只提到了,没有原始最小最大值,我们采用第一种方法。 代码示例(使用映射到16[0,65535]): ```python import numpy as np import cv2 # 假设我们有一个8灰度图像(0-255) img_8bit = cv2.imread('8bit_image.png', cv2.IMREAD_GRAYSCALE) # (假设值) wc = 40 # ww = 400 # # 计算区间 L = wc - ww / 2.0 U = wc + ww / 2.0 # 将8图像归一化到0-1 normalized = img_8bit.astype(np.float32) / 255.0 # 映射到[L, U]区间 p_values = L + normalized * (U - L) # 将[L, U]区间映射到0-65535整数 # 注意:由于浮点运算可能有超出[L,U]的值(理论不会,但实际因计算误差可能有),我们使用clip确保在区间内 p_values_clipped = np.clip(p_values, L, U) normalized_16 = (p_values_clipped - L) / (U - L) img_16bit = (normalized_16 * 65535).astype(np.uint16) # 保存16图像(注意:16PNG) cv2.imwrite('16bit_image.png', img_16bit) ``` 注意:上述代码中,我们使用OpenCV保存16PNG图像。由于区间外的值被裁剪到边界,所以不会超出范围。 但如果原始8图像是通过映射得到的,那么其像素值0和255分别对应原始值小于L和大于U,因此我们将其映射为16的最小值0和最大值65535。中间值线性映射。 这样,我们就实现了从8图像根据映射回16图像。 相关问题: §§相关问题§§ 1. 如何从DICOM文件中获取信息? 2. 除了,还有哪些DICOM显示参数会影响图像显示效果? 3. 如果原始DICOM数据是有符号整数,映射回16如何处理负数? </think> ### 根据将8bit像素值映射回16bit的实现方法 在医学影像处理中,(window center)和(window width)定义了图像显示范围。以下是完整的映射原理和实现方法: #### 映射原理 设8像素输入值为 $D$ (范围0-255),为 $WC$,为 $WW$,则映射回16的公式为: $$ P = \begin{cases} 0 & \text{若 } D = 0 \\ 65535 & \text{若 } D = 255 \\ (WC - \frac{WW}{2}) + \frac{D}{255} \times WW & \text{其他情况} \end{cases} $$ 其中: - $P$ 是输出的16像素值 (范围0-65535) - $WW$ 决定显示范围大小 - $WC$ 决定显示范围中心置 - 映射保留了原始数据的相对灰度关系[^1][^3] #### Python实现示例 ```python import numpy as np import cv2 from pydicom import dcmread def map_8bit_to_16bit(image_8bit, window_center, window_width): """ 将8图像根据映射回16范围 参数: image_8bit: 8灰度图像(numpy数组, uint8) window_center: (浮点数) window_width: (浮点数) 返回: 16图像(numpy数组, uint16) """ # 计算显示范围上下限 w_min = window_center - window_width/2 w_max = window_center + window_width/2 # 归一化8图像到[0,1]范围 normalized = image_8bit.astype(np.float32) / 255.0 # 应用映射 img_16bit = w_min + normalized * window_width # 转换为16整数并裁剪到有效范围 img_16bit = np.clip(img_16bit, 0, 65535) return img_16bit.astype(np.uint16) # 使用示例 if __name__ == "__main__": # 读取DICOM文件获取元数据 ds = dcmread("input.dcm") wc = ds.WindowCenter if hasattr(ds, 'WindowCenter') else 2048 ww = ds.WindowWidth if hasattr(ds, 'WindowWidth') else 4096 # 读取8图像(假设已存在) img_8bit = cv2.imread("8bit_image.png", cv2.IMREAD_GRAYSCALE) # 执行映射 img_16bit = map_8bit_to_16bit(img_8bit, wc, ww) # 保存结果 cv2.imwrite("restored_16bit.png", img_16bit) print(f"映射完成: ={wc}, ={ww}, 输出范围={np.min(img_16bit)}-{np.max(img_16bit)}") ``` #### 关键技术点 1. **数值精度处理**: - 使用`np.float32`保留中间计算精度 - 最终裁剪到[0,65535]避免溢出 - 处理极端值($D=0$和$D=255$) 2. **医学影像特性**: - 定义了临床关注区域 - 映射保留诊断关键区域的灰度层次[^1] - 支持HU值(负值)通过调整裁剪范围 3. **性能优化**: ```python # 使用向量化运算加速 img_16bit = w_min + normalized * window_width ``` - 避免循环操作 - 利用NumPy的并行计算优势 #### 验证方法 ```python # 验证反向映射的准确性 test_values = [0, 128, 255] for val in test_values: input_arr = np.array([[val]], dtype=np.uint8) output = map_8bit_to_16bit(input_arr, wc, ww)[0,0] expected = w_min + (val/255)*ww print(f"输入{val} → 输出{output} (期望值:{expected:.1f}, 误差:{abs(output-expected):.2f})") ```
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值