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!