vendredi 17 juin 2016

Modify Dermographics in DICOM file using Javascript

I want to change the Patient Name, DOB, and Patient ID my DICOM file using javascript.

I found a simpleDeIndentify from https://github.com/chafey/dicomParser, I was able to modify it to change the value for patient name, but it seems that my generated file is corrupted.

I was able to modify the field of Patient Name when you call this in my code.

console.log(dataSet.string('x00100010'));

I think I need to adjust some dataOffset somewhere to place properly the values in the byteArray.

<!DOCTYPE HTML>
<html lang="en">
<head>
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <!-- twitter bootstrap CSS stylesheet - included to make things pretty, not needed or used by dicomParser -->
    <link href="//netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css" rel="stylesheet">

    <style>

        #dropZone {
            border-style: solid;
            border-width: 1px;
            border-color: lightgray;
        }


    </style>

</head>
<body>
<div class="container">

    <div class="page-header">
        <h1>Simple De-identify</h1>
        <p class="lead">
            Drag and drop a DICOM Part 10 file into the region below.
        </p>
        <p>
            This example illustrates how to perform a simple de-identification on top level DICOM
            attributes by overwriting the data/values of the elements with random data of the exact
            same length.
            <br>
            <br>
        <strong>Use of this example require IE10+ or any other modern browser.</strong>
        <br>
            <br>
        <strong>WARNING - This example is meant to demonstrate one very simple way to de-identify.  It does
            not remove all PHI and should not be used for any real world de-identification needs!  Take a look at
            David Clunie's <a href="http://www.dclunie.com/pixelmed/software/webstart/DicomCleanerUsage.html">DicomCleaner</a></strong>

    </div>

    <div id="dropZone" class="container-fluid">

        <br>

        <div id="status" class="alert alert-success">
            <div id="statusText">
                Status: Ready (no file loaded)
            </div>
            <ul id="warnings">

            </ul>
        </div>

        <table class="table table-striped table-bordered">
            <thead>
                <tr>
                    <td>
                        Attribute
                    </td>
                    <td>
                        Original
                    </td>
                    <td>
                        De-identified <button id="download">Download</button>

                    </td>
                </tr>

            </thead>
            <tbody>
                <tr>
                    <td class="col-sm-4">Patient Name</td>
                    <td class="col-sm-4"><span class='contenteditable' data-dicom="x00100010"></span></td>
                    <td class="col-sm-4"><input data-vr="PN" class="col-sm-12" type="text" data-dicom="x00100010" id="pName"/></td>
                </tr>
                <tr>
                    <td class="col-sm-4">Patient ID</td>
                    <td class="col-sm-4"><span data-dicom="x00100020"></span></td>
                    <td class="col-sm-4"><input data-vr="LO" class="col-sm-12" type="text" data-dicom="x00100020"/></td>
                </tr>
                <tr>
                    <td class="col-sm-4">Patient Birth Date</td>
                    <td class="col-sm-4"><span data-dicom="x00100030"></span></td>
                    <td class="col-sm-4"><input data-vr="DA" class="col-sm-12" type="text" data-dicom="x00100030"/></td>
                </tr>
                <tr>
                    <td class="col-sm-4">Study Description</td>
                    <td class="col-sm-4"><span data-dicom="x00081030"></span></td>
                    <td class="col-sm-4"><input data-vr="LO" class="col-sm-12" type="text" data-dicom="x00081030"/></td>
                </tr>
                <tr>
                    <td class="col-sm-4">Protocol Name</td>
                    <td class="col-sm-4"><span data-dicom="x00181030"></span></td>
                    <td class="col-sm-4"><input data-vr="LO" class="col-sm-12" type="text" data-dicom="x00181030"/></td>
                </tr>
                <tr>
                    <td class="col-sm-4">Accession #</td>
                    <td class="col-sm-4"><span data-dicom="x00080050"></span></td>
                    <td class="col-sm-4"><input data-vr="SH" class="col-sm-12" type="text" data-dicom="x00080050"/></td>
                </tr>
                <tr>
                    <td class="col-sm-4">Study Id</td>
                    <td class="col-sm-4"><span data-dicom="x00200010"></span></td>
                    <td class="col-sm-4"><input data-vr="SH" class="col-sm-12" type="text" data-dicom="x00200010"/></td>
                </tr>
                <tr>
                    <td class="col-sm-4">Study Date</td>
                    <td class="col-sm-4"><span data-dicom="x00080020"></span></td>
                    <td class="col-sm-4"><input data-vr="DA" class="col-sm-12" type="text" data-dicom="x00080020"/></td>
                </tr>
                <tr>
                    <td class="col-sm-4">Study Time</td>
                    <td class="col-sm-4"><span data-dicom="x00080030"></span></td>
                    <td class="col-sm-4"><input data-vr="TM" class="col-sm-12" type="text" data-dicom="x00080030"/></td>
                </tr>

                <tr>
                    <td class="col-sm-4">Series Description</td>
                    <td class="col-sm-4"><span data-dicom="x0008103e"></span></td>
                    <td class="col-sm-4"><input data-vr="LO" class="col-sm-12" type="text" data-dicom="x0008103e"/></td>
                </tr>
                <tr>
                    <td class="col-sm-4">Series Date</td>
                    <td class="col-sm-4"><span data-dicom="x00080021"></span></td>
                    <td class="col-sm-4"><input data-vr="DA" class="col-sm-12" type="text" data-dicom="x00080021"/></td>
                </tr>
                <tr>
                    <td class="col-sm-4">Series Time</td>
                    <td class="col-sm-4"><span data-dicom="x00080031"></span></td>
                    <td class="col-sm-4"><input data-vr="TM"class="col-sm-12" type="text" data-dicom="x00080031"/></td>
                </tr>

                <tr>
                    <td class="col-sm-4">Acquisition Date</td>
                    <td class="col-sm-4"><span data-dicom="x00080022"></span></td>
                    <td class="col-sm-4"><input data-vr="DA" class="col-sm-12" type="text" data-dicom="x00080022"/></td>
                </tr>
                <tr>
                    <td class="col-sm-4">Acquisition Time</td>
                    <td class="col-sm-4"><span data-dicom="x00080032"></span></td>
                    <td class="col-sm-4"><input data-vr="TM" class="col-sm-12" type="text" data-dicom="x00080032"/></td>
                </tr>
                <tr>
                    <td class="col-sm-4">Content Date</td>
                    <td class="col-sm-4"><span data-dicom="x00080023"></span></td>
                    <td class="col-sm-4"><input data-vr="DA" class="col-sm-12" type="text" data-dicom="x00080023"/></td>
                </tr>
                <tr>
                    <td class="col-sm-4">Content Time</td>
                    <td class="col-sm-4"><span data-dicom="x00080033"></span></td>
                    <td class="col-sm-4"><input data-vr="TM" class="col-sm-12" type="text" data-dicom="x00080033"/></td>
                </tr>


            </tbody>
        </table>
    </div>

