Bläddra i källkod

Start integrating ctkDICOMDataset -- does not compile yet

Daniel Maleike 14 år sedan
förälder
incheckning
266544bd02
3 ändrade filer med 1225 tillägg och 1 borttagningar
  1. 3 1
      Libs/DICOM/Core/CMakeLists.txt
  2. 945 0
      Libs/DICOM/Core/ctkDICOMDataset.cpp
  3. 277 0
      Libs/DICOM/Core/ctkDICOMDataset.h

+ 3 - 1
Libs/DICOM/Core/CMakeLists.txt

@@ -20,7 +20,9 @@ SET(KIT_SRCS
   ctkDICOMQuery.cpp
   ctkDICOMQuery.h
   ctkDICOMRetrieve.cpp
-  ctkDICOMRetrieve.h
+  ctkDICOMRetrieve.cpp
+  ctkDICOMDataset.h
+  ctkDICOMDataset.cpp
   )
 
 # Headers that should run through moc

+ 945 - 0
Libs/DICOM/Core/ctkDICOMDataset.cpp

@@ -0,0 +1,945 @@
+
+#include "ctkDICOMDataset.h"
+
+#include <dctk.h>
+#include <dcostrmb.h>
+#include <dcistrmb.h>
+
+#include <stdexcept>
+
+
+class ctkDICOMDatasetPrivate
+{
+  public:
+
+    ctkDICOMDatasetPrivate() {}
+
+    QString m_SpecificCharacterSet;
+
+    bool m_DICOMDataSetInitialized;
+
+    static DcmDataDictionary* s_Dictionary;
+};
+
+DcmDataDictionary* ctkDICOMDatasetPrivate::s_Dictionary = new DcmDataDictionary(OFTrue, OFTrue);
+
+// ------------------------------------------- QPersonName -----------------------------------------------------------
+
+class QPersonNamePrivate
+{
+  public:
+
+    QPersonNamePrivate() {}
+
+    QString m_LastName;
+    QString m_FirstName;
+    QString m_MiddleName;
+    QString m_NamePrefix;
+    QString m_NameSuffix;
+};
+
+QPersonName::QPersonName(const QString& lastName,
+                         const QString& firstName,
+                         const QString& middleName,
+                         const QString& namePrefix,
+                         const QString& nameSuffix)
+:d_ptr(new QPersonNamePrivate)
+{
+  Q_D(QPersonName);
+  d->m_LastName = lastName;
+  d->m_FirstName = firstName;
+  d->m_MiddleName = middleName;
+  d->m_NamePrefix = namePrefix;
+  d->m_NameSuffix = nameSuffix;
+}
+
+QString QPersonName::GetFormattedName() const
+{
+  Q_D(const QPersonName);
+  QString result("");
+
+  /* not sortable
+  if (!m_NamePrefix.isEmpty())  result += QString("%1 ").arg(m_NamePrefix);
+  if (!m_FirstName.isEmpty())  result += QString("%1 " ).arg(m_FirstName);
+  if (!m_MiddleName.isEmpty()) result += QString("%1 ").arg(m_MiddleName);
+  if (!m_LastName.isEmpty())   result += QString("%1").arg(m_LastName);
+  if (!m_NameSuffix.isEmpty()) result += QString(", %1").arg(m_NameSuffix); // this might be unclean if last name is empty
+  */
+
+  if (!d->m_LastName.isEmpty())   result += QString("%1").arg(d->m_LastName);
+  if (!d->m_FirstName.isEmpty())  result += QString(", %1" ).arg(d->m_FirstName);
+  if (!d->m_MiddleName.isEmpty()) result += QString(" %1").arg(d->m_MiddleName);
+  if (!d->m_NameSuffix.isEmpty()) result += QString(", %1").arg(d->m_NameSuffix); // this might be unclean if last name is empty
+
+  return result;
+}
+
+QString QPersonName::GetLastName()  const
+{
+  Q_D(const QPersonName);
+  return d->m_LastName;
+}
+
+QString QPersonName::GetFirstName()  const
+{
+  Q_D(const QPersonName);
+  return d->m_FirstName;
+}
+
+QString QPersonName::GetMiddleName()  const
+{
+  Q_D(const QPersonName);
+  return d->m_MiddleName;
+}
+
+QString QPersonName::GetNamePrefix()  const
+{
+  Q_D(const QPersonName);
+  return d->m_NamePrefix;
+}
+
+QString QPersonName::GetNameSuffix()  const
+{
+  Q_D(const QPersonName);
+  return d->m_NameSuffix;
+}
+
+QPersonName::operator QString() const
+{
+  return this->GetFormattedName();
+}
+
+std::string QPersonName::toStdString() const
+{
+  // the complicated looking .toLocal8Bit().constData() is on purpose.
+  // if we'd use .toStdString(), the string would be converted to ASCII
+  // instead of the local 8bit character encoding.
+  // changes for correctly looking output of the strings are higher with .toLocal8Bit()
+  return this->GetFormattedName().toLocal8Bit().constData();
+}
+
+
+
+
+ctkDICOMDataset::ctkDICOMDataset() 
+:d_ptr(new ctkDICOMDatasetPrivate)
+{
+  Q_D(ctkDICOMDataset);
+  d->m_DICOMDataSetInitialized = false;
+}
+
+ctkDICOMDataset::~ctkDICOMDataset() 
+{
+}
+
+
+void ctkDICOMDataset::InitializeFromDataset(DcmDataset* dataset)
+{
+  Q_D(ctkDICOMDataset);
+  if (dataset)
+  {
+    if (!d->m_DICOMDataSetInitialized)
+    {
+      d->m_DICOMDataSetInitialized = true;
+      // remember "specific character set" tag for conversion of strings to the right encoding
+      //std::cerr << "** " << (void*)this << " ctkDICOMDataset: Initialized DcmDataset" << std::endl;
+      if ( CopyElement( dataset, DCM_SpecificCharacterSet, 3 ) )
+      {
+        OFString encoding;
+        if ( CheckCondition( findAndGetOFString(DCM_SpecificCharacterSet, encoding) ) )
+        {
+          d->m_SpecificCharacterSet = encoding.c_str();
+        }
+        else
+        {
+          std::cerr << "Some implementation error in DCMTK. We put something into a box and now the box is empty. This is not ok." << std::endl;
+          //throw std::logic_error("Some implementation error in DCMTK. We put something into a box and now the box is empty. This is not ok.");
+        }
+      }
+      if (d->m_SpecificCharacterSet.isEmpty())
+      {
+        /**
+          see Bug # 6458:
+          There are cases, where different studies of the same person get encoded both with and without the SpecificCharacterSet attribute set.
+          DICOM says: default is ASCII / ISO_IR 6 / ISO 646
+          Since we experienced such mixed data, we supplement missing characterset information with the ISO_IR 100 / Latin1 character set.
+          Since Latin1 is a superset of ASCII, this will not cause problems. PLUS in most cases (Europe) we will guess right and suppress
+          "double patients" in applications.
+        */
+        SetElementAsString( DCM_SpecificCharacterSet, "ISO_IR 100" );
+      }
+    }
+  }
+}
+
+
+
+void ctkDICOMDataset::Serialize()
+{
+  // store content of current DcmDataset (our parent) as QByteArray into m_ctkDICOMDataset
+  Uint32 buffersize = 1024*1024; // reserve 1MB
+  char* writebuffer = new char[buffersize];
+
+  // write into buffer
+  DcmOutputBufferStream dcmbuffer(writebuffer, buffersize);
+  DcmDataset::transferInit();
+  OFCondition condition = DcmDataset::write(dcmbuffer, EXS_LittleEndianImplicit, EET_UndefinedLength, NULL );
+  DcmDataset::transferEnd();
+  if ( condition.bad() )
+  {
+    std::cerr << "Could not DcmDataset::write(..): " << condition.text() << std::endl;
+  }
+
+  // get written contents of buffer
+  offile_off_t datasetsize = 0;
+  void* readbuffer = NULL;
+  dcmbuffer.flushBuffer(readbuffer, datasetsize);
+
+  //std::cerr << "** " << (void*)this << " ctkDICOMDataset: Serializing Dataset into " << datasetsize << " bytes" << std::endl;
+
+  // construct Qt type from that contents
+  QByteArray qtArray = QByteArray::fromRawData( static_cast<const char*>(readbuffer), datasetsize );
+  //std::cerr << "** Buffer size: " << qtArray.size() << std::endl;
+  QString stringbuffer = QString::fromAscii(qtArray.toBase64());
+
+  //std::cerr << "** String of size " << stringbuffer.size() << " looks like this:\n" << stringbuffer.toStdString() << std::endl << std::endl;
+
+  this->SetStoredSerialization( stringbuffer );
+
+  delete[] writebuffer;
+}
+
+void ctkDICOMDataset::MarkForInitialization()
+{
+  Q_D(ctkDICOMDataset);
+  d->m_DICOMDataSetInitialized = false;
+}
+
+void ctkDICOMDataset::EnsureDcmDataSetIsInitialized() const
+{
+  const_cast<ctkDICOMDataset*>(this)->Deserialize();
+}
+
+void ctkDICOMDataset::Deserialize()
+{
+  Q_D(ctkDICOMDataset);
+  // read attribute m_ctkDICOMDataset
+  // construct a DcmDataset from it
+  // calls InitializeData(DcmDataset*)
+
+  // this method can be called both from sub-classes when they get the InitializeData signal from the persistence framework
+  // and from EnsureDcmDataSetIsInitialized() when a GetElement.. or SetElement.. method is called.
+
+  if (d->m_DICOMDataSetInitialized) return; // only need to do this once
+
+  QString stringbuffer = this->GetStoredSerialization();
+  if ( stringbuffer.isEmpty() )
+  {
+    d->m_DICOMDataSetInitialized = true;
+    return; // TODO nicer: hold three states: newly created / loaded but not initialized / restored from DB
+  }
+
+
+  //std::cerr << "** " << (void*)this << " ctkDICOMDataset: Deserialize Dataset from string of size " << stringbuffer.size() << "\n" << stringbuffer.toStdString() << std::endl;
+
+  QByteArray qtArray = QByteArray::fromBase64( stringbuffer.toAscii() );
+  //std::cerr << "** " << (void*)this << " ctkDICOMDataset: Deserialize Dataset from byte array of size " << qtArray.size() << std::endl;
+
+  DcmInputBufferStream dcmbuffer;
+  dcmbuffer.setBuffer( qtArray.data(), qtArray.size() );
+  //std::cerr << "** Buffer state: " << dcmbuffer.status().code() << " " <<  dcmbuffer.good() << " " << dcmbuffer.eos() << " tell " << dcmbuffer.tell() << " avail " << dcmbuffer.avail() << std::endl;
+
+  DcmDataset dataset;
+  dataset.transferInit();
+  //std::cerr << "** Dataset state: " << dataset.transferState() << std::endl << std::endl;
+  OFCondition condition = dataset.read( dcmbuffer, EXS_LittleEndianImplicit );
+  dataset.transferEnd();
+
+  // do this in all cases, even when reading reported an error
+  this->InitializeFromDataset(&dataset);
+
+  if ( condition.bad() )
+  {
+    std::cerr << "** Condition code of Dataset::read() is " << condition.code() << std::endl;
+    std::cerr << "** Buffer state: " << dcmbuffer.status().code() << " " <<  dcmbuffer.good() << " " << dcmbuffer.eos() << " tell " << dcmbuffer.tell() << " avail " << dcmbuffer.avail() << std::endl;
+    std::cerr << "** Dataset state: " << dataset.transferState() << std::endl;
+    std::cerr << std::string("Could not DcmDataset::read(..): ") + condition.text() << std::endl;
+    //throw std::invalid_argument( std::string("Could not DcmDataset::read(..): ") + condition.text() );
+  }
+}
+
+
+OFCondition ctkDICOMDataset::findAndGetElement(const DcmTag& tag, DcmElement*& element, const OFBool searchIntoSub) const
+{
+  // this one const_cast allows us to declare quite a lot of methods nicely with const
+  return ((DcmDataset&)(const_cast<ctkDICOMDataset&>(*this))).findAndGetElement(tag, element, searchIntoSub);
+}
+
+OFCondition ctkDICOMDataset::findAndGetOFString(const DcmTag& tag, OFString& value, const unsigned long pos, const OFBool searchIntoSub) const
+{
+  // this second const_cast allows us to declare quite a lot of methods nicely with const
+  return ((DcmDataset&)(const_cast<ctkDICOMDataset&>(*this))).findAndGetOFString(tag, value, pos, searchIntoSub);
+}
+
+bool ctkDICOMDataset::CheckCondition(const OFCondition& condition)
+{
+  if ( condition.bad() )
+  {
+    //std::cerr << "Bad return code (" << condition.code() << "): " << condition.text() << std::endl;
+  }
+
+  return condition.good();
+}
+
+bool ctkDICOMDataset::CopyElement( DcmDataset* dataset, const DcmTagKey& tag, int type )
+{
+  switch (type)
+  {
+    case 0x1:
+    case 0x1C:
+    case 0x2:
+    case 0x2C:
+    case 0x3:
+      // these are ok
+      break;
+    default:
+      // nothing else is ok
+      std::cerr << "Unknown attribute type. Cannot process call to ExtractElement " << TagKey(tag).toStdString() << std::endl;
+      return false;
+  }
+
+  bool missing(false);
+  bool copied(true);
+
+  if (!dataset) return false;
+
+  // type 1 or 1C must exist AND have a value
+  if (!dataset->tagExistsWithValue( tag ))
+  {
+    if (type == 0x1 || type == 0x1C) missing = true;
+  }
+
+  // type 2 or 2C must exist but may have an empty value
+  if (!dataset->tagExists( tag ))
+  {
+    if (type == 0x1 || type == 0x1C) missing = true;
+    if (type == 0x2 || type == 0x2C) missing = true;
+  }
+  else
+  {
+    // we found this tag
+    DcmElement* element(NULL);
+    dataset->findAndGetElement( tag, element, OFFalse, OFTrue ); // OFTrue is important (copies element), DcmDataset takes ownership and deletes elements on its own destruction
+    if (element)
+    {
+      copied = CheckCondition( DcmDataset::insert(element) );
+    }
+  }
+
+  if (missing)
+  {
+    std::cerr << "Tag " << TagKey(tag).toStdString() << " [" << TagDescription(tag).toStdString() << "] of type " << QString("%1").arg(type,0,16).toStdString() << " missing or empty." << std::endl;
+  }
+
+  if (!copied)
+  {
+    std::cerr << "Tag " << TagKey(tag).toStdString() << " [" << TagDescription(tag).toStdString() << "] not copied successfully" << std::endl;
+  }
+
+  return !missing && copied;
+}
+
+QString ctkDICOMDataset::Decode( const DcmTag& tag, const OFString& raw ) const
+{
+  Q_D(const ctkDICOMDataset);
+  // decode for types LO, LT, PN, SH, ST, UT
+  QString vr = TagVR(tag);
+  if ( !d->m_SpecificCharacterSet.isEmpty()
+    && (vr == "LO" ||
+        vr == "LT" ||
+        vr == "PN" ||
+        vr == "SH" ||
+        vr == "ST" ||
+        vr == "UT" ) )
+  {
+    //std::cout << "Decode from encoding " << d->m_SpecificCharacterSet.toStdString() << std::endl;
+    static QMap<QString, QTextDecoder*> decoders;
+    static QMap<QString, QString> qtEncodingNamesForDICOMEncodingNames;
+
+    if (qtEncodingNamesForDICOMEncodingNames.isEmpty())
+    {
+      // runs only once and fills up a map of encoding names that might be named in DICOM files.
+      // for each encoding we store the name that Qt uses for the same encoding.
+      // This is because there is not yet a standard naming scheme but lots of aliases
+      // out in the real world: e.g. http://www.openi18n.org/subgroups/sa/locnameguide/final/CodesetAliasTable.html
+
+                                              //    DICOM        Qt
+      qtEncodingNamesForDICOMEncodingNames.insert("ISO_IR 192", "UTF-8");
+      qtEncodingNamesForDICOMEncodingNames.insert("ISO_IR 100", "ISO-8859-1");
+      qtEncodingNamesForDICOMEncodingNames.insert("ISO_IR 101", "ISO-8859-2");
+      qtEncodingNamesForDICOMEncodingNames.insert("ISO_IR 109", "ISO-8859-3");
+      qtEncodingNamesForDICOMEncodingNames.insert("ISO_IR 110", "ISO-8859-4");
+      qtEncodingNamesForDICOMEncodingNames.insert("ISO_IR 144", "ISO-8859-5");
+      qtEncodingNamesForDICOMEncodingNames.insert("ISO_IR 127", "ISO-8859-6");
+      qtEncodingNamesForDICOMEncodingNames.insert("ISO_IR 126", "ISO-8859-7");
+      qtEncodingNamesForDICOMEncodingNames.insert("ISO_IR 138", "ISO-8859-8");
+      qtEncodingNamesForDICOMEncodingNames.insert("ISO_IR 148", "ISO-8859-9");
+      qtEncodingNamesForDICOMEncodingNames.insert("ISO_IR 179", "ISO-8859-13");
+      qtEncodingNamesForDICOMEncodingNames.insert("ISO 2022 IR 13", "jisx0201*-0");
+
+      // use all names that Qt knows by itself
+      foreach( QByteArray c, QTextCodec::availableCodecs() )
+      {
+        qtEncodingNamesForDICOMEncodingNames.insert( c.constData(), c.constData() );
+      }
+
+    }
+
+    if ( qtEncodingNamesForDICOMEncodingNames.contains(d->m_SpecificCharacterSet) )
+    {
+      QString encodingName( qtEncodingNamesForDICOMEncodingNames[d->m_SpecificCharacterSet] );
+      if ( !decoders.contains( encodingName ) )
+      {
+        QTextCodec* codec = QTextCodec::codecForName( encodingName.toAscii() );
+        if (!codec)
+        {
+          std::cerr << "Could not create QTextCodec object for '" << encodingName.toStdString() << "'. Using default encoding instead." << std::endl;
+          decoders.insert( encodingName, QTextCodec::codecForCStrings()->makeDecoder() ); // uses Latin1
+        }
+        else
+        {
+          // initialize a QTextDecoder for given encoding
+          decoders.insert( encodingName, codec->makeDecoder() );
+          // We are responsible for deleting the QTextDecoder objects
+          // created by makeDecoder(). BUT as these objects are stored
+          // in a static map that lives until application end AND
+          // nothing application relevant happens during their
+          // destruction, we just let them be destructed by C++ on
+          // application exit.
+          // Any potential leaks that are found by this behavior can
+          // be suppressed.
+        }
+      }
+
+      //std::cout << "Decode '" <<  raw.c_str() << "' to '" << decoders[encodingName]->toUnicode( raw.c_str() ).toLocal8Bit().constData() << "'" << std::endl;
+      return decoders[encodingName]->toUnicode( raw.c_str() );
+    }
+    else
+    {
+      std::cerr << "DICOM dataset contains some encoding that we never thought we would see(" << d->m_SpecificCharacterSet.toStdString() << "). Using default encoding." << std::endl;
+    }
+  }
+
+  return QString::fromLatin1(raw.c_str()); // Latin1 is ISO 8859, which is the default character set of DICOM (PS 3.5-2008, Page 18)
+
+}
+
+OFString ctkDICOMDataset::Encode( const DcmTag& tag, const QString& qstring ) const
+{
+  // TODO: respect given character-set when encoding; see Decode()
+
+  return OFString( qstring.toLatin1().data() ); // Latin1 is ISO 8859, which is the default character set of DICOM (PS 3.5-2008, Page 18)
+}
+    
+QString ctkDICOMDataset::GetAllElementValuesAsString( const DcmTag& tag ) const
+{
+  this->EnsureDcmDataSetIsInitialized();
+
+  QStringList qsl;
+
+  DcmElement* element(NULL);
+  findAndGetElement(tag, element);
+  if (!element) return QString::null;
+
+  const unsigned long count = element->getVM(); // value multiplicity
+  for (unsigned long i = 0; i < count; ++i)
+  {
+    OFString s;
+    if ( CheckCondition( const_cast<ctkDICOMDataset*>(this)->findAndGetOFString(tag, s, i) ) )
+    {
+      qsl << Decode( tag, s );
+    }
+  }
+
+  return qsl.join("|");
+}
+
+
+QString ctkDICOMDataset::GetElementAsString( const DcmTag& tag, unsigned long pos ) const
+{
+  this->EnsureDcmDataSetIsInitialized();
+
+  OFString s;
+  if ( CheckCondition( findAndGetOFString(tag, s, pos) ) )
+  {
+    return Decode( tag, s );
+  }
+  else
+  {
+    return QString::null;
+  }
+}
+
+QStringList ctkDICOMDataset::GetElementAsStringList( const DcmTag& tag ) const
+{
+  this->EnsureDcmDataSetIsInitialized();
+  QStringList qsl;
+
+  DcmElement* element(NULL);
+  findAndGetElement(tag, element);
+  if (!element) return qsl;
+
+  const unsigned long count = element->getVM(); // value multiplicity
+  for (unsigned long i = 0; i < count; ++i)
+  {
+    qsl << GetElementAsString(tag, i);
+  }
+
+  return qsl;
+}
+
+QPersonName ctkDICOMDataset::GetElementAsPersonName( const DcmTag& tag, unsigned long pos ) const
+{
+  this->EnsureDcmDataSetIsInitialized();
+  DcmElement* element(NULL);
+  findAndGetElement(tag, element);
+
+  DcmPersonName* name = dynamic_cast<DcmPersonName*>(element);
+
+  if (!name) return QPersonName(); // invalid
+
+  OFString lastName;
+  OFString firstName;
+  OFString middleName;
+  OFString namePrefix;
+  OFString nameSuffix;
+  if (CheckCondition( name->getNameComponents(lastName, firstName, middleName, namePrefix, nameSuffix, pos) ) )
+  {
+    return QPersonName(
+      Decode(tag, lastName),
+      Decode(tag, firstName),
+      Decode(tag, middleName),
+      Decode(tag, namePrefix),
+      Decode(tag, nameSuffix) );
+  }
+  else
+  {
+    return QPersonName();
+  }
+}
+
+QPersonNameList ctkDICOMDataset::GetElementAsPersonNameList( const DcmTag& tag ) const
+{
+  this->EnsureDcmDataSetIsInitialized();
+  QPersonNameList qpnl;
+
+  DcmElement* element(NULL);
+  findAndGetElement(tag, element);
+  if (!element) return qpnl;
+
+  const unsigned long count = element->getVM(); // value multiplicity
+  for (unsigned long i = 0; i < count; ++i)
+  {
+    qpnl << GetElementAsPersonName(tag, i);
+  }
+
+  return qpnl;
+}
+
+QDate ctkDICOMDataset::GetElementAsDate( const DcmTag& tag, unsigned long pos ) const
+{
+  this->EnsureDcmDataSetIsInitialized();
+  DcmElement* element(NULL);
+  findAndGetElement(tag, element);
+
+  DcmDate* date = dynamic_cast<DcmDate*>(element);
+
+  if (!date) return QDate(); // invalid
+
+  OFString ofs;
+  if (CheckCondition( date->getISOFormattedDate(ofs, pos) ) )
+  {
+    QString qs(ofs.c_str());
+    return QDate::fromString(qs, "yyyy-MM-dd");
+  }
+  else
+  {
+    return QDate();
+  }
+}
+
+QTime ctkDICOMDataset::GetElementAsTime( const DcmTag& tag, unsigned long pos ) const
+{
+  this->EnsureDcmDataSetIsInitialized();
+  DcmElement* element(NULL);
+  findAndGetElement(tag, element);
+
+  DcmTime* time = dynamic_cast<DcmTime*>(element);
+
+  if (!time) return QTime(); // invalid
+
+  OFString ofs;
+  if (CheckCondition( time->getISOFormattedTime(ofs, pos, OFTrue, OFFalse) ) ) // true (seconds), false (fraction of a second)
+  {
+    QString qs(ofs.c_str());
+    return QTime::fromString(qs, "hh:mm:ss");
+  }
+  else
+  {
+    return QTime();
+  }
+}
+
+QDateTime ctkDICOMDataset::GetElementAsDateTime( const DcmTag& tag, unsigned long pos ) const
+{
+  this->EnsureDcmDataSetIsInitialized();
+  DcmElement* element(NULL);
+  findAndGetElement(tag, element);
+
+  DcmDateTime* datetime = dynamic_cast<DcmDateTime*>(element);
+
+  if (!datetime) return QDateTime(); // invalid
+
+  OFString ofs;
+  if (CheckCondition( datetime->getISOFormattedDateTime(ofs, pos, OFTrue, OFFalse, OFTrue) ) ) // true (seconds), false (fraction of a second), true (time zone)
+  {
+    QString qs(ofs.c_str());
+    return QDateTime::fromString(qs, "dd-MM-yyy hh:mm:ss");
+  }
+  else
+  {
+    return QDateTime();
+  }
+}
+
+double ctkDICOMDataset::GetElementAsDouble( const DcmTag& tag, unsigned long pos ) const
+{
+  this->EnsureDcmDataSetIsInitialized();
+  DcmElement* element(NULL);
+  findAndGetElement(tag, element);
+
+  DcmDecimalString* ds = dynamic_cast<DcmDecimalString*>(element);
+
+  if (!ds) throw std::logic_error("Element not found or not a decimal number");
+
+  Float64 d;
+  ds->getFloat64(d, pos);
+
+  return d;
+}
+
+long ctkDICOMDataset::GetElementAsInteger( const DcmTag& tag, unsigned long pos ) const
+{
+  this->EnsureDcmDataSetIsInitialized();
+  DcmElement* element(NULL);
+  findAndGetElement(tag, element);
+
+  DcmIntegerString* is = dynamic_cast<DcmIntegerString*>(element);
+
+  if (!is) throw std::logic_error("Element not found or not an integer");
+
+  Sint32 i;
+  is->getSint32(i, pos);
+
+  return i;
+}
+
+int ctkDICOMDataset::GetElementAsSignedShort( const DcmTag& tag, unsigned long pos ) const // type SS
+{
+  this->EnsureDcmDataSetIsInitialized();
+  DcmElement* element(NULL);
+  findAndGetElement(tag, element);
+
+  DcmSignedShort* ss = dynamic_cast<DcmSignedShort*>(element);
+
+  if (!ss) throw std::logic_error("Element not found or not a signed short integer");
+
+  Sint16 i;
+  ss->getSint16(i, pos);
+
+  return i;
+}
+
+int ctkDICOMDataset::GetElementAsUnsignedShort( const DcmTag& tag, unsigned long pos ) const // type US
+{
+  this->EnsureDcmDataSetIsInitialized();
+  DcmElement* element(NULL);
+  findAndGetElement(tag, element);
+
+  DcmUnsignedShort* us = dynamic_cast<DcmUnsignedShort*>(element);
+
+  if (!us) throw std::logic_error("Element not found or not a unsigned short integer");
+
+  Uint16 i;
+  us->getUint16(i, pos);
+
+  return i;
+}
+
+bool ctkDICOMDataset::SetElementAsString( const DcmTag& tag, QString string )
+{
+  this->EnsureDcmDataSetIsInitialized();
+  // TODO: Evaluate DICOM tag for proper encoding (see GetElementAsString())
+  return CheckCondition( putAndInsertString( tag, string.toLatin1().data() ) );
+}
+
+bool ctkDICOMDataset::SetElementAsStringList( const DcmTag& /*tag*/, QStringList /*stringList*/ )
+{
+  this->EnsureDcmDataSetIsInitialized();
+  // TODO: Find out how this can be implemented with DcmDataset methods; there is no method for
+  // setting a string at a given position
+  return false;
+}
+
+bool ctkDICOMDataset::SetElementAsPersonName( const DcmTag& tag, QPersonName personName )
+{
+  this->EnsureDcmDataSetIsInitialized();
+  DcmPersonName* dcmPersonName = new DcmPersonName( tag ); // TODO leak?
+
+  if ( CheckCondition( dcmPersonName->putNameComponents(
+    Encode( tag, personName.GetLastName() ),
+    Encode( tag, personName.GetFirstName() ),
+    Encode( tag, personName.GetMiddleName() ),
+    Encode( tag, personName.GetNamePrefix() ),
+    Encode( tag, personName.GetNameSuffix() ) ) ) )
+  {
+    return CheckCondition( insert( dcmPersonName ) );
+  }
+
+  return false;
+}
+
+bool ctkDICOMDataset::SetElementAsPersonNameList( const DcmTag& tag, QPersonNameList personNameList )
+{
+  this->EnsureDcmDataSetIsInitialized();
+  // TODO: Find out how this can be implemented with DcmDataset methods; there is no method for
+  // setting an element at a given position
+  return false;
+}
+
+bool ctkDICOMDataset::SetElementAsDate( const DcmTag& tag, QDate date )
+{
+  this->EnsureDcmDataSetIsInitialized();
+  OFDate ofDate( date.year(), date.month(), date.day() );
+  DcmDate* dcmDate = new DcmDate( tag ); // TODO leak?
+
+  if ( CheckCondition( dcmDate->setOFDate( ofDate ) ) )
+  {
+    return CheckCondition( insert( dcmDate ) );
+  }
+
+  return false;
+}
+
+bool ctkDICOMDataset::SetElementAsTime( const DcmTag& tag, QTime time )
+{
+  this->EnsureDcmDataSetIsInitialized();
+  OFTime ofTime( time.hour(), time.minute(), time.second() );
+  DcmTime* dcmTime = new DcmTime( tag ); // TODO leak?
+
+  if ( CheckCondition( dcmTime->setOFTime( ofTime ) ) )
+  {
+    return CheckCondition( insert( dcmTime ) );
+  }
+
+  return false;
+}
+
+bool ctkDICOMDataset::SetElementAsDateTime( const DcmTag& tag, QDateTime dateTime )
+{
+  this->EnsureDcmDataSetIsInitialized();
+  QDate date = dateTime.date();
+  QTime time = dateTime.time();
+
+  OFDateTime ofDateTime;
+  ofDateTime.setDateTime( date.year(), date.month(), date.day(), time.hour(), time.minute(), time.second() );
+  DcmDateTime* dcmDateTime = new DcmDateTime( tag ); // TODO leak?
+
+  if ( CheckCondition( dcmDateTime->setOFDateTime( ofDateTime ) ) )
+  {
+    return CheckCondition( insert( dcmDateTime ) );
+  }
+
+  return false;
+}
+
+bool ctkDICOMDataset::SetElementAsInteger( const DcmTag& tag, long value, unsigned long pos )
+{
+  this->EnsureDcmDataSetIsInitialized();
+  //std::cerr << "TagVR: " << TagVR( tag ).toStdString() << std::endl;
+  return CheckCondition( putAndInsertSint32( tag, value, pos ) );
+}
+
+bool ctkDICOMDataset::SetElementAsSignedShort( const DcmTag& tag, int value, unsigned long pos )
+{
+  this->EnsureDcmDataSetIsInitialized();
+  //std::cerr << "TagVR: " << TagVR( tag ).toStdString() << std::endl;
+  return CheckCondition( putAndInsertSint16( tag, value, pos ) );
+}
+
+bool ctkDICOMDataset::SetElementAsUnsignedShort( const DcmTag& tag, int value, unsigned long pos )
+{
+  this->EnsureDcmDataSetIsInitialized();
+  //std::cerr << "TagVR: " << TagVR( tag ).toStdString() << std::endl;
+  return CheckCondition( putAndInsertUint16( tag, value, pos ) );
+}
+
+QString ctkDICOMDataset::TranslateDefinedTermPatientPosition( const QString& dt )
+{
+  static bool initialized = false;
+  static QMap<QString, QString> descriptionOfTerms;
+  if (!initialized)
+  {
+    descriptionOfTerms.insert("HFP",  "Head First - Prone");
+    descriptionOfTerms.insert("HFDR", "Head First - Decubitus Right");
+    descriptionOfTerms.insert("FFDR", "Feet First - Decubitus Right");
+    descriptionOfTerms.insert("FFP",  "Feet First - Prone");
+    descriptionOfTerms.insert("HFS",  "Head First - Supine");
+    descriptionOfTerms.insert("HFDL", "Head First - Decubitus Left");
+    descriptionOfTerms.insert("FFDL", "Feet First - Decubitus Left");
+    descriptionOfTerms.insert("FFS",  "Feet First - Supine");
+    initialized = true;
+  }
+
+  if ( descriptionOfTerms.contains( dt.toUpper() ) )
+  {
+    return descriptionOfTerms.value(dt.toUpper());
+  }
+  else
+  {
+    std::cerr << "Invalid enum for patient position" << std::endl;
+    return QString::null;
+  }
+}
+
+QString ctkDICOMDataset::TranslateDefinedTermModality( const QString& dt )
+{
+  static bool initialized = false;
+  static QMap<QString, QString> descriptionOfTerms;
+  if (!initialized)
+  {
+    descriptionOfTerms.insert("CR",  "Computed Radiography");
+    descriptionOfTerms.insert("CT",  "Computed Tomography");
+    descriptionOfTerms.insert("MR",  "Magnetic Resonance");
+    descriptionOfTerms.insert("NM",  "Nuclear Medicine");
+    descriptionOfTerms.insert("US",  "Ultrasound");
+    descriptionOfTerms.insert("OT",  "Other");
+    descriptionOfTerms.insert("BI",  "Biomagnetic imaging");
+    descriptionOfTerms.insert("CD",  "Color flow Doppler");
+    descriptionOfTerms.insert("DD",  "Duplex Doppler");
+    descriptionOfTerms.insert("ES",  "Endoscopy");
+    descriptionOfTerms.insert("LS",  "Laser surface scan");
+    descriptionOfTerms.insert("PT",  "Positron emission tomography (PET)");
+    descriptionOfTerms.insert("RG",  "Radiographic imaging (conventional film/screen)");
+    descriptionOfTerms.insert("ST",  "Single-photon emission computed tomograpy (SPECT)");
+    descriptionOfTerms.insert("TG",  "Thermography");
+    descriptionOfTerms.insert("XA",  "X-Ray Aniography");
+    descriptionOfTerms.insert("RF",  "Radio Fluoroscopy");
+    descriptionOfTerms.insert("RTIMAGE",  "Radiotherapy Image");
+    descriptionOfTerms.insert("RTDOSE",  "Radiotherapy Dose");
+    descriptionOfTerms.insert("RTSTRUCT",  "Radiotherapy Structure Set");
+    descriptionOfTerms.insert("RTPLAN",  "Radiotherapy Plan");
+    descriptionOfTerms.insert("RTRECORD",  "RT Treatment Record");
+    descriptionOfTerms.insert("HC",  "Hard Copy");
+    descriptionOfTerms.insert("DX",  "Digital Radiography");
+    descriptionOfTerms.insert("MG",  "Mammography");
+    descriptionOfTerms.insert("IO",  "Intra-oral Radiography");
+    descriptionOfTerms.insert("PX",  "Panoramic X-Ray");
+    descriptionOfTerms.insert("GM",  "General Microscopy");
+    descriptionOfTerms.insert("SM",  "Slide Microscopy");
+    descriptionOfTerms.insert("XC",  "External-camera Photography");
+    descriptionOfTerms.insert("PR",  "Presentation state");
+    descriptionOfTerms.insert("AU",  "Audio");
+    descriptionOfTerms.insert("ECG",  "Electrocardiography");
+    descriptionOfTerms.insert("EPS",  "Cardiac Electrophysiology");
+    descriptionOfTerms.insert("HD",  "Hemodynamic Waveform");
+    descriptionOfTerms.insert("SR",  "SR Document");
+    descriptionOfTerms.insert("IVUS",  "Intravascular Ultrasound");
+    descriptionOfTerms.insert("OP",  "Ophthalmic Photography");
+    descriptionOfTerms.insert("SMR",  "Stereometric Relationship");
+    descriptionOfTerms.insert("OCT",  "Optical Coherence Tomography (non-Ophthalmic)");
+    descriptionOfTerms.insert("OPR",  "Ophthalmic Refraction");
+    descriptionOfTerms.insert("OPV",  "Ophthalmic Visual Field");
+    descriptionOfTerms.insert("OPM",  "Ophthalmic Mapping");
+    descriptionOfTerms.insert("KO",  "Key Object Selection");
+    descriptionOfTerms.insert("SEG",  "Segmentation");
+    descriptionOfTerms.insert("REG",  "Registration");
+    descriptionOfTerms.insert("OPT",  "Ophthalmic Tomography");
+    descriptionOfTerms.insert("BDUS",  "Bone Densitometry (ultrasound)");
+    descriptionOfTerms.insert("BMD",  "Bone Densitometry (X-Ray)");
+    descriptionOfTerms.insert("DOC",  "Document");
+
+    // retired terms (but probably still in use)
+    descriptionOfTerms.insert("DS",  "Digital Subtraction Angiography");
+    descriptionOfTerms.insert("CF",  "Cinefluorography");
+    descriptionOfTerms.insert("DF",  "Digital fluoroscopy");
+    descriptionOfTerms.insert("VF",  "Videofluorography");
+    descriptionOfTerms.insert("AS",  "Angioscopy");
+    descriptionOfTerms.insert("CS",  "Cystoscopy");
+    descriptionOfTerms.insert("EC",  "Echocardiography");
+    descriptionOfTerms.insert("LP",  "Laparoscopy");
+    descriptionOfTerms.insert("FA",  "Fluorescein angiography ");
+    descriptionOfTerms.insert("CP",  "Culposcopy");
+    descriptionOfTerms.insert("DM",  "Digital microscopy");
+    descriptionOfTerms.insert("FS",  "Fundoscopy");
+    descriptionOfTerms.insert("MA",  "Magnetic resonance angiography");
+    descriptionOfTerms.insert("MS",  "Magnetic resonance spectroscopy");
+    initialized = true;
+  }
+
+  if ( descriptionOfTerms.contains( dt.toUpper() ) )
+  {
+    return descriptionOfTerms.value(dt.toUpper());
+  }
+  else
+  {
+    std::cerr << "Invalid enum for patient position" << std::endl;
+    return QString::null;
+  }
+}
+
+QString ctkDICOMDataset::TagKey( const DcmTag& tag )
+{
+  return QString("(%1,%2)").arg( tag.getGroup(), 4, 16, QLatin1Char('0')).arg( tag.getElement(), 4, 16, QLatin1Char('0') );
+}
+
+QString ctkDICOMDataset::TagDescription( const DcmTag& tag )
+{
+  if (!ctkDICOMDatasetPrivate::s_Dictionary->isDictionaryLoaded()) return QString("<no DICOM dictionary loaded. application broken>");
+  const DcmDictEntry* entry = ctkDICOMDatasetPrivate::s_Dictionary->findEntry(tag, NULL);
+  if (entry)
+  {
+    return QString(entry->getTagName());
+  }
+  else
+  {
+    return QString("<unknown>");
+  }
+}
+
+QString ctkDICOMDataset::TagVR( const DcmTag& tag )
+{
+  if (!ctkDICOMDatasetPrivate::s_Dictionary->isDictionaryLoaded()) return QString("<no DICOM dictionary loaded. application broken>");
+  const DcmDictEntry* entry = ctkDICOMDatasetPrivate::s_Dictionary->findEntry(tag, NULL);
+  if (entry)
+  {
+    return QString(entry->getVR().getVRName());
+  }
+  else
+  {
+    return QString("UN"); // unknown
+  }
+}
+
+QString ctkDICOMDataset::GetStoredSerialization()
+{
+  throw std::runtime_error("No serialization implemented for this object!");
+}
+
+
+
+void ctkDICOMDataset::SetStoredSerialization(QString serializedDataset)
+{
+  throw std::runtime_error("No serialization implemented for this object!");
+}
+

