Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 83 additions & 8 deletions src/BufferStream.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import pako from "pako";
import SplitDataView from "./SplitDataView";
import { DicomMetaDictionary } from "./DicomMetaDictionary";

function toInt(val) {
if (isNaN(val)) {
Expand All @@ -15,20 +16,98 @@ function toFloat(val) {
} else return val;
}

/**
* Facilitates the conversion of binary buffers from a DICOM encoding scheme to
* a web supported string encoding scheme and vice versa.
*/
class DicomBufferCODEC {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be it's own file.
Also, the encoder/decoder should be identical because there are cases that both reading and writing occur on a single dicom buffer.
Would like to see a small unit test on this.
The default for both should actually be latin1 until the specific character set gets written.

encoder = new TextEncoder("utf-8");
decoder = new TextDecoder("latin1");

/**
* Use this method if you want to change the decoder directly and do not
* need to have the encoding scheme name translated to one of the encoding
* schemes supported by web browsers.
*
* For example, instead of passing ISO 2022 IR 100, you have to pass latin1.
* Passing an incorrect encoding scheme name will result in an exception.
*
* @param {string} webEncoding
*/
setNativeDecoder(webEncoding) {
this.decoder = new TextDecoder(webEncoding);
}

/**
* Main method for changing decoder.
*
* Given a DICOM encoding scheme like ISO 2022 IR 100, generate the correct
* string to use in JavaScript applications.
*
* Optionally, include whether to ignore or throw an exception if dicom to
* web encoding is not found in our mapping
*
* @param {string} dicomEncoding
* @param {boolean} ignoreErrors
*/
setDecoder(dicomEncoding, ignoreErrors = false) {
let coding = DicomMetaDictionary.getNativeEncoding(
dicomEncoding,
ignoreErrors
);
this.setNativeDecoder(coding);
}

/**
* Unused since we typically default to utf-8. This method is provided for
* convenience in case someone needs to encode a buffer in something else
* before storing in a DICOM header.
*
* @param {string} webEncoding
*/
setNativeEncoder(webEncoding) {
this.decoder = new TextDecoder(webEncoding);
}

/**
* Convenience method as would be found in TextEncoder and TextDecoder APIs.
*
* @param data
* @returns {string}
*/
decode(data) {
return this.decoder.decode(data);
}

/**
* Convenience method as would be found in TextEncoder and TextDecoder APIs.
*
* @param {string} data
* @returns {Uint8Array}
*/
encode(data) {
return this.encoder.encode(data);
}
}

class BufferStream {
offset = 0;
startOffset = 0;
isLittleEndian = false;
size = 0;
view = new SplitDataView();

encoder = new TextEncoder("utf-8");
codec = new DicomBufferCODEC();

constructor(options = null) {
this.isLittleEndian = options?.littleEndian || this.isLittleEndian;
this.view.defaultSize = options?.defaultSize ?? this.view.defaultSize;
}

setDecoder(dicomEncoding, ignoreErrors) {
this.codec.setDecoder(dicomEncoding, ignoreErrors);
}

setEndian(isLittle) {
this.isLittleEndian = isLittle;
}
Expand Down Expand Up @@ -125,7 +204,7 @@ class BufferStream {
}

writeUTF8String(value) {
const encodedString = this.encoder.encode(value);
const encodedString = this.codec.encode(value);
this.checkSize(encodedString.byteLength);
this.view.writeBuffer(encodedString, this.offset);
return this.increment(encodedString.byteLength);
Expand Down Expand Up @@ -243,7 +322,7 @@ class BufferStream {
const view = new DataView(
this.slice(this.offset, this.offset + length)
);
const result = this.decoder.decode(view);
const result = this.codec.decode(view);
this.increment(length);
return result;
}
Expand Down Expand Up @@ -343,7 +422,6 @@ class ReadBufferStream extends BufferStream {
) {
super({ littleEndian });
this.noCopy = options.noCopy;
this.decoder = new TextDecoder("latin1");

if (buffer instanceof BufferStream) {
this.view.from(buffer.view, options);
Expand All @@ -357,10 +435,6 @@ class ReadBufferStream extends BufferStream {
this.endOffset = this.size;
}

setDecoder(decoder) {
this.decoder = decoder;
}

reset() {
this.offset = this.startOffset;
return this;
Expand Down Expand Up @@ -449,6 +523,7 @@ class WriteBufferStream extends BufferStream {
}
}

export { DicomBufferCODEC };
export { ReadBufferStream };
export { DeflatedReadBufferStream };
export { WriteBufferStream };
107 changes: 19 additions & 88 deletions src/DicomMessage.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,68 +13,6 @@ import { log } from "./log.js";
import { deepEqual } from "./utilities/deepEqual";
import { ValueRepresentation } from "./ValueRepresentation.js";

const singleVRs = ["SQ", "OF", "OW", "OB", "UN", "LT"];

const encodingMapping = {
"": "iso-8859-1",
"iso-ir-6": "iso-8859-1",
"iso-ir-13": "shift-jis",
"iso-ir-100": "latin1",
"iso-ir-101": "iso-8859-2",
"iso-ir-109": "iso-8859-3",
"iso-ir-110": "iso-8859-4",
"iso-ir-126": "iso-ir-126",
"iso-ir-127": "iso-ir-127",
"iso-ir-138": "iso-ir-138",
"iso-ir-144": "iso-ir-144",
"iso-ir-148": "iso-ir-148",
"iso-ir-166": "tis-620",
"iso-2022-ir-6": "iso-8859-1",
"iso-2022-ir-13": "shift-jis",
"iso-2022-ir-87": "iso-2022-jp",
"iso-2022-ir-100": "latin1",
"iso-2022-ir-101": "iso-8859-2",
"iso-2022-ir-109": "iso-8859-3",
"iso-2022-ir-110": "iso-8859-4",
"iso-2022-ir-126": "iso-ir-126",
"iso-2022-ir-127": "iso-ir-127",
"iso-2022-ir-138": "iso-ir-138",
"iso-2022-ir-144": "iso-ir-144",
"iso-2022-ir-148": "iso-ir-148",
"iso-2022-ir-149": "euc-kr",
"iso-2022-ir-159": "iso-2022-jp",
"iso-2022-ir-166": "tis-620",
"iso-2022-ir-58": "iso-ir-58",
"iso-ir-192": "utf-8",
gb18030: "gb18030",
"iso-2022-gbk": "gbk",
"iso-2022-58": "gb2312",
gbk: "gbk"
};

const encapsulatedSyntaxes = [
"1.2.840.10008.1.2.4.50",
"1.2.840.10008.1.2.4.51",
"1.2.840.10008.1.2.4.57",
"1.2.840.10008.1.2.4.70",
"1.2.840.10008.1.2.4.80",
"1.2.840.10008.1.2.4.81",
"1.2.840.10008.1.2.4.90",
"1.2.840.10008.1.2.4.91",
"1.2.840.10008.1.2.4.92",
"1.2.840.10008.1.2.4.93",
"1.2.840.10008.1.2.4.94",
"1.2.840.10008.1.2.4.95",
"1.2.840.10008.1.2.5",
"1.2.840.10008.1.2.6.1",
"1.2.840.10008.1.2.4.100",
"1.2.840.10008.1.2.4.102",
"1.2.840.10008.1.2.4.103",
"1.2.840.10008.1.2.4.201",
"1.2.840.10008.1.2.4.202",
"1.2.840.10008.1.2.4.203"
];

class DicomMessage {
static read(
bufferStream,
Expand Down Expand Up @@ -132,18 +70,10 @@ class DicomMessage {
}
if (cleanTagString === "00080005") {
if (readInfo.values.length > 0) {
let coding = readInfo.values[0];
coding = coding.replace(/[_ ]/g, "-").toLowerCase();
if (coding in encodingMapping) {
coding = encodingMapping[coding];
bufferStream.setDecoder(new TextDecoder(coding));
} else if (ignoreErrors) {
log.warn(
`Unsupported character set: ${coding}, using default character set`
);
} else {
throw Error(`Unsupported character set: ${coding}`);
}
bufferStream.setDecoder(
readInfo.values[0],
ignoreErrors
);
}
if (readInfo.values.length > 1) {
if (ignoreErrors) {
Expand Down Expand Up @@ -182,9 +112,9 @@ class DicomMessage {

static _normalizeSyntax(syntax) {
if (
syntax == IMPLICIT_LITTLE_ENDIAN ||
syntax == EXPLICIT_LITTLE_ENDIAN ||
syntax == EXPLICIT_BIG_ENDIAN
syntax === IMPLICIT_LITTLE_ENDIAN ||
syntax === EXPLICIT_LITTLE_ENDIAN ||
syntax === EXPLICIT_BIG_ENDIAN
) {
return syntax;
} else {
Expand All @@ -193,7 +123,7 @@ class DicomMessage {
}

static isEncapsulated(syntax) {
return encapsulatedSyntaxes.indexOf(syntax) != -1;
return DicomMetaDictionary.encapsulatedSyntaxes.indexOf(syntax) !== -1;
}

static readFile(
Expand Down Expand Up @@ -331,12 +261,10 @@ class DicomMessage {
}
) {
const { untilTag, includeUntilTagValue } = options;
var implicit = syntax == IMPLICIT_LITTLE_ENDIAN ? true : false,
var implicit = syntax === IMPLICIT_LITTLE_ENDIAN,
isLittleEndian =
syntax == IMPLICIT_LITTLE_ENDIAN ||
syntax == EXPLICIT_LITTLE_ENDIAN
? true
: false;
syntax === IMPLICIT_LITTLE_ENDIAN ||
syntax === EXPLICIT_LITTLE_ENDIAN;

var oldEndian = stream.isLittleEndian;
stream.setEndian(isLittleEndian);
Expand All @@ -359,11 +287,11 @@ class DicomMessage {
vrType = elementData.vr;
} else {
//unknown tag
if (length == 0xffffffff) {
if (length === 0xffffffff) {
vrType = "SQ";
} else if (tag.isPixelDataTag()) {
vrType = "OW";
} else if (vrType == "xs") {
} else if (vrType === "xs") {
vrType = "US";
} else if (tag.isPrivateCreator()) {
vrType = "LO";
Expand Down Expand Up @@ -413,18 +341,21 @@ class DicomMessage {
} else {
const { rawValue, value } =
vr.read(stream, length, syntax, options) || {};
if (!vr.isBinary() && singleVRs.indexOf(vr.type) == -1) {
if (
!vr.isBinary() &&
ValueRepresentation.singleVRs.indexOf(vr.type) === -1
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use ValueRepresentation.singleVRs.has(vr.type)
with a set or map.

) {
rawValues = rawValue;
values = value;
if (typeof value === "string") {
const delimiterChar = String.fromCharCode(VM_DELIMITER);
rawValues = vr.dropPadByte(rawValue.split(delimiterChar));
values = vr.dropPadByte(value.split(delimiterChar));
}
} else if (vr.type == "SQ") {
} else if (vr.type === "SQ") {
rawValues = rawValue;
values = value;
} else if (vr.type == "OW" || vr.type == "OB") {
} else if (vr.type === "OW" || vr.type === "OB") {
rawValues = rawValue;
values = value;
} else {
Expand Down
Loading