Skip to content

Commit

Permalink
added utility function createJPEGBasicOffsetTable()
Browse files Browse the repository at this point in the history
added test images
  • Loading branch information
chafey committed Jun 6, 2016
1 parent 938209e commit 033c111
Show file tree
Hide file tree
Showing 38 changed files with 321 additions and 18 deletions.
4 changes: 4 additions & 0 deletions .npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
examples
meteor
test
testImages
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,11 +123,14 @@ Key Features
* Packaged using the module pattern, as an AMD module and as a CommonJS module for Node.js
* No external dependencies
* Supports extraction of encapsulated pixel data frames
* NOTE - does not support fragmented frames with no basic offset table as that requires special logic
to determine which fragments are part of each image frame and this library does not include decoders
* Basic Offset Table decoded
* Fragments decoded
* Function to extract image frame when basic offset table is present
* Function to extract image frame from fragments when no basic offset table is present
* Convenient utility functions to parse strings formatted in DA, TM and PN VRs and return JavaScript objects
* Convenient utility function to create a string version of an explicit element
* Convenient utility function to convert a parsed explicit dataSet into a javascript object
* Convenient utility function to generate a basic offset table for JPEG images
* Supports reading incomplete/partial byte streams
* By specifying a tag to stop reading at (e.g. parseDicom(byteArray, {untilTag: "x7fe00010"}); )
* By returning the elements parsed so far in the exception thrown during a parse error (the elements parsed will be
Expand Down
2 changes: 1 addition & 1 deletion bower.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "dicomParser",
"version": "1.6.1",
"version": "1.7.0",
"description": "Javascript parser for DICOM Part 10 data",
"main": "dist/dicomParser.js",
"ignore": [
Expand Down
129 changes: 124 additions & 5 deletions dist/dicomParser.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/*! dicom-parser - v1.6.1 - 2016-05-24 | (c) 2014 Chris Hafey | https://github.com/chafey/dicomParser */
/*! dicom-parser - v1.7.0 - 2016-06-06 | (c) 2014 Chris Hafey | https://github.com/chafey/dicomParser */
(function (root, factory) {

// node.js
Expand Down Expand Up @@ -175,6 +175,124 @@ var dicomParser = (function(dicomParser) {
return dicomParser;
})(dicomParser);

/**
* Utility function for creating a basic offset table for JPEG transfer syntaxes
*/

var dicomParser = (function (dicomParser)
{
"use strict";

if(dicomParser === undefined)
{
dicomParser = {};
}

// Each JPEG image has an end of image marker 0xFFD9
function isEndOfImageMarker(dataSet, position) {
return (dataSet.byteArray[position] === 0xFF &&
dataSet.byteArray[position + 1] === 0xD9);
}

// Each JPEG image has a start of image marker
function isStartOfImageMarker(dataSet, position) {
return (dataSet.byteArray[position] === 0xFF &&
dataSet.byteArray[position + 1] === 0xD8);
}

function isFragmentStartOfImage(dataSet, pixelDataElement, fragmentIndex) {
var fragment = pixelDataElement.fragments[fragmentIndex];
if(isStartOfImageMarker(dataSet, fragment.position)) {
return true;
}
return false;
}

function isFragmentEndOfImage(dataSet, pixelDataElement, fragmentIndex) {
var fragment = pixelDataElement.fragments[fragmentIndex];
// Need to check the last two bytes and the last three bytes for marker since odd length
// fragments are zero padded
if(isEndOfImageMarker(dataSet, fragment.position + fragment.length - 2) ||
isEndOfImageMarker(dataSet, fragment.position + fragment.length - 3)) {
return true;
}
return false;
}

function findLastImageFrameFragmentIndex(dataSet, pixelDataElement, startFragment) {
for(var fragmentIndex=startFragment; fragmentIndex < pixelDataElement.fragments.length; fragmentIndex++) {
if(isFragmentEndOfImage(dataSet, pixelDataElement, fragmentIndex)) {
// if not last fragment, peek ahead to make sure the next fragment has a start of image marker just to
// be safe
if(fragmentIndex === pixelDataElement.fragments.length - 1 ||
isFragmentStartOfImage(dataSet, pixelDataElement, fragmentIndex+1)) {
return fragmentIndex;
}
}
}
}

/**
* Creates a basic offset table by scanning fragments for JPEG start of image and end Of Image markers
* @param {object} dataSet - the parsed dicom dataset
* @param {object} pixelDataElement - the pixel data element
* @param [fragments] - optional array of objects describing each fragment (offset, position, length)
* @returns {Array} basic offset table (array of offsets to beginning of each frame)
*/
dicomParser.createJPEGBasicOffsetTable = function(dataSet, pixelDataElement, fragments) {
// Validate parameters
if(dataSet === undefined) {
throw 'dicomParser.createJPEGBasicOffsetTable: missing required parameter dataSet';
}
if(pixelDataElement === undefined) {
throw 'dicomParser.createJPEGBasicOffsetTable: missing required parameter pixelDataElement';
}
if(pixelDataElement.tag !== 'x7fe00010') {
throw "dicomParser.createJPEGBasicOffsetTable: parameter 'pixelDataElement' refers to non pixel data tag (expected tag = x7fe00010'";
}
if(pixelDataElement.encapsulatedPixelData !== true) {
throw "dicomParser.createJPEGBasicOffsetTable: parameter 'pixelDataElement' refers to pixel data element that does not have encapsulated pixel data";
}
if(pixelDataElement.hadUndefinedLength !== true) {
throw "dicomParser.createJPEGBasicOffsetTable: parameter 'pixelDataElement' refers to pixel data element that does not have encapsulated pixel data";
}
if(pixelDataElement.basicOffsetTable === undefined) {
throw "dicomParser.createJPEGBasicOffsetTable: parameter 'pixelDataElement' refers to pixel data element that does not have encapsulated pixel data";
}
if(pixelDataElement.fragments === undefined) {
throw "dicomParser.createJPEGBasicOffsetTable: parameter 'pixelDataElement' refers to pixel data element that does not have encapsulated pixel data";
}
if(pixelDataElement.fragments.length <= 0) {
throw "dicomParser.createJPEGBasicOffsetTable: parameter 'pixelDataElement' refers to pixel data element that does not have encapsulated pixel data";
}
if(fragments && fragments.length <=0) {
throw "dicomParser.createJPEGBasicOffsetTable: parameter 'fragments' must not be zero length";
}

// Default values
fragments = fragments || pixelDataElement.fragments;

var basicOffsetTable = [];

var startFragmentIndex = 0;
// sanity check first fragment has a JPEG start of image marker
if(!isFragmentStartOfImage(dataSet, pixelDataElement, startFragmentIndex)) {
throw 'first fragment does not have JPEG start of image marker';
}

while(true) {
// Add the offset for the start fragment
basicOffsetTable.push(pixelDataElement.fragments[startFragmentIndex].offset);
var endFragmentIndex = findLastImageFrameFragmentIndex(dataSet, pixelDataElement, startFragmentIndex);
if(endFragmentIndex === undefined || endFragmentIndex === pixelDataElement.fragments.length -1) {
return basicOffsetTable;
}
startFragmentIndex = endFragmentIndex + 1;
}
};

return dicomParser;
}(dicomParser));
var dicomParser = (function (dicomParser) {
"use strict";

Expand Down Expand Up @@ -1905,7 +2023,8 @@ var dicomParser = (function (dicomParser)
* Returns the pixel data for the specified frame in an encapsulated pixel data element that has a non
* empty basic offset table. Note that this function will fail if the basic offset table is empty - in that
* case you need to determine which fragments map to which frames and read them using
* readEncapsulatedPixelDataFromFragments()
* readEncapsulatedPixelDataFromFragments(). Also see the function createJEPGBasicOffsetTable() to see
* how a basic offset table can be created for JPEG images
*
* @param dataSet - the dataSet containing the encapsulated pixel data
* @param pixelDataElement - the pixel data element (x7fe00010) to extract the frame from
Expand Down Expand Up @@ -1990,8 +2109,8 @@ var dicomParser = (function (dicomParser)
}

/**
* Returns the encapsulated pixel data from the specified fragments. Some transfer syntaxes encode a single image
* frame in multiple fragments (specifica
* Returns the encapsulated pixel data from the specified fragments. Use this function when you know
* the fragments you want to extract data from. See
*
* @param dataSet - the dataSet containing the encapsulated pixel data
* @param pixelDataElement - the pixel data element (x7fe00010) to extract the fragment data from
Expand Down Expand Up @@ -2509,7 +2628,7 @@ var dicomParser = (function (dicomParser)
dicomParser = {};
}

dicomParser.version = "1.6.1";
dicomParser.version = "1.7.0";

return dicomParser;
}(dicomParser));
Expand Down
4 changes: 2 additions & 2 deletions dist/dicomParser.min.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/package.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
Package.describe({
name: 'chafey:dicom-parser',
summary: 'Javascript parser for DICOM Part 10 data',
version: '1.6.1',
version: '1.7.0',
git: 'https://github.com/chafey/dicomParser.git/',
documentation: null
});
Expand Down
13 changes: 12 additions & 1 deletion examples/dumpWithDataDictionary/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,18 @@ <h1>DICOM Dump with Data Dictionary v<span id="version"></span></h1>
// to the output array passed into it
function dumpDataSet(dataSet, output)
{

/*
// createJPEGBasicOffsetTable() testing
if(dataSet.elements.x7fe00010 && dataSet.elements.x7fe00010.encapsulatedPixelData) {
var bot = dicomParser.createJPEGBasicOffsetTable(dataSet, dataSet.elements.x7fe00010);
if(bot) {
console.log(dataSet.elements.x7fe00010.basicOffsetTable.length);
console.log(dataSet.elements.x7fe00010.basicOffsetTable);
console.log(bot.length);
console.log(bot);
}
}
*/

function getTag(tag)
{
Expand Down
2 changes: 1 addition & 1 deletion meteor/dicomParser/package.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
Package.describe({
name: 'chafey:dicom-parser',
summary: 'Javascript parser for DICOM Part 10 data',
version: '1.6.1',
version: '1.7.0',
git: 'https://github.com/chafey/dicomParser.git/',
documentation: null
});
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "dicom-parser",
"version": "1.6.1",
"version": "1.7.0",
"description": "Javascript parser for DICOM Part 10 data",
"keywords": [
"DICOM",
Expand Down
3 changes: 2 additions & 1 deletion src/readEncapsulatedImageFrame.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ var dicomParser = (function (dicomParser)
* Returns the pixel data for the specified frame in an encapsulated pixel data element that has a non
* empty basic offset table. Note that this function will fail if the basic offset table is empty - in that
* case you need to determine which fragments map to which frames and read them using
* readEncapsulatedPixelDataFromFragments()
* readEncapsulatedPixelDataFromFragments(). Also see the function createJEPGBasicOffsetTable() to see
* how a basic offset table can be created for JPEG images
*
* @param dataSet - the dataSet containing the encapsulated pixel data
* @param pixelDataElement - the pixel data element (x7fe00010) to extract the frame from
Expand Down
4 changes: 2 additions & 2 deletions src/readEncapsulatedPixelDataFromFragments.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ var dicomParser = (function (dicomParser)
}

/**
* Returns the encapsulated pixel data from the specified fragments. Some transfer syntaxes encode a single image
* frame in multiple fragments (specifica
* Returns the encapsulated pixel data from the specified fragments. Use this function when you know
* the fragments you want to extract data from. See
*
* @param dataSet - the dataSet containing the encapsulated pixel data
* @param pixelDataElement - the pixel data element (x7fe00010) to extract the fragment data from
Expand Down
118 changes: 118 additions & 0 deletions src/util/createJPEGBasicOffsetTable.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
/**
* Utility function for creating a basic offset table for JPEG transfer syntaxes
*/

var dicomParser = (function (dicomParser)
{
"use strict";

if(dicomParser === undefined)
{
dicomParser = {};
}

// Each JPEG image has an end of image marker 0xFFD9
function isEndOfImageMarker(dataSet, position) {
return (dataSet.byteArray[position] === 0xFF &&
dataSet.byteArray[position + 1] === 0xD9);
}

// Each JPEG image has a start of image marker
function isStartOfImageMarker(dataSet, position) {
return (dataSet.byteArray[position] === 0xFF &&
dataSet.byteArray[position + 1] === 0xD8);
}

function isFragmentStartOfImage(dataSet, pixelDataElement, fragmentIndex) {
var fragment = pixelDataElement.fragments[fragmentIndex];
if(isStartOfImageMarker(dataSet, fragment.position)) {
return true;
}
return false;
}

function isFragmentEndOfImage(dataSet, pixelDataElement, fragmentIndex) {
var fragment = pixelDataElement.fragments[fragmentIndex];
// Need to check the last two bytes and the last three bytes for marker since odd length
// fragments are zero padded
if(isEndOfImageMarker(dataSet, fragment.position + fragment.length - 2) ||
isEndOfImageMarker(dataSet, fragment.position + fragment.length - 3)) {
return true;
}
return false;
}

function findLastImageFrameFragmentIndex(dataSet, pixelDataElement, startFragment) {
for(var fragmentIndex=startFragment; fragmentIndex < pixelDataElement.fragments.length; fragmentIndex++) {
if(isFragmentEndOfImage(dataSet, pixelDataElement, fragmentIndex)) {
// if not last fragment, peek ahead to make sure the next fragment has a start of image marker just to
// be safe
if(fragmentIndex === pixelDataElement.fragments.length - 1 ||
isFragmentStartOfImage(dataSet, pixelDataElement, fragmentIndex+1)) {
return fragmentIndex;
}
}
}
}

/**
* Creates a basic offset table by scanning fragments for JPEG start of image and end Of Image markers
* @param {object} dataSet - the parsed dicom dataset
* @param {object} pixelDataElement - the pixel data element
* @param [fragments] - optional array of objects describing each fragment (offset, position, length)
* @returns {Array} basic offset table (array of offsets to beginning of each frame)
*/
dicomParser.createJPEGBasicOffsetTable = function(dataSet, pixelDataElement, fragments) {
// Validate parameters
if(dataSet === undefined) {
throw 'dicomParser.createJPEGBasicOffsetTable: missing required parameter dataSet';
}
if(pixelDataElement === undefined) {
throw 'dicomParser.createJPEGBasicOffsetTable: missing required parameter pixelDataElement';
}
if(pixelDataElement.tag !== 'x7fe00010') {
throw "dicomParser.createJPEGBasicOffsetTable: parameter 'pixelDataElement' refers to non pixel data tag (expected tag = x7fe00010'";
}
if(pixelDataElement.encapsulatedPixelData !== true) {
throw "dicomParser.createJPEGBasicOffsetTable: parameter 'pixelDataElement' refers to pixel data element that does not have encapsulated pixel data";
}
if(pixelDataElement.hadUndefinedLength !== true) {
throw "dicomParser.createJPEGBasicOffsetTable: parameter 'pixelDataElement' refers to pixel data element that does not have encapsulated pixel data";
}
if(pixelDataElement.basicOffsetTable === undefined) {
throw "dicomParser.createJPEGBasicOffsetTable: parameter 'pixelDataElement' refers to pixel data element that does not have encapsulated pixel data";
}
if(pixelDataElement.fragments === undefined) {
throw "dicomParser.createJPEGBasicOffsetTable: parameter 'pixelDataElement' refers to pixel data element that does not have encapsulated pixel data";
}
if(pixelDataElement.fragments.length <= 0) {
throw "dicomParser.createJPEGBasicOffsetTable: parameter 'pixelDataElement' refers to pixel data element that does not have encapsulated pixel data";
}
if(fragments && fragments.length <=0) {
throw "dicomParser.createJPEGBasicOffsetTable: parameter 'fragments' must not be zero length";
}

// Default values
fragments = fragments || pixelDataElement.fragments;

var basicOffsetTable = [];

var startFragmentIndex = 0;
// sanity check first fragment has a JPEG start of image marker
if(!isFragmentStartOfImage(dataSet, pixelDataElement, startFragmentIndex)) {
throw 'first fragment does not have JPEG start of image marker';
}

while(true) {
// Add the offset for the start fragment
basicOffsetTable.push(pixelDataElement.fragments[startFragmentIndex].offset);
var endFragmentIndex = findLastImageFrameFragmentIndex(dataSet, pixelDataElement, startFragmentIndex);
if(endFragmentIndex === undefined || endFragmentIndex === pixelDataElement.fragments.length -1) {
return basicOffsetTable;
}
startFragmentIndex = endFragmentIndex + 1;
}
};

return dicomParser;
}(dicomParser));
2 changes: 1 addition & 1 deletion src/version.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ var dicomParser = (function (dicomParser)
dicomParser = {};
}

dicomParser.version = "1.6.1";
dicomParser.version = "1.7.0";

return dicomParser;
}(dicomParser));
Binary file added testImages/CT1_UNC.explicit_big_endian.dcm
Binary file not shown.
Binary file added testImages/CT1_UNC.explicit_little_endian.dcm
Binary file not shown.
Binary file added testImages/CT1_UNC.implicit_little_endian.dcm
Binary file not shown.
11 changes: 11 additions & 0 deletions testImages/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
Wanted:
* Sequences with undefined length
* elements with undefined length


## implicit big endian
dcmconv +tb CT1_UNC.explicit_little_endian.dcm CT1_UNC.explicit_big_endian.dcm

## implicit little endian
dcmconv +ti CT1_UNC.explicit_little_endian.dcm CT1_UNC.implicit_little_endian.dcm

Loading

0 comments on commit 033c111

Please sign in to comment.