+ 277 - 0
Libs/DICOM/Core/ctkDICOMDataset.h

@@ -0,0 +1,277 @@
+/*=========================================================================
+
+  Library:   CTK
+
+  Copyright (c) Mint Medical GmbH
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+      http://www.commontk.org/LICENSE
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+=========================================================================*/
+
+#ifndef __ctkDICOMDataset_h
+#define __ctkDICOMDataset_h
+
+#include "ctkDICOMCoreExport.h"
+
+#ifndef WIN32
+  #define HAVE_CONFIG_H
+#endif
+#include <dcdatset.h> // DCMTK DcmDataset
+
+#include <QtCore>
+
+class DcmDataDictionary;
+
+class QPersonNamePrivate;
+/**
+  \brief A person's name as modelled in DICOM.
+*/
+class CTK_DICOM_CORE_EXPORT QPersonName
+{
+  public:
+
+    QPersonName(const QString& lastName = QString::null,
+                const QString& firstName = QString::null,
+                const QString& middleName = QString::null,
+                const QString& namePrefix = QString::null,
+                const QString& nameSuffix = QString::null);
+
+    /**
+      \brief "Lastname, FirstName MiddleName, Suffix" (useful for alphabetical sorting)
+    */
+    QString GetFormattedName() const;
+
+    QString GetLastName() const;
+    QString GetFirstName() const;
+    QString GetMiddleName() const;
+    QString GetNamePrefix() const;
+    QString GetNameSuffix() const;
+ 
+    /// cast operator
+    operator QString() const;
+    std::string toStdString() const;
+
+  private:
+    QScopedPointer<QPersonNamePrivate> d_ptr;
+    Q_DECLARE_PRIVATE(QPersonName);
+};
+
+typedef QList<QPersonName> QPersonNameList;
+Q_DECLARE_METATYPE(QPersonName);
+
+class ctkDICOMDatasetPrivate;
+/**
+  \brief Base class for DICOM objects and interface with DICOM toolkit DCMTK.
+
+  This class serves as the base class for all DICOM objects (patient, study, series, image).
+
+  The class is derived from DcmDataset, the data type that is used by the DICOM toolkit when
+  reading an image file or formulating a message request or receiving a message response (e.g. C-FIND).
+
+  Basically it offers a lot of convenience methods for subclasses to read and write DICOM attributes
+  using Qt types. It is the subclasses responsibility to use the correct data types as defined in DICOM.
+
+  \note ONLY the Get.. methods should be used to access the internal dataset.
+
+  When reading string type attributes (LO, LT, PN, SH, ST, UT), we consider the "specific character set"
+  tag and decode the stored string using the correct encoding (using Qt methods). This allows to
+  display e.g. person names written in arabic, hebrew, greek, russian, etc. letters.
+
+  \warning Right now, asian phonetic strings cause problems. We have to learn the concept.
+  \warning Helpers for writing DICOM attributes are not yet implemented. Implementation is straightforward though and can be done when necessary.
+  \warning DateTime objects ignore the timezone at the moment. This is however of secondary importance.
+
+  A subclass could possibly want to store the internal DcmDataset.
+  For this purpose, the internal DcmDataset is serialized into a memory buffer using DcmDataset::write(..). This buffer
+  is stored in a base64 encoded string. For deserialization we decode the string and use DcmDataset::read(..).
+*/
+class CTK_DICOM_CORE_EXPORT ctkDICOMDataset : public DcmDataset
+{
+public:
+    typedef QObject Superclass;
+    ctkDICOMDataset();
+    virtual ~ctkDICOMDataset();
+
+    /**
+      \brief For initialization from a DcmDataset in a constructor / assignment.
+
+      This method should be overwritten by all derived classes. It should
+      be called from the constructor or assignment operators when the class
+      should copy information from a DcmDataset object.
+
+      \warning Derived classes must call PDICOMDataset::InitializeFromDataset(...) to correctly copy encoding information.
+    */
+    virtual void InitializeFromDataset(DcmDataset* dataset);
+
+    /**
+     \brief Store a string representation of the object to a database field.
+
+     The internal DcmDataset is serialized into a memory buffer using DcmDataset::write(..).
+     To store the memory buffer in a simple string database field, we convert it to a base64 encoded string.
+     Doing so prevents errors from encoding conversions that could be made by QString or the database etc.
+    */
+    void Serialize();
+
+    /**
+     \brief Restore the object from a string representation in a database field.
+
+     The database stored string is base64 decoded into a memory buffer. Then
+     the internal DcmDataset is created using DcmDataset::read(..).
+    */
+    void Deserialize();
+
+
+    /**
+      \brief To be called from InitializeData, flags status as dirty.
+
+      This is to allow data to be read as late as possible. All the
+      Get/SetElement... methods ensure initialization, which checks this flag.
+    */
+    void MarkForInitialization();
+
+    /**
+      \brief Called by all Get/Set methods to initialize DcmDataSet if needed.
+    */
+    void EnsureDcmDataSetIsInitialized() const;
+
+
+    /**
+      \brief Find element in dataset and copy it into internal DcmDataset
+
+      Attribute types 1, 1C, 2, 2C, 3 as defined in DICOM can be encoded as
+      hex values 0x1, 0x1C, 0x2, 0x2C, 0x3.
+
+      Conditional attributes are considered MUST attributes. The calling
+      function shall test the conditions before calling CopyElement
+      (since conditions might be complex).
+    */
+    bool CopyElement( DcmDataset* dataset, const DcmTagKey& tag, int type );
+
+    /**
+      \brief creates a QString from the OFString, respecting the "specific character set" of the Dataset.
+
+      This method checks if the dataset has an attribute "specific character set".
+      If so, all attributes of types Long String (LO), Long Text (LT), Person Name (PN), Short String (SH),
+      Short Text (ST), Unlimited Text (UT) should be interpreted as encoded with a special set.
+
+      See implementation for details.
+    */
+    QString Decode(const DcmTag& tag, const OFString& raw) const;
+
+    /**
+      \brief creates an OFString from the QtString
+
+      \warning The method currently assumes that the encoding of the passed string if latin1 and converts
+      it accordingly. The passed DICOM tag is not yet evaluated to determine the actual encoding type.
+    */
+    OFString Encode(const DcmTag& tag, const QString& qstring) const;
+
+    /**
+      \brief A const-correct version of DcmDataset::findAndGetElement.
+    */
+    OFCondition findAndGetElement(const DcmTag& tag, DcmElement*& element, const OFBool searchIntoSub=OFFalse) const; // DCMTK is not const-correct
+
+    /**
+      \brief A const-correct version of DcmDataset::findAndGetOFString.
+    */
+    OFCondition findAndGetOFString(const DcmTag& tag, OFString& value, const unsigned long pos = 0, const OFBool searchIntoSub=OFFalse) const; // DCMTK is not const-correct
+
+    static bool CheckCondition(const OFCondition&);
+
+    /**
+      \brief Get-methods for for all subtypes of DcmByteString
+    */
+    QString          GetAllElementValuesAsString( const DcmTag& tag ) const;
+    QString                   GetElementAsString( const DcmTag& tag, unsigned long pos = 0 ) const;
+    QStringList           GetElementAsStringList( const DcmTag& tag ) const;
+    QPersonName           GetElementAsPersonName( const DcmTag& tag, unsigned long pos = 0 ) const;
+    QPersonNameList   GetElementAsPersonNameList( const DcmTag& tag ) const;
+    QDate                       GetElementAsDate( const DcmTag& tag, unsigned long pos = 0 ) const;
+    QTime                       GetElementAsTime( const DcmTag& tag, unsigned long pos = 0 ) const;
+    double                    GetElementAsDouble( const DcmTag& tag, unsigned long pos = 0 ) const; // type DS
+    long                     GetElementAsInteger( const DcmTag& tag, unsigned long pos = 0 ) const; // type IS
+    int                  GetElementAsSignedShort( const DcmTag& tag, unsigned long pos = 0 ) const; // type SS
+    int                GetElementAsUnsignedShort( const DcmTag& tag, unsigned long pos = 0 ) const; // type US
+
+    /**
+    \warning IGNORES TIME ZONE at the moment!
+    */
+    QDateTime       GetElementAsDateTime( const DcmTag& tag, unsigned long pos = 0 ) const;
+
+
+    /**
+    \brief Set-methods for for all subtypes of DcmByteString
+    */
+    bool SetElementAsString( const DcmTag& tag, QString string );
+    bool SetElementAsStringList( const DcmTag& tag, QStringList stringList ); //> Currently not implemented
+    bool SetElementAsPersonName( const DcmTag& tag, QPersonName personName );
+    bool SetElementAsPersonNameList( const DcmTag& tag, QPersonNameList personNameList ); //> Currently not implemented
+    bool SetElementAsDate( const DcmTag& tag, QDate date );
+    bool SetElementAsTime( const DcmTag& tag, QTime time );
+    bool SetElementAsDateTime( const DcmTag& tag, QDateTime dateTime );
+    bool SetElementAsInteger( const DcmTag& tag, long value, unsigned long pos = 0 ); // type IS
+    bool SetElementAsSignedShort( const DcmTag& tag, int value, unsigned long pos = 0 ); // type SS
+    bool SetElementAsUnsignedShort( const DcmTag& tag, int value, unsigned long pos = 0 ); // type US
+
+
+    /**
+      \brief Get a human-readable version of patient position enumerations used e.g. in DICOM series.
+    */
+    static QString TranslateDefinedTermPatientPosition( const QString& dt );
+
+    /**
+      \brief Get a human-readable version of modality enumerations used e.g. in DICOM series.
+    */
+    static QString TranslateDefinedTermModality( const QString& dt );
+
+    /**
+      \brief Nicely formatted (group,element) version of a tag
+    */
+    static QString TagKey( const DcmTag& tag );
+
+    /**
+      \brief Description (name) of the tag
+    */
+    static QString TagDescription( const DcmTag& tag );
+
+    /**
+      \brief Value Representation
+    */
+    static QString TagVR( const DcmTag& tag );
+
+protected:
+
+    /**
+      \brief Callback for retrieving a serialized version of this class
+
+      You can override this method in a subclass to retrieve a serialized
+      version of the object from some storage mechanism, eg a database
+    */
+    virtual QString GetStoredSerialization();
+
+    /**
+      \brief Callback for storing a serialized version of this class
+
+      You can override this method in a subclass to store a serialized
+      version of the object to some storage mechanism, eg a database
+    */
+    virtual void SetStoredSerialization(QString serializedDataset);
+
+  QScopedPointer<ctkDICOMDatasetPrivate> d_ptr;
+
+private:
+  Q_DECLARE_PRIVATE(ctkDICOMDataset);
+};
+
+#endif
+