wearemothership / dicom.ts

A small, fast, javascript DICOM renderer
Other
35 stars 13 forks source link

Unable to parse Dicom-files which contain Private Tags. #54

Closed rtdicomexplorer closed 1 year ago

rtdicomexplorer commented 1 year ago

See the Example attached... example.zip

nickhingston commented 1 year ago

Looking at this DICOM file, there is no File Preamble or "DCIM" DICOM Prefix.

Furthermore the first tag "(0002, 0001) File Meta Information Version" in this file is corrupt.

How was it generated?

rtdicomexplorer commented 1 year ago

Hi I' ve just found out to that the meta header is wrote in little endian implicit. (Very strange) The most parser can read it. I wrote a patch for Daikon beacause I'm using that for my web volume rendering. I have more whole CT studies from Elekta. Best regards. Michel

Il mar 4 ott 2022, 18:02 Nick Hingston @.***> ha scritto:

Looking at this DICOM file, there is no File Preamble or "DCIM" DICOM Prefix.

Furthermore the first tag "(0002, 0001) File Meta Information Version" in this file is corrupt.

How was it generated?

— Reply to this email directly, view it on GitHub https://github.com/wearemothership/dicom.ts/issues/54#issuecomment-1267230285, or unsubscribe https://github.com/notifications/unsubscribe-auth/ASXTWLJLSNVJYS7SJG5VQETWBRIKRANCNFSM6AAAAAAQRYK2VU . You are receiving this because you authored the thread.Message ID: @.***>

nickhingston commented 1 year ago

Ok, interesting.

From the spec: "Except for the 128 byte preamble and the 4 byte prefix, the File Meta Information shall be encoded using the Explicit VR Little Endian Transfer Syntax (UID=1.2.840.10008.1.2.1) as defined in DICOM [PS3.5]". So what we are currently doing is expected behaviour.

Have you got a link/PR to the patch? The parser is heavily based on Daikon, so would certainly consider porting it, as I can decode it with some other parsers.

rtdicomexplorer commented 1 year ago

