掘金在线查看
链接地址: https://code.juejin.cn/pen/7085931130204979231
在这里查看比较卡
HTML
<div id="dicomImage" style="width: 512px;height: 512px;" oncontextmenu="return false" onmousedown="return false"></div>
<input type="file" id="file" name="">
<script type="text/javascript" src="https://unpkg.com/cornerstone-core@2.3.0/dist/cornerstone.min.js"></script>
<script type="text/javascript" src="https://unpkg.com/cornerstone-wado-image-loader@3.3.1/dist/cornerstoneWADOImageLoader.min.js"></script>
<script type="text/javascript" src="https://unpkg.com/cornerstone-web-image-loader@2.1.1/dist/cornerstoneWebImageLoader.min.js"></script>
<script type="text/javascript" src="https://unpkg.com/dicom-parser@1.8.7/dist/dicomParser.min.js"></script>
JS
cornerstoneWADOImageLoader.external.dicomParser = dicomParser; // 扩展
cornerstoneWADOImageLoader.external.cornerstone = cornerstone; // 扩展
let el = document.querySelector("#dicomImage");
let result = undefined; // 存储当前选中的 DCM文件解析后的 DataSet 对象
let fileImgId = ""; // 当前选中的 DCM文件 imageId
cornerstone.enable(el); // 允许哪个元素渲染 DCM图像
// 添加自定义 provider 每次都会都会执行
// type ----> imagePlaneModule imagePixelModule imagePlaneModule voiLutModule modalityLutModule sopCommonModule overlayPlaneModule petIsotopeModule patientStudyModule generalSeriesModule 默认是返回 imagePixelModule
cornerstone.metaData.addProvider(function(type,imageId){
if(type == "imagePixelModule" && imageId == fileImgId){
return getImagePixelModule(result);
}
return metaDataProvider(type,imageId);
})
重写官方源码中的 wadouri 获取DataSet方法
function extend(){
let loadedDataSets = {}; // 存储对应的 url
const dataSetCacheManager = cornerstoneWADOImageLoader.wadouri.dataSetCacheManager;
let getCache = dataSetCacheManager.get; // 源码中的获取 dataset方法
// 你不重写,那么就自能改源码,否则实现不了,改源码只要添加个 add方法,就不会有重写那么多代码。
cornerstoneWADOImageLoader.wadouri.dataSetCacheManager = {
...dataSetCacheManager,
get(uri){ // 这个是最重要的
if(loadedDataSets[uri])return loadedDataSets[uri].dataSet;
return getCache(uri); // 返回 DataSet
},
add(uri,dataSet){
if(!loadedDataSets[uri]){
loadedDataSets[uri] = {};
}
loadedDataSets[uri].dataSet = dataSet;
}
}
}
源码
重写后
如果你不想重写,那么你就去源码中 ./imageLoader/wadouri/dataSetCacheManager.js 找到这个文件的位置,再里面添加个方法,并 return出来。
返回默认的 imagePixelModule 对象值
function getImagePixelModule(dataSet) {
var imagePixelModule = {
samplesPerPixel: dataSet.uint16('x00280002'),
photometricInterpretation: dataSet.string('x00280004'),
rows: dataSet.uint16('x00280010'),
columns: dataSet.uint16('x00280011'),
bitsAllocated: dataSet.uint16('x00280100'),
bitsStored: dataSet.uint16('x00280101'),
highBit: dataSet.uint16('x00280102'),
pixelRepresentation: dataSet.uint16('x00280103'),
planarConfiguration: dataSet.uint16('x00280006'),
pixelAspectRatio: dataSet.string('x00280034')
};
populateSmallestLargestPixelValues(dataSet, imagePixelModule);
if (imagePixelModule.photometricInterpretation === 'PALETTE COLOR' && dataSet.elements.x00281101) {
populatePaletteColorLut(dataSet, imagePixelModule);
}
return imagePixelModule;
}
function populateSmallestLargestPixelValues(dataSet, imagePixelModule) {
var pixelRepresentation = dataSet.uint16('x00280103');
if (pixelRepresentation === 0) {
imagePixelModule.smallestPixelValue = dataSet.uint16('x00280106');
imagePixelModule.largestPixelValue = dataSet.uint16('x00280107');
} else {
imagePixelModule.smallestPixelValue = dataSet.int16('x00280106');
imagePixelModule.largestPixelValue = dataSet.int16('x00280107');
}
}
function populatePaletteColorLut(dataSet, imagePixelModule) {
imagePixelModule.redPaletteColorLookupTableDescriptor = getLutDescriptor(dataSet, 'x00281101');
imagePixelModule.greenPaletteColorLookupTableDescriptor = getLutDescriptor(dataSet, 'x00281102');
imagePixelModule.bluePaletteColorLookupTableDescriptor = getLutDescriptor(dataSet, 'x00281103');
if (imagePixelModule.redPaletteColorLookupTableDescriptor[0] === 0) {
imagePixelModule.redPaletteColorLookupTableDescriptor[0] = 65536;
imagePixelModule.greenPaletteColorLookupTableDescriptor[0] = 65536;
imagePixelModule.bluePaletteColorLookupTableDescriptor[0] = 65536;
}
var numLutEntries = imagePixelModule.redPaletteColorLookupTableDescriptor[0];
var lutData = dataSet.elements.x00281201;
var lutBitsAllocated = lutData.length === numLutEntries ? 8 : 16;
if (imagePixelModule.redPaletteColorLookupTableDescriptor[2] !== lutBitsAllocated) {
imagePixelModule.redPaletteColorLookupTableDescriptor[2] = lutBitsAllocated;
imagePixelModule.greenPaletteColorLookupTableDescriptor[2] = lutBitsAllocated;
imagePixelModule.bluePaletteColorLookupTableDescriptor[2] = lutBitsAllocated;
}
imagePixelModule.redPaletteColorLookupTableData = getLutData(dataSet, 'x00281201', imagePixelModule.redPaletteColorLookupTableDescriptor);
imagePixelModule.greenPaletteColorLookupTableData = getLutData(dataSet, 'x00281202', imagePixelModule.greenPaletteColorLookupTableDescriptor);
imagePixelModule.bluePaletteColorLookupTableData = getLutData(dataSet, 'x00281203', imagePixelModule.bluePaletteColorLookupTableDescriptor);
}
function getLutDescriptor(dataSet, tag) {
if (!dataSet.elements[tag] || dataSet.elements[tag].length !== 6) {
return;
}
return [dataSet.uint16(tag, 0), dataSet.uint16(tag, 1), dataSet.uint16(tag, 2)];
}
function getLutData(lutDataSet, tag, lutDescriptor) {
var lut = [];
var lutData = lutDataSet.elements[tag];
for (var i = 0; i < lutDescriptor[0]; i++) {
if (lutDescriptor[2] === 16) {
lut[i] = lutDataSet.uint16(tag, i);
} else {
lut[i] = lutDataSet.byteArray[i + lutData.dataOffset];
}
}
return lut;
}
改写 metaDataProvider 获取
function metaDataProvider(type, imageId) {
var parsedImageId = cornerstoneWADOImageLoader.wadouri.parseImageId(imageId);
// cornerstoneWADOImageLoader.wadouri.dataSetCacheManager.get 这里就是被改写后的获取,若是不改写,源码是获取不到我们加入的 DataSet 内容
// 原因在于 源码每次返回的都是一个新对象,且 loadedDataSets 是不暴露的
var dataSet = cornerstoneWADOImageLoader.wadouri.dataSetCacheManager.get(parsedImageId.url);
if (!dataSet) {
return;
}
if (type === 'generalSeriesModule') {
return {
modality: dataSet.string('x00080060'),
seriesInstanceUID: dataSet.string('x0020000e'),
seriesNumber: dataSet.intString('x00200011'),
studyInstanceUID: dataSet.string('x0020000d'),
seriesDate: dicomParser.parseDA(dataSet.string('x00080021')),
seriesTime: dicomParser.parseTM(dataSet.string('x00080031') || '')
};
}
if (type === 'patientStudyModule') {
return {
patientAge: dataSet.intString('x00101010'),
patientSize: dataSet.floatString('x00101020'),
patientWeight: dataSet.floatString('x00101030')
};
}
if (type === 'imagePlaneModule') {
var imageOrientationPatient = cornerstoneWADOImageLoader.wadouri.metaData.getNumberValues(dataSet, 'x00200037', 6);
var imagePositionPatient = cornerstoneWADOImageLoader.wadouri.metaData.getNumberValues(dataSet, 'x00200032', 3);
var pixelSpacing = cornerstoneWADOImageLoader.wadouri.metaData.getNumberValues(dataSet, 'x00280030', 2);
var columnPixelSpacing = null;
var rowPixelSpacing = null;
if (pixelSpacing) {
rowPixelSpacing = pixelSpacing[0];
columnPixelSpacing = pixelSpacing[1];
}
var rowCosines = null;
var columnCosines = null;
if (imageOrientationPatient) {
rowCosines = [parseFloat(imageOrientationPatient[0]), parseFloat(imageOrientationPatient[1]), parseFloat(imageOrientationPatient[2])];
columnCosines = [parseFloat(imageOrientationPatient[3]), parseFloat(imageOrientationPatient[4]), parseFloat(imageOrientationPatient[5])];
}
return {
frameOfReferenceUID: dataSet.string('x00200052'),
rows: dataSet.uint16('x00280010'),
columns: dataSet.uint16('x00280011'),
imageOrientationPatient: imageOrientationPatient,
rowCosines: rowCosines,
columnCosines: columnCosines,
imagePositionPatient: imagePositionPatient,
sliceThickness: dataSet.floatString('x00180050'),
sliceLocation: dataSet.floatString('x00201041'),
pixelSpacing: pixelSpacing,
rowPixelSpacing: rowPixelSpacing,
columnPixelSpacing: columnPixelSpacing
};
}
if (type === 'imagePixelModule') {
return cornerstoneWADOImageLoader.wadouri.metaData.getImagePixelModule(dataSet);
}
if (type === 'modalityLutModule') {
return {
rescaleIntercept: dataSet.floatString('x00281052'),
rescaleSlope: dataSet.floatString('x00281053'),
rescaleType: dataSet.string('x00281054'),
modalityLUTSequence: cornerstoneWADOImageLoader.wadouri.metaData.getLUTs(dataSet.uint16('x00280103'), dataSet.elements.x00283000)
};
}
if (type === 'voiLutModule') {
var modalityLUTOutputPixelRepresentation = cornerstoneWADOImageLoader.wadouri.metaData.getModalityLUTOutputPixelRepresentation(dataSet);
return {
windowCenter: cornerstoneWADOImageLoader.wadouri.metaData.getNumberValues(dataSet, 'x00281050', 1),
windowWidth: cornerstoneWADOImageLoader.wadouri.metaData.getNumberValues(dataSet, 'x00281051', 1),
voiLUTSequence: cornerstoneWADOImageLoader.wadouri.metaData.getLUTs(modalityLUTOutputPixelRepresentation, dataSet.elements.x00283010)
};
}
if (type === 'sopCommonModule') {
return {
sopClassUID: dataSet.string('x00080016'),
sopInstanceUID: dataSet.string('x00080018')
};
}
if (type === 'petIsotopeModule') {
var radiopharmaceuticalInfo = dataSet.elements.x00540016;
if (radiopharmaceuticalInfo === undefined) {
return;
}
var firstRadiopharmaceuticalInfoDataSet = radiopharmaceuticalInfo.items[0].dataSet;
return {
radiopharmaceuticalInfo: {
radiopharmaceuticalStartTime: dicomParser.parseTM(firstRadiopharmaceuticalInfoDataSet.string('x00181072') || ''),
radionuclideTotalDose: firstRadiopharmaceuticalInfoDataSet.floatString('x00181074'),
radionuclideHalfLife: firstRadiopharmaceuticalInfoDataSet.floatString('x00181075')
}
};
}
if (type === 'overlayPlaneModule') {
return getOverlayPlaneModule(dataSet);
}
}
function getOverlayPlaneModule(dataSet) {
var overlays = [];
for (var overlayGroup = 0x00; overlayGroup <= 0x1e; overlayGroup += 0x02) {
var groupStr = "x60".concat(overlayGroup.toString(16));
if (groupStr.length === 4) {
groupStr = "x600".concat(overlayGroup.toString(16));
}
var data = dataSet.elements["".concat(groupStr, "3000")];
if (!data) {
continue;
}
var pixelData = [];
for (var i = 0; i < data.length; i++) {
for (var k = 0; k < 8; k++) {
var byte_as_int = dataSet.byteArray[data.dataOffset + i];
pixelData[i * 8 + k] = byte_as_int >> k & 1; // eslint-disable-line no-bitwise
}
}
overlays.push({
rows: dataSet.uint16("".concat(groupStr, "0010")),
columns: dataSet.uint16("".concat(groupStr, "0011")),
type: dataSet.string("".concat(groupStr, "0040")),
x: dataSet.int16("".concat(groupStr, "0050"), 1) - 1,
y: dataSet.int16("".concat(groupStr, "0050"), 0) - 1,
pixelData: pixelData,
description: dataSet.string("".concat(groupStr, "0022")),
label: dataSet.string("".concat(groupStr, "1500")),
roiArea: dataSet.string("".concat(groupStr, "1301")),
roiMean: dataSet.string("".concat(groupStr, "1302")),
roiStandardDeviation: dataSet.string("".concat(groupStr, "1303"))
});
}
return {
overlays: overlays
};
}
添加 file 事件
document.getElementById("file").onchange = function(e){
let files = e.target.files;
if(!files || !files.length)return;
let file = files[0];
cornerstone.disable(el); // 选择新的渲染之前一定要清掉上一次的图像
let read = new FileReader();
read.readAsArrayBuffer(file);
read.onload = function(){
// ArrayBuffer 转成 Uint8Array,再解析成 DataSet 对象
result = dicomParser.parseDicom(new Uint8Array(this.result));
let url = "http://"+file.name;
fileImgId = "wadouri:"+url; // 这个要唯一,若是想重复用,直接 loadAndCacheImage(fileImgId) 就行,前提是必须是已经加载过
// 这个也是上面重写后的方法
cornerstoneWADOImageLoader.wadouri.dataSetCacheManager.add(url,result);
//把DataSet 对象存进cornerstone.imageCache.cachedImages 第二个参数要包装成 cornerstone加载时需要的 对象
cornerstone.imageCache.putImageLoadObject(fileImgId,cornerstoneWADOImageLoader.wadouri.loadImageFromPromise(new Promise((res)=>{
res(result);
}),fileImgId))
cornerstone.loadAndCacheImage(fileImgId).then(img => {
cornerstone.enable(el);
cornerstone.displayImage(el, img); // 渲染图像在 canvas上
});
}
}
这样就能本地读取DCM并渲染图像处理,最终效果:
目前测试没有任何问题,有问题可自行解决。