cornerstone.js 使用总结
dicom 简介
dicom(Digital Imaging and Communications in Medicine), 医学影像的一组协议。包含了存储、打印、传输等。具体详细介绍。官网
dicom file, 存储包含病人、检查、影像信息的文件,通常以dcm为扩展名。文件格式
dicom file中由data elements 构成。每一个data element 都有一个tag(xxxx, xxxx)x为十六进制数,vr数据类型,vl数据长度,数据值。dicom tags
例如CT扫描生成的图像,其CT值在tag为(7FE0,0010)的element中。
cornertone-core简介
由javascript编写,构建web端的医学影像平台中的显示环节。对于复杂的web端的医学影像可视化项目,底层可以依赖于cornerstone-core做二维图像渲染。
渲染一张dicom格式的图像
图像信息
对于一张由CT扫描的图像,要准确的渲染成一张数字图像。需要理既以下相关的 data elements
- Samples per Pixel (0028, 0002), 字面理解每个像素的采样数,灰度图1, RGB图为3
- Photometric Interpretation (0028, 0004) 颜色空间
- Planar Configuration (0028,0006), 只对于samples per pixel 大于1时起作用,主要针对rgb图像,其三个颜色成分的存储排列。0: color by pixel, R1-G1-B1-R2-G2-B2…, 1: color By Plane, R1-R2-R3…-G1-G2…-B1-B2…
- Rows (0028, 0010), 垂直方向上向下采样因子的倍数。简单认为在在图像上一列像素的总数
- Columns (0028, 0011), 类似Rows,方向为水平方向
- Bits Allocated (0028,0100) 给每个采样的像素值分配的存储空间
- Bits Stored (0028,0101) 每个采样的像素值存储时所占的bit数
- High Bit (0028, 0102) 采样像素的最高有效位
- Pixel Representation (0028, 0103), 像素值数据类型0:无符号整型, 1:有符号数
- Pixel Data (7FE0, 0010) 构成图像的像素数据流,从左到右,从上到下。左上角的像素位置记为(1, 1)
- Slice Thickness(0018, 0050), 切片的层厚
- Image Position(Patient) (0020, 0032) 图像左上角第一个像素在病人坐标系下的值
- Image Orientation(Patient) (0020, 0037) 在病人坐标系下,图像的第一行与第一列的方向余弦值。?待图解
- Pixel Spacing (0028, 0030), 相邻两行(两列)像素在病人坐标系的物理距离值
图像显示
假定dicom file已经解析完成。所有tag数据以key/value形式作为dicom实列的属性。
const result = parseDicomFile(url);
const dicom = {
pixels: result.pixelsData
rows: result.rows
...
}
渲染dicom图像
- "<“img src=“imageURL” />”
- "<“svg />”
- "<“canvas />”
使用canvas方式显示,既然知道pixel data, 只需将其值映射成屏幕像素值即可。
由于pixel值的范围并不是屏幕像素0-255范围内,需要做相应的映射。modality lut, voi lut - modality lut, 像素存储值到实际输出值(OD值,CT值等)的映射,output = slope(0028, 1053) * store_value + intercept(0028, 1052)
- voi lut ,映射函数由linear、linear_exact、sigmoid,其具体取值由voi lut function(0028, 1056)决定,缺省值为linear. voi lut 函数的参数是windowCenter(0028, 1050)和window width(0028, 1051).对于linear形式,窗宽窗位决定了一维直线上的一段区域,落在该区域的CT值(假定)将会被线性映射到0-255区间内,在其范围外的将被clamp
- 像素值的映射有一定的顺序,先做modality lut, 在做voi lut
const {
pixels, rows, columns} = dicom;
const canvas = initCanvas();
const context = canvas.getContext('2d');
// draw
for (let i = 0; i < rows; i++) {
for (let j = 0; j < coloumns; j++) {
const value = voiLutFunc(modalityLutFunc(pixels[i * columns + j]))
context.save();
context.fillStyle(value)
context.fillRect(i, j, 1, 1);
context.restore();
}
}
太低效
canvasContext2d.putImageData(imageData, dx, dy)
imageData 对象,表示一定矩形区域内的像素值。每个像数值由RGBA四分分量,每个分量的数据类型为Uint8ClampedArray(对于超出0-255区间内的值会被裁剪)。data属性为Uint8ClampedArray数组,像素值在数组中依次排列。
imageData对象,可以由CanvasRenderingContext2D.createImageData/getImageData创建。
//...
// 假设图像为灰度图
const imageData = context.createImageData(rows, columns);
const storedPixels = imageData.data;
let index = 0;
for (let i = 0; i < rows; i++) {
for (let j = 0; j < coloumns; j++) {
const grayValue = voiLutFunc(modalityLutFunc(pixels[i*columns + j]))
storedPixels[index++] <