Skip to content

Commit

Permalink
Fix multiframe read/write (#147)
Browse files Browse the repository at this point in the history
- Use offset table for reading fragments if present, otherwise, loop until end of stream/sequence
- Add option to not fragment encapsulated pixel data (use fragments equal to frame size)

# Conflicts:
#	src/ValueRepresentation.js
  • Loading branch information
jmhmd committed Sep 10, 2020
1 parent 26d0001 commit d92ffc4
Show file tree
Hide file tree
Showing 5 changed files with 103 additions and 47 deletions.
6 changes: 3 additions & 3 deletions src/DicomDict.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ class DicomDict {
}
}

write() {
write(writeOptions = {}) {
var metaSyntax = EXPLICIT_LITTLE_ENDIAN;
var fileStream = new WriteBufferStream(4096, true);
fileStream.writeHex("00".repeat(128));
Expand All @@ -30,7 +30,7 @@ class DicomDict {
Value: [EXPLICIT_LITTLE_ENDIAN]
};
}
DicomMessage.write(this.meta, metaStream, metaSyntax);
DicomMessage.write(this.meta, metaStream, metaSyntax, writeOptions);
DicomMessage.writeTagObject(
fileStream,
"00020000",
Expand All @@ -41,7 +41,7 @@ class DicomDict {
fileStream.concat(metaStream);

var useSyntax = this.meta["00020010"].Value[0];
DicomMessage.write(this.dict, fileStream, useSyntax);
DicomMessage.write(this.dict, fileStream, useSyntax, writeOptions);
return fileStream.getBuffer();
}
}
Expand Down
10 changes: 8 additions & 2 deletions src/DicomMessage.js
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ class DicomMessage {
tag.write(stream, vr, values, syntax);
}

static write(jsonObjects, useStream, syntax) {
static write(jsonObjects, useStream, syntax, writeOptions) {
var written = 0;

var sortedTags = Object.keys(jsonObjects).sort();
Expand All @@ -110,7 +110,13 @@ class DicomMessage {
vrType = tagObject.vr,
values = tagObject.Value;

written += tag.write(useStream, vrType, values, syntax);
written += tag.write(
useStream,
vrType,
values,
syntax,
writeOptions
);
});

return written;
Expand Down
12 changes: 9 additions & 3 deletions src/Tag.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ class Tag {
return Tag.fromNumbers(group, element);
}

write(stream, vrType, values, syntax) {
write(stream, vrType, values, syntax, writeOptions) {
var vr = ValueRepresentation.createByTypeString(vrType),
useSyntax = DicomMessage._normalizeSyntax(syntax);

Expand All @@ -113,10 +113,16 @@ class Tag {
tagStream,
values,
useSyntax,
isEncapsulated
isEncapsulated,
writeOptions
);
} else {
valueLength = vr.writeBytes(tagStream, values, useSyntax);
valueLength = vr.writeBytes(
tagStream,
values,
useSyntax,
writeOptions
);
}

if (vrType == "SQ") {
Expand Down
56 changes: 44 additions & 12 deletions src/ValueRepresentation.js
Original file line number Diff line number Diff line change
Expand Up @@ -241,9 +241,10 @@ class BinaryRepresentation extends ValueRepresentation {
super(type);
}

writeBytes(stream, value, syntax, isEncapsulated) {
writeBytes(stream, value, syntax, isEncapsulated, writeOptions) {
var i;
var binaryStream;
var { fragmentMultiframe = true } = writeOptions;
if (isEncapsulated) {
var fragmentSize = 1024 * 20,
frames = value.length,
Expand All @@ -253,16 +254,24 @@ class BinaryRepresentation extends ValueRepresentation {
1024 * 1024 * 20,
stream.isLittleEndian
);

for (i = 0; i < frames; i++) {
startOffset.push(binaryStream.size);
var frameBuffer = value[i],
frameStream = new ReadBufferStream(frameBuffer),
frameStream = new ReadBufferStream(frameBuffer);

var fragmentsLength = 1;
if (fragmentMultiframe) {
fragmentsLength = Math.ceil(
frameStream.size / fragmentSize
);
}

for (var j = 0, fragmentStart = 0; j < fragmentsLength; j++) {
var fragmentEnd = fragmentStart + fragmentSize;
var fragmentEnd = fragmentStart + frameStream.size;
if (fragmentMultiframe) {
fragmentEnd = fragmentStart + fragmentSize;
}
if (j == fragmentsLength - 1) {
fragmentEnd = frameStream.size;
}
Expand Down Expand Up @@ -318,26 +327,49 @@ class BinaryRepresentation extends ValueRepresentation {
offsets.push(stream.readUint32());
}
} else {
offsets = [0];
offsets = [];
}

for (let i = 0; i < offsets.length; i++) {
const nextTag = Tag.readTag(stream);
// If there is an offset table, use that to loop through pixel data sequence
// FIX: These two loops contain the exact same code, but I couldn't think of a way
// to combine the for and while loops non-confusingly so went with the explicit but
// redundant approach.
if (offsets.length > 0) {
for (let i = 0; i < offsets.length; i++) {
const nextTag = Tag.readTag(stream);

if (!nextTag.is(0xfffee000)) {
break;
if (!nextTag.is(0xfffee000)) {
break;
}

const frameItemLength = stream.readUint32();
const fragmentStream = stream.more(frameItemLength);

frames.push(fragmentStream.buffer);
}
}
// If no offset table, loop through remainder of stream looking for termination tag
else {
while (stream.offset < stream.size) {
const nextTag = Tag.readTag(stream);

if (!nextTag.is(0xfffee000)) {
break;
}

const frameItemLength = stream.readUint32();
const fragmentStream = stream.more(frameItemLength);
const frameItemLength = stream.readUint32();
const fragmentStream = stream.more(frameItemLength);

frames.push(fragmentStream.buffer);
frames.push(fragmentStream.buffer);
}
}

// Read SequenceDelimitationItem Tag
stream.readUint32();
// Read SequenceDelimitationItem value.
stream.readUint32();
if (stream.size - stream.offset >= 4) {
stream.readUint32();
}
} else {
throw new Error(
"Item tag not found after undefined binary length"
Expand Down
66 changes: 39 additions & 27 deletions test/test_data.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ const expect = require('chai').expect;
const dcmjs = require('../build/dcmjs');

const fs = require("fs");
const {http, https} = require("follow-redirects");
const { http, https } = require("follow-redirects");
const os = require("os");
const path = require("path");
const unzipper = require("unzipper");
Expand All @@ -14,34 +14,34 @@ fileMetaInformationVersionArray[1] = 1;

const metadata = {
"00020001": {
"Value": [
fileMetaInformationVersionArray.buffer
],
"vr": "OB"
"Value": [
fileMetaInformationVersionArray.buffer
],
"vr": "OB"
},
"00020012": {
"Value": [
"1.2.840.113819.7.1.1997.1.0"
],
"vr": "UI"
"Value": [
"1.2.840.113819.7.1.1997.1.0"
],
"vr": "UI"
},
"00020002": {
"Value": [
"1.2.840.10008.5.1.4.1.1.4"
],
"vr": "UI"
"Value": [
"1.2.840.10008.5.1.4.1.1.4"
],
"vr": "UI"
},
"00020003": {
"Value": [
DicomMetaDictionary.uid()
],
"vr": "UI"
"Value": [
DicomMetaDictionary.uid()
],
"vr": "UI"
},
"00020010": {
"Value": [
"1.2.840.10008.1.2"
],
"vr": "UI"
"Value": [
"1.2.840.10008.1.2"
],
"vr": "UI"
}
};

Expand Down Expand Up @@ -74,7 +74,7 @@ const sequenceMetadata = {
}

function downloadToFile(url, filePath) {
return new Promise( (resolve,reject) => {
return new Promise((resolve, reject) => {
const fileStream = fs.createWriteStream(filePath);
const request = https.get(url, (response) => {
response.pipe(fileStream);
Expand Down Expand Up @@ -152,9 +152,9 @@ const tests = {
const unzipPath = path.join(os.tmpdir(), "test_multiframe_1");

downloadToFile(url, zipFilePath)
.then( () => {
.then(() => {
fs.createReadStream(zipFilePath)
.pipe(unzipper.Extract( {path: unzipPath} )
.pipe(unzipper.Extract({ path: unzipPath })
.on('close', () => {
const mrHeadPath = path.join(unzipPath, "MRHead");
fs.readdir(mrHeadPath, (err, fileNames) => {
Expand Down Expand Up @@ -188,9 +188,9 @@ const tests = {
const segFilePath = path.join(os.tmpdir(), "Lesion1_onesliceSEG.dcm");

downloadToFile(ctPelvisURL, zipFilePath)
.then( () => {
.then(() => {
fs.createReadStream(zipFilePath)
.pipe(unzipper.Extract( {path: unzipPath} )
.pipe(unzipper.Extract({ path: unzipPath })
.on('close', () => {
const ctPelvisPath = path.join(unzipPath, "Series-1.2.840.113704.1.111.1916.1223562191.15");
fs.readdir(ctPelvisPath, (err, fileNames) => {
Expand All @@ -211,7 +211,7 @@ const tests = {
expect(roundedSpacing).to.equal(5);

downloadToFile(segURL, segFilePath)
.then( () => {
.then(() => {
const arrayBuffer = fs.readFileSync(segFilePath).buffer;
const dicomDict = DicomMessage.readFile(arrayBuffer);
const dataset = DicomMetaDictionary.naturalizeDataset(dicomDict.dict);
Expand All @@ -225,6 +225,18 @@ const tests = {
);
});
},

test_multiframe_us: () => {
const file = fs.readFileSync(path.join(__dirname, 'cine-test.dcm'));
const dicomData = dcmjs.data.DicomMessage.readFile(file.buffer, {
// ignoreErrors: true,
});
const dataset = dcmjs.data.DicomMetaDictionary.naturalizeDataset(dicomData.dict);
// eslint-disable-next-line no-underscore-dangle
dataset._meta = dcmjs.data.DicomMetaDictionary.namifyDataset(dicomData.meta);
expect(dataset.NumberOfFrames).to.equal(117);
console.log("Finished test_multiframe_us")
}
}


Expand Down

0 comments on commit d92ffc4

Please sign in to comment.