/*========================================================================= Program: DICOMParser Module: $RCSfile: DICOMParser.cxx,v $ Language: C++ Date: $Date: 2006/03/24 15:59:50 $ Version: $Revision: 1.14.4.1 $ Copyright (c) 2003 Matt Turek All rights reserved. See Copyright.txt for details. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the above copyright notice for more information. =========================================================================*/ #ifdef _MSC_VER #pragma warning ( disable : 4514 ) #pragma warning ( disable : 4786 ) #pragma warning ( disable : 4503 ) #pragma warning ( disable : 4710 ) #pragma warning ( push, 3 ) #endif #include #if !defined(__MWERKS__) #include #endif #include #include #if !defined(__MWERKS__) #include #endif #include #include "DICOMConfig.h" #include "DICOMParser.h" #include "DICOMCallback.h" // Define DEBUG_DICOM to get debug messages sent to dicom_stream::cerr // #define DEBUG_DICOM #define DICOMPARSER_IGNORE_MAGIC_NUMBER #ifdef DEBUG_DICOM #define DICOM_DBG_MSG(x) {dicom_stream::cout x} #else #define DICOM_DBG_MSG(x) #endif static const char* DICOM_MAGIC = "DICM"; static const int OPTIONAL_SKIP = 128; class DICOMParserImplementation { public: DICOMParserImplementation() : Groups(), Elements(), Datatypes(), Map(), TypeMap() { }; dicom_stl::vector Groups; dicom_stl::vector Elements; dicom_stl::vector Datatypes; // // Stores a map from pair keys to // values of pair, datatype> // DICOMParserMap Map; // // Stores a map from pair keys to // values of datatype. We use this to store the // datatypes for implicit keys that we are // interested in. // DICOMImplicitTypeMap TypeMap; }; DICOMParser::DICOMParser() : ParserOutputFile() { this->Implementation = new DICOMParserImplementation(); this->DataFile = NULL; this->ToggleByteSwapImageData = false; this->TransferSyntaxCB = new DICOMMemberCallback; this->InitTypeMap(); this->FileName = ""; } const dicom_stl::string& DICOMParser::GetFileName() { return this->FileName; } bool DICOMParser::OpenFile(const dicom_stl::string& filename) { if (this->DataFile) { // Deleting the DataFile closes the file delete this->DataFile; } this->DataFile = new DICOMFile(); bool val = this->DataFile->Open(filename); if (val) { this->FileName = filename; } #ifdef DEBUG_DICOM if (this->ParserOutputFile.rdbuf()->is_open()) { this->ParserOutputFile.flush(); this->ParserOutputFile.close(); } dicom_stl::string fn(filename); dicom_stl::string append(".parser.txt"); dicom_stl::string parseroutput(fn + append); // dicom_stl::string parseroutput(dicom_stl::string(dicom_stl::string(filename) + dicom_stl::string(".parser.txt"))); this->ParserOutputFile.open(parseroutput.c_str()); //, dicom_stream::ios::app); #endif return val; } DICOMParser::~DICOMParser() { // // Delete the callbacks. // this->ClearAllDICOMTagCallbacks(); if (this->DataFile) { delete this->DataFile; } delete this->TransferSyntaxCB; delete this->Implementation; #ifdef DEBUG_DICOM this->ParserOutputFile.flush(); this->ParserOutputFile.close(); #endif } bool DICOMParser::ReadHeader() { bool dicom = this->IsDICOMFile(this->DataFile); if (!dicom) { return false; } this->TransferSyntaxCB->SetCallbackFunction(this, &DICOMParser::TransferSyntaxCallback); this->AddDICOMTagCallback(0x0002, 0x0010, DICOMParser::VR_UI, this->TransferSyntaxCB); this->ToggleByteSwapImageData = false; doublebyte group = 0; doublebyte element = 0; DICOMParser::VRTypes datatype = DICOMParser::VR_UNKNOWN; this->Implementation->Groups.clear(); this->Implementation->Elements.clear(); this->Implementation->Datatypes.clear(); long fileSize = DataFile->GetSize(); do { this->ReadNextRecord(group, element, datatype); this->Implementation->Groups.push_back(group); this->Implementation->Elements.push_back(element); this->Implementation->Datatypes.push_back(datatype); } while ((DataFile->Tell() >= 0) && (DataFile->Tell() < fileSize)); return true; } // // read magic number from file // return true if this is your image type, false if it is not // bool DICOMParser::IsDICOMFile(DICOMFile* file) { char magic_number[4]; file->SkipToStart(); file->Read((void*)magic_number,4); if (CheckMagic(magic_number)) { return(true); } // try with optional skip else { file->Skip(OPTIONAL_SKIP-4); file->Read((void*)magic_number,4); if (CheckMagic(magic_number)) { return true; } else { #ifndef DICOMPARSER_IGNORE_MAGIC_NUMBER return false; #else // // Try it anyways... // file->SkipToStart(); doublebyte group = file->ReadDoubleByte(); bool dicom; if (group == 0x0002 || group == 0x0008) { dicom_stream::cerr << "No DICOM magic number found, but file appears to be DICOM." << dicom_stream::endl; dicom_stream::cerr << "Proceeding without caution." << dicom_stream::endl; dicom = true; } else { dicom = false; } file->SkipToStart(); return dicom; #endif // DICOMPARSER_IGNORE_MAGIC_NUMBER } } } bool DICOMParser::IsValidRepresentation(doublebyte rep, quadbyte& len, VRTypes &mytype) { switch (rep) { case DICOMParser::VR_AW: case DICOMParser::VR_AE: case DICOMParser::VR_AS: case DICOMParser::VR_CS: case DICOMParser::VR_UI: case DICOMParser::VR_DA: case DICOMParser::VR_DS: case DICOMParser::VR_DT: case DICOMParser::VR_IS: case DICOMParser::VR_LO: case DICOMParser::VR_LT: case DICOMParser::VR_PN: case DICOMParser::VR_ST: case DICOMParser::VR_TM: case DICOMParser::VR_UT: // new case DICOMParser::VR_SH: case DICOMParser::VR_FL: case DICOMParser::VR_SL: case DICOMParser::VR_AT: case DICOMParser::VR_UL: case DICOMParser::VR_US: case DICOMParser::VR_SS: case DICOMParser::VR_FD: len = DataFile->ReadDoubleByte(); mytype = VRTypes(rep); return true; case DICOMParser::VR_OB: // OB - LE case DICOMParser::VR_OW: case DICOMParser::VR_UN: case DICOMParser::VR_SQ: DataFile->ReadDoubleByte(); len = DataFile->ReadQuadByte(); mytype = VRTypes(rep); return true; default: // // // Need to comment out in new paradigm. // DataFile->Skip(-2); len = DataFile->ReadQuadByte(); mytype = DICOMParser::VR_UNKNOWN; return false; } } void DICOMParser::ReadNextRecord(doublebyte& group, doublebyte& element, DICOMParser::VRTypes& mytype) { // // WE SHOULD IMPLEMENT THIS ALGORITHM. // // FIND A WAY TO STOP IF THERE ARE NO MORE CALLBACKS. // // DO WE NEED TO ENSURE THAT WHEN A CALLBACK IS ADDED THAT // THE IMPLICIT TYPE MAP IS UPDATED? ONLY IF THERE ISN'T // A VALUE IN THE IMPLICIT TYPE MAP. // // New algorithm: // // 1. Read group & element // 2. ParseExplicitRecord // a. Check to see if the next doublebyte is a valid datatype // b. If the type is valid, lookup type to find the size of // the length field. // 3. If ParseExplicitRecord fails, ParseImplicitRecord // a. Lookup implicit datatype // 4. Check to see if there is a registered callback for the group,element. // 5. If there are callbacks, read the data and call them, otherwise // skip ahead to the next record. // // group = DataFile->ReadDoubleByte(); element = DataFile->ReadDoubleByte(); doublebyte representation = DataFile->ReadDoubleByteAsLittleEndian(); quadbyte length = 0; mytype = DICOMParser::VR_UNKNOWN; this->IsValidRepresentation(representation, length, mytype); DICOMParserMap::iterator iter = Implementation->Map.find(DICOMMapKey(group,element)); VRTypes callbackType; if (iter != Implementation->Map.end()) { // // Only read the data if there's a registered callback. // unsigned char* tempdata = (unsigned char*) DataFile->ReadAsciiCharArray(length); DICOMMapKey ge = (*iter).first; callbackType = VRTypes(((*iter).second.first)); if (callbackType != mytype && mytype != VR_UNKNOWN) { // // mytype is not VR_UNKNOWN if the file is in Explicit format. // callbackType = mytype; } #ifdef DEBUG_DICOM this->DumpTag(this->ParserOutputFile, group, element, callbackType, tempdata, length); #endif dicom_stl::pair p = *iter; DICOMMapValue mv = p.second; bool doSwap = (this->ToggleByteSwapImageData ^ this->DataFile->GetPlatformIsBigEndian()) && callbackType == VR_OW; if (group == 0x7FE0 && element == 0x0010 ) { if (doSwap) { #ifdef DEBUG_DICOM dicom_stream::cout << "==============================" << dicom_stream::endl; dicom_stream::cout << "TOGGLE BS FOR IMAGE" << dicom_stream::endl; dicom_stream::cout << " ToggleByteSwapImageData : " << this->ToggleByteSwapImageData << dicom_stream::endl; dicom_stream::cout << " DataFile Byte Swap : " << this->DataFile->GetPlatformIsBigEndian() << dicom_stream::endl; dicom_stream::cout << "==============================" << dicom_stream::endl; #endif DICOMFile::swap2((ushort*) tempdata, (ushort*) tempdata, length/sizeof(ushort)); } else { #ifdef DEBUG_DICOM dicom_stream::cout << "==============================" << dicom_stream::endl; dicom_stream::cout << " AT IMAGE DATA " << dicom_stream::endl; dicom_stream::cout << " ToggleByteSwapImageData : " << this->ToggleByteSwapImageData << dicom_stream::endl; dicom_stream::cout << " DataFile Byte Swap : " << this->DataFile->GetPlatformIsBigEndian() << dicom_stream::endl; int t2 = int((0x0000FF00 & callbackType) >> 8); int t1 = int((0x000000FF & callbackType)); if (t1 == 0 && t2 == 0) { t1 = '?'; t2 = '?'; } char ct2(t2); char ct1(t1); dicom_stream::cout << " Callback type : " << ct1 << ct2 << dicom_stream::endl; dicom_stream::cout << "==============================" << dicom_stream::endl; #endif } } else { if (this->DataFile->GetPlatformIsBigEndian() == true) { switch (callbackType) { case DICOMParser::VR_OW: case DICOMParser::VR_US: case DICOMParser::VR_SS: DICOMFile::swap2((ushort*) tempdata, (ushort*) tempdata, length/sizeof(ushort)); // dicom_stream::cout << "16 bit byte swap needed!" << dicom_stream::endl; break; case DICOMParser::VR_FL: case DICOMParser::VR_FD: DICOMFile::swap4((uint*) tempdata, (uint*) tempdata, length/sizeof(uint)); // dicom_stream::cout << "Float byte swap needed!" << dicom_stream::endl; break; case DICOMParser::VR_SL: case DICOMParser::VR_UL: DICOMFile::swap4((uint*) tempdata, (uint*) tempdata, length/sizeof(uint)); // dicom_stream::cout << "32 bit byte swap needed!" << dicom_stream::endl; break; case DICOMParser::VR_AT: // dicom_stream::cout << "ATTRIBUTE Byte swap needed!" << dicom_stream::endl; break; default: break; } } } dicom_stl::vector * cbVector = mv.second; for (dicom_stl::vector::iterator cbiter = cbVector->begin(); cbiter != cbVector->end(); cbiter++) { (*cbiter)->Execute(this, // parser ge.first, // group ge.second, // element callbackType, // type tempdata, // data length); // length } delete [] tempdata; } else { // // Some lengths are negative, but we don't // want to back up the file pointer. // if (length > 0) { DataFile->Skip(length); } #ifdef DEBUG_DICOM this->DumpTag(this->ParserOutputFile, group, element, mytype, (unsigned char*) "Unread.", length); #endif } } void DICOMParser::InitTypeMap() { DICOMRecord dicom_tags[] = {{0x0002, 0x0002, DICOMParser::VR_UI}, // Media storage SOP class uid {0x0002, 0x0003, DICOMParser::VR_UI}, // Media storage SOP inst uid {0x0002, 0x0010, DICOMParser::VR_UI}, // Transfer syntax uid {0x0002, 0x0012, DICOMParser::VR_UI}, // Implementation class uid {0x0008, 0x0018, DICOMParser::VR_UI}, // Image UID {0x0008, 0x0020, DICOMParser::VR_DA}, // Series date {0x0008, 0x0030, DICOMParser::VR_TM}, // Series time {0x0008, 0x0060, DICOMParser::VR_SH}, // Modality {0x0008, 0x0070, DICOMParser::VR_SH}, // Manufacturer {0x0008, 0x1060, DICOMParser::VR_SH}, // Physician {0x0018, 0x0050, DICOMParser::VR_FL}, // slice thickness {0x0018, 0x0060, DICOMParser::VR_FL}, // kV {0x0018, 0x0088, DICOMParser::VR_FL}, // slice spacing {0x0018, 0x1100, DICOMParser::VR_SH}, // Recon diameter {0x0018, 0x1151, DICOMParser::VR_FL}, // mA {0x0018, 0x1210, DICOMParser::VR_SH}, // Recon kernel {0x0020, 0x000d, DICOMParser::VR_UI}, // Study UID {0x0020, 0x000e, DICOMParser::VR_UI}, // Series UID {0x0020, 0x0013, DICOMParser::VR_IS}, // Image number {0x0020, 0x0032, DICOMParser::VR_SH}, // Patient position {0x0020, 0x0037, DICOMParser::VR_SH}, // Patient position cosines {0x0028, 0x0010, DICOMParser::VR_US}, // Num rows {0x0028, 0x0011, DICOMParser::VR_US}, // Num cols {0x0028, 0x0030, DICOMParser::VR_FL}, // pixel spacing {0x0028, 0x0100, DICOMParser::VR_US}, // Bits allocated {0x0028, 0x0120, DICOMParser::VR_UL}, // pixel padding {0x0028, 0x1052, DICOMParser::VR_FL}, // pixel offset {0x7FE0, 0x0010, DICOMParser::VR_OW} // pixel data }; int num_tags = sizeof(dicom_tags)/sizeof(DICOMRecord); doublebyte group; doublebyte element; VRTypes datatype; for (int i = 0; i < num_tags; i++) { group = dicom_tags[i].group; element = dicom_tags[i].element; datatype = (VRTypes) dicom_tags[i].datatype; Implementation->TypeMap.insert(dicom_stl::pair(DICOMMapKey(group, element), datatype)); } } void DICOMParser::SetDICOMTagCallbacks(doublebyte group, doublebyte element, VRTypes datatype, dicom_stl::vector* cbVector) { Implementation->Map.insert(dicom_stl::pair(DICOMMapKey(group, element), DICOMMapValue((int)datatype, cbVector))); } bool DICOMParser::CheckMagic(char* magic_number) { return ( (magic_number[0] == DICOM_MAGIC[0]) && (magic_number[1] == DICOM_MAGIC[1]) && (magic_number[2] == DICOM_MAGIC[2]) && (magic_number[3] == DICOM_MAGIC[3]) ); } void DICOMParser::DumpTag(dicom_stream::ostream& out, doublebyte group, doublebyte element, VRTypes vrtype, unsigned char* tempdata, quadbyte length) { int t2 = int((0x0000FF00 & vrtype) >> 8); int t1 = int((0x000000FF & vrtype)); if (t1 == 0 && t2 == 0) { t1 = '?'; t2 = '?'; } char ct2(t2); char ct1(t1); out << "(0x"; out.width(4); char prev = out.fill('0'); out << dicom_stream::hex << group; out << ",0x"; out.width(4); out.fill('0'); out << dicom_stream::hex << element; out << ") "; out.fill(prev); out << dicom_stream::dec; out << " " << ct1 << ct2 << " "; out << "[" << length << " bytes] "; if (group == 0x7FE0 && element == 0x0010) { out << "Image data not printed." ; } else { out << (tempdata ? (char*) tempdata : "NULL"); } out << dicom_stream::dec << dicom_stream::endl; out.fill(prev); out << dicom_stream::dec; return; } void DICOMParser::ModalityTag(doublebyte, doublebyte, VRTypes, unsigned char* tempdata, quadbyte) { if (!strcmp( (char*)tempdata, "MR")) { // this->AddMRTags(); } else if (!strcmp((char*) tempdata, "CT")) { } else if (!strcmp((char*) tempdata, "US")) { } } void DICOMParser::AddDICOMTagCallbacks(doublebyte group, doublebyte element, VRTypes datatype, dicom_stl::vector* cbVector) { DICOMParserMap::iterator miter = Implementation->Map.find(DICOMMapKey(group,element)); if (miter != Implementation->Map.end()) { for (dicom_stl::vector::iterator iter = cbVector->begin(); iter != cbVector->end(); iter++) { dicom_stl::vector* callbacks = (*miter).second.second; callbacks->push_back(*iter); } } else { this->SetDICOMTagCallbacks(group, element, datatype, cbVector); } } void DICOMParser::AddDICOMTagCallback(doublebyte group, doublebyte element, VRTypes datatype, DICOMCallback* cb) { DICOMParserMap::iterator miter = Implementation->Map.find(DICOMMapKey(group,element)); if (miter != Implementation->Map.end()) { dicom_stl::vector* callbacks = (*miter).second.second; callbacks->push_back(cb); } else { dicom_stl::vector* callback = new dicom_stl::vector; callback->push_back(cb); this->SetDICOMTagCallbacks(group, element, datatype, callback); } } void DICOMParser::AddDICOMTagCallbackToAllTags(DICOMCallback* cb) { DICOMParserMap::iterator miter; for (miter = Implementation->Map.begin(); miter != Implementation->Map.end(); miter++); { dicom_stl::vector* callbacks = (*miter).second.second; callbacks->push_back(cb); } } bool DICOMParser::ParseExplicitRecord(doublebyte, doublebyte, quadbyte& length, VRTypes& represent) { doublebyte representation = DataFile->ReadDoubleByte(); bool valid = this->IsValidRepresentation(representation, length, represent); if (valid) { return true; } else { represent = VR_UNKNOWN; length = 0; return false; } } bool DICOMParser::ParseImplicitRecord(doublebyte group, doublebyte element, quadbyte& length, VRTypes& represent) { DICOMImplicitTypeMap::iterator iter = Implementation->TypeMap.find(DICOMMapKey(group,element)); represent = VRTypes((*iter).second); // // length? // length = DataFile->ReadQuadByte(); return false; } void DICOMParser::TransferSyntaxCallback(DICOMParser *, doublebyte, doublebyte, DICOMParser::VRTypes, unsigned char* val, quadbyte) { #ifdef DEBUG_DICOM dicom_stream::cout << "DICOMParser::TransferSyntaxCallback" << dicom_stream::endl; #endif const char* TRANSFER_UID_EXPLICIT_BIG_ENDIAN = "1.2.840.10008.1.2.2"; const char* TRANSFER_UID_GE_PRIVATE_IMPLICIT_BIG_ENDIAN = "1.2.840.113619.5.2"; // char* fileEndian = "LittleEndian"; // char* dataEndian = "LittleEndian"; this->ToggleByteSwapImageData = false; if (strcmp(TRANSFER_UID_EXPLICIT_BIG_ENDIAN, (char*) val) == 0) { #ifdef DEBUG_DICOM dicom_stream::cout << "EXPLICIT BIG ENDIAN" << dicom_stream::endl; #endif this->ToggleByteSwapImageData = true; // // Data byte order is big endian // // We're always reading little endian in the beginning, // so now we need to swap. } else if (strcmp(TRANSFER_UID_GE_PRIVATE_IMPLICIT_BIG_ENDIAN, (char*) val) == 0) { this->ToggleByteSwapImageData = true; #ifdef DEBUG_DICOM dicom_stream::cout << "GE PRIVATE TRANSFER SYNTAX" << dicom_stream::endl; dicom_stream::cout << "ToggleByteSwapImageData : " << this->ToggleByteSwapImageData << dicom_stream::endl; #endif } else { } } void DICOMParser::GetGroupsElementsDatatypes(dicom_stl::vector& groups, dicom_stl::vector& elements, dicom_stl::vector& datatypes) { groups.clear(); elements.clear(); datatypes.clear(); dicom_stl::vector::iterator giter; // = this->Groups.begin(); dicom_stl::vector::iterator eiter; // = this->Elements.begin(); dicom_stl::vector::iterator diter; // = this->Datatypes.begin(); for (giter = this->Implementation->Groups.begin(), eiter = this->Implementation->Elements.begin(), diter = this->Implementation->Datatypes.begin(); giter != this->Implementation->Groups.end(), eiter != this->Implementation->Elements.end(), diter != this->Implementation->Datatypes.end(); giter++, eiter++, diter++) { groups.push_back(*giter); elements.push_back(*eiter); datatypes.push_back(*diter); } } void DICOMParser::ClearAllDICOMTagCallbacks() { DICOMParserMap::iterator mapIter; for (mapIter = this->Implementation->Map.begin(); mapIter != this->Implementation->Map.end(); mapIter++) { dicom_stl::pair mapPair = *mapIter; DICOMMapValue mapVal = mapPair.second; dicom_stl::vector* cbVector = mapVal.second; delete cbVector; } this->Implementation->Map.clear(); } DICOMParser::DICOMParser(const DICOMParser&) { dicom_stream::cerr << "DICOMParser copy constructor should not be called!" << dicom_stream::endl; } void DICOMParser::operator=(const DICOMParser&) { dicom_stream::cerr << "DICOMParser assignment operator should not be called!" << dicom_stream::endl; } #ifdef _MSC_VER #pragma warning ( pop ) #endif