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