DICOM to PNG error

Hey everyone,

So I have DICOM medical images I want to convert and view as PNG, without cropping or resizing. It uses an external function - npm , but I get a rendering error which indicates something is missing in the function, or maybe it is related to the PhotometricInterpretation DICOM tag value. However, it works fine over the template node.

Here's what I have so far:

Flow:

[{"id":"61dce39602934bc2","type":"tab","label":"Dicom Viewer","disabled":false,"info":""},{"id":"4762fc781032e120","type":"template","z":"61dce39602934bc2","name":"dcmjs-imaging","field":"payload","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"\n    <style>\n      html,\n      body {\n        margin: 0;\n        padding: 0;\n        width: 100%;\n        height: 100%;\n        display: table;\n      }\n      .container {\n        display: table-cell;\n        text-align: center;\n        vertical-align: middle;\n      }\n      .content {\n        display: inline-block;\n        text-align: center;\n      }\n    </style>\n  </head>\n  <body>\n    <div id=\"dropZone\" class=\"container\">\n      <div class=\"content\">\n        <p id=\"infoText\">\n          Drag and drop a DICOM Part 10 file to render it!<br />Nothing gets uploaded anywhere.\n        </p>\n        <canvas id=\"renderingcanvas\"></canvas>\n      </div>\n    </div>\n  </body>\n  <script src=\"https://unpkg.com/dcmjs-imaging@0.0.4/build/dcmjs-imaging.min.js\"></script>\n  <script>\n    const { DicomImage, WindowLevel } = window.dcmjsImaging;\n\n    function renderFile(file) {\n      const reader = new FileReader();\n      reader.onload = function (file) {\n        const arrayBuffer = reader.result;\n        const canvasElement = document.getElementById('renderingcanvas');\n        canvasElement.onwheel = undefined;\n        canvasElement.onmousedown = undefined;\n        canvasElement.onmousemove = undefined;\n        canvasElement.onmouseup = undefined;\n        const infoTextElement = document.getElementById('infoText');\n        infoTextElement.innerText = '';\n\n        const t0 = performance.now();\n\n        let frame = 0;\n        let windowing = false;\n        let x = 0;\n        let y = 0;\n\n        const image = new DicomImage(arrayBuffer);\n        const windowLevels = WindowLevel.fromDicomImage(image);\n        const windowLevel =\n          windowLevels.length > 0 ? windowLevels[0] : createDefaultWindowLevel(image);\n\n        const t1 = performance.now();\n        console.log('Parsing time: ' + (t1 - t0) + ' ms');\n        console.log('Width: ', image.getWidth());\n        console.log('Height: ', image.getHeight());\n        console.log('Number of frames: ', image.getNumberOfFrames());\n        console.log('Transfer syntax UID: ', image.getTransferSyntaxUid());\n\n        if (image.getNumberOfFrames() > 1) {\n          canvasElement.onwheel = (event) => {\n            event.preventDefault();\n\n            const next = event.deltaY > 0 ? 1 : -1;\n            if (frame + next > image.getNumberOfFrames() - 1 || frame + next < 0) {\n              return;\n            }\n\n            frame = frame + next;\n            renderFrame(image, frame, windowLevel, canvasElement, infoTextElement);\n          };\n        }\n\n        if (windowLevel) {\n          canvasElement.onmousedown = (event) => {\n            x = event.offsetX;\n            y = event.offsetY;\n            windowing = true;\n          };\n          canvasElement.onmousemove = (event) => {\n            if (windowing) {\n              const diffX = event.offsetX - x;\n              const diffY = event.offsetY - y;\n              x = event.offsetX;\n              y = event.offsetY;\n\n              const ww = windowLevel.getWindow();\n              const wl = windowLevel.getLevel();\n              windowLevel.setWindow(ww + diffX);\n              windowLevel.setLevel(wl + diffY);\n              renderFrame(image, frame, windowLevel, canvasElement, infoTextElement);\n            }\n          };\n          canvasElement.onmouseup = (event) => {\n            x = 0;\n            y = 0;\n            windowing = false;\n          };\n        }\n\n        renderFrame(image, frame, windowLevel, canvasElement, infoTextElement);\n      };\n      reader.readAsArrayBuffer(file);\n    }\n\n    function renderFrame(image, frame, windowLevel, canvasElement, infoTextElement) {\n      const ctx = canvasElement.getContext('2d');\n      canvasElement.width = image.getWidth();\n      canvasElement.height = image.getHeight();\n      ctx.clearRect(0, 0, canvasElement.width, canvasElement.height);\n\n      try {\n        const t0 = performance.now();\n        const renderedPixels = new Uint8Array(image.render(frame, windowLevel));\n        const t1 = performance.now();\n\n        const imageData = ctx.createImageData(image.getWidth(), image.getHeight());\n        const canvasPixels = imageData.data;\n        for (let i = 0; i < 4 * image.getWidth() * image.getHeight(); i++) {\n          canvasPixels[i] = renderedPixels[i];\n        }\n        ctx.putImageData(imageData, 0, 0);\n        const t2 = performance.now();\n\n        infoTextElement.innerHTML = '';\n        console.log('Rendering frame: ' + frame);\n        if (windowLevel) {\n          console.log('Rendering window: ' + windowLevel.toString());\n        }\n        console.log('Rendering time: ' + (t1 - t0) + ' ms');\n        console.log('Drawing time: ' + (t2 - t1) + ' ms');\n      } catch (err) {\n        infoTextElement.innerText = 'Error: ' + err.message;\n        throw err;\n      }\n    }\n\n    function createDefaultWindowLevel(image) {\n      const photometricInterpretation = image.getElement('PhotometricInterpretation');\n      if (!photometricInterpretation) {\n        return;\n      }\n      if (['MONOCHROME1', 'MONOCHROME2'].indexOf(photometricInterpretation) == -1) {\n        return;\n      }\n      const bitsStored = image.getElement('BitsStored');\n      if (!bitsStored) {\n        return;\n      }\n      const pixelRepresentation = image.getElement('PixelRepresentation') || 0;\n      const signed = pixelRepresentation !== 0;\n\n      const minValue = signed ? -(1 << (bitsStored - 1)) : 0;\n      const maxValue = signed ? (1 << (bitsStored - 1)) - 1 : (1 << bitsStored) - 1;\n\n      return new WindowLevel(maxValue - minValue, (minValue + maxValue) / 2);\n    }\n\n    function handleFileSelect(evt) {\n      evt.stopPropagation();\n      evt.preventDefault();\n      const files = evt.dataTransfer.files;\n      renderFile(files[0]);\n    }\n\n    function handleDragOver(evt) {\n      evt.stopPropagation();\n      evt.preventDefault();\n      evt.dataTransfer.dropEffect = 'copy';\n    }\n\n    const dropZone = document.getElementById('dropZone');\n    dropZone.addEventListener('dragover', handleDragOver, false);\n    dropZone.addEventListener('drop', handleFileSelect, false);\n  </script>\n\n<script> \n(function(scope) { \nscope.$watch('msg', function(msg) {\n if (msg) { \n// Do something when msg arrives \n$(scope.send)[0].src = msg.payload; \n} \n});\n})(scope);\n </script> \n","output":"str","x":320,"y":100,"wires":[["4f4e0f7ab3b3c744","b189c1910676197a"]]},{"id":"9aad85ab0f07e665","type":"inject","z":"61dce39602934bc2","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"true","payloadType":"bool","x":150,"y":400,"wires":[["afec55764d6f52d9"]]},{"id":"afec55764d6f52d9","type":"http request","z":"61dce39602934bc2","name":"DICOM2.dcm","method":"GET","ret":"bin","paytoqs":"ignore","url":"https://raw.githubusercontent.com/ivmartel/dwv/master/tests/data/bbmri-53323851.dcm","tls":"","persist":false,"proxy":"","authType":"","senderr":false,"credentials":{"user":"","password":""},"x":240,"y":340,"wires":[["ad5fee391ca75efb"]]},{"id":"5636e05a7f2a69ad","type":"http in","z":"61dce39602934bc2","name":"","url":"/dcmjs","method":"get","upload":false,"swaggerDoc":"","x":130,"y":100,"wires":[["4762fc781032e120"]]},{"id":"4f4e0f7ab3b3c744","type":"http response","z":"61dce39602934bc2","name":"","statusCode":"","headers":{},"x":490,"y":100,"wires":[]},{"id":"c024f1a485174791","type":"inject","z":"61dce39602934bc2","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"true","payloadType":"bool","x":130,"y":220,"wires":[["7095c0ff60104272"]]},{"id":"7095c0ff60104272","type":"http request","z":"61dce39602934bc2","name":"DICOM1.dcm","method":"GET","ret":"bin","paytoqs":"ignore","url":"https://raw.githubusercontent.com/wearemothership/dicom-test-files/main/dicom-ts-issues/parse-issue-19.dcm","tls":"","persist":false,"proxy":"","authType":"","senderr":false,"credentials":{"user":"","password":""},"x":320,"y":220,"wires":[["ad5fee391ca75efb"]]},{"id":"ad5fee391ca75efb","type":"function","z":"61dce39602934bc2","name":"dcmjs-imaging","func":"const arrayBuffer = msg.payload;\n\n// Create an ArrayBuffer with the contents of the DICOM P10 byte stream\n\nconst { DicomImage } = dcmjsImaging;\nconst image = new DicomImage(arrayBuffer);\n \n// Render image in an RGBA pixels ArrayBuffer\n// Optionally provide a frame index in case of multiframe datasets\n// const renderedPixels = image.render(); \n \nconst renderedPixels = Buffer.from(image.render());\n\n    msg.payload = renderedPixels;\n    return msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[{"var":"dcmjsImaging","module":"dcmjs-imaging"},{"var":"bmp","module":"bmp-js"},{"var":"fs","module":"fs"}],"x":380,"y":280,"wires":[["2d58ac109cd35ac7","ce16d978a4c669d7"]]},{"id":"2d58ac109cd35ac7","type":"debug","z":"61dce39602934bc2","name":"buffer","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":570,"y":280,"wires":[]},{"id":"141abd217a81f924","type":"fileinject","z":"61dce39602934bc2","name":"","x":180,"y":280,"wires":[["ad5fee391ca75efb"]]},{"id":"2a1d0cb53de651fa","type":"comment","z":"61dce39602934bc2","name":"NPMJS: dcmjs-imaging ","info":"https://www.npmjs.com/package/dcmjs-imaging\n\nhttps://github.com/PantelisGeorgiadis/dcmjs-imaging","x":160,"y":60,"wires":[]},{"id":"ce16d978a4c669d7","type":"image viewer","z":"61dce39602934bc2","name":"","width":160,"data":"payload","dataType":"msg","active":true,"x":570,"y":320,"wires":[[]]},{"id":"9f63577616220e9c","type":"comment","z":"61dce39602934bc2","name":"External Function: dcmjs-imaging ","info":"https://www.npmjs.com/package/dcmjs-imaging\n\nhttps://github.com/PantelisGeorgiadis/dcmjs-imaging","x":190,"y":180,"wires":[]},{"id":"b189c1910676197a","type":"debug","z":"61dce39602934bc2","d":true,"name":"buffer","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":490,"y":160,"wires":[]}]

Any help with this would be greatly appreciated!
Thanks!

If you want any help on this, you will need to provide DICOM1.dcm and DICOM2.dcm otherwise we cannot test and see what the issue is.

Never mind - I found a sample dcm file from your other post

Demo...

Flow...

[{"id":"ad5fee391ca75efb","type":"function","z":"61dce39602934bc2","name":"dcmjs-imaging","func":"const buf = msg.payload;\n\nconst { DicomImage, WindowLevel } = dcmjsImaging;\n\nmsg.payload = renderFile(buf.buffer);\nmsg.payload.jimp = {\n    w: msg.payload.image.width, h: msg.payload.image.height, background: \"black\"\n}\nreturn msg;\n\n\n\nfunction renderFile(arrayBuffer) {\n    // debugger\n    let frame = 0;\n    const image = new DicomImage(arrayBuffer);\n    const windowLevels = WindowLevel.fromDicomImage(image);\n    const windowLevel =\n        windowLevels.length > 0 ? windowLevels[0] : createDefaultWindowLevel(image);\n\n    const result = {\n        framecount: image.getNumberOfFrames()\n    }\n\n    result.image = renderFrame(image, frame, windowLevel);\n    return result;\n}\n\nfunction renderFrame(image, frame, windowLevel) {\n\n    const result = {\n        height: image.getHeight(),\n        width: image.getWidth(),\n        bitmap: null\n    }\n  \n    try {\n\n        const renderedPixels = new Uint8Array(image.render(frame, windowLevel));\n        // const imageData = ctx.createImageData(image.getWidth(), image.getHeight());\n        // const canvasPixels = imageData.data;\n        const canvasPixels = [];\n        for (let i = 0; i < 4 * image.getWidth() * image.getHeight(); i++) {\n            canvasPixels[i] = renderedPixels[i];\n        }\n        // ctx.putImageData(imageData, 0, 0);\n        result.bitmap = canvasPixels;\n        return result;\n    } catch (err) {\n        throw err;\n    }\n}\n\nfunction createDefaultWindowLevel(image) {\n    const photometricInterpretation = image.getElement('PhotometricInterpretation');\n    if (!photometricInterpretation) {\n        return;\n    }\n    if (['MONOCHROME1', 'MONOCHROME2'].indexOf(photometricInterpretation) == -1) {\n        return;\n    }\n    const bitsStored = image.getElement('BitsStored');\n    if (!bitsStored) {\n        return;\n    }\n    const pixelRepresentation = image.getElement('PixelRepresentation') || 0;\n    const signed = pixelRepresentation !== 0;\n\n    const minValue = signed ? -(1 << (bitsStored - 1)) : 0;\n    const maxValue = signed ? (1 << (bitsStored - 1)) - 1 : (1 << bitsStored) - 1;\n\n    return new WindowLevel(maxValue - minValue, (minValue + maxValue) / 2);\n}","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[{"var":"dcmjsImaging","module":"dcmjs-imaging"}],"x":1900,"y":120,"wires":[["0559c20ace56b130"]]},{"id":"ce16d978a4c669d7","type":"image viewer","z":"61dce39602934bc2","name":"","width":"175","data":"image","dataType":"msg","active":true,"x":1870,"y":180,"wires":[[]]},{"id":"3faeb01f9beb7838","type":"inject","z":"61dce39602934bc2","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":1440,"y":120,"wires":[["a48ea31d02b63d22"]]},{"id":"a48ea31d02b63d22","type":"http request","z":"61dce39602934bc2","name":"http get bbmri-53323851.dcm","method":"GET","ret":"bin","paytoqs":"ignore","url":"https://raw.githubusercontent.com/ivmartel/dwv/master/tests/data/bbmri-53323851.dcm","tls":"","persist":false,"proxy":"","authType":"","senderr":false,"x":1660,"y":120,"wires":[["ad5fee391ca75efb"]]},{"id":"0559c20ace56b130","type":"jimp-image","z":"61dce39602934bc2","name":"create blank image","data":"payload.jimp","dataType":"msg","ret":"img","parameter1":"","parameter1Type":"msg","parameter2":"","parameter2Type":"msg","parameter3":"","parameter3Type":"msg","parameter4":"","parameter4Type":"msg","parameter5":"","parameter5Type":"msg","parameter6":"","parameter6Type":"msg","parameter7":"","parameter7Type":"msg","parameter8":"","parameter8Type":"msg","sendProperty":"image","sendPropertyType":"msg","parameterCount":0,"jimpFunction":"none","selectedJimpFunction":{"name":"none","fn":"none","description":"Just loads the image.","parameters":[]},"x":1490,"y":180,"wires":[["d3710a9bf19f1a47"]]},{"id":"d3710a9bf19f1a47","type":"change","z":"61dce39602934bc2","name":"set image pixels","rules":[{"t":"set","p":"image.bitmap.data","pt":"msg","to":"payload.image.bitmap","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":1700,"y":180,"wires":[["ce16d978a4c669d7"]]}]

If you want to save as PNG, add another image node set to "write" operation & it will save the internal JIMP object as a PNG (or whatever format you set)


NOTE:

For performance reasons, avoid creating branches as more than one wire leaving a node will generate LARGE duplicates of the image data (only process nodes in series when working with large data like images)

3 Likes

Thanks @Steve-Mcl so much:) You nailed it. I truly appreciate your help!

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.