1 //-----------------------------------------------------------------------------
2 //
3 // ImageLib Sources
4 // Copyright (C) 2000-2009 by Denton Woods
5 // Last modified: 02/14/2009
6 //
7 // Filename: src-IL/src/il_dicom.c
8 //
9 // Description: Reads from a Digital Imaging and Communications in Medicine
10 // (DICOM) file. Specifications can be found at
11 // http://en.wikipedia.org/wiki/Dicom.
12 //
13 //-----------------------------------------------------------------------------
14
15
16 #include "il_internal.h"
17 #ifndef IL_NO_DICOM
18
19 typedef struct DICOMHEAD
20 {
21 ILubyte Signature[4];
22 ILuint Version;
23 ILuint Width;
24 ILuint Height;
25 ILuint Depth;
26 ILuint Samples;
27 ILuint BitsAllocated;
28 ILuint BitsStored;
29 ILuint DataLen;
30 ILboolean BigEndian;
31 ILenum Encoding;
32
33 // For DevIL use only
34 ILenum Format;
35 ILenum Type;
36 } DICOMHEAD;
37
38 ILboolean iIsValidDicom(void);
39 ILboolean iCheckDicom(DICOMHEAD *Header);
40 ILboolean iLoadDicomInternal(void);
41 ILboolean iGetDicomHead(DICOMHEAD *Header);
42 ILboolean SkipElement(DICOMHEAD *Header, ILushort GroupNum, ILushort ElementNum);
43 ILboolean GetNumericValue(DICOMHEAD *Header, ILushort GroupNum, ILuint *Number);
44 ILboolean GetUID(ILubyte *UID);
45 ILuint GetGroupNum(DICOMHEAD *Header);
46 ILuint GetShort(DICOMHEAD *Header, ILushort GroupNum);
47 ILuint GetInt(DICOMHEAD *Header, ILushort GroupNum);
48 ILfloat GetFloat(DICOMHEAD *Header, ILushort GroupNum);
49
50 //! Checks if the file specified in FileName is a valid DICOM file.
ilIsValidDicom(ILconst_string FileName)51 ILboolean ilIsValidDicom(ILconst_string FileName)
52 {
53 ILHANDLE DicomFile;
54 ILboolean bDicom = IL_FALSE;
55
56 if (!iCheckExtension(FileName, IL_TEXT("dicom")) && !iCheckExtension(FileName, IL_TEXT("dcm"))) {
57 ilSetError(IL_INVALID_EXTENSION);
58 return bDicom;
59 }
60
61 DicomFile = iopenr(FileName);
62 if (DicomFile == NULL) {
63 ilSetError(IL_COULD_NOT_OPEN_FILE);
64 return bDicom;
65 }
66
67 bDicom = ilIsValidDicomF(DicomFile);
68 icloser(DicomFile);
69
70 return bDicom;
71 }
72
73
74 //! Checks if the ILHANDLE contains a valid DICOM file at the current position.
ilIsValidDicomF(ILHANDLE File)75 ILboolean ilIsValidDicomF(ILHANDLE File)
76 {
77 ILuint FirstPos;
78 ILboolean bRet;
79
80 iSetInputFile(File);
81 FirstPos = itell();
82 bRet = iIsValidDicom();
83 iseek(FirstPos, IL_SEEK_SET);
84
85 return bRet;
86 }
87
88
89 //! Checks if Lump is a valid DICOM lump.
ilIsValidDicomL(const void * Lump,ILuint Size)90 ILboolean ilIsValidDicomL(const void *Lump, ILuint Size)
91 {
92 iSetInputLump(Lump, Size);
93 return iIsValidDicom();
94 }
95
96
97 // Internal function to get the header and check it.
iIsValidDicom(void)98 ILboolean iIsValidDicom(void)
99 {
100 DICOMHEAD Header;
101 ILuint Pos = itell();
102
103 // Clear the header to all 0s to make checks later easier.
104 memset(&Header, 0, sizeof(DICOMHEAD));
105 if (!iGetDicomHead(&Header))
106 return IL_FALSE;
107 // The length of the header varies, so we just go back to the original position.
108 iseek(Pos, IL_SEEK_CUR);
109
110 return iCheckDicom(&Header);
111 }
112
113
114 // Internal function used to get the DICOM header from the current file.
iGetDicomHead(DICOMHEAD * Header)115 ILboolean iGetDicomHead(DICOMHEAD *Header)
116 {
117 ILushort GroupNum, ElementNum;
118 ILboolean ReachedData = IL_FALSE;
119 ILubyte Var2, UID[65];
120
121 // Signature should be "DICM" at position 128.
122 iseek(128, IL_SEEK_SET);
123 if (iread(Header->Signature, 1, 4) != 4)
124 return IL_FALSE;
125
126 //@TODO: What about the case when we are reading an image with Big Endian data?
127
128 do {
129 GroupNum = GetGroupNum(Header);
130 ElementNum = GetShort(Header, GroupNum);;
131
132 switch (GroupNum)
133 {
134 case 0x02:
135 switch (ElementNum)
136 {
137 /*case 0x01: // Version number
138 if (!GetNumericValue(&Header->Version))
139 return IL_FALSE;
140 if (Header->Version != 0x0100)
141 return IL_FALSE;
142 break;*/
143
144 case 0x10:
145 //@TODO: Look at pg. 60 of 07_05pu.pdf (PS 3.5) for more UIDs.
146 if (!GetUID(UID))
147 return IL_FALSE;
148 if (!strncmp(UID, "1.2.840.10008.1.2.2", 64)) // Explicit big endian
149 Header->BigEndian = IL_TRUE;
150 else if (!strncmp(UID, "1.2.840.10008.1.2.1", 64)) // Explicit little endian
151 Header->BigEndian = IL_FALSE;
152 else if (!strncmp(UID, "1.2.840.10008.1.2", 64)) // Implicit little endian
153 Header->BigEndian = IL_FALSE;
154 else
155 return IL_FALSE; // Unrecognized UID.
156 break;
157
158 default:
159 if (!SkipElement(Header, GroupNum, ElementNum)) // We do not understand this entry, so we just skip it.
160 return IL_FALSE;
161 }
162 break;
163
164 case 0x28:
165 switch (ElementNum)
166 {
167 case 0x02: // Samples per pixel
168 if (!GetNumericValue(Header, GroupNum, &Header->Samples))
169 return IL_FALSE;
170 break;
171
172 case 0x08: // Number of frames, or depth
173 if (!GetNumericValue(Header, GroupNum, &Header->Depth))
174 return IL_FALSE;
175 break;
176
177 case 0x10: // The number of rows
178 if (!GetNumericValue(Header, GroupNum, &Header->Height))
179 return IL_FALSE;
180 break;
181
182 case 0x11: // The number of columns
183 if (!GetNumericValue(Header, GroupNum, &Header->Width))
184 return IL_FALSE;
185 break;
186
187 case 0x100: // Bits allocated per sample
188 if (!GetNumericValue(Header, GroupNum, &Header->BitsAllocated))
189 return IL_FALSE;
190 break;
191
192 case 0x101: // Bits stored per sample - Do we really need this information?
193 if (!GetNumericValue(Header, GroupNum, &Header->BitsStored))
194 return IL_FALSE;
195 break;
196
197 default:
198 if (!SkipElement(Header, GroupNum, ElementNum)) // We do not understand this entry, so we just skip it.
199 return IL_FALSE;
200 }
201 break;
202
203 case 0x7FE0:
204 switch (ElementNum)
205 {
206 case 0x10: // This element is the actual pixel data. We are done with the header here.
207 if (igetc() != 'O') // @TODO: Can we assume that this is always 'O'?
208 return IL_FALSE;
209 Var2 = igetc();
210 if (Var2 != 'B' && Var2 != 'W' && Var2 != 'F') // 'OB', 'OW' and 'OF' accepted for this element.
211 return IL_FALSE;
212 GetLittleUShort(); // Skip the 2 reserved bytes.
213 Header->DataLen = GetInt(Header, GroupNum);//GetLittleUInt();
214 ReachedData = IL_TRUE;
215 break;
216 default:
217 if (!SkipElement(Header, GroupNum, ElementNum)) // We do not understand this entry, so we just skip it.
218 return IL_FALSE;
219 }
220 break;
221
222 default:
223 if (!SkipElement(Header, GroupNum, ElementNum)) // We do not understand this entry, so we just skip it.
224 return IL_FALSE;
225 }
226 } while (!ieof() && !ReachedData);
227
228 if (ieof())
229 return IL_FALSE;
230
231 // Some DICOM images do not have the depth (number of frames) field.
232 if (Header->Depth == 0)
233 Header->Depth = 1;
234
235 switch (Header->BitsAllocated)
236 {
237 case 8:
238 Header->Type = IL_UNSIGNED_BYTE;
239 break;
240 case 16:
241 Header->Type = IL_UNSIGNED_SHORT;
242 break;
243 case 32:
244 Header->Type = IL_FLOAT; //@TODO: Is this ever an integer?
245 break;
246 default: //@TODO: Any other types we can deal with?
247 return IL_FALSE;
248 }
249
250 // Cannot handle more than 4 channels in an image.
251 if (Header->Samples > 4)
252 return IL_FALSE;
253 Header->Format = ilGetFormatBpp(Header->Samples);
254
255 return IL_TRUE;
256 }
257
258
SkipElement(DICOMHEAD * Header,ILushort GroupNum,ILushort ElementNum)259 ILboolean SkipElement(DICOMHEAD *Header, ILushort GroupNum, ILushort ElementNum)
260 {
261 ILubyte VR1, VR2;
262 ILuint ValLen;
263
264 // 2 byte character string telling what type this element is ('OB', 'UI', etc.)
265 VR1 = igetc();
266 VR2 = igetc();
267
268 if ((VR1 == 'O' && VR2 == 'B') || (VR1 == 'O' && VR2 == 'W') || (VR1 == 'O' && VR2 == 'F') ||
269 (VR1 == 'S' && VR2 == 'Q') || (VR1 == 'U' && VR2 == 'T') || (VR1 == 'U' && VR2 == 'N')) {
270 // These all have a different format than the other formats, since they can be up to 32 bits long.
271 GetLittleUShort(); // Values reserved, we do not care about them.
272 ValLen = GetInt(Header, GroupNum);//GetLittleUInt(); // Length of the rest of the element
273 if (ValLen % 2) // This length must be even, according to the specs.
274 return IL_FALSE;
275 if (ElementNum != 0x00) // Element numbers of 0 tell the size of the full group, so we do not skip this.
276 // @TODO: We could use this to skip groups that we do not care about.
277 if (iseek(ValLen, IL_SEEK_CUR))
278 return IL_FALSE;
279 }
280 else {
281 // These have a length of 16 bits.
282 ValLen = GetShort(Header, GroupNum);//GetLittleUShort();
283 //if (ValLen % 2) // This length must be even, according to the specs.
284 // ValLen++; // Add the extra byte to seek.
285 //if (ElementNum != 0x00) // Element numbers of 0 tell the size of the full group, so we do not skip this.
286 // @TODO: We could use this to skip groups that we do not care about.
287 if (iseek(ValLen, IL_SEEK_CUR))
288 return IL_FALSE;
289 }
290
291 return IL_TRUE;
292 }
293
294
GetGroupNum(DICOMHEAD * Header)295 ILuint GetGroupNum(DICOMHEAD *Header)
296 {
297 ILushort GroupNum;
298
299 iread(&GroupNum, 1, 2);
300 // The 0x02 group is always little endian.
301 if (GroupNum == 0x02) {
302 UShort(&GroupNum);
303 return GroupNum;
304 }
305 // Now we have to swizzle it if it is not 0x02.
306 if (Header->BigEndian)
307 BigUShort(&GroupNum);
308 else
309 UShort(&GroupNum);
310
311 return GroupNum;
312 }
313
314
GetShort(DICOMHEAD * Header,ILushort GroupNum)315 ILuint GetShort(DICOMHEAD *Header, ILushort GroupNum)
316 {
317 ILushort Num;
318
319 iread(&Num, 1, 2);
320 // The 0x02 group is always little endian.
321 if (GroupNum == 0x02) {
322 UShort(&Num);
323 return Num;
324 }
325 // Now we have to swizzle it if it is not 0x02.
326 if (Header->BigEndian)
327 BigUShort(&Num);
328 else
329 UShort(&Num);
330
331 return Num;
332 }
333
334
GetInt(DICOMHEAD * Header,ILushort GroupNum)335 ILuint GetInt(DICOMHEAD *Header, ILushort GroupNum)
336 {
337 ILuint Num;
338
339 iread(&Num, 1, 4);
340 // The 0x02 group is always little endian.
341 if (GroupNum == 0x02) {
342 UInt(&Num);
343 return Num;
344 }
345 // Now we have to swizzle it if it is not 0x02.
346 if (Header->BigEndian)
347 BigUInt(&Num);
348 else
349 UInt(&Num);
350
351 return Num;
352 }
353
354
GetFloat(DICOMHEAD * Header,ILushort GroupNum)355 ILfloat GetFloat(DICOMHEAD *Header, ILushort GroupNum)
356 {
357 ILfloat Num;
358
359 iread(&Num, 1, 4);
360 // The 0x02 group is always little endian.
361 if (GroupNum == 0x02) {
362 Float(&Num);
363 return Num;
364 }
365 // Now we have to swizzle it if it is not 0x02.
366 if (Header->BigEndian)
367 BigFloat(&Num);
368 else
369 Float(&Num);
370
371 return Num;
372 }
373
374
GetNumericValue(DICOMHEAD * Header,ILushort GroupNum,ILuint * Number)375 ILboolean GetNumericValue(DICOMHEAD *Header, ILushort GroupNum, ILuint *Number)
376 {
377 ILubyte VR1, VR2;
378 ILushort ValLen;
379
380 // 2 byte character string telling what type this element is ('OB', 'UI', etc.)
381 VR1 = igetc();
382 VR2 = igetc();
383
384 if (VR1 == 'U' && VR2 == 'S') { // Unsigned short
385 ValLen = GetShort(Header, GroupNum);//GetLittleUShort();
386 if (ValLen != 2) // Must always be 2 for short ('US')
387 return IL_FALSE;
388 *((ILushort*)Number) = GetShort(Header, GroupNum);//GetLittleUShort();
389 return IL_TRUE;
390 }
391 if (VR1 == 'U' && VR2 == 'L') { // Unsigned long
392 ValLen = GetInt(Header, GroupNum);//GetLittleUInt();
393 if (ValLen != 4) // Must always be 4 for long ('UL')
394 return IL_FALSE;
395 *Number = GetInt(Header, GroupNum);
396 return IL_TRUE;
397 }
398 if (VR1 == 'S' && VR2 == 'S') { // Signed short
399 ValLen = GetShort(Header, GroupNum);
400 if (ValLen != 2) // Must always be 2 for short ('US')
401 return IL_FALSE;
402 *((ILshort*)Number) = GetShort(Header, GroupNum);
403 return IL_TRUE;
404 }
405 if (VR1 == 'S' && VR2 == 'L') { // Signed long
406 ValLen = GetInt(Header, GroupNum);
407 if (ValLen != 4) // Must always be 4 for long ('UL')
408 return IL_FALSE;
409 *((ILint*)Number) = GetInt(Header, GroupNum);
410 return IL_TRUE;
411 }
412
413 return IL_FALSE;
414 }
415
416
GetUID(ILubyte * UID)417 ILboolean GetUID(ILubyte *UID)
418 {
419 ILubyte VR1, VR2;
420 ILushort ValLen;
421
422 // 2 byte character string telling what type this element is ('OB', 'UI', etc.)
423 VR1 = igetc();
424 VR2 = igetc();
425
426 if (VR1 != 'U' || VR2 != 'I') // 'UI' == UID
427 return IL_FALSE;
428
429 ValLen = GetLittleUShort();
430 if (iread(UID, ValLen, 1) != 1)
431 return IL_FALSE;
432 UID[64] = 0; // Just to make sure that our string is terminated.
433
434 return IL_TRUE;
435 }
436
437 // Internal function used to check if the HEADER is a valid DICOM header.
iCheckDicom(DICOMHEAD * Header)438 ILboolean iCheckDicom(DICOMHEAD *Header)
439 {
440 // Always has the signature "DICM" at position 0x80.
441 if (strncmp(Header->Signature, "DICM", 4))
442 return IL_FALSE;
443 // Does not make sense to have any dimension = 0.
444 if (Header->Width == 0 || Header->Height == 0 || Header->Depth == 0)
445 return IL_FALSE;
446 // We can only deal with images that have byte-aligned data.
447 //@TODO: Take care of any others?
448 if (Header->BitsAllocated % 8)
449 return IL_FALSE;
450 // Check for an invalid format being set (or not set at all).
451 if (ilGetBppFormat(Header->Format) == 0)
452 return IL_FALSE;
453 // Check for an invalid type being set (or not set at all).
454 if (ilGetBpcType(Header->Type) == 0)
455 return IL_FALSE;
456 return IL_TRUE;
457 }
458
459
460 //! Reads a DICOM file
ilLoadDicom(ILconst_string FileName)461 ILboolean ilLoadDicom(ILconst_string FileName)
462 {
463 ILHANDLE DicomFile;
464 ILboolean bDicom = IL_FALSE;
465
466 DicomFile = iopenr(FileName);
467 if (DicomFile == NULL) {
468 ilSetError(IL_COULD_NOT_OPEN_FILE);
469 return bDicom;
470 }
471
472 bDicom = ilLoadDicomF(DicomFile);
473 icloser(DicomFile);
474
475 return bDicom;
476 }
477
478
479 //! Reads an already-opened DICOM file
ilLoadDicomF(ILHANDLE File)480 ILboolean ilLoadDicomF(ILHANDLE File)
481 {
482 ILuint FirstPos;
483 ILboolean bRet;
484
485 iSetInputFile(File);
486 FirstPos = itell();
487 bRet = iLoadDicomInternal();
488 iseek(FirstPos, IL_SEEK_SET);
489
490 return bRet;
491 }
492
493
494 //! Reads from a memory "lump" that contains a DICOM
ilLoadDicomL(const void * Lump,ILuint Size)495 ILboolean ilLoadDicomL(const void *Lump, ILuint Size)
496 {
497 iSetInputLump(Lump, Size);
498 return iLoadDicomInternal();
499 }
500
501
502 // Internal function used to load the DICOM.
iLoadDicomInternal(void)503 ILboolean iLoadDicomInternal(void)
504 {
505 DICOMHEAD Header;
506 ILuint i;
507 ILushort TempS, *ShortPtr;
508 ILfloat TempF, *FloatPtr;
509 ILboolean Swizzle = IL_FALSE;
510
511 if (iCurImage == NULL) {
512 ilSetError(IL_ILLEGAL_OPERATION);
513 return IL_FALSE;
514 }
515
516 // Clear the header to all 0s to make checks later easier.
517 memset(&Header, 0, sizeof(DICOMHEAD));
518 if (!iGetDicomHead(&Header)) {
519 ilSetError(IL_INVALID_FILE_HEADER);
520 return IL_FALSE;
521 }
522 if (!iCheckDicom(&Header))
523 return IL_FALSE;
524
525 if (!ilTexImage(Header.Width, Header.Height, Header.Depth, ilGetBppFormat(Header.Format), Header.Format, Header.Type, NULL))
526 return IL_FALSE;
527 //@TODO: Find out if the origin is always in the upper left.
528 iCurImage->Origin = IL_ORIGIN_UPPER_LEFT;
529 // Header.DataLen may be larger than SizeOfData, since it has to be padded with a NULL if it is not an even length,
530 // so we just test to make sure it is at least large enough.
531 //@TODO: Do this check before ilTexImage call.
532 if (Header.DataLen < iCurImage->SizeOfData) {
533 ilSetError(IL_INVALID_FILE_HEADER);
534 return IL_FALSE;
535 }
536
537 // We may have to swap the order of the data.
538 #if BYTE_ORDER == BIG_ENDIAN
539 if (!Header.BigEndian) {
540 if (Header.Format == IL_RGB)
541 Header.Format = IL_BGR;
542 else if (Header.Format == IL_RGBA)
543 Swizzle = IL_TRUE;
544 }
545 #else // Little endian
546 if (Header.BigEndian) {
547 if (Header.Format == IL_RGB)
548 Header.Format = IL_BGR;
549 if (Header.Format == IL_RGBA)
550 Swizzle = IL_TRUE;
551 }
552 #endif
553
554 switch (Header.Type)
555 {
556 case IL_UNSIGNED_BYTE:
557 if (iread(iCurImage->Data, iCurImage->SizeOfData, 1) != 1)
558 return IL_FALSE;
559
560 // Swizzle the data from ABGR to RGBA.
561 if (Swizzle) {
562 for (i = 0; i < iCurImage->SizeOfData; i += 4) {
563 iSwapUInt((ILuint*)(iCurImage->Data + i));
564 }
565 }
566 break;
567
568 case IL_UNSIGNED_SHORT:
569 for (i = 0; i < iCurImage->SizeOfData; i += 2) {
570 *((ILushort*)(iCurImage->Data + i)) = GetShort(&Header, 0);//GetLittleUShort();
571 }
572
573 // Swizzle the data from ABGR to RGBA.
574 if (Swizzle) {
575 ShortPtr = (ILushort*)iCurImage->Data;
576 for (i = 0; i < iCurImage->SizeOfData / 2; i += 4) {
577 TempS = ShortPtr[i];
578 ShortPtr[i] = ShortPtr[i+3];
579 ShortPtr[i+3] = TempS;
580 }
581 }
582 break;
583
584 case IL_FLOAT:
585 for (i = 0; i < iCurImage->SizeOfData; i += 4) {
586 *((ILfloat*)(iCurImage->Data + i)) = GetFloat(&Header, 0);//GetLittleFloat();
587 }
588
589 // Swizzle the data from ABGR to RGBA.
590 if (Swizzle) {
591 FloatPtr = (ILfloat*)iCurImage->Data;
592 for (i = 0; i < iCurImage->SizeOfData / 4; i += 4) {
593 TempF = FloatPtr[i];
594 FloatPtr[i] = FloatPtr[i+3];
595 FloatPtr[i+3] = TempF;
596 }
597 }
598 break;
599 }
600
601 return ilFixImage();
602 }
603
604
605
606 #endif//IL_NO_DICOM
607