Kaynağa Gözat

Merge branch 'dicom-queries' of github.com:commontk/CTK into dicom-queries

nherlambang 14 yıl önce
ebeveyn
işleme
810612fa0b
30 değiştirilmiş dosya ile 1769 ekleme ve 139 silme
  1. 14 1
      Applications/Testing/Cpp/ctkDICOMApplicationTest1.cpp
  2. 1 22
      Applications/ctkDICOMViewer/ctkDICOMViewerMain.cpp
  3. 5 1
      Libs/DICOM/Core/CMakeLists.txt
  4. 1 2
      Libs/DICOM/Core/Testing/Cpp/CMakeLists.txt
  5. 8 2
      Libs/DICOM/Core/Testing/Cpp/ctkDICOMImageTest1.cpp
  6. 851 0
      Libs/DICOM/Core/ctkDICOMDataset.cpp
  7. 243 0
      Libs/DICOM/Core/ctkDICOMDataset.h
  8. 6 5
      Libs/DICOM/Core/ctkDICOMImage.cpp
  9. 1 1
      Libs/DICOM/Core/ctkDICOMImage.h
  10. 35 5
      Libs/DICOM/Core/ctkDICOMIndexer.cpp
  11. 1 2
      Libs/DICOM/Core/ctkDICOMIndexer.h
  12. 106 0
      Libs/DICOM/Core/ctkDICOMPersonName.cpp
  13. 54 0
      Libs/DICOM/Core/ctkDICOMPersonName.h
  14. 5 0
      Libs/DICOM/Core/ctkDICOMQuery.cpp
  15. 5 1
      Libs/DICOM/Core/ctkDICOMQuery.h
  16. 5 1
      Libs/DICOM/Widgets/CMakeLists.txt
  17. 1 1
      Libs/DICOM/Widgets/Resources/UI/ctkDICOMAppWidget.ui
  18. 1 1
      Libs/DICOM/Widgets/Resources/UI/ctkDICOMQueryRetrieveWidget.ui
  19. 51 7
      Libs/DICOM/Widgets/Resources/UI/ctkDICOMServerNodeWidget.ui
  20. 8 0
      Libs/DICOM/Widgets/Testing/Cpp/CMakeLists.txt
  21. 78 0
      Libs/DICOM/Widgets/Testing/Cpp/ctkDICOMModelTest2.cpp
  22. 25 0
      Libs/DICOM/Widgets/Testing/Cpp/ctkDICOMQueryRetrieveWidgetTest1.cpp
  23. 47 11
      Libs/DICOM/Widgets/ctkDICOMAppWidget.cpp
  24. 1 1
      Libs/DICOM/Widgets/ctkDICOMAppWidget.h
  25. 42 17
      Libs/DICOM/Widgets/ctkDICOMQueryRetrieveWidget.cpp
  26. 1 0
      Libs/DICOM/Widgets/ctkDICOMQueryRetrieveWidget.h
  27. 75 56
      Libs/DICOM/Widgets/ctkDICOMServerNodeWidget.cpp
  28. 9 2
      Libs/DICOM/Widgets/ctkDICOMServerNodeWidget.h
  29. 2 0
      Libs/Widgets/Testing/Cpp/CMakeLists.txt
  30. 87 0
      Libs/Widgets/Testing/Cpp/ctkCheckableHeaderViewTest2.cpp

+ 14 - 1
Applications/Testing/Cpp/ctkDICOMApplicationTest1.cpp

@@ -24,6 +24,19 @@
 
 
 ./CTK-build/bin/ctkDICOMRetrieve 1.2.840.113619.2.135.3596.6358736.5118.1115807980.182  /tmp/hoot CTK_AE 11113 CTK_AE localhost 11112 CTK_CLIENT_AE
+
+As invoked by ctest:
+
+% ./CTK-build/bin/CTKApplicationCppTests ctkDICOMApplicationTest1 \
+    ./CMakeExternals/Install/bin/dcmqrscp ./CTK-build/Testing/Temporary/dcmqrscp.cfg \
+        ./CMakeExternals/Source/CTKData/Data/DICOM/MRHEAD/000055.IMA \
+        ./CMakeExternals/Source/CTKData/Data/DICOM/MRHEAD/000056.IMA \
+    ./CMakeExternals/Install/bin/storescu \
+    ./CTK-build/bin/ctkDICOMQuery \
+    ./CTK-build/Testing/Temporary/ctkDICOMApplicationTest1.db \
+    ./CTK-build/bin/ctkDICOMRetrieve \
+    ./CTK-build/Testing/Temporary/ctkDICOMRetrieveStorage
+
 */
 
 int ctkDICOMApplicationTest1(int argc, char * argv []) {
@@ -73,7 +86,7 @@ int ctkDICOMApplicationTest1(int argc, char * argv []) {
 
 
   //
-  // now push some dicom data in using storescp
+  // now push some dicom data in using storescu
   //
 
   QProcess *storescu = new QProcess(0);

+ 1 - 22
Applications/ctkDICOMViewer/ctkDICOMViewerMain.cpp

@@ -80,30 +80,9 @@ int main(int argc, char** argv)
     }
   }
 
-  QString databaseFileName = databaseDirectory + QString("/ctkDICOM.sql");
-
-  ctkDICOMDatabase myCTK;
-  try { myCTK.openDatabase( databaseFileName ); }
-  catch (std::exception e)
-  {
-    std::cerr << "Database error: " << qPrintable(myCTK.GetLastError()) << "\n";
-    myCTK.closeDatabase();
-    return EXIT_FAILURE;
-  }
-
-  ctkDICOMModel model;
-  model.setDatabase(myCTK.database());
-  
   ctkDICOMAppWidget DICOMApp;
 
-  QTreeView *treeView = DICOMApp.findChild<QTreeView *>("treeView");
-  if (!treeView)
-    {
-    std::cerr << "Could not access tree view from QueryRetrieve widget\n";
-    return EXIT_FAILURE;
-    }
-  treeView->setModel(&model);
-
+  DICOMApp.setDatabaseDirectory(databaseDirectory);
   DICOMApp.show();
   DICOMApp.raise();
 

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

