本篇续 cornerstone 基础概念篇(一), 主要对下列概念逐一说明:
- Images
- Pixel Coordinate System
- Rendering Loop
- Libraries
- Rendering Pipeline
- Metadata Providers
Images
Image 是cornerstone 用来封装图像的对象,上一篇中提到 Image Loader 返回的promise内容就是Image 对象,包含属性如下:
知识扩展
slope和intercept (斜率和截距)指定从存储在磁盘表示中的像素到成像设备中的像素的线性转换,设磁盘存储的值为 SV, 转换后的结果为 OutputUnits 那么
OutputUnits = slope∗SV+intercept
。
CT图象的CT值
反映组织对X 射线吸收值(衰减系数u),其单位为 Hounsfield Unit(Hu),以水的衰减系数为参照,即水的CT值为0;物质衰减系数大于水为正值,小于水为负值;以骨皮质和空气的衰减系数为上限和下限,定为+1000 和-1000。一般我们所处理的图象是经过量化后的灰度图象,标准的CT灰度图象为12位灰度图象。
RGBA
, 可以理解为RGB通道基础上再加上一个alpha通道,这个alpha通道用来表示透明程度,0完全透明,100则完全不透明
Pixel Coordinate System
如下图所示,左上角为第一个像素,像素中心坐标为 (0.5,0.5), 最后一个像素右下角顶点坐标为 (columns,rows)
四个常用应用场景:
- 在页面中,可以通过cornerstone.pageToPixel(element, e.pageX,e.pageY) 方法将浏览器事件换为像素坐标系中的坐标。
- 在图像上绘制几何图形的时候,可以先使用setToPixelCoordinateSystem将canvas context 设置为像素坐标系,这样在图像渲染的时候就能够准确定位。
- This coordinate system matches that specified in the DICOM Grayscale Softcopy Presentation State Graphic Annotation module for graphics drawn using the PIXEL annotation units.(暂时没弄懂具体含义)
- pageToPixel 得到的是小数坐标,可以使用Math.ceil() 将其转为整数,进而获取像素里的值。
Rendering Loop
通俗点讲就是持续渲染的意思,通过使用Window.requestAnimationFrame() 这个API实现每16ms渲染一次图像,想要理解Rendering Loop, 看源码更加高效直接,我在 概念篇一 中演示了渲染影像文件的基本步骤,先回顾一下
let element = document.getElementById('dicomImage');
let imageId = 'example://1';
cornerstone.enable(element);
cornerstone.loadImage(imageId).then(function(image) {
cornerstone.displayImage(element, image);
...
})
这里的cornerstone.enable 调用的是enable.js 这个文件,为了减少篇幅,我把里面的和本次主题不太相关的代码做了省略并做了简要注释,如下:
export default function (element, options) {
// 获得canvas上下文
const canvas = getCanvas(element);
const enabledElement = {...};
addEnabledElement(enabledElement);
// 执行cornerstone 激活元素事件
triggerEvent(events, EVENTS.ELEMENT_ENABLED, enabledElement);
resize(element, true);
function draw (timestamp) {
if (enabledElement.canvas === undefined) {
return;
}
const eventDetails = {
enabledElement,
timestamp
};
// 执行cornerstone 图像渲染前事件
triggerEvent(enabledElement.element, EVENTS.PRE_RENDER, eventDetails);
// 判断是否需要重绘、是否有图像
if (enabledElement.needsRedraw && hasImageOrLayers(enabledElement)) {
// 绘制图像
drawImageSync(enabledElement, enabledElement.invalid);
}
requestAnimationFrame(draw);
}
draw();
}
当我们调用cornerstone.enable(element)
这个函数时,实际上就是调用了enable.js 默认导出函数,而在这个函数最后一行 调用了 draw()
函数 ,draw函数中最后一行又调用了 requestAnimationFrame(draw)
, 看到这 大概能够明白cornerstone渲染图像的过程:
- 先调用cornerstone.enable(element) 激活元素,创建canvas,并将绘图交给requestAnimationFrame,每16ms 执行一次draw。
- 再调用cornerstone.loadImage 加载图像,获得图像,draw 方法判断有图像后(
hasImageOrLayers
),绘制图像 。
以上帮助理解 Rendering Loop,实际上在代码实现上还是有很多细节,后面会写源码系列进行分析,有兴趣的朋友可以关注我之后的更新。
requestAnimationFrame (RAF),在效果上和setTimeout、setInterval 类似,定时执行一些操作,但是requestAnimationFrame 这个API 在dom渲染上做了一些性能优化,比如集中渲染,隐藏的元素不渲染等,所以在动画绘制上有一定优势,但是需要注意的是,这个API 只有现代浏览器支持,上面代码中requestAnimationFrame函数内部是做了浏览器兼容的。
Libraries
cornerstone 团队开发的一些成品库
库 | 说明 |
---|---|
Cornerstone Core | 提供图标渲染、加载、缓存和视口转换等中心库 |
Cornerstone Tools | 对构建工具的扩展支持,对鼠标、键盘以及触摸设备的支持库,可以基于此开发一些实用的图像操作工具 |
Cornerstone WADO Image Loader | wado协议的影像加载,wado在dicom标准第10部分有介绍,DICOM 标准后续有会有文章进行解读 |
Cornerstone Web Image Loader | png/jpeg 图像加载 |
Cornerstone Math | 支持开发的一些实用的数据工具型函数和类 |
dicomParser | dicom解析 |
Rendering Pipeline
在drawImageSync.js
文件中有这样一段代码
import { renderColorImage } from '../rendering/renderColorImage.js';
import { renderGrayscaleImage } from '../rendering/renderGrayscaleImage.js';
import { renderPseudoColorImage } from '../rendering/renderPseudoColorImage.js';
import { renderLabelMapImage } from '../rendering/renderLabelMapImage.js';
...
export default function (enabledElement, invalidated) {
...
if (!render) {
if (enabledElement.viewport.colormap &&
enabledElement.viewport.colormap !== '' &&
enabledElement.image.labelmap === true) {
render = renderLabelMapImage;
} else if (enabledElement.viewport.colormap && enabledElement.viewport.colormap !== '') {
render = renderPseudoColorImage;
} else if (image.color) {
render = renderColorImage;
} else {
render = renderGrayscaleImage;
}
}
render(enabledElement, invalidated);
}
从代码中可以看出 cornerstone 对不同图像渲染会采用不同函数,而每个函数处理图像的过程就是这里的renderpipeline的概念,官网给出了下面的这幅图:
渲染函数 | 说明 |
---|---|
renderGrayscaleImage | 灰度图像的默认渲染流程,包含 Modality and VOI LUT 转换 |
renderPseudoColorImage | 当Viewport设置了colormap属性时会使用该流程, Modality and VOI LUT 转换之后还会通过Pseudocolor(伪彩) LUT转换 |
renderColorImage | 彩色图像的默认渲染流程,如果Image的rgba属性为false, 那么 Alpha通道设置都是不透明。 |
renderWebImage | png/jpeg 图像的渲染流程,可以选择是否需要VOI LUT转换,不需要的话直接通过getImage获取图像像素数据,交由canvas绘图。 |
renderLabelMapImage | 渲染标签图,直接用伪彩LUT转换成像素数据进行绘图 |
Metadata Providers
元数据提供函数,cornerstone 支持自定义元数据provider,要弄明白这个概念,首先必须得知道这里的元数据 (metadata)是什么含义。
医学图像通常带有大量非像素级元数据,例如图像的像素间距、患者ID或扫描获取日期。对于某些文件类型(如DICOM),这些信息存储在文件头中,可以读取、解析并在应用程序中传递。对于其他图像(如JPEG、PNG),这些信息需要独立于实际的像素数据提供。然而,即使对于DICOM图像,应用程序开发人员也通常独立于从服务器到客户机的像素数据传输来提供元数据,因为这可以大大提高性能。
为了处理这些场景,cornerstone为Metadata Providers的定义和使用提供了基础。Metadata Providers 其实就是一个获取元数据的函数,这个函数包含imageId和指定的元数据类型 type两个参数,下面是官网的示例
function metaDataProvider(type, imageId)
if (type === 'imagePlaneModule') {
if (imageId === 'ct://1') {
return {
frameOfReferenceUID:
"1.3.6.1.4.1.5962.99.1.2237260787.1662717184.1234892907507.1411.0",
rows: 512,
columns: 512,
rowCosines: {
x: 1,
y: 0,
z: 0
},
columnCosines: {
x: 0,
y: 1,
z: 0
},
imagePositionPatient: {
x: -250,
y: -250,
z: -399.100006
},
rowPixelSpacing: 0.976562,
columnPixelSpacing: 0.976562
};
}
}
}
// Register this provider with CornerstoneJS
cornerstone.metaData.addProvider(metaDataProvider);
// Retrieve this metaData
var imagePlaneModule = cornerstone.metaData.get('imagePlaneModule', 'ct://1');