</div>
</body>

<!-- include the dicomParser library -->
<script src="../../dist/dicomParser.js"></script>
<script src="../../dist/FileSaver.min.js"></script>
<!-- jquery - included to make things easier to demo, not needed by dicomParser -->
<script src="../jquery.min.js"></script>

<script>

    function dumpDataSet(dataSet)
    {
        $('span[data-dicom]').each(function(index, value)
        {
            var attr = $(value).attr('data-dicom');
            var element = dataSet.elements[attr];
            var text = "";
            if(element !== undefined)
            {
                var str = dataSet.string(attr);
                if(str !== undefined) {
                    text = str;
                }
            }
            $(value).text(text);
        });

        $('span[data-dicomUint]').each(function(index, value)
        {
            var attr = $(value).attr('data-dicomUint');
            var element = dataSet.elements[attr];
            var text = "";
            if(element !== undefined)
            {
                if(element.length === 2)
                {
                    text += dataSet.uint16(attr);
                }
                else if(element.length === 4)
                {
                    text += dataSet.uint32(attr);
                }
            }

            $(value).text(text);
        });
    }
    // from http://stackoverflow.com/questions/1349404/generate-a-string-of-5-random-characters-in-javascript
    function makeRandomString(length)
    {
        var text = "";
        var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";

        for( var i=0; i < length; i++ )
            text += possible.charAt(Math.floor(Math.random() * possible.length));

        return text;
    }

    function pad(num, size) {
        var s = num+"";
        while (s.length < size) s = "0" + s;
        return s;
    }


    function makeDeIdentifiedValue(length, vr) {
        if(vr === 'LO' || vr === "SH" || vr === "PN") {
            return makeRandomString(length);
        }
        else if(vr === 'DA') {
            var now = new Date();
            return now.getYear() + 1900 + pad(now.getMonth() + 1, 2) + pad(now.getDate(), 2);
        } else if(vr === 'TM') {
            var now = new Date();
            return pad(now.getHours(), 2) + pad(now.getMinutes(), 2) + pad(now.getSeconds(), 2);
        }
        console.log('unknown VR:' + vr);
    }


    var dataSet;

    // This function will read the file into memory and then start dumping it
    function dumpFile(file)
    {
        $('#status').removeClass('alert-warning alert-success alert-danger').addClass('alert-info');
        $('#warnings').empty();
        document.getElementById('statusText').innerHTML = 'Status: Loading file, please wait..';

        var reader = new FileReader();
        reader.onload = function(file) {
            var arrayBuffer = reader.result;

            // Here we have the file data as an ArrayBuffer.  dicomParser requires as input a
            // Uint8Array so we create that here
            var byteArray = new Uint8Array(arrayBuffer);

            var kb = byteArray.length / 1024;
            var mb = kb / 1024;
            var byteStr = mb > 1 ? mb.toFixed(3) + " MB" : kb.toFixed(0) + " KB";
            document.getElementById('statusText').innerHTML = '<span class="glyphicon glyphicon-cog"></span>Status: Parsing ' + byteStr + ' bytes, please wait..';

            // set a short timeout to do the parse so the DOM has time to update itself with the above message
            setTimeout(function() {

                // Invoke the paresDicom function and get back a DataSet object with the contents
                try {
                    var start = new Date().getTime();

                    dataSet = dicomParser.parseDicom(byteArray);
                    // Here we call dumpDataSet to update the DOM with the contents of the dataSet
                    dumpDataSet(dataSet);

                    var end = new Date().getTime();
                    var time = end - start;
                    if(dataSet.warnings.length > 0)
                    {
                        $('#status').removeClass('alert-success alert-info alert-danger').addClass('alert-warning');
                        $('#statusText').html('Status: Warnings encountered while parsing file (file of size '+ byteStr + ' parsed in ' + time + 'ms)');

                        dataSet.warnings.forEach(function(warning) {
                            $("#warnings").append('<li>' + warning +'</li>');
                        });
                    }
                    else
                    {
                        var pixelData = dataSet.elements.x7fe00010;
                        if(pixelData) {
                            $('#status').removeClass('alert-warning alert-info alert-danger').addClass('alert-success');
                            $('#statusText').html('Status: Ready (file of size '+ byteStr + ' parsed in ' + time + 'ms)');
                        }
                        else
                        {
                            $('#status').removeClass('alert-warning alert-info alert-danger').addClass('alert-success');
                            $('#statusText').html('Status: Ready - no pixel data found (file of size ' + byteStr + ' parsed in ' + time + 'ms)');
                        }
                    }

                    // Create de-identified values for each element
                    /*$('input').each(function(index, input) {
                        var attr = $(input).attr('data-dicom');
                        var element = dataSet.elements[attr];
                        var text = "";
                        var vr = $(input).attr('data-vr');
                        if(element !== undefined)
                        {
                            var str = dataSet.string(attr);
                            if(str !== undefined) {
                                text = str;
                            }
                        }
                        var deIdentifiedValue = makeDeIdentifiedValue(text.length, vr);
                        $(input).val(deIdentifiedValue);
                        //$(input).prop('readonly', true);

                    });*/

                    // Create de-identified values for each element
                    $('input').each(function(index, input) {
                        var attr = $(input).attr('data-dicom');
                        var element = dataSet.elements[attr];
                        var text = "";
                        var vr = $(input).attr('data-vr');
                        if(element !== undefined)
                        {
                            var str = dataSet.string(attr);
                            if(str !== undefined) {
                                text = str;
                            }
                        }
                        $(input).val(text);

                        //var deIdentifiedValue = makeDeIdentifiedValue(text.length, vr);
                        //$(input).val(deIdentifiedValue);
                        //$(input).prop('readonly', true);

                    });
                }
                catch(err)
                {
                    $('#status').removeClass('alert-success alert-info alert-warning').addClass('alert-danger');
                    document.getElementById('statusText').innerHTML = 'Status: Error - ' + err + ' (file of size ' + byteStr + ' )';
                }

            }, 30);
        };

        reader.readAsArrayBuffer(file);
    }


    // this function gets called once the user drops the file onto the div
    function handleFileSelect(evt) {
        evt.stopPropagation();
        evt.preventDefault();

        // Get the FileList object that contains the list of files that were dropped
        var files = evt.dataTransfer.files;

        // this UI is only built for a single file so just dump the first one
        dumpFile(files[0]);
    }

    function handleDragOver(evt) {
        evt.stopPropagation();
        evt.preventDefault();
        evt.dataTransfer.dropEffect = 'copy'; // Explicitly show this is a copy.
    }

    // Setup the dnd listeners.
    var dropZone = document.getElementById('dropZone');
    dropZone.addEventListener('dragover', handleDragOver, false);
    dropZone.addEventListener('drop', handleFileSelect, false);

    $('#download').click(function() {
        console.log(dataSet);

        //console.log(element);
        var newValue1 = $("#pName").val();
        var elementz = dataSet.elements[$("#pName").attr('data-dicom')];

        var nval = newValue1.length;
        var eval = elementz.length;
        var addx = 0;
        console.log(dataSet.byteArray.length);
        console.log(elementz.length);
        console.log(newValue1.length);
        //create new Uint8Array
        if (nval > eval)
        {
            addx = nval - eval;
        }
        else if (nval < eval)
        {
            addx = eval - nval;
        }
        console.log(addx);
        //recreate array to add length depending in the size of patient name
        var newDs = new Uint8Array(dataSet.byteArray.length + addx);
        console.log(newDs.length);
        // iterate through each input field and replace the original value with the de-identified value.
        // Note that the deidentified value must be <= length of the original value - longer values
        // will require resizing the Uint8Array and updating the length of the element and I don't feel
        // like doing that right now :)
        $('input').each(function(index, input) {
            var attr = $(input).attr('data-dicom');                     

            var newValue = $(input).val();                      
            dataSet.elements[attr].length = newValue.length;            
            var element = dataSet.elements[attr];

            for(var i=0; i < element.length; i++) {
                var char = (newValue.length > i) ? newValue.charCodeAt(i) : 32;
                //console.log(char);    
                newDs[element.dataOffset + i] = char;
            }           
        });     
        dataSet.byteArray = newDs;
        /*var patientName = dataSet.string('x00100010');
        var pixelDataElement = dataSet.elements.x00100010;
        var newDs = new Uint16Array(dataSet.byteArray.buffer, pixelDataElement.dataOffSet, $("#pName").val().length);
        dataSet.byteArray = newDs;*/
        /*$('input').each(function(index, input) {
            var attr = $(input).attr('data-dicom');
            var element = dataSet.elements[attr];
            var newValue = $(input).val();
            for(var i=0; i < element.length; i++) {
                var char = (newValue.length > i) ? newValue.charCodeAt(i) : 32;
                console.log(char);              
                dataSet.byteArray[element.dataOffset + i] = char;
            }
        });*/

        console.log(dataSet);
        console.log(dataSet.string('x00100010'));
        // download the de-identified DICOM P10 bytestream
        var blob = new Blob([dataSet.byteArray], {type: "application/dicom"});
        var url = window.URL.createObjectURL(blob);
        saveAs(blob, url);
        //window.open(url);
        //window.URL.revokeObjectURL(url);
    });


</script>
</html>

Aucun commentaire:

Enregistrer un commentaire