@@ -21,7 +21,11 @@ SET(KIT_SRCS
   ctkDICOMQuery.h
   ctkDICOMRetrieve.cpp
   ctkDICOMRetrieve.h
-  )
+  ctkDICOMDataset.cpp
+  ctkDICOMDataset.h
+  ctkDICOMPersonName.cpp
+  ctkDICOMPersonName.h
+)
 
 # Headers that should run through moc
 SET(KIT_MOC_SRCS

+ 1 - 2
Libs/DICOM/Core/Testing/Cpp/CMakeLists.txt

@@ -2,8 +2,8 @@ SET(KIT ${PROJECT_NAME})
 
 CREATE_TEST_SOURCELIST(Tests ${KIT}CppTests.cpp
   ctkDICOMImageTest1.cpp
+  ctkDICOMModelTest1.cpp
   ctkDICOMTest1.cpp
-  ctkDICOMImageTest1.cpp
   )
 
 SET (TestsToRun ${Tests})
@@ -28,7 +28,6 @@ ADD_TEST( ctkDICOMImageTest1 ${KIT_TESTS}
           ctkDICOMImageTest1 ${CTKData_DIR}/Data/DICOM/MRHEAD/000055.IMA)
 SET_PROPERTY(TEST ctkDICOMImageTest1 PROPERTY LABELS ${PROJECT_NAME})
 
-
 ADD_TEST( ctkDICOMModelTest1 ${KIT_TESTS}
           ctkDICOMModelTest1 ${CMAKE_CURRENT_BINARY_DIR}/dicom.db
                              ${CMAKE_CURRENT_SOURCE_DIR}/../../Resources/dicom-sample.sql)

+ 8 - 2
Libs/DICOM/Core/Testing/Cpp/ctkDICOMImageTest1.cpp

@@ -29,12 +29,18 @@ int ctkDICOMImageTest1( int argc, char * argv [] )
   ctkDICOMImage ctkImage(&dcmtkImage);
 
   QLabel qtImage;
-  qtImage.setPixmap(ctkImage.getPixmap(0));
+  QPixmap pixmap = QPixmap::fromImage(ctkImage.getImage(0),Qt::AvoidDither);
+  if (pixmap.isNull())
+    {
+    std::cerr << "Failed to convert QImage to QPixmap" ;
+    return EXIT_FAILURE;
+    }
+  qtImage.setPixmap(pixmap);
   qtImage.show();
 
   if (argc > 2 && QString(argv[2]) == "-I")
     {
-      return app.exec();
+    return app.exec();
     }
   return EXIT_SUCCESS;
 }

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

@@ -0,0 +1,851 @@
+
+#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 const QScopedPointer<const DcmDataDictionary> s_Dictionary;
+};
+
+const QScopedPointer<const DcmDataDictionary> ctkDICOMDatasetPrivate::s_Dictionary(new DcmDataDictionary(OFTrue, OFTrue));
+// = QScopedPointer<const DcmDataDictionary>(new DcmDataDictionary(OFTrue, OFTrue));
+
+
+
+
+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!");
+}
+

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

@@ -0,0 +1,243 @@
+/*=========================================================================
+
+  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"
+
+#include "ctkDICOMPersonName.h"
+
+#ifndef WIN32
+  #define HAVE_CONFIG_H
+#endif
+#include <dcdatset.h> // DCMTK DcmDataset
+
+#include <QtCore>
+
+class DcmDataDictionary;
+
+
+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
+

+ 6 - 5
Libs/DICOM/Core/ctkDICOMImage.cpp

@@ -90,13 +90,13 @@ DicomImage* ctkDICOMImage::getDicomImage() const
   Q_D(const ctkDICOMImage);
   return d->DicomImage;
 }
-QPixmap ctkDICOMImage::getPixmap(int frame) const
+QImage ctkDICOMImage::getImage(int frame) const
 {
   Q_D(const ctkDICOMImage);
 
   // this way of converting the dicom image to a qpixmap was adopted from some code from
   // the DCMTK forum, posted by Joerg Riesmayer, see http://forum.dcmtk.org/viewtopic.php?t=120
-  QPixmap pixmap;
+  QImage image;
   if ((d->DicomImage != NULL) && (d->DicomImage->getStatus() == EIS_Normal))
   {
     /* get image extension */
@@ -114,11 +114,12 @@ QPixmap ctkDICOMImage::getPixmap(int frame) const
 
     if (d->DicomImage->getOutputData(static_cast<void *>(buffer.data() + offset), length - offset, 8, frame))
     {
-      if (!pixmap.loadFromData(buffer, "PGM", Qt::AvoidDither))
+
+      if (!image.loadFromData( buffer ))
       {
-        logger.error("Pixmap couldn't created");
+        logger.error("QImage couldn't created");
       }
     }
   }
-  return pixmap;
+  return image;
 }

+ 1 - 1
Libs/DICOM/Core/ctkDICOMImage.h

@@ -38,7 +38,7 @@ public:
   explicit ctkDICOMImage(DicomImage* dicomImage, QObject* parent = 0);
   virtual ~ctkDICOMImage();
   DicomImage* getDicomImage() const;
-  QPixmap getPixmap(int frame = 0) const;
+  QImage getImage(int frame = 0) const;
   unsigned long frameCount() const;
   Q_PROPERTY(unsigned long frameCount READ frameCount);
 

+ 35 - 5
Libs/DICOM/Core/ctkDICOMIndexer.cpp

@@ -29,9 +29,13 @@
 #include <QDirIterator>
 #include <QFileInfo>
 #include <QDebug>
+#include <QPixmap>
+
 
 // ctkDICOM includes
 #include "ctkDICOMIndexer.h"
+#include "ctkDICOMImage.h"
+
 
 // DCMTK includes
 #ifndef WIN32
@@ -45,6 +49,7 @@
 #include <dcmtk/ofstd/ofstring.h>
 #include <dcmtk/ofstd/ofstd.h>        /* for class OFStandard */
 #include <dcmtk/dcmdata/dcddirif.h>   /* for class DicomDirInterface */
+#include "dcmimage.h"
 
 #define MITK_ERROR std::cout
 #define MITK_INFO std::cout
@@ -85,7 +90,7 @@ ctkDICOMIndexer::~ctkDICOMIndexer()
 }
 
 //------------------------------------------------------------------------------
