DumpExamCard.cxx
/*=========================================================================
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 <iomanip>
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<uint32_t> 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 garbage 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
{
// <Attribute Name="CGEN_force_par_mode" Type="enum">0</Attribute>
os << " <Attribute";
os << " Name=\"" << name << "\"";
os << " Type=\"" << gettypenamefromtype(type) << "\"";
if( dim != 1 )
{
os << " ArraySize=\"" << dim << "\"";
}
os << ">";
printvalue( os, is );
os << "</Attribute>\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( "<ExamCardBlob>" );
std::string::size_type pos2 = dup.find( "</ExamCardBlob>" );
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<gdcm::VR::SL,gdcm::VM::VM1> 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 bookkeeping:
uint32_t nfloats = 0;
uint32_t nints = 0;
uint32_t nstrings = 0;
for( std::vector<param>::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;
}
gdcm::Base64::Decode
static size_t Decode(char *dst, size_t dlen, const char *src, size_t slen)
Decode a base64-formatted buffer.
gdcm::Base64::GetDecodeLength
static size_t GetDecodeLength(const char *src, size_t len)
gdcm::ByteValue
Class to represent binary value (array of bytes).
Definition gdcmByteValue.h:35
gdcm::ByteValue::GetPointer
const char * GetPointer() const
Definition gdcmByteValue.h:110
gdcm::ByteValue::GetLength
VL GetLength() const override
Definition gdcmByteValue.h:77
gdcm::DataElement
Class to represent a Data Element either Implicit or Explicit.
Definition gdcmDataElement.h:59
gdcm::DataElement::GetByteValue
const ByteValue * GetByteValue() const
Definition gdcmDataElement.h:133
gdcm::DataElement::IsEmpty
bool IsEmpty() const
Check if Data Element is empty.
Definition gdcmDataElement.h:106
gdcm::DataElement::GetValueAsSQ
SmartPointer< SequenceOfItems > GetValueAsSQ() const
gdcm::DataSet
Class to represent a Data Set (which contains Data Elements).
Definition gdcmDataSet.h:56
gdcm::DataSet::GetDataElement
const DataElement & GetDataElement(const Tag &t) const
Definition gdcmDataSet.h:188
gdcm::DataSet::FindDataElement
bool FindDataElement(const PrivateTag &t) const
Look up if private tag 't' is present in the dataset:
gdcm::Element
Element class.
Definition gdcmElement.h:70
gdcm::Element::GetValue
const VRToType< TVR >::Type & GetValue(unsigned int idx=0) const
Definition gdcmElement.h:94
gdcm::Element::SetFromDataElement
void SetFromDataElement(DataElement const &de)
Definition gdcmElement.h:110
gdcm::File::GetDataSet
const DataSet & GetDataSet() const
Get Data Set.
Definition gdcmFile.h:57
gdcm::Item
Class to represent an Item.
Definition gdcmItem.h:46
gdcm::Item::GetNestedDataSet
const DataSet & GetNestedDataSet() const
Definition gdcmItem.h:80
gdcm::PrivateTag
Class to represent a Private DICOM Data Element (Attribute) Tag (Group, Element, Owner).
Definition gdcmPrivateTag.h:39
gdcm::Reader
Reader ala DOM (Document Object Model).
Definition gdcmReader.h:54
gdcm::Reader::GetFile
const File & GetFile() const
Set/Get File.
Definition gdcmReader.h:72
gdcm::Reader::Read
virtual bool Read()
Main function to read a file.
gdcm::Reader::SetFileName
void SetFileName(const char *filename_native)
gdcm::SequenceOfItems
Class to represent a Sequence Of Items.
Definition gdcmSequenceOfItems.h:40
gdcm::SequenceOfItems::GetNumberOfItems
SizeType GetNumberOfItems() const
Definition gdcmSequenceOfItems.h:86
gdcm::SequenceOfItems::GetItem
const Item & GetItem(SizeType position) const
gdcm::SequenceOfItems::SizeType
ItemVector::size_type SizeType
Definition gdcmSequenceOfItems.h:44
gdcm::String<'\\', 64 >::Trim
std::string Trim() const
Definition gdcmString.h:96
gdcmBase64.h
gdcmDataSet.h
gdcmPrivateTag.h
gdcmReader.h
gdcm::terminal::dim
@ dim
Definition gdcmTerminal.h:52