-
Notifications
You must be signed in to change notification settings - Fork 230
/
parseDicom.js
170 lines (135 loc) · 6.45 KB
/
parseDicom.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
import alloc from './alloc.js';
import bigEndianByteArrayParser from './bigEndianByteArrayParser.js';
import ByteStream from './byteStream.js';
import DataSet from './dataSet.js';
import littleEndianByteArrayParser from './littleEndianByteArrayParser.js';
import readPart10Header from './readPart10Header.js';
import sharedCopy from './sharedCopy.js';
import * as byteArrayParser from './byteArrayParser.js';
import * as parseDicomDataSet from './parseDicomDataSet.js';
// LEE (Little Endian Explicit) is the transfer syntax used in dimse operations when there is a split
// between the header and data.
const LEE = '1.2.840.10008.1.2.1';
// LEI (Little Endian Implicit) is the transfer syntax in raw files
const LEI = '1.2.840.10008.1.2';
// BEI (Big Endian Implicit) is deprecated, but needs special parse handling
const BEI = '1.2.840.10008.1.2.2';
/**
* Parses a DICOM P10 byte array and returns a DataSet object with the parsed elements.
* If the options argument is supplied and it contains the untilTag property, parsing
* will stop once that tag is encoutered. This can be used to parse partial byte streams.
*
* @param byteArray the byte array
* @param options object to control parsing behavior (optional)
* @returns {DataSet}
* @throws error if an error occurs while parsing. The exception object will contain a
* property dataSet with the elements successfully parsed before the error.
*/
export default function parseDicom(byteArray, options = {}) {
if (byteArray === undefined) {
throw new Error('dicomParser.parseDicom: missing required parameter \'byteArray\'');
}
const readTransferSyntax = (metaHeaderDataSet) => {
if (metaHeaderDataSet.elements.x00020010 === undefined) {
throw new Error('dicomParser.parseDicom: missing required meta header attribute 0002,0010');
}
const transferSyntaxElement = metaHeaderDataSet.elements.x00020010;
return transferSyntaxElement && transferSyntaxElement.Value ||
byteArrayParser.readFixedString(byteArray, transferSyntaxElement.dataOffset, transferSyntaxElement.length);
}
function isExplicit(transferSyntax) {
// implicit little endian
if (transferSyntax === '1.2.840.10008.1.2') {
return false;
}
// all other transfer syntaxes should be explicit
return true;
}
function getDataSetByteStream(transferSyntax, position) {
// Detect whether we are inside a browser or Node.js
const isNode = (Object.prototype.toString.call(typeof process !== 'undefined' ? process : 0) === '[object process]');
if (transferSyntax === '1.2.840.10008.1.2.1.99') {
// if an infalter callback is registered, use it
if (options && options.inflater) {
const fullByteArrayCallback = options.inflater(byteArray, position);
return new ByteStream(littleEndianByteArrayParser, fullByteArrayCallback, 0);
}
// if running on node, use the zlib library to inflate
// http://stackoverflow.com/questions/4224606/how-to-check-whether-a-script-is-running-under-node-js
else if (isNode === true) {
// inflate it
const zlib = require('zlib');
const deflatedBuffer = sharedCopy(byteArray, position, byteArray.length - position);
const inflatedBuffer = zlib.inflateRawSync(deflatedBuffer);
// create a single byte array with the full header bytes and the inflated bytes
const fullByteArrayBuffer = alloc(byteArray, inflatedBuffer.length + position);
byteArray.copy(fullByteArrayBuffer, 0, 0, position);
inflatedBuffer.copy(fullByteArrayBuffer, position);
return new ByteStream(littleEndianByteArrayParser, fullByteArrayBuffer, 0);
}
// if pako is defined - use it. This is the web browser path
// https://github.com/nodeca/pako
else if (typeof pako !== 'undefined') {
// inflate it
const deflated = byteArray.slice(position);
const inflated = pako.inflateRaw(deflated);
// create a single byte array with the full header bytes and the inflated bytes
const fullByteArray = alloc(byteArray, inflated.length + position);
fullByteArray.set(byteArray.slice(0, position), 0);
fullByteArray.set(inflated, position);
return new ByteStream(littleEndianByteArrayParser, fullByteArray, 0);
}
// throw exception since no inflater is available
throw 'dicomParser.parseDicom: no inflater available to handle deflate transfer syntax';
}
// explicit big endian
if (transferSyntax === BEI) {
return new ByteStream(bigEndianByteArrayParser, byteArray, position);
}
// all other transfer syntaxes are little endian; only the pixel encoding differs
// make a new stream so the metaheader warnings don't come along for the ride
return new ByteStream(littleEndianByteArrayParser, byteArray, position);
}
function mergeDataSets(metaHeaderDataSet, instanceDataSet) {
for (const propertyName in metaHeaderDataSet.elements) {
if (metaHeaderDataSet.elements.hasOwnProperty(propertyName)) {
instanceDataSet.elements[propertyName] = metaHeaderDataSet.elements[propertyName];
}
}
if (metaHeaderDataSet.warnings !== undefined) {
instanceDataSet.warnings = metaHeaderDataSet.warnings.concat(instanceDataSet.warnings);
}
return instanceDataSet;
}
function readDataSet(metaHeaderDataSet) {
const transferSyntax = readTransferSyntax(metaHeaderDataSet);
const explicit = isExplicit(transferSyntax);
const dataSetByteStream = getDataSetByteStream(transferSyntax, metaHeaderDataSet.position);
const elements = {};
const dataSet = new DataSet(dataSetByteStream.byteArrayParser, dataSetByteStream.byteArray, elements);
dataSet.warnings = dataSetByteStream.warnings;
try {
if (explicit) {
parseDicomDataSet.parseDicomDataSetExplicit(dataSet, dataSetByteStream, dataSetByteStream.byteArray.length, options);
} else {
parseDicomDataSet.parseDicomDataSetImplicit(dataSet, dataSetByteStream, dataSetByteStream.byteArray.length, options);
}
} catch (e) {
const ex = {
exception: e,
dataSet
};
throw ex;
}
return dataSet;
}
// main function here
function parseTheByteStream() {
const metaHeaderDataSet = readPart10Header(byteArray, options);
const dataSet = readDataSet(metaHeaderDataSet);
return mergeDataSets(metaHeaderDataSet, dataSet);
}
// This is where we actually start parsing
return parseTheByteStream();
}
export { LEI, LEE, BEI };