summaryrefslogtreecommitdiff
path: root/DevIL/src-IL/src/il_dicom.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'DevIL/src-IL/src/il_dicom.cpp')
-rw-r--r--DevIL/src-IL/src/il_dicom.cpp608
1 files changed, 608 insertions, 0 deletions
diff --git a/DevIL/src-IL/src/il_dicom.cpp b/DevIL/src-IL/src/il_dicom.cpp
new file mode 100644
index 00000000..9fc087de
--- /dev/null
+++ b/DevIL/src-IL/src/il_dicom.cpp
@@ -0,0 +1,608 @@
+//-----------------------------------------------------------------------------
+//
+// ImageLib Sources
+// Copyright (C) 2000-2009 by Denton Woods
+// Last modified: 02/14/2009
+//
+// Filename: src-IL/src/il_dicom.c
+//
+// Description: Reads from a Digital Imaging and Communications in Medicine
+// (DICOM) file. Specifications can be found at
+// http://en.wikipedia.org/wiki/Dicom.
+//
+//-----------------------------------------------------------------------------
+
+
+#include "il_internal.h"
+#ifndef IL_NO_DICOM
+
+typedef struct DICOMHEAD
+{
+ ILubyte Signature[4];
+ ILuint Version;
+ ILuint Width;
+ ILuint Height;
+ ILuint Depth;
+ ILuint Samples;
+ ILuint BitsAllocated;
+ ILuint BitsStored;
+ ILuint DataLen;
+ ILboolean BigEndian;
+ ILenum Encoding;
+
+ // For DevIL use only
+ ILenum Format;
+ ILenum Type;
+} DICOMHEAD;
+
+ILboolean iIsValidDicom(void);
+ILboolean iCheckDicom(DICOMHEAD *Header);
+ILboolean iLoadDicomInternal(void);
+ILboolean iGetDicomHead(DICOMHEAD *Header);
+ILboolean SkipElement(DICOMHEAD *Header, ILushort GroupNum, ILushort ElementNum);
+ILboolean GetNumericValue(DICOMHEAD *Header, ILushort GroupNum, ILuint *Number);
+ILboolean GetUID(ILubyte *UID);
+ILuint GetGroupNum(DICOMHEAD *Header);
+ILuint GetShort(DICOMHEAD *Header, ILushort GroupNum);
+ILuint GetInt(DICOMHEAD *Header, ILushort GroupNum);
+ILfloat GetFloat(DICOMHEAD *Header, ILushort GroupNum);
+
+//! Checks if the file specified in FileName is a valid DICOM file.
+ILboolean ilIsValidDicom(ILconst_string FileName)
+{
+ ILHANDLE DicomFile;
+ ILboolean bDicom = IL_FALSE;
+
+ if (!iCheckExtension(FileName, IL_TEXT("dicom")) && !iCheckExtension(FileName, IL_TEXT("dcm"))) {
+ ilSetError(IL_INVALID_EXTENSION);
+ return bDicom;
+ }
+
+ DicomFile = iopenr(FileName);
+ if (DicomFile == NULL) {
+ ilSetError(IL_COULD_NOT_OPEN_FILE);
+ return bDicom;
+ }
+
+ bDicom = ilIsValidDicomF(DicomFile);
+ icloser(DicomFile);
+
+ return bDicom;
+}
+
+
+//! Checks if the ILHANDLE contains a valid DICOM file at the current position.
+ILboolean ilIsValidDicomF(ILHANDLE File)
+{
+ ILuint FirstPos;
+ ILboolean bRet;
+
+ iSetInputFile(File);
+ FirstPos = itell();
+ bRet = iIsValidDicom();
+ iseek(FirstPos, IL_SEEK_SET);
+
+ return bRet;
+}
+
+
+//! Checks if Lump is a valid DICOM lump.
+ILboolean ilIsValidDicomL(const void *Lump, ILuint Size)
+{
+ iSetInputLump(Lump, Size);
+ return iIsValidDicom();
+}
+
+
+// Internal function to get the header and check it.
+ILboolean iIsValidDicom(void)
+{
+ DICOMHEAD Header;
+ ILuint Pos = itell();
+
+ // Clear the header to all 0s to make checks later easier.
+ memset(&Header, 0, sizeof(DICOMHEAD));
+ if (!iGetDicomHead(&Header))
+ return IL_FALSE;
+ // The length of the header varies, so we just go back to the original position.
+ iseek(Pos, IL_SEEK_CUR);
+
+ return iCheckDicom(&Header);
+}
+
+
+// Internal function used to get the DICOM header from the current file.
+ILboolean iGetDicomHead(DICOMHEAD *Header)
+{
+ ILushort GroupNum, ElementNum;
+ ILboolean ReachedData = IL_FALSE;
+ ILubyte Var2, UID[65];
+
+ // Signature should be "DICM" at position 128.
+ iseek(128, IL_SEEK_SET);
+ if (iread(Header->Signature, 1, 4) != 4)
+ return IL_FALSE;
+
+//@TODO: What about the case when we are reading an image with Big Endian data?
+
+ do {
+ GroupNum = GetGroupNum(Header);
+ ElementNum = GetShort(Header, GroupNum);;
+
+ switch (GroupNum)
+ {
+ case 0x02:
+ switch (ElementNum)
+ {
+ /*case 0x01: // Version number
+ if (!GetNumericValue(&Header->Version))
+ return IL_FALSE;
+ if (Header->Version != 0x0100)
+ return IL_FALSE;
+ break;*/
+
+ case 0x10:
+ //@TODO: Look at pg. 60 of 07_05pu.pdf (PS 3.5) for more UIDs.
+ if (!GetUID(UID))
+ return IL_FALSE;
+ if (!strncmp(UID, "1.2.840.10008.1.2.2", 64)) // Explicit big endian
+ Header->BigEndian = IL_TRUE;
+ else if (!strncmp(UID, "1.2.840.10008.1.2.1", 64)) // Explicit little endian
+ Header->BigEndian = IL_FALSE;
+ else if (!strncmp(UID, "1.2.840.10008.1.2", 64)) // Implicit little endian
+ Header->BigEndian = IL_FALSE;
+ else
+ return IL_FALSE; // Unrecognized UID.
+ break;
+
+ default:
+ if (!SkipElement(Header, GroupNum, ElementNum)) // We do not understand this entry, so we just skip it.
+ return IL_FALSE;
+ }
+ break;
+
+ case 0x28:
+ switch (ElementNum)
+ {
+ case 0x02: // Samples per pixel
+ if (!GetNumericValue(Header, GroupNum, &Header->Samples))
+ return IL_FALSE;
+ break;
+
+ case 0x08: // Number of frames, or depth
+ if (!GetNumericValue(Header, GroupNum, &Header->Depth))
+ return IL_FALSE;
+ break;
+
+ case 0x10: // The number of rows
+ if (!GetNumericValue(Header, GroupNum, &Header->Height))
+ return IL_FALSE;
+ break;
+
+ case 0x11: // The number of columns
+ if (!GetNumericValue(Header, GroupNum, &Header->Width))
+ return IL_FALSE;
+ break;
+
+ case 0x100: // Bits allocated per sample
+ if (!GetNumericValue(Header, GroupNum, &Header->BitsAllocated))
+ return IL_FALSE;
+ break;
+
+ case 0x101: // Bits stored per sample - Do we really need this information?
+ if (!GetNumericValue(Header, GroupNum, &Header->BitsStored))
+ return IL_FALSE;
+ break;
+
+ default:
+ if (!SkipElement(Header, GroupNum, ElementNum)) // We do not understand this entry, so we just skip it.
+ return IL_FALSE;
+ }
+ break;
+
+ case 0x7FE0:
+ switch (ElementNum)
+ {
+ case 0x10: // This element is the actual pixel data. We are done with the header here.
+ if (igetc() != 'O') // @TODO: Can we assume that this is always 'O'?
+ return IL_FALSE;
+ Var2 = igetc();
+ if (Var2 != 'B' && Var2 != 'W' && Var2 != 'F') // 'OB', 'OW' and 'OF' accepted for this element.
+ return IL_FALSE;
+ GetLittleUShort(); // Skip the 2 reserved bytes.
+ Header->DataLen = GetInt(Header, GroupNum);//GetLittleUInt();
+ ReachedData = IL_TRUE;
+ break;
+ default:
+ if (!SkipElement(Header, GroupNum, ElementNum)) // We do not understand this entry, so we just skip it.
+ return IL_FALSE;
+ }
+ break;
+
+ default:
+ if (!SkipElement(Header, GroupNum, ElementNum)) // We do not understand this entry, so we just skip it.
+ return IL_FALSE;
+ }
+ } while (!ieof() && !ReachedData);
+
+ if (ieof())
+ return IL_FALSE;
+
+ // Some DICOM images do not have the depth (number of frames) field.
+ if (Header->Depth == 0)
+ Header->Depth = 1;
+
+ switch (Header->BitsAllocated)
+ {
+ case 8:
+ Header->Type = IL_UNSIGNED_BYTE;
+ break;
+ case 16:
+ Header->Type = IL_UNSIGNED_SHORT;
+ break;
+ case 32:
+ Header->Type = IL_FLOAT; //@TODO: Is this ever an integer?
+ break;
+ default: //@TODO: Any other types we can deal with?
+ return IL_FALSE;
+ }
+
+ // Cannot handle more than 4 channels in an image.
+ if (Header->Samples > 4)
+ return IL_FALSE;
+ Header->Format = ilGetFormatBpp(Header->Samples);
+
+ return IL_TRUE;
+}
+
+
+ILboolean SkipElement(DICOMHEAD *Header, ILushort GroupNum, ILushort ElementNum)
+{
+ ILubyte VR1, VR2;
+ ILuint ValLen;
+
+ // 2 byte character string telling what type this element is ('OB', 'UI', etc.)
+ VR1 = igetc();
+ VR2 = igetc();
+
+ if ((VR1 == 'O' && VR2 == 'B') || (VR1 == 'O' && VR2 == 'W') || (VR1 == 'O' && VR2 == 'F') ||
+ (VR1 == 'S' && VR2 == 'Q') || (VR1 == 'U' && VR2 == 'T') || (VR1 == 'U' && VR2 == 'N')) {
+ // These all have a different format than the other formats, since they can be up to 32 bits long.
+ GetLittleUShort(); // Values reserved, we do not care about them.
+ ValLen = GetInt(Header, GroupNum);//GetLittleUInt(); // Length of the rest of the element
+ if (ValLen % 2) // This length must be even, according to the specs.
+ return IL_FALSE;
+ if (ElementNum != 0x00) // Element numbers of 0 tell the size of the full group, so we do not skip this.
+ // @TODO: We could use this to skip groups that we do not care about.
+ if (iseek(ValLen, IL_SEEK_CUR))
+ return IL_FALSE;
+ }
+ else {
+ // These have a length of 16 bits.
+ ValLen = GetShort(Header, GroupNum);//GetLittleUShort();
+ //if (ValLen % 2) // This length must be even, according to the specs.
+ // ValLen++; // Add the extra byte to seek.
+ //if (ElementNum != 0x00) // Element numbers of 0 tell the size of the full group, so we do not skip this.
+ // @TODO: We could use this to skip groups that we do not care about.
+ if (iseek(ValLen, IL_SEEK_CUR))
+ return IL_FALSE;
+ }
+
+ return IL_TRUE;
+}
+
+
+ILuint GetGroupNum(DICOMHEAD *Header)
+{
+ ILushort GroupNum;
+
+ iread(&GroupNum, 1, 2);
+ // The 0x02 group is always little endian.
+ if (GroupNum == 0x02) {
+ UShort(&GroupNum);
+ return GroupNum;
+ }
+ // Now we have to swizzle it if it is not 0x02.
+ if (Header->BigEndian)
+ BigUShort(&GroupNum);
+ else
+ UShort(&GroupNum);
+
+ return GroupNum;
+}
+
+
+ILuint GetShort(DICOMHEAD *Header, ILushort GroupNum)
+{
+ ILushort Num;
+
+ iread(&Num, 1, 2);
+ // The 0x02 group is always little endian.
+ if (GroupNum == 0x02) {
+ UShort(&Num);
+ return Num;
+ }
+ // Now we have to swizzle it if it is not 0x02.
+ if (Header->BigEndian)
+ BigUShort(&Num);
+ else
+ UShort(&Num);
+
+ return Num;
+}
+
+
+ILuint GetInt(DICOMHEAD *Header, ILushort GroupNum)
+{
+ ILuint Num;
+
+ iread(&Num, 1, 4);
+ // The 0x02 group is always little endian.
+ if (GroupNum == 0x02) {
+ UInt(&Num);
+ return Num;
+ }
+ // Now we have to swizzle it if it is not 0x02.
+ if (Header->BigEndian)
+ BigUInt(&Num);
+ else
+ UInt(&Num);
+
+ return Num;
+}
+
+
+ILfloat GetFloat(DICOMHEAD *Header, ILushort GroupNum)
+{
+ ILfloat Num;
+
+ iread(&Num, 1, 4);
+ // The 0x02 group is always little endian.
+ if (GroupNum == 0x02) {
+ Float(&Num);
+ return Num;
+ }
+ // Now we have to swizzle it if it is not 0x02.
+ if (Header->BigEndian)
+ BigFloat(&Num);
+ else
+ Float(&Num);
+
+ return Num;
+}
+
+
+ILboolean GetNumericValue(DICOMHEAD *Header, ILushort GroupNum, ILuint *Number)
+{
+ ILubyte VR1, VR2;
+ ILushort ValLen;
+
+ // 2 byte character string telling what type this element is ('OB', 'UI', etc.)
+ VR1 = igetc();
+ VR2 = igetc();
+
+ if (VR1 == 'U' && VR2 == 'S') { // Unsigned short
+ ValLen = GetShort(Header, GroupNum);//GetLittleUShort();
+ if (ValLen != 2) // Must always be 2 for short ('US')
+ return IL_FALSE;
+ *((ILushort*)Number) = GetShort(Header, GroupNum);//GetLittleUShort();
+ return IL_TRUE;
+ }
+ if (VR1 == 'U' && VR2 == 'L') { // Unsigned long
+ ValLen = GetInt(Header, GroupNum);//GetLittleUInt();
+ if (ValLen != 4) // Must always be 4 for long ('UL')
+ return IL_FALSE;
+ *Number = GetInt(Header, GroupNum);
+ return IL_TRUE;
+ }
+ if (VR1 == 'S' && VR2 == 'S') { // Signed short
+ ValLen = GetShort(Header, GroupNum);
+ if (ValLen != 2) // Must always be 2 for short ('US')
+ return IL_FALSE;
+ *((ILshort*)Number) = GetShort(Header, GroupNum);
+ return IL_TRUE;
+ }
+ if (VR1 == 'S' && VR2 == 'L') { // Signed long
+ ValLen = GetInt(Header, GroupNum);
+ if (ValLen != 4) // Must always be 4 for long ('UL')
+ return IL_FALSE;
+ *((ILint*)Number) = GetInt(Header, GroupNum);
+ return IL_TRUE;
+ }
+
+ return IL_FALSE;
+}
+
+
+ILboolean GetUID(ILubyte *UID)
+{
+ ILubyte VR1, VR2;
+ ILushort ValLen;
+
+ // 2 byte character string telling what type this element is ('OB', 'UI', etc.)
+ VR1 = igetc();
+ VR2 = igetc();
+
+ if (VR1 != 'U' || VR2 != 'I') // 'UI' == UID
+ return IL_FALSE;
+
+ ValLen = GetLittleUShort();
+ if (ValLen > 64)
+ return IL_FALSE;
+ if (iread(UID, ValLen, 1) != 1)
+ return IL_FALSE;
+ UID[ValLen] = 0; // Just to make sure that our string is terminated.
+
+ return IL_TRUE;
+}
+
+// Internal function used to check if the HEADER is a valid DICOM header.
+ILboolean iCheckDicom(DICOMHEAD *Header)
+{
+ // Always has the signature "DICM" at position 0x80.
+ if (strncmp(Header->Signature, "DICM", 4))
+ return IL_FALSE;
+ // Does not make sense to have any dimension = 0.
+ if (Header->Width == 0 || Header->Height == 0 || Header->Depth == 0)
+ return IL_FALSE;
+ // We can only deal with images that have byte-aligned data.
+ //@TODO: Take care of any others?
+ if (Header->BitsAllocated % 8)
+ return IL_FALSE;
+ // Check for an invalid format being set (or not set at all).
+ if (ilGetBppFormat(Header->Format) == 0)
+ return IL_FALSE;
+ // Check for an invalid type being set (or not set at all).
+ if (ilGetBpcType(Header->Type) == 0)
+ return IL_FALSE;
+ return IL_TRUE;
+}
+
+
+//! Reads a DICOM file
+ILboolean ilLoadDicom(ILconst_string FileName)
+{
+ ILHANDLE DicomFile;
+ ILboolean bDicom = IL_FALSE;
+
+ DicomFile = iopenr(FileName);
+ if (DicomFile == NULL) {
+ ilSetError(IL_COULD_NOT_OPEN_FILE);
+ return bDicom;
+ }
+
+ bDicom = ilLoadDicomF(DicomFile);
+ icloser(DicomFile);
+
+ return bDicom;
+}
+
+
+//! Reads an already-opened DICOM file
+ILboolean ilLoadDicomF(ILHANDLE File)
+{
+ ILuint FirstPos;
+ ILboolean bRet;
+
+ iSetInputFile(File);
+ FirstPos = itell();
+ bRet = iLoadDicomInternal();
+ iseek(FirstPos, IL_SEEK_SET);
+
+ return bRet;
+}
+
+
+//! Reads from a memory "lump" that contains a DICOM
+ILboolean ilLoadDicomL(const void *Lump, ILuint Size)
+{
+ iSetInputLump(Lump, Size);
+ return iLoadDicomInternal();
+}
+
+
+// Internal function used to load the DICOM.
+ILboolean iLoadDicomInternal(void)
+{
+ DICOMHEAD Header;
+ ILuint i;
+ ILushort TempS, *ShortPtr;
+ ILfloat TempF, *FloatPtr;
+ ILboolean Swizzle = IL_FALSE;
+
+ if (iCurImage == NULL) {
+ ilSetError(IL_ILLEGAL_OPERATION);
+ return IL_FALSE;
+ }
+
+ // Clear the header to all 0s to make checks later easier.
+ memset(&Header, 0, sizeof(DICOMHEAD));
+ if (!iGetDicomHead(&Header)) {
+ ilSetError(IL_INVALID_FILE_HEADER);
+ return IL_FALSE;
+ }
+ if (!iCheckDicom(&Header))
+ return IL_FALSE;
+
+ if (!ilTexImage(Header.Width, Header.Height, Header.Depth, ilGetBppFormat(Header.Format), Header.Format, Header.Type, NULL))
+ return IL_FALSE;
+ //@TODO: Find out if the origin is always in the upper left.
+ iCurImage->Origin = IL_ORIGIN_UPPER_LEFT;
+ // Header.DataLen may be larger than SizeOfData, since it has to be padded with a NULL if it is not an even length,
+ // so we just test to make sure it is at least large enough.
+ //@TODO: Do this check before ilTexImage call.
+ if (Header.DataLen < iCurImage->SizeOfData) {
+ ilSetError(IL_INVALID_FILE_HEADER);
+ return IL_FALSE;
+ }
+
+ // We may have to swap the order of the data.
+#ifdef __BIG_ENDIAN__
+ if (!Header.BigEndian) {
+ if (Header.Format == IL_RGB)
+ Header.Format = IL_BGR;
+ else if (Header.Format == IL_RGBA)
+ Swizzle = IL_TRUE;
+ }
+#else // Little endian
+ if (Header.BigEndian) {
+ if (Header.Format == IL_RGB)
+ Header.Format = IL_BGR;
+ if (Header.Format == IL_RGBA)
+ Swizzle = IL_TRUE;
+ }
+#endif
+
+ switch (Header.Type)
+ {
+ case IL_UNSIGNED_BYTE:
+ if (iread(iCurImage->Data, iCurImage->SizeOfData, 1) != 1)
+ return IL_FALSE;
+
+ // Swizzle the data from ABGR to RGBA.
+ if (Swizzle) {
+ for (i = 0; i < iCurImage->SizeOfData; i += 4) {
+ iSwapUInt((ILuint*)(iCurImage->Data + i));
+ }
+ }
+ break;
+
+ case IL_UNSIGNED_SHORT:
+ for (i = 0; i < iCurImage->SizeOfData; i += 2) {
+ *((ILushort*)(iCurImage->Data + i)) = GetShort(&Header, 0);//GetLittleUShort();
+ }
+
+ // Swizzle the data from ABGR to RGBA.
+ if (Swizzle) {
+ ShortPtr = (ILushort*)iCurImage->Data;
+ for (i = 0; i < iCurImage->SizeOfData / 2; i += 4) {
+ TempS = ShortPtr[i];
+ ShortPtr[i] = ShortPtr[i+3];
+ ShortPtr[i+3] = TempS;
+ }
+ }
+ break;
+
+ case IL_FLOAT:
+ for (i = 0; i < iCurImage->SizeOfData; i += 4) {
+ *((ILfloat*)(iCurImage->Data + i)) = GetFloat(&Header, 0);//GetLittleFloat();
+ }
+
+ // Swizzle the data from ABGR to RGBA.
+ if (Swizzle) {
+ FloatPtr = (ILfloat*)iCurImage->Data;
+ for (i = 0; i < iCurImage->SizeOfData / 4; i += 4) {
+ TempF = FloatPtr[i];
+ FloatPtr[i] = FloatPtr[i+3];
+ FloatPtr[i+3] = TempF;
+ }
+ }
+ break;
+ }
+
+ return ilFixImage();
+}
+
+
+
+#endif//IL_NO_DICOM