Skip to content

Commit

Permalink
fix(VR): added support for specific character set (#291)
Browse files Browse the repository at this point in the history
* added support for specific character set
refactored VR classes
refactored reading/writing ascii and encoded strings
changed hex writing to direct uint8 writing
reimplemented null padded read
removed unused code
unified test data loading and cache the test data locally for faster test runs

* re-enabled test and removed wrong only

* fixed test case that was overlooked

Co-authored-by: flink <florian.link@mevis.fraunhofer.de>
  • Loading branch information
florianlink and flink committed Jul 10, 2022
1 parent 307d60a commit f103d19
Show file tree
Hide file tree
Showing 9 changed files with 390 additions and 344 deletions.
2 changes: 1 addition & 1 deletion packages/adapters/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"example": "examples"
},
"scripts": {
"test": "jest",
"test": "jest --testTimeout 60000",
"build": "rollup -c",
"build:examples": "npm run build && npx cpx 'build/**/*.{js,map}' examples/js",
"start": "rollup -c -w",
Expand Down
153 changes: 64 additions & 89 deletions packages/adapters/src/BufferStream.js
Original file line number Diff line number Diff line change
@@ -1,38 +1,3 @@
//http://jonisalonen.com/2012/from-utf-16-to-utf-8-in-javascript/
function toUTF8Array(str) {
var utf8 = [];
for (var i = 0; i < str.length; i++) {
var charcode = str.charCodeAt(i);
if (charcode < 0x80) utf8.push(charcode);
else if (charcode < 0x800) {
utf8.push(0xc0 | (charcode >> 6), 0x80 | (charcode & 0x3f));
} else if (charcode < 0xd800 || charcode >= 0xe000) {
utf8.push(
0xe0 | (charcode >> 12),
0x80 | ((charcode >> 6) & 0x3f),
0x80 | (charcode & 0x3f)
);
}
// surrogate pair
else {
i++;
// UTF-16 encodes 0x10000-0x10FFFF by
// subtracting 0x10000 and splitting the
// 20 bits of 0x0-0xFFFFF into two halves
charcode =
0x10000 +
(((charcode & 0x3ff) << 10) | (str.charCodeAt(i) & 0x3ff));
utf8.push(
0xf0 | (charcode >> 18),
0x80 | ((charcode >> 12) & 0x3f),
0x80 | ((charcode >> 6) & 0x3f),
0x80 | (charcode & 0x3f)
);
}
}
return utf8;
}

function toInt(val) {
if (isNaN(val)) {
throw new Error("Not a number: " + val);
Expand Down Expand Up @@ -62,6 +27,7 @@ class BufferStream {
this.offset = 0;
this.isLittleEndian = littleEndian || false;
this.size = 0;
this.encoder = new TextEncoder("utf-8");
}

setEndian(isLittle) {
Expand All @@ -74,6 +40,15 @@ class BufferStream {
return this.increment(1);
}

writeUint8Repeat(value, count) {
const v = toInt(value);
this.checkSize(count);
for (let i = 0; i < count; i++) {
this.view.setUint8(this.offset + i, v);
}
return this.increment(count);
}

writeInt8(value) {
this.checkSize(1);
this.view.setInt8(this.offset, toInt(value));
Expand Down Expand Up @@ -129,40 +104,23 @@ class BufferStream {
return this.increment(8);
}

writeString(value) {
value = value || "";
var utf8 = toUTF8Array(value),
bytelen = utf8.length;
writeUTF8String(value) {
const encodedString = this.encoder.encode(value);
this.checkSize(encodedString.byteLength);
new Uint8Array(this.buffer).set(encodedString, this.offset);
return this.increment(encodedString.byteLength);
}

this.checkSize(bytelen);
writeAsciiString(value) {
value = value || "";
var len = value.length;
this.checkSize(len);
var startOffset = this.offset;
for (var i = 0; i < bytelen; i++) {
this.view.setUint8(startOffset, utf8[i]);
startOffset++;
for (let i = 0; i < len; i++) {
var charcode = value.charCodeAt(i);
this.view.setUint8(startOffset + i, charcode);
}
return this.increment(bytelen);
}

writeHex(value) {
var len = value.length,
blen = len / 2,
startOffset = this.offset;
this.checkSize(blen);
for (var i = 0; i < len; i += 2) {
var code = parseInt(value[i], 16),
nextCode;
if (i == len - 1) {
nextCode = null;
} else {
nextCode = parseInt(value[i + 1], 16);
}
if (nextCode !== null) {
code = (code << 4) | nextCode;
}
this.view.setUint8(startOffset, code);
startOffset++;
}
return this.increment(blen);
return this.increment(len);
}

readUint32() {
Expand All @@ -183,6 +141,10 @@ class BufferStream {
return val;
}

peekUint8(offset) {
return this.view.getUint8(this.offset + offset);
}

readUint8Array(length) {
var arr = new Uint8Array(this.buffer, this.offset, length);
this.increment(length);
Expand Down Expand Up @@ -224,18 +186,36 @@ class BufferStream {
return val;
}

readString(length) {
var chars = [];
readAsciiString(length) {
var result = "";
var start = this.offset;
var end = this.offset + length;
if (end >= this.buffer.byteLength) {
end = this.buffer.byteLength;
}
for (let i = start; i < end; ++i) {
chars.push(String.fromCharCode(this.view.getUint8(i)));
this.increment(1);
result += String.fromCharCode(this.view.getUint8(i));
}
this.increment(end - start);
return result;
}

readVR() {
var vr =
String.fromCharCode(this.view.getUint8(this.offset)) +
String.fromCharCode(this.view.getUint8(this.offset + 1));
this.increment(2);
return vr;
}

readEncodedString(length) {
if (this.offset + length >= this.buffer.byteLength) {
length = this.buffer.byteLength - this.offset;
}
return chars.join("");
const view = new DataView(this.buffer, this.offset, length);
const result = this.decoder.decode(view);
this.increment(length);
return result;
}

readHex(length) {
Expand Down Expand Up @@ -354,6 +334,11 @@ class ReadBufferStream extends BufferStream {
this.noCopy = options.noCopy;
this.startOffset = this.offset;
this.endOffset = this.size;
this.decoder = new TextDecoder("latin1");
}

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

getBuffer(start, end) {
Expand All @@ -368,20 +353,6 @@ class ReadBufferStream extends BufferStream {
return this.buffer.slice(start, end);
}

readString(length) {
var chars = [];
var start = this.offset;
var end = this.offset + length;
if (end >= this.endOffset) {
end = this.endOffset;
}
for (let i = start; i < end; ++i) {
chars.push(String.fromCharCode(this.view.getUint8(i)));
this.increment(1);
}
return chars.join("");
}

reset() {
this.offset = this.startOffset;
return this;
Expand All @@ -399,6 +370,10 @@ class ReadBufferStream extends BufferStream {
throw new Error(value, "writeUint8 not implemented");
}

writeUint8Repeat(value, count) {
throw new Error(value, "writeUint8Repeat not implemented");
}

writeInt8(value) {
throw new Error(value, "writeInt8 not implemented");
}
Expand Down Expand Up @@ -431,12 +406,12 @@ class ReadBufferStream extends BufferStream {
throw new Error(value, "writeDouble not implemented");
}

writeString(value) {
throw new Error(value, "writeString not implemented");
writeAsciiString(value) {
throw new Error(value, "writeAsciiString not implemented");
}

writeHex(value) {
throw new Error(value, "writeHex not implemented");
writeUTF8String(value) {
throw new Error(value, "writeUTF8String not implemented");
}

checkSize(step) {
Expand Down
4 changes: 2 additions & 2 deletions packages/adapters/src/DicomDict.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ class DicomDict {
write(writeOptions = { allowInvalidVRLength: false }) {
var metaSyntax = EXPLICIT_LITTLE_ENDIAN;
var fileStream = new WriteBufferStream(4096, true);
fileStream.writeHex("00".repeat(128));
fileStream.writeString("DICM");
fileStream.writeUint8Repeat(0, 128);
fileStream.writeAsciiString("DICM");

var metaStream = new WriteBufferStream(1024);
if (!this.meta["00020010"]) {
Expand Down
36 changes: 32 additions & 4 deletions packages/adapters/src/DicomMessage.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ const EXPLICIT_LITTLE_ENDIAN = "1.2.840.10008.1.2.1";
const EXPLICIT_BIG_ENDIAN = "1.2.840.10008.1.2.2";
const singleVRs = ["SQ", "OF", "OW", "OB", "UN", "LT"];

const encodingMapping = {
"iso-ir-192": "utf-8",
"": "latin1"
};

const encapsulatedSyntaxes = [
"1.2.840.10008.1.2.4.50",
"1.2.840.10008.1.2.4.51",
Expand Down Expand Up @@ -79,7 +84,30 @@ class DicomMessage {
options
);
const cleanTagString = readInfo.tag.toCleanString();

if (cleanTagString === "00080005") {
if (readInfo.values.length > 0) {
let coding = readInfo.values[0];
coding = coding
.replaceAll("_", "-")
.replaceAll(" ", "-")
.toLowerCase();
if (coding in encodingMapping) {
coding = encodingMapping[coding];
}
try {
bufferStream.setDecoder(new TextDecoder(coding));
} catch (error) {
console.warn(error);
}
}
if (readInfo.values.length > 1) {
console.warn(
"multiple encodings not supported, using first encoding!",
readInfo.values
);
}
readInfo.values = ["ISO_IR 192"]; // change SpecificCharacterSet to UTF-8
}
dict[cleanTagString] = {
vr: readInfo.vr.type,
Value: readInfo.values
Expand Down Expand Up @@ -130,7 +158,7 @@ class DicomMessage {
useSyntax = EXPLICIT_LITTLE_ENDIAN;
stream.reset();
stream.increment(128);
if (stream.readString(4) !== "DICM") {
if (stream.readAsciiString(4) !== "DICM") {
throw new Error("Invalid a dicom file");
}
var el = DicomMessage._readTag(stream, useSyntax),
Expand Down Expand Up @@ -161,7 +189,7 @@ class DicomMessage {
var written = 0;

var sortedTags = Object.keys(jsonObjects).sort();
sortedTags.forEach(function(tagString) {
sortedTags.forEach(function (tagString) {
var tag = Tag.fromString(tagString),
tagObject = jsonObjects[tagString],
vrType = tagObject.vr,
Expand Down Expand Up @@ -230,7 +258,7 @@ class DicomMessage {
}
vr = ValueRepresentation.createByTypeString(vrType);
} else {
vrType = stream.readString(2);
vrType = stream.readVR();
vr = ValueRepresentation.createByTypeString(vrType);
if (vr.isExplicit()) {
stream.increment(2);
Expand Down
34 changes: 7 additions & 27 deletions packages/adapters/src/Tag.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,37 +17,17 @@ class Tag {
toString() {
return (
"(" +
paddingLeft(
"0000",
this.group()
.toString(16)
.toUpperCase()
) +
paddingLeft("0000", this.group().toString(16).toUpperCase()) +
"," +
paddingLeft(
"0000",
this.element()
.toString(16)
.toUpperCase()
) +
paddingLeft("0000", this.element().toString(16).toUpperCase()) +
")"
);
}

toCleanString() {
return (
paddingLeft(
"0000",
this.group()
.toString(16)
.toUpperCase()
) +
paddingLeft(
"0000",
this.element()
.toString(16)
.toUpperCase()
)
paddingLeft("0000", this.group().toString(16).toUpperCase()) +
paddingLeft("0000", this.element().toString(16).toUpperCase())
);
}

Expand Down Expand Up @@ -147,12 +127,12 @@ class Tag {
written += 4;
} else {
if (vr.isExplicit()) {
stream.writeString(vr.type);
stream.writeHex("0000");
stream.writeAsciiString(vr.type);
stream.writeUint16(0);
stream.writeUint32(valueLength);
written += 8;
} else {
stream.writeString(vr.type);
stream.writeAsciiString(vr.type);
stream.writeUint16(valueLength);
written += 4;
}
Expand Down
Loading

0 comments on commit f103d19

Please sign in to comment.