Yes you are right, very strange.. Unfortunately I didn't have time to post the solution also because I haven't tested it enough with all the possible variations .. I simply added a new parameter "metaExplicit" in parser and added a check around that. I am attaching the parser.js file.(I've changed the extension because gmail block the *.js ...)

Am Mi., 5. Okt. 2022 um 10:31 Uhr schrieb Nick Hingston < @.***>:

Ok, interesting.

From the spec: "Except for the 128 byte preamble and the 4 byte prefix, the File Meta Information shall be encoded using the Explicit VR Little Endian Transfer Syntax (UID=1.2.840.10008.1.2.1) as defined in DICOM [PS3.5]". So what we are currently doing is expected behaviour.

Have you got a link/PR to the patch? The parser is heavily based on Daikon, so would certainly consider porting it, as I can decode it with some other parsers.

— Reply to this email directly, view it on GitHub https://github.com/wearemothership/dicom.ts/issues/54#issuecomment-1268118422, or unsubscribe https://github.com/notifications/unsubscribe-auth/ASXTWLJFL733SAF6K3YL7HTWBU4FJANCNFSM6AAAAAAQRYK2VU . You are receiving this because you authored the thread.Message ID: @.***>

/jslint browser: true, node: true / /global require, module /

"use strict";

/ Imports / var daikon = daikon || {}; daikon.Tag = daikon.Tag || ((typeof require !== 'undefined') ? require('./tag.js') : null); daikon.Utils = daikon.Utils || ((typeof require !== 'undefined') ? require('./utilities.js') : null); daikon.Dictionary = daikon.Dictionary || ((typeof require !== 'undefined') ? require('./dictionary.js') : null); daikon.Image = daikon.Image || ((typeof require !== 'undefined') ? require('./image.js') : null);

var pako = pako || ((typeof require !== 'undefined') ? require('pako') : null);

/ Constructor /

/**

/ Static Fields /

/**

/ Static Pseudo-constants /

daikon.Parser.MAGIC_COOKIE_OFFSET = 128; daikon.Parser.MAGIC_COOKIE = [68, 73, 67, 77]; daikon.Parser.VRS = ["AE", "AS", "AT", "CS", "DA", "DS", "DT", "FL", "FD", "IS", "LO", "LT", "OB", "OD", "OF", "OW", "PN", "SH", "SL", "SS", "ST", "TM", "UI", "UL", "UN", "US", "UT"]; daikon.Parser.DATA_VRS = ["OB", "OW", "OF", "SQ", "UT", "UN"]; daikon.Parser.RAW_DATA_VRS = ["OB", "OD", "OF", "OW", "UN"]; daikon.Parser.TRANSFER_SYNTAX_IMPLICIT_LITTLE = "1.2.840.10008.1.2"; daikon.Parser.TRANSFER_SYNTAX_EXPLICIT_LITTLE = "1.2.840.10008.1.2.1"; daikon.Parser.TRANSFER_SYNTAX_EXPLICIT_BIG = "1.2.840.10008.1.2.2"; daikon.Parser.TRANSFER_SYNTAX_COMPRESSION_JPEG = "1.2.840.10008.1.2.4"; daikon.Parser.TRANSFER_SYNTAX_COMPRESSION_JPEG_LOSSLESS = "1.2.840.10008.1.2.4.57"; daikon.Parser.TRANSFER_SYNTAX_COMPRESSION_JPEG_LOSSLESS_SEL1 = "1.2.840.10008.1.2.4.70"; daikon.Parser.TRANSFER_SYNTAX_COMPRESSION_JPEG_BASELINE_8BIT = "1.2.840.10008.1.2.4.50"; daikon.Parser.TRANSFER_SYNTAX_COMPRESSION_JPEG_BASELINE_12BIT = "1.2.840.10008.1.2.4.51"; daikon.Parser.TRANSFER_SYNTAX_COMPRESSION_JPEG_LS_LOSSLESS = "1.2.840.10008.1.2.4.80"; daikon.Parser.TRANSFER_SYNTAX_COMPRESSION_JPEG_LS = "1.2.840.10008.1.2.4.81"; daikon.Parser.TRANSFER_SYNTAX_COMPRESSION_JPEG_2000_LOSSLESS = "1.2.840.10008.1.2.4.90"; daikon.Parser.TRANSFER_SYNTAX_COMPRESSION_JPEG_2000 = "1.2.840.10008.1.2.4.91"; daikon.Parser.TRANSFER_SYNTAX_COMPRESSION_RLE = "1.2.840.10008.1.2.5"; daikon.Parser.TRANSFER_SYNTAX_COMPRESSION_DEFLATE = "1.2.840.10008.1.2.1.99"; daikon.Parser.UNDEFINED_LENGTH = 0xFFFFFFFF;

/ Static Methods /

/**

/ Prototype Methods /

/**

daikon.Parser.prototype.parseEncapsulated = function (data) { var offset = 0, tag, tags = [];

this.encapsulation = true;

try {
    tag = this.getNextTag(data, offset);

    while (tag !== null) {
        if (tag.isSublistItem()) {
            tags.push(tag);
        }

        if (daikon.Parser.verbose) {
            console.log(tag.toString());
        }

        tag = this.getNextTag(data, tag.offsetEnd);
    }
} catch (err) {
    this.error = err;

}

return tags;

};

daikon.Parser.prototype.testForValidTag = function (data) { var offset, tag = null;

try {
    offset = this.findFirstTagOffset(data);
    tag = this.getNextTag(data, offset, false);
} catch (err) {
    this.error = err;
}

return tag;

};

daikon.Parser.prototype.getNextTag = function (data, offset, testForTag) { var group = 0, element, value = null, offsetStart = offset, offsetValue, length = 0, little = true, vr = null, tag;

if (offset >= data.byteLength) {
    return null;
}

if (this.metaFinished) {
    little = this.littleEndian;
    group = data.getUint16(offset, little);
} else {
    group = data.getUint16(offset, true);

    if (((this.metaFinishedOffset !== -1) && (offset >= this.metaFinishedOffset)) || (group !== 0x0002)) {
        this.metaFinished = true;
        little = this.littleEndian;
        group = data.getUint16(offset, little);
    } else {
        little = true;
    }
}

if (!this.metaFound && (group === 0x0002)) {
    this.metaFound = true;
}

offset += 2;

element = data.getUint16(offset, little);
offset += 2;

   if (this.explicit || (!this.metaFinished && this.metaExplicit)) {
    vr = daikon.Utils.getStringAt(data, offset, 2);

    if (!this.metaFound && this.metaFinished && (daikon.Parser.VRS.indexOf(vr) === -1)) {
        vr = daikon.Dictionary.getVR(group, element);
        length = data.getUint32(offset, little);
        offset += 4;
        this.explicit = false;
    } else {
        offset += 2;

        if (daikon.Parser.DATA_VRS.indexOf(vr) !== -1) {
            offset += 2;  // skip two empty bytes

            length = data.getUint32(offset, little);
            offset += 4;
        } else {
            length = data.getUint16(offset, little);
            offset += 2;
            if (this.metaFound && !this.metaFinished && (daikon.Parser.VRS.indexOf(vr) === -1)) {
                vr = daikon.Dictionary.getVR(group, element);
                this.metaExplicit = false;
                this.explicit= false;
                offset += 2;
                value = data.getUint16(offset, little);
          }

        }
    }
} else {
    vr = daikon.Dictionary.getVR(group, element);
    length = data.getUint32(offset, little);

    if (length === daikon.Parser.UNDEFINED_LENGTH) {
        vr = 'SQ';
    }

    offset += 4;
}

offsetValue = offset;

var isPixelData = ((group === daikon.Tag.TAG_PIXEL_DATA[0]) && (element === daikon.Tag.TAG_PIXEL_DATA[1]));

if ((vr === 'SQ') || (!isPixelData && !this.encapsulation && (daikon.Parser.DATA_VRS.indexOf(vr) !== -1))) {
    value = this.parseSublist(data, offset, length, vr !== 'SQ');

    if (length === daikon.Parser.UNDEFINED_LENGTH) {
        length = value[value.length - 1].offsetEnd - offset;
    }
} else if ((length > 0) && !testForTag) {
    if (length === daikon.Parser.UNDEFINED_LENGTH) {
        if (isPixelData) {
            length = (data.byteLength - offset);
        }
    }

    value = data.buffer.slice(offset, offset + length);
}

offset += length;
tag = new daikon.Tag(group, element, vr, value, offsetStart, offsetValue, offset, this.littleEndian);

if (tag.isTransformSyntax()) {
    if (tag.value[0] === daikon.Parser.TRANSFER_SYNTAX_IMPLICIT_LITTLE) {
        this.explicit = false;
        this.littleEndian = true;
    } else if (tag.value[0] === daikon.Parser.TRANSFER_SYNTAX_EXPLICIT_BIG) {
        this.explicit = true;
        this.littleEndian = false;
    } else if (tag.value[0] === daikon.Parser.TRANSFER_SYNTAX_COMPRESSION_DEFLATE) {
        this.needsDeflate = true;
        this.explicit = true;
        this.littleEndian = true;
    } else {
        this.explicit = true;
        this.littleEndian = true;
    }
} else if (tag.isMetaLength()) {
    this.metaFinishedOffset = tag.value[0] + offset;
}

return tag;

};

daikon.Parser.prototype.parseSublist = function (data, offset, length, raw) { var sublistItem, offsetEnd = offset + length, tags = [];

this.level++;

if (length === daikon.Parser.UNDEFINED_LENGTH) {
    sublistItem = this.parseSublistItem(data, offset, raw);

    while (!sublistItem.isSequenceDelim()) {
        tags.push(sublistItem);
        offset = sublistItem.offsetEnd;
        sublistItem = this.parseSublistItem(data, offset, raw);
    }

    tags.push(sublistItem);
} else {
    while (offset < offsetEnd) {
        sublistItem = this.parseSublistItem(data, offset, raw);
        tags.push(sublistItem);
        offset = sublistItem.offsetEnd;
    }
}

this.level--;

return tags;

};

daikon.Parser.prototype.parseSublistItem = function (data, offset, raw) { var group, element, length, offsetEnd, tag, offsetStart = offset, value = null, offsetValue, sublistItemTag, tags = [];

group = data.getUint16(offset, this.littleEndian);
offset += 2;

element = data.getUint16(offset, this.littleEndian);
offset += 2;

length = data.getUint32(offset, this.littleEndian);
offset += 4;

offsetValue = offset;

if (length === daikon.Parser.UNDEFINED_LENGTH) {
    tag = this.getNextTag(data, offset);

    while (!tag.isSublistItemDelim()) {
        tags.push(tag);
        offset = tag.offsetEnd;
        tag = this.getNextTag(data, offset);
    }

    tags.push(tag);
    offset = tag.offsetEnd;
} else if (raw) {
    value = data.buffer.slice(offset, offset + length);
    offset = offset + length;
} else {
    offsetEnd = offset + length;

    while (offset < offsetEnd) {
        tag = this.getNextTag(data, offset);
        tags.push(tag);
        offset = tag.offsetEnd;
    }
}

sublistItemTag = new daikon.Tag(group, element, null, value || tags, offsetStart, offsetValue, offset, this.littleEndian);

return sublistItemTag;

};

daikon.Parser.prototype.findFirstTagOffset = function (data) { var offset = 0, magicCookieLength = daikon.Parser.MAGIC_COOKIE.length, searchOffsetMax = daikon.Parser.MAGIC_COOKIE_OFFSET * 5, found = false, ctr = 0, ctrIn = 0, ch = 0;

if (daikon.Parser.isMagicCookieFound(data)) {
    offset = daikon.Parser.MAGIC_COOKIE_OFFSET + magicCookieLength;
} else {
    for (ctr = 0; ctr < searchOffsetMax; ctr += 1) {
        ch = data.getUint8(ctr);
        if (ch === daikon.Parser.MAGIC_COOKIE[0]) {
            found = true;
            for (ctrIn = 1; ctrIn < magicCookieLength; ctrIn += 1) {
                if (data.getUint8(ctr + ctrIn) !== daikon.Parser.MAGIC_COOKIE[ctrIn]) {
                    found = false;
                }
            }

            if (found) {
                offset = ctr + magicCookieLength;
                break;
            }
        }
    }
}

return offset;

};

daikon.Parser.prototype.hasError = function () { return (this.error !== null); };

/ Exports /

var moduleType = typeof module; if ((moduleType !== 'undefined') && module.exports) { module.exports = daikon.Parser; }