Skip to content

Commit 39783ea

Browse files
author
Alexis Girault
committed
WIP: read volume (3d image) data
https://forum.dcmtk.org/viewtopic.php?f=1&t=3762
1 parent 8fa5c7f commit 39783ea

File tree

2 files changed

+141
-45
lines changed

2 files changed

+141
-45
lines changed

examples/Dicom/src/index.js

+9
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,21 @@ const outputFileInformation = curry(async function outputFileInformation (output
1717
// Select DICOM serie
1818
outputTextArea.textContent = "Please select serie..."
1919
setupDicomForm(patients, async (serie) => {
20+
console.time('customRead:')
21+
const image1 = serie.getImageData()
22+
console.log(image1)
23+
console.warn(image1.data.length)
24+
console.timeEnd('customRead:')
2025
outputTextArea.textContent = "Loading..."
2126

2227
// Read DICOM serie
28+
console.time('itkRead:')
2329
const files = Object.values(serie.images).map((image) => image.file)
2430
const { image, webWorker } = await readImageDICOMFileSeries(null, files)
2531
webWorker.terminate()
32+
console.log(image)
33+
console.warn(image.data.length)
34+
console.timeEnd('itkRead:')
2635

2736
// Display
2837
function replacer (key, value) {

examples/Dicom/src/parseDicomFiles.js

+132-45
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,18 @@ import "regenerator-runtime/runtime";
55

66
import DICOM_TAG_DICT from './dicomTags'
77

8+
function concatenate(resultConstructor, arrays) {
9+
const totalLength = arrays.reduce((total, arr) => {
10+
return total + arr.length
11+
}, 0);
12+
const result = new resultConstructor(totalLength);
13+
arrays.reduce((offset, arr) => {
14+
result.set(arr, offset);
15+
return offset + arr.length;
16+
}, 0);
17+
return result;
18+
}
19+
820
class DICOMEntity {
921
constructor() {
1022
this.metaData = {}
@@ -120,6 +132,72 @@ class DICOMSeries extends DICOMEntity {
120132
}
121133
this.images[imageNumber] = new DICOMImage(metaData, file)
122134
}
135+
136+
getImageData() {
137+
function numArrayFromString(str, separator = '\\') {
138+
const strArray = str.split(separator)
139+
return strArray.map(Number)
140+
}
141+
142+
const slices = Object.values(this.images)
143+
const meta = slices[0].metaData
144+
const origin = numArrayFromString(meta.ImagePositionPatient)
145+
const spacing = numArrayFromString(meta.PixelSpacing)
146+
spacing.push(Number(meta.SliceThickness))
147+
const size = [
148+
meta.Rows,
149+
meta.Columns,
150+
Object.keys(this.images).length
151+
]
152+
const directionCosines = numArrayFromString(meta.ImageOrientationPatient)
153+
const iDirCos = directionCosines.slice(0,3)
154+
const jDirCos = directionCosines.slice(3,6)
155+
const kDirCos = [
156+
iDirCos[1]*jDirCos[2] - iDirCos[2]*jDirCos[1],
157+
iDirCos[2]*jDirCos[0] - iDirCos[0]*jDirCos[2],
158+
iDirCos[0]*jDirCos[1] - iDirCos[1]*jDirCos[0],
159+
]
160+
const direction = [
161+
iDirCos[0], jDirCos[0], kDirCos[0],
162+
iDirCos[1], jDirCos[1], kDirCos[1],
163+
iDirCos[2], jDirCos[2], kDirCos[2],
164+
]
165+
166+
const unsigned = (meta.PixelRepresentation === 0)
167+
const bits = meta.BitsAllocated
168+
let ArrayType
169+
switch (bits) {
170+
case 8:
171+
ArrayType = unsigned ? Uint8Array : Int8Array
172+
break
173+
case 16:
174+
ArrayType = unsigned ? Uint16Array : Int16Array
175+
break
176+
case 32:
177+
ArrayType = unsigned ? Uint32Array : Int32Array
178+
break
179+
}
180+
181+
const pixelDataArrays = slices.map((image) => {
182+
const value = image.metaData.PixelData
183+
return new ArrayType(value.buffer, value.offset)
184+
})
185+
const data = concatenate(ArrayType, pixelDataArrays)
186+
const imageType = {
187+
// TODO: should be based on PhotometricInterpretation instead?
188+
// pixelType: meta.PixelRepresentation,
189+
components: meta.SamplesPerPixel
190+
}
191+
192+
return {
193+
imageType,
194+
origin,
195+
spacing,
196+
direction,
197+
size,
198+
data
199+
}
200+
}
123201
}
124202

125203
class DICOMImage extends DICOMEntity {
@@ -147,6 +225,7 @@ class DICOMImage extends DICOMEntity {
147225
'BitsStored',
148226
'HighBit',
149227
'PixelRepresentation',
228+
'PixelData'
150229
]
151230
}
152231

@@ -228,55 +307,63 @@ async function parseDicomFiles(fileList, ignoreFailedFiles = false) {
228307
return
229308
}
230309

231-
let vr = element.vr
232-
if (vr === undefined) {
233-
if (tagInfo === undefined || tagInfo.vr === undefined) {
234-
console.warn(`${tagName} vr is unknown, skipping`)
310+
let value = undefined
311+
312+
if (tagName === 'PixelData') {
313+
value = {
314+
buffer: dataSet.byteArray.buffer,
315+
offset: element.dataOffset,
316+
length: element.length
317+
}
318+
} else {
319+
let vr = element.vr
320+
if (vr === undefined) {
321+
if (tagInfo === undefined || tagInfo.vr === undefined) {
322+
console.warn(`${tagName} vr is unknown, skipping`)
323+
}
324+
vr = tagInfo.vr
235325
}
236-
vr = tagInfo.vr
237-
}
238326

239-
let value = undefined
240-
switch (vr) {
241-
case 'US':
242-
value = dataSet.uint16(tag)
243-
break
244-
case 'SS':
245-
value = dataSet.int16(tag)
246-
break
247-
case 'UL':
248-
value = dataSet.uint32(tag)
249-
break
250-
case 'US':
251-
value = dataSet.int32(tag)
252-
break
253-
case 'FD':
254-
value = dataSet.double(tag)
255-
break
256-
case 'FL':
257-
value = dataSet.float(tag)
258-
break
259-
case 'AT':
260-
value = `(${dataSet.uint16(tag, 0)},${dataSet.uint16(tag, 1)})`
261-
break
262-
case 'OB':
263-
case 'OW':
264-
case 'UN':
265-
case 'OF':
266-
case 'UT':
267-
// TODO: binary data? is this correct?
268-
if (element.length === 2) {
327+
switch (vr) {
328+
case 'US':
269329
value = dataSet.uint16(tag)
270-
} else if (element.length === 4) {
330+
break
331+
case 'SS':
332+
value = dataSet.int16(tag)
333+
break
334+
case 'UL':
271335
value = dataSet.uint32(tag)
272-
} else {
273-
// don't store binary data, only meta data
274-
return
275-
}
276-
break
277-
default: //string
278-
value = dataSet.string(tag)
279-
break
336+
break
337+
case 'US':
338+
value = dataSet.int32(tag)
339+
break
340+
case 'FD':
341+
value = dataSet.double(tag)
342+
break
343+
case 'FL':
344+
value = dataSet.float(tag)
345+
break
346+
case 'AT':
347+
value = `(${dataSet.uint16(tag, 0)},${dataSet.uint16(tag, 1)})`
348+
break
349+
case 'OB':
350+
case 'OW':
351+
case 'UN':
352+
case 'OF':
353+
case 'UT':
354+
// TODO: binary data? is this correct?
355+
if (element.length === 2) {
356+
value = dataSet.uint16(tag)
357+
} else if (element.length === 4) {
358+
value = dataSet.uint32(tag)
359+
} else {
360+
return
361+
}
362+
break
363+
default: //string
364+
value = dataSet.string(tag)
365+
break
366+
}
280367
}
281368

282369
metaData[tagName] = value

0 commit comments

Comments
 (0)