vue医学图像处理包cornerstone.js基本使用
本文为编辑2D图像的
先上参考链接:
cornerstonejs/cornerstoneTools: A framework for tools built on top of Cornerstone. (github.com)
Introduction · cornerstone-tools (cornerstonejs.org)
Cornerstone Tools: Examples (cornerstonejs.org)
Cornerstone Tools: API Docs (cornerstonejs.org)
Modules · cornerstone-tools (cornerstonejs.org)
🎉 🎉 🎉 CornerstoneTools 4.0 🎉 🎉 🎉 · Issue #1061 · cornerstonejs/cornerstoneTools (github.com)
安装依赖
npm install --save cornerstone-core cornerstone-math cornerstone-tools hammerjs cornerstone-web-image-loader
PS:这里应该是没落下东西,以防万一,这里写一下
package.json
:"dependencies": { "bootstrap": "^5.3.2", "bootstrap-vue-next": "^0.14.10", "cornerstone-core": "^2.6.1", "cornerstone-math": "^0.1.10", "cornerstone-tools": "^6.0.10", "cornerstone-web-image-loader": "^2.1.1", "hammerjs": "^2.0.8", "nprogress": "^0.2.0", "vue": "^3.3.4", "vue-router": "^4.2.5" },
code
初始化
首先肯定是在html也就是<template>
标签里面写好自己要写入的位置:
<div id="cornerstone"/>
在<script>
标签里面引入需要的依赖
// cornerstone tool
import cornerstone from 'cornerstone-core';
import cornerstoneMath from 'cornerstone-math';
import cornerstoneTools from 'cornerstone-tools';
import Hammer from 'hammerjs';
import cornerstoneWebImageLoader from "cornerstone-web-image-loader"
// external
cornerstoneTools.external.cornerstone = cornerstone;
cornerstoneTools.external.Hammer = Hammer;
cornerstoneTools.external.cornerstoneMath = cornerstoneMath;
cornerstoneWebImageLoader.external.cornerstone = cornerstone;
// init
cornerstoneTools.init(
{
/**
* Most tools have an associated canvas or SVG cursor. Enabling this flag
* causes the cursor to be shown when the tool is active, bound to left
* click, and the user is hovering the enabledElement.
*/
showSVGCursors: true,
outlineWidth: 2
},
{
// 这里的配置项用于裁剪功能
moduleName: 'segmentation',
configuration: {
outlineWidth: 2
}
}
);
// segmentation
const { setters, getters } = cornerstoneTools.getModule('segmentation');
Module
属性见(后面导出裁剪后的图片要用):
Modules · cornerstone-tools (cornerstonejs.org)
下面使用的vue3,vue2也差不太多,首先看mounted
里面的初始化信息(代码后面有简介):
export default defineComponent({
mounted() {
this.imageUrl = '你的图片地址';
this.tools = {
PanTool: cornerstoneTools.PanTool,
LengthTool: cornerstoneTools.LengthTool,
MagnifyTool: cornerstoneTools.MagnifyTool,
AngleTool: cornerstoneTools.AngleTool,
FreehandScissorsTool: cornerstoneTools.FreehandScissorsTool,
CircleScissorsTool: cornerstoneTools.CircleScissorsTool,
RectangleScissorsTool: cornerstoneTools.RectangleScissorsTool
}
// Make sure we have at least one element Enabled
this.element = document.querySelector('#cornerstone');
cornerstone.enable(
this.element,
{
colormap: "" // 玄学的对象,留着吧
}
);
const viewport = cornerstone.getViewport(this.element);
/*
简单列一下属性,我这里没用他们
hflip : 水平旋转
vflip : 垂直旋转
invert : 颜色反转
rotation : 旋转角度
scale : 缩放
translation : 位移
voi : 切片
windowWidth : 窗口宽度
windowCenter : 窗口中心
*/
const imageIds = [
this.imageUrl
];
// stack,使用裁剪功能需要的东西,Segmentation required
const stack = {
currentImageIdIndex: 0,
imageIds: imageIds,
};
cornerstone.loadAndCacheImage(imageIds[0]).then((image) => {
// 图片的大小
this.imgShape = [image.width, image.height];
// 图片的数据
this.imgData = image.getPixelData();
cornerstoneTools.addStackStateManager(this.element, ['stack']); // Segmentation required
cornerstoneTools.addToolState(this.element, 'stack', stack); // Segmentation required
cornerstone.displayImage(this.element, image);
cornerstone.setViewport(this.element, viewport);
// default add zoom tool to ALL currently Enabled elements
cornerstoneTools.addTool(cornerstoneTools.ZoomMouseWheelTool);
cornerstoneTools.setToolActive('ZoomMouseWheel', { mouseButtonMask: 1 })
});
},
data() {
return {
imageUrl: '',
element: null,
tools: [],
selected: '',
name: {
PanTool: 'Pan',
LengthTool: 'Length',
MagnifyTool: 'Magnify',
AngleTool: 'Angle',
FreehandScissorsTool: 'FreehandScissors',
CircleScissorsTool: 'CircleScissors',
RectangleScissorsTool: 'RectangleScissors'
},
imgData: [],
imgShape: []
}
},
...
这里的this.tools
和this.name
里面的数据可以参考:
Cornerstone Tools: Examples (cornerstonejs.org)
PS:截止本人写博客的时间2023.12.13,这个页面还是没有完善
methods
接下来看methods
里面的方法
激活工具
activaTool(tool) {
// Adds tool to ALL currently Enabled elements
this.selected = tool;
/*
下面是将裁剪工具设置为FILL_INSIDE,也就是内部填充
还有属性:
FILL_OUTSIDE: 外部填充,
ERASE_OUTSIDE: 擦除外部,
ERASE_INSIDE: 擦除内部,
*/
if(tool.includes('Scissors')) {
cornerstoneTools.addTool(this.tools[tool], { defaultStrategy: "FILL_INSIDE" });
}else {
cornerstoneTools.addTool(this.tools[tool]);
}
cornerstoneTools.setToolEnabled(this.name[tool], { mouseButtonMask: 1 });
cornerstoneTools.setToolActive(this.name[tool], { mouseButtonMask: 1 })
},
使用的时候传入工具名称就好,例如在this.tool
里面的属性LengthTool
:
activaTool('LengthTool')
清空所有基本工具
这个方法清空所有工具,除了scissors
clearAllTool() {
for(let k in this.name) {
cornerstoneTools.clearToolState(this.element, this.name[k]);
}
// 写了他才能让界面也更新清除了tool的视图
cornerstone.updateImage(this.element);
// reset viewport将视图重置位置
cornerstone.reset(this.element);
},
直接保存视图,所见所得
saveRes() {
// 直接保存viewport,所见所得
cornerstoneTools.SaveAs(this.element, “你的文件名” + Date.now() + ".jpg");
},
图片后缀是jpg
还是png
都行
※※※保存Scissors工具覆盖的区域
saveCrop() {
const labelmap2D = getters.labelmap2D(this.element).labelmap2D;
// 获取像素数据
const pixelData = labelmap2D.pixelData;
// 使用 filter 过滤出非0值
let nonZeroValues = pixelData.filter(element => element !== 0);
// 非0 的数量不为0(有mask)
if(nonZeroValues.length) {
// 初始化边界值为图像覆盖区域的的宽度和高度
let minX = this.imgShape[0];
let minY = this.imgShape[1];
let maxX = 0;
let maxY = 0;
// 创建一个新的Uint8ClampedArray,用于存储标记区数据
var roi = new Uint8ClampedArray(this.imgData.length);
// 遍历 pixelData
for (let i = 0; i < pixelData.length; i++) {
// 如果像素值不为0
if (pixelData[i] !== 0) {
// 计算像素的坐标
const x = i % this.imgShape[0];
const y = Math.floor(i / this.imgShape[0]);
// 更新边界值
minX = Math.min(minX, x);
minY = Math.min(minY, y);
maxX = Math.max(maxX, x);
maxY = Math.max(maxY, y);
// 如果mask值非零,则将对应位置的data值复制到roi数组
roi[i * 4] = this.imgData[i * 4]; // R component
roi[i * 4 + 1] = this.imgData[i * 4 + 1]; // G component
roi[i * 4 + 2] = this.imgData[i * 4 + 2]; // B component
roi[i * 4 + 3] = this.imgData[i * 4 + 3]; // Alpha component
}else {
// 如果mask值为零,则将对应位置的roi值设为0
roi[i * 4] = 0;
roi[i * 4 + 1] = 0;
roi[i * 4 + 2] = 0;
roi[i * 4 + 3] = 0; // 设置 alpha 值为 0,表示完全透明
}
}
// 计算覆盖区域的长度和宽度
const coveredWidth = maxX - minX + 1;
const coveredHeight = maxY - minY + 1;
// 下面是导出标记区域
// 创建一个Canvas元素
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
// 设置Canvas的宽度和高度
canvas.width = coveredWidth;
canvas.height = coveredHeight;
// 创建ImageData对象,用于设置图像数据
const imageData = ctx.createImageData(coveredWidth, coveredHeight);
// 将roi数据复制到ImageData对象中,但是要根据有效区域进行偏移
for (let i = 0; i < coveredHeight; i++) {
for (let j = 0; j < coveredWidth; j++) {
const sourceIndex = ((minY + i) * this.imgShape[0] + minX + j) * 4;
const targetIndex = (i * coveredWidth + j) * 4;
for (let k = 0; k < 4; k++) {
imageData.data[targetIndex + k] = roi[sourceIndex + k];
}
}
}
// 将ImageData对象绘制到Canvas上
ctx.putImageData(imageData, 0, 0);
// 将Canvas转换为DataURL
const dataURL = canvas.toDataURL('image/png');
// 创建一个虚拟链接
const a = document.createElement('a');
// 设置链接的href属性为DataURL
a.href = dataURL;
// 设置链接的下载属性和文件名
let fileName = “自己设置自己的文件名字”;
// 文件名加上随机值
a.download = fileName + Math.floor(Math.random() * new Date().getTime()) + '.png';
// 模拟点击链接以触发下载
a.click();
}else {
alert('请选择一个区域');
}
},
最后保存的是png后缀的,只有标记区域,就算是不规则形状也是
其余区域透明
undo框选
PS:框选不能用我的
clearAllTool
函数清除
myUndo() {
setters.undo(this.element);
},
redo框选
myRedo() {
setters.redo(this.element);
}
清除所有框选区域
clearMask() {
const labelmap2D = getters.labelmap2D(this.element).labelmap2D;
const pixelData = labelmap2D.pixelData;
pixelData.fill(0);
setters.updateSegmentsOnLabelmap2D(labelmap2D)
// 写了他才能让界面也更新清除了mask的视图
cornerstone.updateImage(this.element);
},
还原所有操作
这里需要调用前面写好的两个函数了就
resetAll() {
this.clearAllTool();
this.clearMask();
}