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/docs/latest/modules/segmentation.md at master · cornerstonejs/cornerstoneTools (github.com)
🎉 🎉 🎉 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(); }
复制