写点什么

cornerstone 基础概念篇(二)

用户头像
Lazy
关注
发布于: 2021 年 05 月 14 日
cornerstone 基础概念篇(二)

本篇续 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)


注:哈哈,pc 画图工具作品,凑合看


四个常用应用场景:


  • 在页面中,可以通过 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 团队开发的一些成品库


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 的概念,官网给出了下面的这幅图:



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 CornerstoneJScornerstone.metaData.addProvider(metaDataProvider);// Retrieve this metaDatavar imagePlaneModule = cornerstone.metaData.get('imagePlaneModule', 'ct://1');
复制代码


发布于: 2021 年 05 月 14 日阅读数: 23
用户头像

Lazy

关注

某知名软件上市公司基层软件开发。 2019.10.17 加入

助力于脑科学软件工程,为中国脑计划舔砖加瓦。 脑科学软件工程包含多种模态数据的处理分析(如脑影像、基因、行为评测等)、脑相关科研平台、研究成果转化。其应用可覆盖到医疗、教育、生活娱乐等各个领域。

评论

发布
暂无评论
cornerstone 基础概念篇(二)