查看本地dicom文件

880 阅读3分钟

掘金在线查看

链接地址:  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;
                }
        }
}

源码

image.png

重写后

image.png

如果你不想重写,那么你就去源码中 ./imageLoader/wadouri/dataSetCacheManager.js 找到这个文件的位置,再里面添加个方法,并 return出来。

image.png

返回默认的 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并渲染图像处理,最终效果:

image.png

image.png

目前测试没有任何问题,有问题可自行解决。