-void ctkDICOMIndexer::addDirectory(ctkDICOMDatabase& database, const QString& directoryName,const QString& destinationDirectoryName, bool createHierarchy)
+void ctkDICOMIndexer::addDirectory(ctkDICOMDatabase& database, const QString& directoryName,const QString& destinationDirectoryName, bool createHierarchy, bool createThumbnails)
 {
   QSqlDatabase db = database.database();
   const std::string src_directory(directoryName.toStdString());
@@ -366,6 +371,11 @@ void ctkDICOMIndexer::addDirectory(ctkDICOMDatabase& database, const QString& di
     }
 
     lastSeriesInstanceUID = seriesInstanceUID;
+    QString studySeriesDirectory;
+    if (createHierarchy || createThumbnails)
+    {
+      studySeriesDirectory = QString(studyInstanceUID.c_str()) + "/" + seriesInstanceUID.c_str();
+    }
 
     //----------------------------------
     //Move file to destination directory
@@ -375,16 +385,36 @@ void ctkDICOMIndexer::addDirectory(ctkDICOMDatabase& database, const QString& di
       {
       QFile currentFile( qfilename );
       QDir destinationDir(destinationDirectoryName + "/dicom");
-      QString destFileName = seriesInstanceUID.c_str();
+      QString destFileName = sopInstanceUID.c_str();
       if (createHierarchy)
       {
-        QString uniqueDirName = QString(studyInstanceUID.c_str()) + "/" + seriesInstanceUID.c_str();
-        destinationDir.mkpath(uniqueDirName);
-        destFileName.prepend( destinationDir.absolutePath() + "/"  + uniqueDirName + "/" );
+        destinationDir.mkpath(studySeriesDirectory);
+        destFileName.prepend( destinationDir.absolutePath() + "/"  + studySeriesDirectory + "/" );
       }
       currentFile.copy(destFileName);
       qfilename = destFileName;
     }
+
+    if (createThumbnails)
+    {
+      QString databaseFile = database.GetDatabaseFilename();
+      if (!QFileInfo(databaseFile).isAbsolute())
+      {
+        databaseFile.prepend(QDir::currentPath() + "/");
+      }
+
+      QString thumbnailBaseDir = QFileInfo ( databaseFile ).absoluteDir().path() + "/thumbs/";
+      QString thumbnailFilename = thumbnailBaseDir + "/" + studySeriesDirectory + "/" + sopInstanceUID.c_str() + ".png";
+      QFileInfo thumbnailInfo(thumbnailFilename);
+      if ( ! ( thumbnailInfo.exists() && thumbnailInfo.lastModified() < QFileInfo(qfilename).lastModified() ) )
+      {
+        QDir(thumbnailBaseDir).mkpath(studySeriesDirectory);
+        DicomImage dcmtkImage(qfilename.toAscii());
+        ctkDICOMImage ctkImage(&dcmtkImage);
+        QImage image( ctkImage.getImage(0) );
+        image.scaled(128,128,Qt::KeepAspectRatio).save(thumbnailFilename,"PNG");
+      }
+    }
     // */
     //------------------------
     //Add Filename to Database

+ 1 - 2
Libs/DICOM/Core/ctkDICOMIndexer.h

@@ -34,9 +34,8 @@ public:
   explicit ctkDICOMIndexer();
   virtual ~ctkDICOMIndexer();
   /// add directory to database and optionally copy files to destinationDirectory
-  void addDirectory(ctkDICOMDatabase& database, const QString& directoryName, const QString& destinationDirectoryName = "", bool createHierarchy = true);
+  void addDirectory(ctkDICOMDatabase& database, const QString& directoryName, const QString& destinationDirectoryName = "", bool createHierarchy = true, bool createThumbnails = true);
   void refreshDatabase(ctkDICOMDatabase& database, const QString& directoryName);
-
 protected:
   QScopedPointer<ctkDICOMIndexerPrivate> d_ptr;
   

+ 106 - 0
Libs/DICOM/Core/ctkDICOMPersonName.cpp

@@ -0,0 +1,106 @@
+#include <QSharedData>
+
+#include "ctkDICOMPersonName.h"
+
+
+// ------------------------------------------- QPersonName -----------------------------------------------------------
+
+class QPersonNameData : public QSharedData
+{
+  public:
+
+    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(new QPersonNameData)
+{
+  d->m_LastName = lastName;
+  d->m_FirstName = firstName;
+  d->m_MiddleName = middleName;
+  d->m_NamePrefix = namePrefix;
+  d->m_NameSuffix = nameSuffix;
+}
+
+QPersonName::QPersonName(const QPersonName& other) : d(other.d)
+{
+}
+
+QPersonName& QPersonName::operator=(const QPersonName& other)
+{
+  d=other.d;
+  return *this;
+}
+
+QPersonName::~QPersonName()
+{
+}
+
+
+QString QPersonName::GetFormattedName() const
+{
+  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
+{
+  return d->m_LastName;
+}
+
+QString QPersonName::GetFirstName()  const
+{
+  return d->m_FirstName;
+}
+
+QString QPersonName::GetMiddleName()  const
+{
+  return d->m_MiddleName;
+}
+
+QString QPersonName::GetNamePrefix()  const
+{
+  return d->m_NamePrefix;
+}
+
+QString QPersonName::GetNameSuffix()  const
+{
+  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();
+}
+

+ 54 - 0
Libs/DICOM/Core/ctkDICOMPersonName.h

@@ -0,0 +1,54 @@
+#ifndef CTKDICOMPERSONNAME_H
+#define CTKDICOMPERSONNAME_H
+
+#include "ctkDICOMCoreExport.h"
+
+#include <QString>
+#include <QSharedDataPointer>
+#include <QMetaType>
+
+#include <string>
+
+class QPersonNameData;
+/**
+  \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);
+
+    QPersonName(const QPersonName& other);
+    QPersonName& operator=(const QPersonName& other);
+
+    virtual ~QPersonName();
+    /**
+      \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:
+    QSharedDataPointer<QPersonNameData> d;
+};
+
+typedef QList<QPersonName> QPersonNameList;
+Q_DECLARE_METATYPE(QPersonName);
+
+
+#endif // CTKDICOMPERSONNAME_H

+ 5 - 0
Libs/DICOM/Core/ctkDICOMQuery.cpp

@@ -158,6 +158,11 @@ QMap<QString,QVariant> ctkDICOMQuery::filters()
   Q_D(ctkDICOMQuery);
   return d->Filters;
 }
+QStringList ctkDICOMQuery::studyInstanceUIDQueried()
+{
+  Q_D(ctkDICOMQuery);
+  return d->StudyInstanceUIDList;
+}
 
 
 //------------------------------------------------------------------------------

+ 5 - 1
Libs/DICOM/Core/ctkDICOMQuery.h

@@ -59,7 +59,11 @@ public:
   void query(ctkDICOMDatabase& database);
 
   /// Add a StudyInstanceUID to be queried
-  void addStudyInstanceUID ( QString StudyInstanceUID );
+  void addStudyInstanceUID( QString StudyInstanceUID );
+
+  /// Access the list of study instance UIDs from the last query
+  QStringList studyInstanceUIDQueried();
+
 
   ///
   // Filters are keyword/value pairs as generated by

+ 5 - 1
Libs/DICOM/Widgets/CMakeLists.txt

@@ -27,6 +27,8 @@ SET(KIT_SRCS
   ctkDICOMThumbnailWidget.h
   ctkDICOMThumbnailListWidget.cpp
   ctkDICOMThumbnailListWidget.h
+  ctkDICOMImportWidget.cpp
+  ctkDICOMImportWidget.h
   )
 
 # Headers that should run through moc
@@ -37,6 +39,7 @@ SET(KIT_MOC_SRCS
   ctkDICOMAppWidget.h
   ctkDICOMThumbnailWidget.h
   ctkDICOMThumbnailListWidget.h
+  ctkDICOMImportWidget.h
   )
 
 # UI files - includes new widgets
@@ -49,6 +52,7 @@ SET(KIT_UI_FORMS
   Resources/UI/ctkDICOMAppWidget.ui
   Resources/UI/ctkDICOMThumbnailWidget.ui
   Resources/UI/ctkDICOMThumbnailListWidget.ui
+  Resources/UI/ctkDICOMImportWidget.ui
 )
 
 # Resources
@@ -75,5 +79,5 @@ ctkMacroBuildLib(
 
 # Testing
 IF(BUILD_TESTING)
-  #ADD_SUBDIRECTORY(Testing)
+  ADD_SUBDIRECTORY(Testing)
 ENDIF(BUILD_TESTING)

+ 1 - 1
Libs/DICOM/Widgets/Resources/UI/ctkDICOMAppWidget.ui

@@ -182,7 +182,7 @@
     <string>Send</string>
    </property>
    <property name="toolTip">
-    <string>Send DIOCM studies to a DICOM node</string>
+    <string>Send DICOM Studies to a DICOM node</string>
    </property>
   </action>
  </widget>

+ 1 - 1
Libs/DICOM/Widgets/Resources/UI/ctkDICOMQueryRetrieveWidget.ui

@@ -7,7 +7,7 @@
     <x>0</x>
     <y>0</y>
     <width>843</width>
-    <height>530</height>
+    <height>613</height>
    </rect>
   </property>
   <property name="windowTitle">

+ 51 - 7
Libs/DICOM/Widgets/Resources/UI/ctkDICOMServerNodeWidget.ui

@@ -15,16 +15,28 @@
   </property>
   <layout class="QVBoxLayout" name="verticalLayout">
    <item>
-    <widget class="QLabel" name="label_2">
-     <property name="text">
-      <string>Calling AETitle</string>
+    <widget class="QFrame" name="frame_2">
+     <property name="frameShape">
+      <enum>QFrame::StyledPanel</enum>
+     </property>
+     <property name="frameShadow">
+      <enum>QFrame::Raised</enum>
      </property>
+     <layout class="QHBoxLayout" name="horizontalLayout_2">
+      <item>
+       <widget class="QLabel" name="label_2">
+        <property name="text">
+         <string>Calling AETitle</string>
+        </property>
+       </widget>
+      </item>
+      <item>
+       <widget class="QLineEdit" name="CallingAETitle"/>
+      </item>
+     </layout>
     </widget>
    </item>
    <item>
-    <widget class="QLineEdit" name="CallingAETitle"/>
-   </item>
-   <item>
     <widget class="QLabel" name="label">
      <property name="text">
       <string>Servers</string>
@@ -42,7 +54,7 @@
         <x>0</x>
         <y>0</y>
         <width>565</width>
-        <height>392</height>
+        <height>342</height>
        </rect>
       </property>
       <layout class="QVBoxLayout" name="verticalLayout_2">
@@ -106,6 +118,38 @@
      </layout>
     </widget>
    </item>
+   <item>
+    <widget class="QFrame" name="frame_3">
+     <property name="frameShape">
+      <enum>QFrame::StyledPanel</enum>
+     </property>
+     <property name="frameShadow">
+      <enum>QFrame::Raised</enum>
+     </property>
+     <layout class="QHBoxLayout" name="horizontalLayout_3">
+      <item>
+       <widget class="QLabel" name="label_3">
+        <property name="text">
+         <string>Storage AETitle</string>
+        </property>
+       </widget>
+      </item>
+      <item>
+       <widget class="QLineEdit" name="StorageAETitle"/>
+      </item>
+      <item>
+       <widget class="QLabel" name="label_4">
+        <property name="text">
+         <string>Storage Port</string>
+        </property>
+       </widget>
+      </item>
+      <item>
+       <widget class="QLineEdit" name="StoragePort"/>
+      </item>
+     </layout>
+    </widget>
+   </item>
   </layout>
  </widget>
  <resources/>

+ 8 - 0
Libs/DICOM/Widgets/Testing/Cpp/CMakeLists.txt

@@ -1,6 +1,8 @@
 SET(KIT ${PROJECT_NAME})
 
 CREATE_TEST_SOURCELIST(Tests ${KIT}CppTests.cpp
+  ctkDICOMModelTest2.cpp
+  ctkDICOMQueryRetrieveWidgetTest1.cpp
   )
 
 SET (TestsToRun ${Tests})
@@ -22,3 +24,9 @@ ENDMACRO( SIMPLE_TEST  )
 # Add Tests
 #
 
+ADD_TEST( ctkDICOMModelTest2 ${KIT_TESTS}
+          ctkDICOMModelTest2 ${CMAKE_CURRENT_BINARY_DIR}/dicom.db
+                             ${CMAKE_CURRENT_SOURCE_DIR}/../../../Core/Resources/dicom-sample.sql)
+SET_PROPERTY(TEST ctkDICOMModelTest2 PROPERTY LABELS ${PROJECT_NAME})
+
+SIMPLE_TEST(ctkDICOMQueryRetrieveWidgetTest1)

+ 78 - 0
Libs/DICOM/Widgets/Testing/Cpp/ctkDICOMModelTest2.cpp

@@ -0,0 +1,78 @@
+
+// Qt includes
+#include <QApplication>
+#include <QDebug>
+#include <QFileInfo>
+#include <QHBoxLayout>
+#include <QTimer>
+#include <QTreeView>
+
+// ctkDICOMCore includes
+#include "ctkDICOMDatabase.h"
+#include "ctkDICOMModel.h"
+#include "ctkModelTester.h"
+
+// CTK includes
+#include "ctkCheckableHeaderView.h"
+
+// STD includes
+#include <iostream>
+
+/* Test from build directory:
+ ./CTK-build/bin/CTKDICOMCoreCxxTests ctkDICOMModelTest1 test.db ../CTK/Libs/DICOM/Core/Resources/dicom-sample.sql
+*/
+
+int ctkDICOMModelTest2( int argc, char * argv [] )
+{
+  QApplication app(argc, argv);
+  
+  if (argc <= 2)
+    {
+    std::cerr << "Warning, no sql file given. Test stops" << std::endl;
+    std::cerr << "Usage: qctkDICOMModelTest1 <scratch.db> <dumpfile.sql>" << std::endl;
+    return EXIT_FAILURE;
+    }
+  
+  try
+  {
+    ctkDICOMDatabase myCTK( argv[1] );
+
+    if (!myCTK.initializeDatabase(argv[2]))
+    {
+      std::cerr << "Error when initializing the data base: " << argv[2]
+          << " error: " << myCTK.GetLastError().toStdString();
+    }
+ 
+    ctkDICOMModel model;
+    model.setDatabase(myCTK.database());
+
+    QWidget topLevel;
+    QTreeView viewer;
+    QHBoxLayout* layout = new QHBoxLayout;
+    layout->addWidget(&viewer);
+    topLevel.setLayout(layout);
+    viewer.setModel(&model);
+    
+    QHeaderView* previousHeaderView = viewer.header();
+    qDebug() << "previous: " << previousHeaderView->isHidden();
+    ctkCheckableHeaderView* headerView = new ctkCheckableHeaderView(Qt::Horizontal, &viewer);
+    headerView->setClickable(previousHeaderView->isClickable());
+    headerView->setMovable(previousHeaderView->isMovable());
+    headerView->setHighlightSections(previousHeaderView->highlightSections());
+    headerView->setPropagateToItems(true);
+    viewer.setHeader(headerView);
+    qDebug() << "new: " << headerView->isHidden();
+    topLevel.show();
+    if (argc <= 3 || QString(argv[3]) != "-I")
+      {
+      QTimer::singleShot(200, &app, SLOT(quit()));
+      }
+    return app.exec();
+  }
+  catch (std::exception e)
+    {
+    std::cerr << "Error when opening the data base file: " << argv[1]
+        << " error: " << e.what();
+    return EXIT_FAILURE;
+    }
+}

+ 25 - 0
Libs/DICOM/Widgets/Testing/Cpp/ctkDICOMQueryRetrieveWidgetTest1.cpp

@@ -0,0 +1,25 @@
+// Qt includes
+#include <QApplication>
+#include <QDebug>
+#include <QTimer>
+
+// ctkDICOMCore includes
+#include "ctkDICOMQueryRetrieveWidget.h"
+
+// STD includes
+#include <iostream>
+
+int ctkDICOMQueryRetrieveWidgetTest1( int argc, char * argv [] )
+{
+  QApplication app(argc, argv);
+ 
+  ctkDICOMQueryRetrieveWidget widget;
+  widget.show();
+
+  if (argc <= 1 || QString(argv[1]) != "-I")
+    {
+    QTimer::singleShot(200, &app, SLOT(quit()));
+    }
+
+  return app.exec();
+}

+ 47 - 11
Libs/DICOM/Widgets/ctkDICOMAppWidget.cpp

@@ -1,3 +1,7 @@
+// std includes
+#include <iostream>
+
+// Qt includes
 #include <QDebug>
 #include <QTreeView>
 #include <QTabBar>
@@ -13,7 +17,7 @@
 #include "ui_ctkDICOMAppWidget.h"
 #include "ctkDirectoryButton.h"
 #include "ctkDICOMQueryRetrieveWidget.h"
-
+#include "ctkDICOMImportWidget.h"
 
 //logger
 #include <ctkLogger.h>
@@ -23,14 +27,21 @@ static ctkLogger logger("org.commontk.DICOM.Widgets.ctkDICOMAppWidget");
 class ctkDICOMAppWidgetPrivate: public Ui_ctkDICOMAppWidget
 {
 public:
-  ctkDICOMQueryRetrieveWidget* queryRetrieveWidget;
+  ctkDICOMAppWidgetPrivate();
+
+  ctkDICOMImportWidget* ImportWidget;
+  ctkDICOMQueryRetrieveWidget* QueryRetrieveWidget;
 
-  ctkDICOMAppWidgetPrivate(){}
+  ctkDICOMDatabase DICOMDatabase;
+  ctkDICOMModel DICOMModel;
 };
 
 //----------------------------------------------------------------------------
 // ctkDICOMAppWidgetPrivate methods
 
+ctkDICOMAppWidgetPrivate::ctkDICOMAppWidgetPrivate(){
+
+}
 
 //----------------------------------------------------------------------------
 // ctkDICOMAppWidget methods
@@ -46,8 +57,12 @@ ctkDICOMAppWidget::ctkDICOMAppWidget(QWidget* _parent):Superclass(_parent),
   //Set toolbar button style
   d->toolBar->setToolButtonStyle(Qt::ToolButtonTextUnderIcon);
 
-  d->queryRetrieveWidget = new ctkDICOMQueryRetrieveWidget();
-  connect(d->directoryButton, SIGNAL(directoryChanged(const QString&)), this, SLOT(onDatabaseDirectoryChanged(const QString&)));
+  //Initialize Q/R widget
+  d->QueryRetrieveWidget = new ctkDICOMQueryRetrieveWidget();
+  connect(d->directoryButton, SIGNAL(directoryChanged(const QString&)), this, SLOT(setDatabaseDirectory(const QString&)));
+
+  //Initialize import widget
+  d->ImportWidget = new ctkDICOMImportWidget();
 
   //Set thumbnails width in thumbnail widget
   d->thumbnailsWidget->setThumbnailWidth(128);
@@ -64,19 +79,37 @@ ctkDICOMAppWidget::~ctkDICOMAppWidget()
 {
   Q_D(ctkDICOMAppWidget);  
 
-  d->queryRetrieveWidget->deleteLater();
+  d->QueryRetrieveWidget->deleteLater();
+  d->ImportWidget->deleteLater();
 }
 
 //----------------------------------------------------------------------------
-void ctkDICOMAppWidget::onDatabaseDirectoryChanged(const QString& directory)
+void ctkDICOMAppWidget::setDatabaseDirectory(const QString& directory)
 {
-  //Q_D(ctkDICOMAppWidget);  
+  Q_D(ctkDICOMAppWidget);  
 
   QSettings settings;
   settings.setValue("DatabaseDirectory", directory);
   settings.sync();
 
+  //close the active DICOM database
+  d->DICOMDatabase.closeDatabase();
+  
+  //open DICOM database on the directory
+  QString databaseFileName = directory + QString("/ctkDICOM.sql");
+  try { d->DICOMDatabase.openDatabase( databaseFileName ); }
+  catch (std::exception e)
+  {
+    std::cerr << "Database error: " << qPrintable(d->DICOMDatabase.GetLastError()) << "\n";
+    d->DICOMDatabase.closeDatabase();
+    return;
+  }
   
+  d->DICOMModel.setDatabase(d->DICOMDatabase.database());
+  d->treeView->setModel(&d->DICOMModel);
+
+  //pass DICOM database instance to Import widget
+  d->ImportWidget->setDICOMDatabase(&d->DICOMDatabase);
 }
 
 void ctkDICOMAppWidget::onAddToDatabase()
@@ -88,7 +121,10 @@ void ctkDICOMAppWidget::onAddToDatabase()
 
 //----------------------------------------------------------------------------
 void ctkDICOMAppWidget::onImport(){
-
+  Q_D(ctkDICOMAppWidget);
+  
+  d->ImportWidget->show();
+  d->ImportWidget->raise();
 }
 
 void ctkDICOMAppWidget::onExport(){
@@ -98,8 +134,8 @@ void ctkDICOMAppWidget::onExport(){
 void ctkDICOMAppWidget::onQuery(){
   Q_D(ctkDICOMAppWidget);
 
-  d->queryRetrieveWidget->show();
-  d->queryRetrieveWidget->raise();
+  d->QueryRetrieveWidget->show();
+  d->QueryRetrieveWidget->raise();
 }
 
 void ctkDICOMAppWidget::onDICOMModelSelected(const QModelIndex& index){

+ 1 - 1
Libs/DICOM/Widgets/ctkDICOMAppWidget.h

@@ -39,7 +39,7 @@ public:
   virtual ~ctkDICOMAppWidget();
 
 public slots:
-    void onDatabaseDirectoryChanged(const QString& directory);
+    void setDatabaseDirectory(const QString& directory);
     void onAddToDatabase();
 
     void onDICOMModelSelected(const QModelIndex& index);

+ 42 - 17
Libs/DICOM/Widgets/ctkDICOMQueryRetrieveWidget.cpp

@@ -2,6 +2,7 @@
 #include <QTreeView>
 #include <QTabBar>
 #include <QSettings>
+#include <QHBoxLayout>
 
 /// CTK includes
 #include <ctkCheckableHeaderView.h>
@@ -27,7 +28,10 @@ class ctkDICOMQueryRetrieveWidgetPrivate: public Ui_ctkDICOMQueryRetrieveWidget
 public:
   ctkDICOMQueryRetrieveWidgetPrivate(){}
 
-  QMap<QString, ctkDICOMQuery*> queries;
+  QMap<QString, ctkDICOMQuery*> queriesByServer;
+  QMap<QString, ctkDICOMQuery*> queriesByStudyUID;
+  QMap<QString, ctkDICOMRetrieve*> retrievalsByStudyUID;
+  ctkDICOMDatabase queryResultDatabase;
   ctkDICOMModel model;
 };
 
@@ -47,6 +51,7 @@ ctkDICOMQueryRetrieveWidget::ctkDICOMQueryRetrieveWidget(QWidget* _parent):Super
   d->setupUi(this);
 
   connect(d->QueryButton, SIGNAL(clicked()), this, SLOT(processQuery()));
+  connect(d->RetrieveButton, SIGNAL(clicked()), this, SLOT(processRetrieve()));
 }
 
 //----------------------------------------------------------------------------
@@ -77,14 +82,12 @@ void ctkDICOMQueryRetrieveWidget::processQuery()
 
   d->RetrieveButton->setEnabled(false);
   
-  ctkDICOMDatabase queryResultDatabase;
-
   // create a database in memory to hold query results
-  try { queryResultDatabase.openDatabase( ":memory:" ); }
+  try { d->queryResultDatabase.openDatabase( ":memory:" ); }
   catch (std::exception e)
   {
-    logger.error ( "Database error: " + queryResultDatabase.GetLastError() );
-    queryResultDatabase.closeDatabase();
+    logger.error ( "Database error: " + d->queryResultDatabase.GetLastError() );
+    d->queryResultDatabase.closeDatabase();
     return;
   }
 
@@ -96,29 +99,36 @@ void ctkDICOMQueryRetrieveWidget::processQuery()
     if ( parameters["CheckState"] == Qt::Checked )
     {
       // create a query for the current server
-      d->queries[server] = new ctkDICOMQuery;
-      d->queries[server]->setCallingAETitle(d->ServerNodeWidget->callingAETitle());
-      d->queries[server]->setCalledAETitle(parameters["AETitle"].toString());
-      d->queries[server]->setHost(parameters["Address"].toString());
-      d->queries[server]->setPort(parameters["Port"].toInt());
+      d->queriesByServer[server] = new ctkDICOMQuery;
+      d->queriesByServer[server]->setCallingAETitle(d->ServerNodeWidget->callingAETitle());
+      d->queriesByServer[server]->setCalledAETitle(parameters["AETitle"].toString());
+      d->queriesByServer[server]->setHost(parameters["Address"].toString());
+      d->queriesByServer[server]->setPort(parameters["Port"].toInt());
 
       // populate the query with the current search options
-      d->queries[server]->setFilters( d->QueryWidget->parameters() );
+      d->queriesByServer[server]->setFilters( d->QueryWidget->parameters() );
 
       try
       {
         // run the query against the selected server and put results in database
-        d->queries[server]->query ( queryResultDatabase );
+        d->queriesByServer[server]->query ( d->queryResultDatabase );
       }
       catch (std::exception e)
       {
         logger.error ( "Query error: " + parameters["Name"].toString() );
       }
+
+      foreach( QString studyUID, d->queriesByServer[server]->studyInstanceUIDQueried() )
+      {
+        d->queriesByStudyUID[studyUID] = d->queriesByServer[server];
+      }
     }
   }
 
   // checkable headers - allow user to select the patient/studies to retrieve
   d->results->setModel(&d->model);
+  d->model.setDatabase(d->queryResultDatabase.database());
+
   d->model.setHeaderData(0, Qt::Horizontal, Qt::Unchecked, Qt::CheckStateRole);
   QHeaderView* previousHeaderView = d->results->header();
   ctkCheckableHeaderView* headerView = new ctkCheckableHeaderView(Qt::Horizontal, d->results);
@@ -127,12 +137,27 @@ void ctkDICOMQueryRetrieveWidget::processQuery()
   headerView->setHighlightSections(previousHeaderView->highlightSections());
   headerView->setPropagateToItems(true);
   d->results->setHeader(headerView);
+  // headerView is hidden because it was created with a visisble parent widget 
+  headerView->setHidden(false);
 
-  d->model.setDatabase(queryResultDatabase.database());
-  d->results->setModel(&d->model);
+  d->RetrieveButton->setEnabled(d->model.rowCount());
+}
+
+//----------------------------------------------------------------------------
+void ctkDICOMQueryRetrieveWidget::processRetrieve()
+{
+  Q_D(ctkDICOMQueryRetrieveWidget);
 
-  if ( d->model.rowCount() > 0 )
+  foreach( QString studyUID, d->queriesByStudyUID.keys() )
   {
-    d->RetrieveButton->setEnabled(true);
+    logger.debug("need to retrieve " + studyUID + " from " + d->queriesByStudyUID[studyUID]->host());
+    ctkDICOMQuery *query = d->queriesByStudyUID[studyUID];
+    ctkDICOMRetrieve *retrieve = new ctkDICOMRetrieve;
+    d->retrievalsByStudyUID[studyUID] = retrieve;
+    retrieve->setCallingAETitle( query->callingAETitle() );
+    retrieve->setCalledAETitle( query->calledAETitle() );
+    retrieve->setHost( query->host() );
+    //retrieve->setCallingPort( query->port() );
+    retrieve->setCalledPort( query->port() );
   }
 }

+ 1 - 0
Libs/DICOM/Widgets/ctkDICOMQueryRetrieveWidget.h

@@ -40,6 +40,7 @@ public slots:
     void setRetrieveDirectory(const QString& directory);
     void setRetrieveDatabaseFileName(const QString& fileName);
     void processQuery();
+    void processRetrieve();
 
 protected:
   QScopedPointer<ctkDICOMQueryRetrieveWidgetPrivate> d_ptr;

+ 75 - 56
Libs/DICOM/Widgets/ctkDICOMServerNodeWidget.cpp

@@ -18,6 +18,14 @@
 
 =========================================================================*/
 
+// Qt includes
+#include <QDebug>
+#include <QList>
+#include <QMap>
+#include <QSettings>
+#include <QTableWidgetItem>
+#include <QVariant>
+
 /// CTK includes
 #include <ctkCheckableHeaderView.h>
 
@@ -27,14 +35,6 @@
 
 // STD includes
 #include <iostream>
-
-// Qt includes
-#include <QList>
-#include <QMap>
-#include <QVariant>
-#include <QSettings>
-#include <QTableWidgetItem>
-
 //----------------------------------------------------------------------------
 class ctkDICOMServerNodeWidgetPrivate: public Ui_ctkDICOMServerNodeWidget
 {
@@ -69,44 +69,14 @@ ctkDICOMServerNodeWidget::ctkDICOMServerNodeWidget(QWidget* _parent):Superclass(
 
   d->RemoveButton->setEnabled(false);
 
-
-  QSettings settings;
-
-  QMap<QString, QVariant> node;
-  if ( settings.value("ServerNodeCount").toInt() == 0 )
-  {
-    settings.setValue("ServerNodeCount", 1);
-    settings.setValue("ServerNodes/0", QVariant(node));
-    node["Name"] = "ExampleHost";
-    node["CheckState"] = Qt::Checked;
-    node["AETitle"] = "ANY-SCP";
-    node["Address"] = "localhost";
-    node["Port"] = "11112";
-    settings.setValue("ServerNodes/1", QVariant(node));
-    settings.setValue("CallingAETitle", "FINDSCU");
-    settings.sync();
-  }
-
-  d->CallingAETitle->setText(settings.value("CallingAETitle").toString());
-  int count = settings.value("ServerNodeCount").toInt();
-  d->NodeTable->setRowCount(count);
-  for (int row = 0; row < count; row++)
-  {
-    node = settings.value(QString("ServerNodes/%1").arg(row)).toMap();
-    QTableWidgetItem *newItem;
-    newItem = new QTableWidgetItem( node["Name"].toString() );
-    newItem->setCheckState( Qt::CheckState(node["CheckState"].toInt()) );
-    d->NodeTable->setItem(row, 0, newItem);
-    newItem = new QTableWidgetItem( node["AETitle"].toString() );
-    d->NodeTable->setItem(row, 1, newItem);
-    newItem = new QTableWidgetItem( node["Address"].toString() );
-    d->NodeTable->setItem(row, 2, newItem);
-    newItem = new QTableWidgetItem( node["Port"].toString() );
-    d->NodeTable->setItem(row, 3, newItem);
-  }
+  this->readSettings();
 
   connect(d->CallingAETitle, SIGNAL(textChanged(const QString&)),
     this, SLOT(saveSettings()));
+  connect(d->StorageAETitle, SIGNAL(textChanged(const QString&)),
+    this, SLOT(saveSettings()));
+  connect(d->StoragePort, SIGNAL(textChanged(const QString&)),
+    this, SLOT(saveSettings()));
   connect(d->AddButton, SIGNAL(clicked()),
     this, SLOT(addNode()));
   connect(d->RemoveButton, SIGNAL(clicked()),
@@ -169,28 +139,77 @@ void ctkDICOMServerNodeWidget::saveSettings()
   Q_D(ctkDICOMServerNodeWidget);
 
   QSettings settings;
-  QMap<QString, QVariant> node;
-  int count = d->NodeTable->rowCount();
-  QStringList keys;
-  keys << "Name" << "AETitle" << "Address" << "Port";
-  for (int row = 0; row < count; row++)
-  {
-    for (int k = 0; k < keys.size(); ++k)
+  const int rowCount = d->NodeTable->rowCount();
+  const int columnCount = d->NodeTable->columnCount();
+  
+  settings.setValue("ServerNodeCount", rowCount);
+  for (int row = 0; row < rowCount; ++row)
     {
-      if ( d->NodeTable->item(row,k) )
+    QMap<QString, QVariant> node;
+    for (int k = 0; k < columnCount; ++k)
       {
-        node[keys.at(k)] = d->NodeTable->item(row,k)->text();
+      if (!d->NodeTable->item(row,k))
+        {
+        continue;
+        }
+      QString label = d->NodeTable->horizontalHeaderItem(k)->text();
+      node[label] = d->NodeTable->item(row, k)->data(Qt::DisplayRole);
       }
-      node["CheckState"] = d->NodeTable->item(row,0)->checkState();
-      settings.setValue(QString("ServerNodes/%1").arg(row), QVariant(node));
+    node["CheckState"] = d->NodeTable->item(row,0) ?
+      d->NodeTable->item(row,0)->checkState() : Qt::Unchecked;
+    settings.setValue(QString("ServerNodes/%1").arg(row), QVariant(node));
     }
-  }
-  settings.setValue("ServerNodeCount", count);
   settings.setValue("CallingAETitle", d->CallingAETitle->text());
+  settings.setValue("StorageAETitle", d->StorageAETitle->text());
+  settings.setValue("StoragePort", d->StoragePort->text());
   settings.sync();
 }
 
 //----------------------------------------------------------------------------
+void ctkDICOMServerNodeWidget::readSettings()
+{
+  Q_D(ctkDICOMServerNodeWidget);
+
+  QSettings settings;
+
+  QMap<QString, QVariant> node;
+  if (settings.status() == QSettings::AccessError ||
+      settings.value("ServerNodeCount").toInt() == 0)
+    {
+    d->StorageAETitle->setText("CTKSTORE");
+    d->StoragePort->setText("11112");
+    d->CallingAETitle->setText("FINDSCU");
+    d->NodeTable->setRowCount(1);
+    d->NodeTable->setItem(0, NameColumn, new QTableWidgetItem("ExampleHost"));
+    d->NodeTable->item(0, NameColumn)->setCheckState( Qt::Checked );
+    d->NodeTable->setItem(0, AETitleColumn, new QTableWidgetItem("ANY-SCP"));
+    d->NodeTable->setItem(0, AddressColumn, new QTableWidgetItem("localhost"));
+    d->NodeTable->setItem(0, PortColumn, new QTableWidgetItem("11112"));
+    return;
+    }
+
+  d->StorageAETitle->setText(settings.value("StorageAETitle").toString());
+  d->StoragePort->setText(settings.value("StoragePort").toString());
+  d->CallingAETitle->setText(settings.value("CallingAETitle").toString());
+  const int count = settings.value("ServerNodeCount").toInt();
+  d->NodeTable->setRowCount(count);
+  for (int row = 0; row < count; row++)
+    {
+    node = settings.value(QString("ServerNodes/%1").arg(row)).toMap();
+    QTableWidgetItem *newItem;
+    newItem = new QTableWidgetItem( node["Name"].toString() );
+    newItem->setCheckState( Qt::CheckState(node["CheckState"].toInt()) );
+    d->NodeTable->setItem(row, NameColumn, newItem);
+    newItem = new QTableWidgetItem( node["AETitle"].toString() );
+    d->NodeTable->setItem(row, AETitleColumn, newItem);
+    newItem = new QTableWidgetItem( node["Address"].toString() );
+    d->NodeTable->setItem(row, AddressColumn, newItem);
+    newItem = new QTableWidgetItem( node["Port"].toString() );
+    d->NodeTable->setItem(row, PortColumn, newItem);
+    }
+}
+
+//----------------------------------------------------------------------------
 QString ctkDICOMServerNodeWidget::callingAETitle()
 {
   Q_D(ctkDICOMServerNodeWidget);

+ 9 - 2
Libs/DICOM/Widgets/ctkDICOMServerNodeWidget.h

@@ -49,11 +49,18 @@ public slots:
   void removeNode ();
   void onCellChanged (int row, int column);
   void onCurrentItemChanged(QTableWidgetItem* current, QTableWidgetItem *previous);
-  void saveSettings ();
+
+  void readSettings();
+  void saveSettings();
 
 protected:
   QScopedPointer<ctkDICOMServerNodeWidgetPrivate> d_ptr;
-
+  enum ServerColumns{
+    NameColumn = 0,
+    AETitleColumn,
+    AddressColumn,
+    PortColumn
+  };
 private:
   Q_DECLARE_PRIVATE(ctkDICOMServerNodeWidget);
   Q_DISABLE_COPY(ctkDICOMServerNodeWidget);

+ 2 - 0
Libs/Widgets/Testing/Cpp/CMakeLists.txt

@@ -7,6 +7,7 @@ CREATE_TEST_SOURCELIST(Tests ${KIT}CppTests.cxx
   ctkButtonGroupTest1.cpp
   ctkCheckBoxPixmapsTest1.cpp
   ctkCheckableHeaderViewTest1.cpp
+  ctkCheckableHeaderViewTest2.cpp
   ctkCheckablePushButtonTest1.cpp
   ctkCollapsibleButtonTest1.cpp
   ctkCollapsibleGroupBoxTest1.cpp
@@ -90,6 +91,7 @@ SIMPLE_TEST( ctkAxesWidgetTest1 )
 SIMPLE_TEST( ctkButtonGroupTest1 )
 SIMPLE_TEST( ctkCheckBoxPixmapsTest1 )
 SIMPLE_TEST( ctkCheckableHeaderViewTest1 )
+SIMPLE_TEST( ctkCheckableHeaderViewTest2 )
 SIMPLE_TEST( ctkCheckablePushButtonTest1 )
 SIMPLE_TEST( ctkCollapsibleButtonTest1 )
 SIMPLE_TEST( ctkCollapsibleGroupBoxTest1 )

+ 87 - 0
Libs/Widgets/Testing/Cpp/ctkCheckableHeaderViewTest2.cpp

@@ -0,0 +1,87 @@
+/*=========================================================================
+
+  Library:   CTK
+
+  Copyright (c) Kitware Inc.
+
+  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.
+
+=========================================================================*/
+
+// Qt includes
+#include <QDebug>
+#include <QApplication>
+#include <QFocusEvent>
+#include <QTreeView>
+#include <QStandardItem>
+#include <QStandardItemModel>
+#include <QTimer>
+
+// CTK includes
+#include "ctkCheckableHeaderView.h"
+
+// STD includes
+#include <cstdlib>
+#include <iostream>
+
+//-----------------------------------------------------------------------------
+int ctkCheckableHeaderViewTest2(int argc, char * argv [] )
+{
+  QApplication app(argc, argv);
+
+  QStringList headers;
+  headers << "Title 1" << "Title 2" << "Title 3";
+  QStandardItemModel model;
+  model.setHorizontalHeaderLabels(headers);
+  QList<QStandardItem*> row0;
+  row0 << new QStandardItem << new QStandardItem << new QStandardItem;
+  row0[0]->setText("not user checkable");
+  model.appendRow(row0);
+  QList<QStandardItem*> row1;
+  row1 << new QStandardItem << new QStandardItem << new QStandardItem;
+  row1[0]->setCheckable(true);
+  row1[0]->setText("checkable");
+  model.appendRow(row1);
+  QList<QStandardItem*> row2;
+  row2 << new QStandardItem << new QStandardItem << new QStandardItem;
+  row2[0]->setCheckable(true);
+  row2[0]->setText("checkable");
+  model.appendRow(row2);
+
+  QTreeView view;
+  view.setModel(&model);
+
+  model.setHeaderData(0, Qt::Horizontal, Qt::Checked, Qt::CheckStateRole);
+
+  QHeaderView* previousHeaderView = view.header();
+  bool oldClickable = previousHeaderView->isClickable();
+
+  ctkCheckableHeaderView* headerView = new ctkCheckableHeaderView(Qt::Horizontal, &view);  
+  headerView->setClickable(oldClickable);
+  headerView->setMovable(previousHeaderView->isMovable());
+  headerView->setHighlightSections(previousHeaderView->highlightSections());
+  headerView->setPropagateToItems(true);
+
+  // sets the model to the headerview
+  view.setHeader(headerView);
+  headers << "4";
+  model.setHorizontalHeaderLabels(headers);
+  view.show();
+
+  if (argc < 2 || QString(argv[1]) != "-I" )
+    {
+    QTimer::singleShot(500, &app, SLOT(quit()));
+    }
+  
+  return app.exec();
+}