cornerstone支持overlay 数据渲染
1. 引子
在医学影像领域,DICOM(Digital Imaging and Communications in Medicine)是一种广泛使用的标准格式,用于存储和传输医学图像。Overlay 标签在 DICOM 中用于表示图像上的叠加层,它们可以包含图像标记、文本或其他信息。这些 Overlay 数据通过 60xx
标签组进行表示,其中 xx
可以从 00
到 1F
,这允许在同一图像中存在多个 Overlay 平面。
常用的 Overlay 相关标签包括:
- (60xx,0010) Overlay Rows: 行数,表示叠加层的高度。
- (60xx,0011) Overlay Columns: 列数,表示叠加层的宽度。
- (60xx,0040) Overlay Type: 叠加层的类型,通常为
'G'
(图形)或'R'
(区域)。 - (60xx,0050) Overlay Origin: 叠加层的起始位置(x, y)。
- (60xx,0100) Overlay Bits Allocated: 每个像素分配的位数,通常为 1。
- (60xx,0102) Overlay Bit Position: 数据的位位置,通常为 0。
- (60xx,3000) Overlay Data: 实际叠加层数据,存储为二进制位图。
2. 解析 Overlay 标签
为了从 DICOM 文件中提取并解析 Overlay 数据,首先需要遍历所有可能存在的 Overlay 标签组(即 60xx
标签组),并收集相关的信息。
getOverlayTags
方法
该方法遍历可能的 Overlay 标签组,从 60xx
开始,直到 601F
。每个标签组代表一个可能的 Overlay 平面。方法会检查每个标签组是否包含 Overlay 数据,并将这些数据收集到一个数组中。
function getOverlayTags(dataSet) {
const overlays = [];
for (let group = 0x6000; group <= 0x601F; group += 2) {
const groupHex = group.toString(16).padStart(4, '0');
const overlay = {
rows: dataSet.uint16(`x${groupHex}0010`),
columns: dataSet.uint16(`x${groupHex}0011`),
type: dataSet.string(`x${groupHex}0040`),
origin: [dataSet.int16(`x${groupHex}0050`, 0), dataSet.int16(`x${groupHex}0050`, 1)],
bitPosition: dataSet.uint16(`x${groupHex}0102`),
bitsAllocated: dataSet.uint16(`x${groupHex}0100`),
data: dataSet.elements[`x${groupHex}3000`],
description: dataSet.string(`x${groupHex}0022`),
};
if (overlay.data) {
overlays.push(overlay);
}
}
return overlays;
}
getOverlayData
方法
getOverlayData
方法从特定的 Overlay 标签中提取二进制数据。这些数据被存储为 DICOM 数据集的字节数组,并通过位操作提取每个像素的数据。
function getOverlayData (dataSet, overlay) {
const overlayData = overlay.data
if (!overlayData) {
return
}
const data = [];
for (let i = 0; i < overlayData.length; i++) {
for (let k = 0; k < 8; k++) {
const byte_as_int = dataSet.byteArray[overlayData.dataOffset + i];
data[i * 8 + k] = (byte_as_int >> k) & 0b1;
}
}
return data
}
注意事项
- 字节顺序:在 DICOM 数据中,Overlay 数据通常以位图的形式存储,其中每个字节的每一位代表一个像素。因此,需要通过位操作从字节中提取每一位数据。
- 位位置:
Overlay Bit Position
标签指示了 Overlay 数据的位位置,通常为 0。
这部分都是在cornerstoneWADOImageLoader源码里修改的,在其处理了dicom返回image对象里新加了个getOverlayData方法,用于获取overlay data。
因为其他同事做过一次修改,直接将image.data的byteArray给删除了,目的是节约空间,导致我之前都没获取到完整的byteArray,也就无法根据位置信息获取overlay data,没对该js文件做处理可以。
3. 渲染 Overlay 数据
渲染过程通过 handleRender
函数实现。这个函数在 Canvas 上绘制从 getOverlayData
方法获得的二进制 Overlay 数据。
handleRender
方法
此方法监听图像渲染事件,在 Canvas 上绘制解析后的 Overlay 数据。
// 这里Overlay 数据默认取6000
function handleRender(e) {
const eventData = e.detail;
const dataSet = eventData.image.data;
if (eventData && eventData.image && dataSet.uint16 && dataSet.uint16(OVERLAYTAGS.OverlayBitPosition) === 0) {
context.save();
const width = dataSet.uint16(OVERLAYTAGS.OverlayRows);
const height = dataSet.uint16(OVERLAYTAGS.OverlayColumns);
const overlayData = eventData.image.getOverlayData()
const layerCanvas = document.createElement('canvas');
layerCanvas.width = width;
layerCanvas.height = height;
const layerContext = layerCanvas.getContext('2d');
layerContext.save();
layerContext.fillStyle = "#ffffff";
if (dataSet.string(OVERLAYTAGS.OverlayType) === 'R') {
layerContext.fillRect(0, 0, layerCanvas.width, layerCanvas.height);
layerContext.globalCompositeOperation = 'xor';
}
let i = 0;
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
const pixel = overlayData[i++];
if (pixel > 0) {
layerContext.fillRect(x, y, 1, 1);
}
}
}
layerContext.restore();
context.drawImage(layerCanvas, 0, 0);
context.restore();
}
}
因为是同步添加覆盖层,所以不需要考虑放大、缩小、旋转等操作,会跟原图保持同步。
关键点
- 缩放与变换:在渲染之前,确保应用正确的缩放和变换矩阵,以保证 Overlay 数据在图像上的正确位置。
- 位图绘制:使用
fillRect
方法逐个像素地绘制 Overlay 数据,确保精确显示。
4. 总结
通过上述方法,可以从 DICOM 图像中有效解析并渲染 Overlay 数据。这包括提取相关标签、解析 Overlay 位图数据,以及使用 Canvas 在图像上绘制叠加层。这种方式不仅能处理常见的 Overlay 情况,还能扩展到处理更复杂的多层叠加。
后续优化
- 性能优化:对于大型 DICOM 图像或多个 Overlay 平面,可以考虑使用 Web Workers 或 OffscreenCanvas 来提升渲染性能。
- 兼容性处理:确保对各种 DICOM 标准变体和特例的兼容性,如处理不同的 Bit Position 或 Bits Allocated 值。
通过以上步骤,你可以全面、准确地处理 DICOM Overlay 数据。如果在实际应用中遇到特殊情况或需要进一步优化,欢迎继续讨论。