Skip to content

Commit 972ce66

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 972ce66

File tree

2 files changed

+184
-45
lines changed

2 files changed

+184
-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

+175-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 = {}
@@ -110,6 +122,7 @@ class DICOMSeries extends DICOMEntity {
110122
this.images = {}
111123
this.extractTags(metaData)
112124
this.addMetaData(metaData, file)
125+
this.constructedImageData = 0
113126
}
114127

115128
addMetaData(metaData, file) {
@@ -120,6 +133,112 @@ class DICOMSeries extends DICOMEntity {
120133
}
121134
this.images[imageNumber] = new DICOMImage(metaData, file)
122135
}
136+
137+
getImageData() {
138+
if (this.constructedImageData === 1) {
139+
console.warn(
140+
'DICOMSeries.getImageData was called more than once. ' +
141+
'Since DICOMSeries does not store the image data ' +
142+
'(to save memory while keeping access to its metadata), ' +
143+
'getImageData recomputes the image data each time it is called. ' +
144+
'Users of this API should cache the resulting image data ' +
145+
'if they need to access it again to improve performance.'
146+
)
147+
}
148+
149+
function numArrayFromString(str, separator = '\\') {
150+
const strArray = str.split(separator)
151+
return strArray.map(Number)
152+
}
153+
154+
const slices = Object.values(this.images)
155+
const meta = slices[0].metaData
156+
157+
// Origin
158+
const origin = numArrayFromString(meta.ImagePositionPatient)
159+
160+
// Spacing
161+
const spacing = numArrayFromString(meta.PixelSpacing)
162+
spacing.push(Number(meta.SliceThickness))
163+
164+
// Dimensions
165+
const size = [
166+
meta.Rows,
167+
meta.Columns,
168+
Object.keys(this.images).length
169+
]
170+
171+
// Direction matrix (3x3)
172+
const directionCosines = numArrayFromString(meta.ImageOrientationPatient)
173+
const iDirCos = directionCosines.slice(0,3)
174+
const jDirCos = directionCosines.slice(3,6)
175+
const kDirCos = [
176+
iDirCos[1]*jDirCos[2] - iDirCos[2]*jDirCos[1],
177+
iDirCos[2]*jDirCos[0] - iDirCos[0]*jDirCos[2],
178+
iDirCos[0]*jDirCos[1] - iDirCos[1]*jDirCos[0],
179+
]
180+
const direction = [
181+
iDirCos[0], jDirCos[0], kDirCos[0],
182+
iDirCos[1], jDirCos[1], kDirCos[1],
183+
iDirCos[2], jDirCos[2], kDirCos[2],
184+
]
185+
186+
// Image info
187+
const imageType = {
188+
// TODO: should be based on PhotometricInterpretation instead?
189+
// pixelType: meta.PixelRepresentation,
190+
components: meta.SamplesPerPixel
191+
}
192+
193+
// Pixel data type
194+
const unsigned = (meta.PixelRepresentation === 0)
195+
const bits = meta.BitsAllocated // TODO: or stored?
196+
let ArrayType
197+
switch (bits) {
198+
case 8:
199+
ArrayType = unsigned ? Uint8Array : Int8Array
200+
break
201+
case 16:
202+
ArrayType = unsigned ? Uint16Array : Int16Array
203+
break
204+
case 32:
205+
ArrayType = unsigned ? Uint32Array : Int32Array
206+
break
207+
default:
208+
throw Error(`Unknown pixel bit type (${bits})`)
209+
}
210+
211+
// Pixel data
212+
const pixelDataArrays = slices.map((image) => {
213+
const value = image.metaData.PixelData
214+
return new ArrayType(value.buffer, value.offset)
215+
})
216+
let data = concatenate(ArrayType, pixelDataArrays)
217+
218+
// Rescale
219+
// TODO: ArrayType can change sign with this
220+
const b = Number(meta.RescaleIntercept)
221+
const m = Number(meta.RescaleSlope)
222+
const hasIntercept = !isNaN(b) && b !== 0
223+
const hasSlope = !isNaN(m) && m !== 1
224+
if (hasIntercept && hasSlope) {
225+
data = data.map((SV) => m * SV + b)
226+
} else if (hasIntercept) {
227+
data = data.map((SV) => SV + b)
228+
} else if (hasSlope) {
229+
data = data.map((SV) => m * SV)
230+
}
231+
232+
this.constructedImageData = true
233+
return {
234+
imageType,
235+
origin,
236+
spacing,
237+
direction,
238+
size,
239+
data
240+
}
241+
}
123242
}
124243

125244
class DICOMImage extends DICOMEntity {
@@ -147,6 +266,9 @@ class DICOMImage extends DICOMEntity {
147266
'BitsStored',
148267
'HighBit',
149268
'PixelRepresentation',
269+
'PixelData',
270+
'RescaleIntercept',
271+
'RescaleSlope',
150272
]
151273
}
152274

@@ -228,55 +350,63 @@ async function parseDicomFiles(fileList, ignoreFailedFiles = false) {
228350
return
229351
}
230352

231-
let vr = element.vr
232-
if (vr === undefined) {
233-
if (tagInfo === undefined || tagInfo.vr === undefined) {
234-
console.warn(`${tagName} vr is unknown, skipping`)
353+
let value = undefined
354+
355+
if (tagName === 'PixelData') {
356+
value = {
357+
buffer: dataSet.byteArray.buffer,
358+
offset: element.dataOffset,
359+
length: element.length
360+
}
361+
} else {
362+
let vr = element.vr
363+
if (vr === undefined) {
364+
if (tagInfo === undefined || tagInfo.vr === undefined) {
365+
console.warn(`${tagName} vr is unknown, skipping`)
366+
}
367+
vr = tagInfo.vr
235368
}
236-
vr = tagInfo.vr
237-
}
238369

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) {
370+
switch (vr) {
371+
case 'US':
269372
value = dataSet.uint16(tag)
270-
} else if (element.length === 4) {
373+
break
374+
case 'SS':
375+
value = dataSet.int16(tag)
376+
break
377+
case 'UL':
271378
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
379+
break
380+
case 'US':
381+
value = dataSet.int32(tag)
382+
break
383+
case 'FD':
384+
value = dataSet.double(tag)
385+
break
386+
case 'FL':
387+
value = dataSet.float(tag)
388+
break
389+
case 'AT':
390+
value = `(${dataSet.uint16(tag, 0)},${dataSet.uint16(tag, 1)})`
391+
break
392+
case 'OB':
393+
case 'OW':
394+
case 'UN':
395+
case 'OF':
396+
case 'UT':
397+
// TODO: binary data? is this correct?
398+
if (element.length === 2) {
399+
value = dataSet.uint16(tag)
400+
} else if (element.length === 4) {
401+
value = dataSet.uint32(tag)
402+
} else {
403+
return
404+
}
405+
break
406+
default: //string
407+
value = dataSet.string(tag)
408+
break
409+
}
280410
}
281411

282412
metaData[tagName] = value

0 commit comments

Comments
 (0)