/*========================================================================= Program: GDCM (Grassroots DICOM). A DICOM library Copyright (c) 2006-2011 Mathieu Malaterre All rights reserved. See Copyright.txt or http://gdcm.sourceforge.net/Copyright.html 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. =========================================================================*/ /* Try to extract contents of Philips RAW storage class: (0002,0002) UI [1.2.840.10008.5.1.4.1.1.66] # 26,1 Media Storage SOP Class UID (0002,0003) UI [1.3.46.670589.11.17240.5.23.4.1.3012.2010032409482568018] # 56,1 Media Storage SOP Instance UID (0002,0010) UI [1.2.840.10008.1.2.1] # 20,1 Transfer Syntax UID (0002,0012) UI [1.3.46.670589.11.0.0.51.4.4.1] # 30,1 Implementation Class UID (0002,0013) SH [MR DICOM 4.1] # 12,1 Implementation Version Name * Everything done in this code is for the sole purpose of writing interoperable * software under Sect. 1201 (f) Reverse Engineering exception of the DMCA. * If you believe anything in this code violates any law or any of your rights, * please contact us (gdcm-developers@lists.sourceforge.net) so that we can * find a solution. * * Everything you do with this code is at your own risk, since decompression * algorithm was not written from specification documents. * * Special thanks to: * Triplett,William T for bringing to your attention on this ExamCard stuff */ #include "gdcmReader.h" #include "gdcmDataSet.h" #include "gdcmPrivateTag.h" #include "gdcmBase64.h" #include static bool compfn(const char *s1, const char *s2) { return strcmp(s1,s2) < 0 ? true : false; } static const char *PDFStrings[] = { // Keep me ordered please "COILSTATE", // series of string ? "HARDWARE_CONFIG", // series of number ? "PDF_CONTROL_GEN_PARS", "PDF_CONTROL_PREP_PARS", "PDF_CONTROL_RECON_PARS", "PDF_CONTROL_SCAN_PARS", "PDF_EXAM_PARS", "PDF_HARDWARE_PARS", "PDF_PREP_PARS", "PDF_PRESCAN_COIL_PARS", "PDF_SPT_PARS", }; static bool isvalidpdfstring( const char *pdfstring ) { assert( pdfstring ); static const size_t n = sizeof( PDFStrings ) / sizeof( *PDFStrings ); static const char **begin = PDFStrings; static const char **end = begin + n; return std::binary_search(begin, end, pdfstring, compfn); } typedef enum { param_float = 0, param_integer = 1, // 1 << 0 param_string = 2, // 1 << 1 param_3, // ?? param_enum = 4 // 1 << 2 } param_type; static const char *gettypenamefromtype( int i) { const char *ret = nullptr; param_type e = (param_type)i; switch( e ) { case param_float: ret = "float"; break; case param_integer: ret = "int"; break; case param_string: ret = "string"; break; case param_3: ret = "??"; break; case param_enum: ret = "enum"; break; } assert( ret ); return ret; } struct header { /* * TODO: * Looks as if we could read all int*, float* and string* at once... */ int32_t v1; // offset to int pointer array ? uint16_t nints; // number of ints (max number?) uint16_t v3; // always 0 ? int32_t v4; // offset to float pointer array ? uint32_t nfloats; int32_t v6; // offset to string pointer array ? uint32_t nstrings; int32_t v8; // always 8 ?? uint32_t numparams; uint32_t getnints() const { return nints; } uint32_t getnfloats() const { return nfloats; } uint32_t getnstrings() const { return nstrings; } uint32_t getnparams() const { return numparams; } void read( std::istream & is ) { is.read( (char*)&v1,sizeof(v1)); if( v1 == 0x01 ) { // direct (FIXME how should we detect this, much like TIFF ???) nints = 0; v3 = 0; v4 = 0; nfloats = 0; v6 = 0; nstrings = 0; v8 = 0; numparams = 0; uint32_t bla; is.read( (char*)&bla, sizeof(bla) ); assert( bla == 0x2 || bla == 0x3 ); nstrings = 1; numparams = 1; } else { // indirect is.read( (char*)&nints,sizeof(nints)); is.read( (char*)&v3,sizeof(v3)); assert( v3 == 0 ); // looks like this is always 0 is.read( (char*)&v4,sizeof(v4)); is.read( (char*)&nfloats,sizeof(nfloats)); is.read( (char*)&v6,sizeof(v6)); is.read( (char*)&nstrings,sizeof(nstrings)); is.read( (char*)&v8,sizeof(v8)); assert( v8 == 8 ); is.read( (char*)&numparams,sizeof(numparams)); } } void print( std::ostream & os ) { os << v1 << ","; os << nints << ","; os << v3 << ","; os << v4 << ","; os << nfloats << ","; os << v6 << ","; os << nstrings << ","; os << v8 << ","; os << numparams << std::endl; } }; struct param { char name[32+1]; uint8_t boolean; int32_t type; uint32_t dim; union { uint32_t val; char * ptr; } v4; int32_t /*std::streamoff*/ offset; param_type gettype() const { return (param_type)type; } uint32_t getdim() const { return dim; } void read_direct_int( std::istream & is ) { uint32_t bla; int max = 9; std::vector v; for( int i = 0; i < max; ++i ) { is.read( (char*)&bla, sizeof(bla) ); v.push_back( bla ); } is.read( (char*)&bla, sizeof(bla) ); char name0[32]; memset(name0,0,sizeof(name0)); assert( bla < sizeof(name0) ); is.read( name0, bla); size_t l = strlen(name0); assert( l == bla ); (void)l; char * ptr = strdup( name0 ); v4.ptr = ptr; type = param_string; dim = 1; offset = 0; // important ! } void read_direct_string( std::istream & is ) { uint32_t bla; is.read( (char*)&bla, sizeof(bla) ); char name0[32]; memset(name0,0,sizeof(name0)); assert( bla < sizeof(name0) ); is.read( name0, bla); size_t l = strlen(name0); assert( l == bla ); (void)l; memcpy( this->name, name0, bla ); is.read( (char*)&bla, sizeof(bla) ); assert( bla == 0x1 ); is.read( (char*)&bla, sizeof(bla) ); char value[32]; memset(value,0,sizeof(value)); assert( bla < sizeof(value) ); is.read( value, bla); is.read( (char*)&bla, sizeof(bla) ); assert( bla == 0 ); // trailing stuff ? is.read( (char*)&bla, sizeof(bla) ); assert( bla == 0 ); // trailing stuff ? const uint32_t cur = (uint32_t)is.tellg(); std::cerr << "offset:" << cur << std::endl; if( cur == 65 ) is.read( (char*)&bla, 1 ); else if( cur == 66 ) is.read( (char*)&bla, 1 ); else if( cur == 122 ) is.read( (char*)&bla, 2 ); else assert(0); type = param_string; dim = 1; // FIXME: store the value in v4 for now: char * ptr = strdup( value ); v4.ptr = ptr; offset = 0; // important ! } void read( std::istream & is ) { is.read( name, 32 + 1); // This is always the same issue the string can contains garbarge from previous run, // we need to print only until the first \0 character: assert( strlen( name ) <= 32 ); is.read( (char*)&boolean,1); assert( boolean == 0 || boolean == 1 || boolean == 0x69 ); // some kind of bool, or digital trash ? is.read( (char*)&type, sizeof( type ) ); assert( gettypenamefromtype( type ) ); is.read( (char*)&dim, sizeof( dim ) ); // number of elements is.read( (char*)&v4.val, sizeof( v4.val ) ); //assert( v4.val == 0 ); // always 0 ? sometimes not... const uint32_t cur = (uint32_t)is.tellg(); is.read( (char*)&offset, sizeof( offset ) ); assert( offset != 0 ); offset += cur; } void print( std::ostream & os ) const { os << name << ","; os << (int)boolean << ","; os << type << ","; os << dim << ","; os << v4.val << ","; os << offset << std::endl; } void printvalue( std::ostream & os, std::istream & is ) const { if( offset ) { is.seekg( offset ); switch( type ) { case param_float: { os.precision(2); os << std::fixed; for( uint32_t idx = 0; idx < dim; ++idx ) { if( idx ) os << ","; float v; is.read( (char*)&v, sizeof(v) ); os << v; // what if the string contains \0 ? } } break; case param_integer: { int32_t v; for( uint32_t idx = 0; idx < dim; ++idx ) { if( idx ) os << ","; is.read( (char*)&v, sizeof(v) ); os << v; } } break; case param_string: { int size = 81; std::string v; v.resize( size ); for( uint32_t idx = 0; idx < dim; ++idx ) { if( idx ) os << ";"; is.read( &v[0], size ); os << v.c_str(); } } break; case param_enum: { int32_t v; for( uint32_t idx = 0; idx < dim; ++idx ) { if( idx ) os << ","; is.read( (char*)&v, sizeof(v) ); os << v; } } break; } } else { #if 1 // direct assert ( type == param_string ); char * ptr = v4.ptr; //std::string v; //v.resize( dim ); //is.read( &v[0], dim ); os << ptr; #endif } } void printxml( std::ostream & os, std::istream & is ) const { // 0 os << " "; printvalue( os, is ); os << "\n"; } void printcsv( std::ostream & os, std::istream & is ) const { os << std::setw(32) << std::left << name << ","; os << std::setw(7) << std::right << gettypenamefromtype(type) << ","; os << std::setw(4) << dim << ","; os << " "; printvalue( os, is ); os << ",\n"; } }; static bool ProcessNested( gdcm::DataSet & ds ) { /* TODO: Looks like the real length of the blob is stored here: (2005,1132) SQ # u/l,1 ? (fffe,e000) na (Item with undefined length) (2005,0011) LO [Philips MR Imaging DD 002 ] # 26,1 Private Creator (2005,1143) SL 3103 # 4,1 ? Wotsit ? (2005,1132) SQ # u/l,1 ? (fffe,e000) na (Item with undefined length) (2005,0011) LO [Philips MR Imaging DD 002 ] # 26,1 Private Creator (2005,1147) CS [Y ] # 2,1 ? */ bool ret = false; // (2005,1137) PN (LO) [PDF_CONTROL_GEN_PARS] # 20,1 Protocol Data Name const gdcm::PrivateTag pt0(0x2005,0x37,"Philips MR Imaging DD 002"); if( !ds.FindDataElement( pt0 ) ) return false; const gdcm::DataElement &de0 = ds.GetDataElement( pt0 ); if( de0.IsEmpty() ) return false; const gdcm::ByteValue * bv0 = de0.GetByteValue(); std::string s0( bv0->GetPointer() , bv0->GetLength() ); // (2005,1139) LO [IEEE_PDF] # 8,1 Protocol Data Type const gdcm::PrivateTag pt1(0x2005,0x39,"Philips MR Imaging DD 002"); if( !ds.FindDataElement( pt1 ) ) return false; const gdcm::DataElement &de1 = ds.GetDataElement( pt1 ); // (2005,1143) SL 53 # 4,1 Protocol Data Block Length (non-padded) const gdcm::PrivateTag pt2(0x2005,0x43,"Philips MR Imaging DD 002"); if( !ds.FindDataElement( pt2 ) ) return false; const gdcm::DataElement &de2 = ds.GetDataElement( pt2 ); // (2005,1147) CS [Y ] # 2,1 Protocol Data Boolean const gdcm::PrivateTag pt3(0x2005,0x47,"Philips MR Imaging DD 002"); if( !ds.FindDataElement( pt3 ) ) return false; const gdcm::DataElement &de3 = ds.GetDataElement( pt3 ); (void)de3; // (2005,1144) OW 00\00\00\00\05\00\00\00\35\2e\31\2e\37\00 # 54,1 Protocol Data Block const gdcm::PrivateTag pt(0x2005,0x44,"Philips MR Imaging DD 002"); if( !ds.FindDataElement( pt ) ) return false; const gdcm::DataElement &de = ds.GetDataElement( pt ); if( de.IsEmpty() ) return false; const gdcm::ByteValue * bv = de.GetByteValue(); if( s0 == "ExamCardBlob" ) { assert( de1.IsEmpty() ); std::string fn = gdcm::LOComp::Trim( s0.c_str() ); // remove trailing space fn += ".xml"; std::ofstream out( fn.c_str() ); // remove trailing \0 size_t len = strlen( bv->GetPointer() ); out.write( bv->GetPointer() , len ); out.close(); // Extract binary64 thingy (this is a ugly hack, better use an XML parser) std::string dup( bv->GetPointer(), len ); std::string::size_type pos1 = dup.find( "" ); std::string::size_type pos2 = dup.find( "" ); std::string b64( bv->GetPointer() + pos1 + 14, pos2 - (pos1 + 14) ); // ulgy hack to remove \r\n from input base64: std::string::iterator r_pos = std::remove(b64.begin(), b64.end(), '\r'); b64.erase(r_pos, b64.end()); std::string::iterator n_pos = std::remove(b64.begin(), b64.end(), '\n'); b64.erase(n_pos, b64.end()); #if 0 std::ofstream out2( "debug" ); out2.write( b64.c_str(), b64.size() ); out2.close(); #endif const size_t dlen = gdcm::Base64::GetDecodeLength(b64.c_str(), b64.size() ); std::string decoded; decoded.resize( dlen ); gdcm::Base64::Decode( &decoded[0], decoded.size(), b64.c_str(), b64.size() ); std::ofstream f64( "soap.xml" ); f64.write( decoded.c_str(), decoded.size() ); f64.close(); ret = true; } else { if( de1.IsEmpty() ) return false; const gdcm::ByteValue * bv1 = de1.GetByteValue(); gdcm::Element dlen = {{0l}}; dlen.SetFromDataElement( de2 ); std::string s1( bv1->GetPointer() , bv1->GetLength() ); if( s1 == "IEEE_PDF" ) { std::istringstream is; assert( bv->GetLength() == (size_t)dlen.GetValue() || bv->GetLength() == (size_t)(dlen.GetValue() + 1) ); std::string dup( bv->GetPointer(), dlen.GetValue() /*bv->GetLength()*/ ); is.str( dup ); header h; h.read( is ); //assert( is.peek() && is.eof() ); #if 1 static int c = 0; std::string fn0 = gdcm::LOComp::Trim( s1.c_str() ); // remove trailing space std::stringstream ss; ss << fn0 << "_" << c++; if( h.v1 == 0x01 ) ss << ".direct"; else ss << ".indirect"; std::cout << "fn0=" << ss.str() << " Len= " << bv->GetLength() << std::endl; std::ofstream out( ss.str().c_str() ); out.write( bv->GetPointer(), bv->GetLength() ); out.close(); #endif #if 1 std::cout << dup.c_str() << std::endl; h.print( std::cout ); #endif std::vector< param > params; if( h.v1 == 0x01 ) { for( uint32_t i = 0; i < 1 /* h.getnparams()*/; ++i ) { param p; if( s0 == "HARDWARE_CONFIG " ) { p.read_direct_int( is ); } else if( s0 == "COILSTATE " ) { p.read_direct_string( is ); } else { assert(0); } params.push_back( p ); } } else { assert( is.tellg() == std::streampos(0x20) ); is.seekg( 0x20 ); param p; for( uint32_t i = 0; i < h.getnparams(); ++i ) { p.read( is ); //p.print( std::cout ); params.push_back( p ); } } std::string fn = gdcm::LOComp::Trim( s0.c_str() ); // remove trailing space bool b1 = isvalidpdfstring( fn.c_str() ); assert( b1 ); (void)b1; fn += ".csv"; //fn += ".xml"; std::ofstream csv( fn.c_str() ); // let's do some bookeeping: uint32_t nfloats = 0; uint32_t nints = 0; uint32_t nstrings = 0; for( std::vector::const_iterator it = params.begin(); it != params.end(); ++it ) { param_type type = it->gettype(); switch( type ) { case param_float: nfloats += it->getdim(); break; case param_integer: nints += it->getdim(); break; case param_string: nstrings += it->getdim(); break; default: ; } } #if 0 std::cout << "Stats:" << std::endl; std::cout << "nfloats:" << nfloats << std::endl; std::cout << "nints:" << nints << std::endl; std::cout << "nstrings:" << nstrings << std::endl; #endif assert( h.getnints() >= nints ); assert( h.getnfloats() >= nfloats ); assert( h.getnstrings() >= nstrings); for( uint32_t i = 0; i < h.getnparams(); ++i ) { params[i].printcsv( csv, is ); //params[i].printxml( csv, is ); } csv.close(); ret = true; } else if( s1 == "ASCII " ) { #if 0 std::cerr << "ASCII is not handled" << std::endl; std::string fn = gdcm::LOComp::Trim( s0.c_str() ); // remove trailing space fn += ".asc"; std::ofstream out( fn.c_str() ); out.write( bv->GetPointer() , bv->GetLength() ); out.close(); #endif std::string fn = gdcm::LOComp::Trim( s0.c_str() ); // remove trailing space fn += ".sin"; std::ofstream sin( fn.c_str() ); const char *beg = bv->GetPointer(); const char *end = beg + bv->GetLength(); assert( *beg == 0 ); const char *p = beg + 1; // skip first \0 size_t prev = 0; for( ; p != end; ++p ) { if( *p == 0 ) { const char *s = beg + prev + 1; if( *s ) { sin << s << std::endl; } else { sin << std::endl; } prev = p - beg; } } sin.close(); ret = true; } else if( s1 == "BINARY" ) { std::cerr << "BINARY is not handled" << std::endl; std::string fn = gdcm::LOComp::Trim( s0.c_str() ); // remove trailing space fn += ".bin"; std::ofstream out( fn.c_str() ); //out.write( bv->GetPointer() + 512, bv->GetLength() - 512); out.write( bv->GetPointer() , bv->GetLength() ); out.close(); #if 0 int array[ 128 ]; memcpy( array, bv->GetPointer(), 512 ); for( int i = 0; i < 14; ++i ) { std::cout << array[i] << std::endl; } #endif ret = true; } } // else -> ret == false assert( ret ); return ret; } int main(int argc, char *argv[]) { if( argc < 2 ) return 1; const char *filename = argv[1]; gdcm::Reader reader; reader.SetFileName( filename ); if( !reader.Read() ) { std::cerr << "Failed to read: " << filename << std::endl; return 1; } const gdcm::DataSet& ds = reader.GetFile().GetDataSet(); /* (2005,1132) SQ # u/l,1 ? (fffe,e000) na (Item with undefined length) (2005,0011) LO [Philips MR Imaging DD 002 ] # 26,1 Private Creator (2005,1137) PN (LO) [PDF_CONTROL_GEN_PARS] # 20,1 ? (2005,1138) PN (LO) (no value) # 0,1 ? (2005,1139) PN (LO) [IEEE_PDF] # 8,1 ? (2005,1140) PN (LO) (no value) # 0,1 ? (2005,1141) PN (LO) (no value) # 0,1 ? (2005,1143) SL 3103 # 4,1 ? (2005,1144) OW 66\05\00\00\3b\01\00\00\4a\0a\00\00\0e\00\00\00\7a\0a\00\00\95\01\00\00\08\00\00\00\1b\00\00\00\43\47\45\4e\5f\75\73\65\72\5f\64\65\66\5f\6b\65\79\5f\6e\61\6d\65\73\00\00\00\00\00\00\00\00\00 # 3104,1 ? (2005,1147) CS [Y ] # 2,1 ? (fffe,e00d) */ const gdcm::PrivateTag pt(0x2005,0x32,"Philips MR Imaging DD 002"); if( !ds.FindDataElement( pt ) ) return 1; const gdcm::DataElement &de = ds.GetDataElement( pt ); if( de.IsEmpty() ) return 1; gdcm::SequenceOfItems *sqi = de.GetValueAsSQ(); if ( !sqi ) return 1; gdcm::SequenceOfItems::SizeType s = sqi->GetNumberOfItems(); for( gdcm::SequenceOfItems::SizeType i = 1; i <= s; ++i ) { gdcm::Item &item = sqi->GetItem(i); gdcm::DataSet &nestedds = item.GetNestedDataSet(); if( !ProcessNested( nestedds ) ) { std::cerr << "Error processing Item #" << i << std::endl; } } return 0; }