cornerstone 基础概念篇(二)

本篇续 cornerstone 基础概念篇(一), 主要对下列概念逐一说明:

  • Images
  • Pixel Coordinate System
  • Rendering Loop
  • Libraries
  • Rendering Pipeline
  • Metadata Providers

Images

Image 是cornerstone 用来封装图像的对象,上一篇中提到 Image Loader 返回的promise内容就是Image 对象,包含属性如下:


image.png

知识扩展

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


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');
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,602评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,442评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,878评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,306评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,330评论 5 373
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,071评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,382评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,006评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,512评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,965评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,094评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,732评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,283评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,286评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,512评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,536评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,828评论 2 345

推荐阅读更多精彩内容