Browse Source

Merge branch 'ctkdicom-core-tests'

* ctkdicom-core-tests:
  Add ctkDICOMQueryTest2 to test network query communications
  Add ctkDICOMTester to provide testing tools for DICOM
  Alphabetically sort file order in CMakeLists.txt
  Cleanup header files for DICOM Core unit tests
  Add ctkDICOMQueryTest1, a simple test for ctkDICOMQuery
Julien Finet 14 years ago
parent
commit
f54ab467a9

+ 14 - 6
Libs/DICOM/Core/CMakeLists.txt

@@ -9,22 +9,24 @@ SET(KIT_export_directive "CTK_DICOM_CORE_EXPORT")
   
 # Source files
 SET(KIT_SRCS
+  ctkDICOMDatabase.cpp
+  ctkDICOMDatabase.h
+  ctkDICOMDataset.cpp
+  ctkDICOMDataset.h
   ctkDICOMImage.cpp
   ctkDICOMImage.h
   ctkDICOMIndexer.cpp
   ctkDICOMIndexer.h
-  ctkDICOMDatabase.cpp
-  ctkDICOMDatabase.h
   ctkDICOMModel.cpp
   ctkDICOMModel.h
+  ctkDICOMPersonName.cpp
+  ctkDICOMPersonName.h
   ctkDICOMQuery.cpp
   ctkDICOMQuery.h
   ctkDICOMRetrieve.cpp
   ctkDICOMRetrieve.h
-  ctkDICOMDataset.cpp
-  ctkDICOMDataset.h
-  ctkDICOMPersonName.cpp
-  ctkDICOMPersonName.h
+  ctkDICOMTester.cpp
+  ctkDICOMTester.h
 )
 
 # Headers that should run through moc
@@ -34,6 +36,7 @@ SET(KIT_MOC_SRCS
   ctkDICOMModel.h
   ctkDICOMQuery.h
   ctkDICOMRetrieve.h
+  ctkDICOMTester.h
   )
 
 # UI files
@@ -48,6 +51,11 @@ SET(KIT_resources
 # Target libraries - See CMake/ctkFunctionGetTargetLibraries.cmake
 # The following macro will read the target libraries from the file 'target_libraries.cmake'
 ctkFunctionGetTargetLibraries(KIT_target_libraries)
+  
+# create a dcm query/retrieve service config file that points to the build dir
+set (DCMQRSCP_STORE_DIR ${CMAKE_CURRENT_BINARY_DIR}/Testing)
+CONFIGURE_FILE( Resources/dcmqrscp.cfg.in dcmqrscp.cfg )
+set (DCMQRSCP_CONFIG ${CMAKE_CURRENT_BINARY_DIR}/dcmqrscp.cfg)
 
 ctkMacroBuildLib(
   NAME ${PROJECT_NAME}

+ 84 - 0
Libs/DICOM/Core/Resources/dcmqrscp.cfg.in

@@ -0,0 +1,84 @@
+#-----------------------------------------------------------------------
+#
+# Example configuration file for the dcmqrdb and dcmqrti applications.
+#
+# - this file is specialized for use as a local 'pacs' simulator for testing
+#   ctkDICOM* applications
+# - the location of the dicom store is configured by cmake to be in the build tree
+#
+#-----------------------------------------------------------------------
+
+# Global Configuration Parameters
+NetworkType     = "tcp"
+NetworkTCPPort  = 11112
+MaxPDUSize      = 16384
+MaxAssociations = 16
+Display         = "no"
+
+HostTable BEGIN
+#
+# The HostTable defines symbolic names for collections of network
+# DICOM Application Entities.  A symbolic name can represent a single
+# application entity or it can represent a group of application entities.
+# Each DICOM application entity is defined by a triple consisting of 
+# Application Entitiy Title, host name and TCP/IP port number.
+#
+# Entry Format: SymbolicName = ( AETitle, HostName, Portnumber ), ...   |
+#               SymbolicName = SymbolicName, ...
+#
+# NOTE: in the current implementation you cannot substitute an IP address
+# for a hostname.
+#
+#ctk           = (ACME1, acmehost1, 5678)
+#acme2           = (ACME2, acmehost2, 5678)
+#acmeCTcompany   = acme1, acme2
+#united1         = (UNITED1, unitedhost1, 104)
+#united2         = (UNITED2, unitedhost2, 104)
+#unitedMRcompany = united1, united2
+#
+commontk        = (CTK_AE,localhost,11112)
+commontk_client = (CTK_CLIENT_AE, localhost, 11113)
+#
+HostTable END
+
+VendorTable BEGIN
+#
+# The VendorTable is used by the dcmqrdb and dcmqrti applications.
+# You can give a vendor name (r.h.s. entry below) to the dcmqrti 
+# program and it will talk to all hosts and AEs of the vendor.
+# The dcmqrdb program can use the vendor table to restrict move destination
+# to hosts belonging to a vendor.
+# Also, the dcmqrti and dcmqrdb programs use the name defined on the left hand side
+# as the vendor name to display above images.
+#
+# The format: 
+#       VendorName = SymbolicName
+# The symbolic name should be defined in the HostTable.
+#
+#"Acme CT Company"   = acmeCTcompany
+#"United MR Company" = unitedMRcompany
+"The Common Toolkit" = commontk
+"The Common Toolkit Client" = commontk_client
+#
+VendorTable END
+
+AETable BEGIN
+#
+# Each row of the AETable defines an Application Entities (AE) Title known
+# to the dcmqrdb application.  Each AE Title represents a separate
+# image database located in the specified file system directory (storage area).
+# Each AE Title has read/write, quota and peer access restrictions.
+#
+# Entry Format: AETitle  StorageArea  Access  Quota  Peers
+# AccessFormat: R | RW | W
+# Quota Format: ( maxStudies, maxBytesPerStudy )
+# Peers Format: ( Hostname, AETitle, Portnumber ), ...  |
+#               Entry in HostTable                      |
+#               ANY
+#
+#COMMON       /home/dicom/db/COMMON       R  (200, 1024mb) ANY
+#ACME_STORE   /home/dicom/db/ACME_STORE   RW (9, 1024mb)   acmeCTcompany
+#UNITED_STORE /home/dicom/db/UNITED_STORE RW (9, 1024mb)   unitedMRcompany
+CTK_AE     @DCMQRSCP_STORE_DIR@        RW (200, 1024mb) ANY
+#
+AETable END

+ 16 - 0
Libs/DICOM/Core/Testing/Cpp/CMakeLists.txt

@@ -6,8 +6,12 @@ CREATE_TEST_SOURCELIST(Tests ${KIT}CppTests.cpp
   ctkDICOMImageTest1.cpp
   ctkDICOMIndexerTest1.cpp
   ctkDICOMModelTest1.cpp
+  ctkDICOMQueryTest1.cpp
+  ctkDICOMQueryTest2.cpp
   ctkDICOMPersonNameTest1.cpp
   ctkDICOMTest1.cpp
+  ctkDICOMTesterTest1.cpp
+  ctkDICOMTesterTest2.cpp
   )
 
 SET (TestsToRun ${Tests})
@@ -43,7 +47,19 @@ ADD_TEST( ctkDICOMModelTest1 ${KIT_TESTS}
                              ${CMAKE_CURRENT_SOURCE_DIR}/../../Resources/dicom-sample.sql)
 SET_PROPERTY(TEST ctkDICOMModelTest1 PROPERTY LABELS ${PROJECT_NAME})
 
+# ctkDICOMQuery
+SIMPLE_TEST( ctkDICOMQueryTest1)
+ADD_TEST( ctkDICOMQueryTest2 ${KIT_TESTS}
+          ctkDICOMQueryTest2 ${CTKData_DIR}/Data/DICOM/MRHEAD/000055.IMA ${CTKData_DIR}/Data/DICOM/MRHEAD/000056.IMA)
+SET_PROPERTY(TEST ctkDICOMQueryTest2 PROPERTY LABELS ${PROJECT_NAME})
+
 ADD_TEST( ctkDICOMTest1 ${KIT_TESTS}
           ctkDICOMTest1 ${CMAKE_CURRENT_BINARY_DIR}/dicom.db
                         ${CMAKE_CURRENT_SOURCE_DIR}/../../Resources/dicom-sample.sql)
 SET_PROPERTY(TEST ctkDICOMTest1 PROPERTY LABELS ${PROJECT_NAME})
+
+# ctkDICOMTester
+SIMPLE_TEST(ctkDICOMTesterTest1)
+ADD_TEST( ctkDICOMTesterTest2 ${KIT_TESTS}
+          ctkDICOMTesterTest2 ${CTKData_DIR}/Data/DICOM/MRHEAD/000055.IMA ${CTKData_DIR}/Data/DICOM/MRHEAD/000056.IMA)
+SET_PROPERTY(TEST ctkDICOMTesterTest2 PROPERTY LABELS ${PROJECT_NAME})

+ 22 - 8
Libs/DICOM/Core/Testing/Cpp/ctkDICOMDatabaseTest1.cpp

@@ -1,6 +1,25 @@
+/*=========================================================================
+
+  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 <QApplication>
+#include <QCoreApplication>
 #include <QDir>
 #include <QTimer>
 
@@ -14,7 +33,7 @@
 
 int ctkDICOMDatabaseTest1( int argc, char * argv [] )
 {
-  QApplication app(argc, argv);
+  QCoreApplication app(argc, argv);
 
   ctkDICOMDatabase database;
   QDir databaseDirectory = QDir::temp();
@@ -74,10 +93,5 @@ int ctkDICOMDatabaseTest1( int argc, char * argv [] )
   database.closeDatabase();
   database.initializeDatabase();
 
-  if (argc <= 1 || QString(argv[1]) != "-I")
-    {
-    QTimer::singleShot(200, &app, SLOT(quit()));
-    }
-
-  return app.exec();
+  return EXIT_SUCCESS;
 }

+ 18 - 4
Libs/DICOM/Core/Testing/Cpp/ctkDICOMDatasetTest1.cpp

@@ -1,8 +1,22 @@
+/*=========================================================================
 
-// Qt includes
-#include <QApplication>
-#include <QDir>
-#include <QTimer>
+  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.
+
+=========================================================================*/
 
 // ctkDICOMCore includes
 #include "ctkDICOMDataset.h"

+ 21 - 2
Libs/DICOM/Core/Testing/Cpp/ctkDICOMIndexerTest1.cpp

@@ -1,6 +1,25 @@
+/*=========================================================================
+
+  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 <QApplication>
+#include <QCoreApplication>
 #include <QDebug>
 #include <QDir>
 #include <QFileInfo>
@@ -16,7 +35,7 @@
 
 int ctkDICOMIndexerTest1( int argc, char * argv [] )
 {
-  QApplication app(argc, argv);
+  QCoreApplication app(argc, argv);
   
   ctkDICOMDatabase database;
   ctkDICOMIndexer indexer;

+ 19 - 0
Libs/DICOM/Core/Testing/Cpp/ctkDICOMModelTest1.cpp

@@ -1,3 +1,22 @@
+/*=========================================================================
+
+  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 <QApplication>

+ 21 - 3
Libs/DICOM/Core/Testing/Cpp/ctkDICOMPersonNameTest1.cpp

@@ -1,7 +1,25 @@
+/*=========================================================================
+
+  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 <QApplication>
-#include <QDir>
+#include <QCoreApplication>
 #include <QTimer>
 
 // ctkDICOMCore includes
@@ -14,7 +32,7 @@
 
 int ctkDICOMPersonNameTest1( int argc, char * argv [] )
 {
-  QApplication app(argc, argv);
+  QCoreApplication app(argc, argv);
 
   ctkDICOMPersonName personName("lastName", "firstName", "middleName", "namePrefix", "nameSuffix");
   if (personName.lastName() != "lastName")

+ 119 - 0
Libs/DICOM/Core/Testing/Cpp/ctkDICOMQueryTest1.cpp

@@ -0,0 +1,119 @@
+/*=========================================================================
+
+  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 <QCoreApplication>
+#include <QStringList>
+#include <QVariant>
+
+// ctkDICOMCore includes
+#include "ctkDICOMQuery.h"
+
+// STD includes
+#include <iostream>
+
+// Simple test that check the values are correctly set
+int ctkDICOMQueryTest1( int argc, char * argv [] )
+{
+  QCoreApplication app(argc, argv);
+
+  ctkDICOMQuery query;
+
+  // check default values
+  if (!query.callingAETitle().isEmpty() ||
+      !query.calledAETitle().isEmpty() ||
+      !query.host().isEmpty() ||
+      query.port() != 0 ||
+      !query.filters().isEmpty() ||
+      !query.studyInstanceUIDQueried().isEmpty())
+    {
+    std::cerr << "ctkDICOMQuery::ctkDICOMQuery() failed: "
+              << qPrintable(query.callingAETitle()) << " "
+              << qPrintable(query.calledAETitle()) << " "
+              << qPrintable(query.host()) << " "
+              << query.port() << " "
+              << std::endl;
+    return EXIT_FAILURE;
+    }
+
+  query.setCallingAETitle("CallingAETitle");
+  if (query.callingAETitle() != "CallingAETitle")
+    {
+    std::cerr << "ctkDICOMQuery::setCallingAETitle() failed: "
+              << qPrintable(query.callingAETitle()) << std::endl;
+    return EXIT_FAILURE;
+    }
+
+  query.setCalledAETitle("CalledAETitle");
+  if (query.calledAETitle() != "CalledAETitle")
+    {
+    std::cerr << "ctkDICOMQuery::setCalledAETitle() failed: "
+              << qPrintable(query.calledAETitle()) << std::endl;
+    return EXIT_FAILURE;
+    }
+
+  query.setHost("host");
+  if (query.host() != "host")
+    {
+    std::cerr << "ctkDICOMQuery::setHost() failed: "
+              << qPrintable(query.host()) << std::endl;
+    return EXIT_FAILURE;
+    }
+
+  query.setPort(80);
+  if (query.port() != 80)
+    {
+    std::cerr << "ctkDICOMQuery::setPort() failed: "
+              << query.port() << std::endl;
+    return EXIT_FAILURE;
+    }
+
+  query.setCallingAETitle("CallingAETitle");
+  if (query.callingAETitle() != "CallingAETitle")
+    {
+    std::cerr << "ctkDICOMQuery::setCallingAETitle() failed: "
+              << qPrintable(query.callingAETitle()) << std::endl;
+    return EXIT_FAILURE;
+    }
+
+  QMap<QString,QVariant> filters;
+  filters["Name"] = QString("JohnDoe");
+  filters["StartDate"] = QString("20090101");
+  filters["EndDate"] = QString("20091231");
+
+  query.setFilters(filters);
+  if (query.filters() != filters)
+    {
+    std::cerr << "ctkDICOMDatabase::setFilters() failed: "
+              << query.filters().count() << std::endl;
+    return EXIT_FAILURE;
+    }
+  ctkDICOMDatabase database;
+  query.query(database);
+  
+  // Queried studies should be empty because we use an empty database.
+  if (!query.studyInstanceUIDQueried().isEmpty())
+    {
+    std::cerr << "ctkDICOMDatabase::query() failed: "
+              << query.studyInstanceUIDQueried().count() << std::endl;
+    return EXIT_FAILURE;
+    }
+  return EXIT_SUCCESS;
+}

+ 79 - 0
Libs/DICOM/Core/Testing/Cpp/ctkDICOMQueryTest2.cpp

@@ -0,0 +1,79 @@
+/*=========================================================================
+
+  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 <QCoreApplication>
+#include <QDebug>
+#include <QStringList>
+#include <QVariant>
+
+// ctkDICOMCore includes
+#include "ctkDICOMDatabase.h"
+#include "ctkDICOMQuery.h"
+#include "ctkDICOMTester.h"
+
+// STD includes
+#include <iostream>
+
+void ctkDICOMQueryTest2PrintUsage()
+{
+  std::cout << " ctkDICOMQueryTest2 images" << std::endl;
+}
+
+// Test on a real local database
+int ctkDICOMQueryTest2( int argc, char * argv [] )
+{
+  QCoreApplication app(argc, argv);
+
+  ctkDICOMTester tester;
+  tester.startDCMQRSCP();
+  
+  QStringList arguments = app.arguments();
+  arguments.pop_front(); // remove application name
+  arguments.pop_front(); // remove test name
+  if (!arguments.count())
+    {
+    ctkDICOMQueryTest2PrintUsage();
+    return EXIT_FAILURE;
+    }
+  tester.storeData(arguments);
+
+  ctkDICOMDatabase database;
+
+  ctkDICOMQuery query;
+  query.setCallingAETitle("CTK_AE");
+  query.setCalledAETitle("CTK_AE");
+  query.setHost("localhost");
+  query.setPort(tester.dcmqrscpPort());
+
+  bool res = query.query(database);
+  if (!res)
+    {
+    std::cout << "ctkDICOMQuery::query() failed" << std::endl;
+    return EXIT_FAILURE;
+    }
+  if (query.studyInstanceUIDQueried().count() == 0)
+    {
+    std::cout << "ctkDICOMQuery::query() failed."
+              << "No study instance retrieved" << std::endl;
+    return EXIT_FAILURE;
+    }
+  return EXIT_SUCCESS;
+}

+ 2 - 2
Libs/DICOM/Core/Testing/Cpp/ctkDICOMTest1.cpp

@@ -1,7 +1,7 @@
 
 // Qt includes
+#include <QCoreApplication>
 #include <QTextStream>
-#include <QApplication>
 
 // ctkDICOMCore includes
 #include "ctkDICOMDatabase.h"
@@ -12,7 +12,7 @@
 
 int ctkDICOMTest1(int argc, char * argv []) {
   
-  QApplication app(argc, argv);
+  QCoreApplication app(argc, argv);
   QTextStream out(stdout);
   try
   {

+ 59 - 0
Libs/DICOM/Core/Testing/Cpp/ctkDICOMTesterTest1.cpp

@@ -0,0 +1,59 @@
+// Qt includes
+#include <QCoreApplication>
+
+// ctkDICOMCore includes
+#include "ctkDICOMTester.h"
+
+// STD includes
+#include <iostream>
+#include <cstdlib>
+
+void printUsage()
+{
+  std::cout << " ctkDICOMTesterTest1 [<dcmqrscp>] [<configfile>]" << std::endl;
+}
+
+int ctkDICOMTesterTest1(int argc, char * argv [])
+{
+  QCoreApplication app(argc, argv);
+
+  ctkDICOMTester tester;
+  
+  if (argc > 1)
+    {
+    tester.setDCMQRSCPExecutable(argv[1]);
+    }
+  if (argc > 2)
+    {
+    tester.setDCMQRSCPConfigFile(argv[2]);
+    }
+
+  QProcess* process = tester.startDCMQRSCP();
+  if (!process)
+    {
+    std::cerr << "Failed to start dcmqrscp" << argv[1] << " with config file:"
+              << argv[2] << std::endl;
+    return EXIT_FAILURE;
+    }
+  tester.stopDCMQRSCP();
+
+  QProcess* process2 = tester.startDCMQRSCP();
+  if (!process2 || process2 == process)
+    {
+    std::cerr << "Failed to start dcmqrscp" << argv[1] << " with config file:"
+              << argv[2] << std::endl;
+    return EXIT_FAILURE;
+    }
+  QProcess* process3 = tester.startDCMQRSCP();
+  if (!process3 || process3 != process2)
+    {
+    std::cerr << "Failed to start dcmqrscp" << argv[1] << " with config file:"
+              << argv[2] << std::endl;
+    return EXIT_FAILURE;
+    }
+  tester.stopDCMQRSCP();
+  tester.stopDCMQRSCP();
+
+  return EXIT_SUCCESS;
+}
+

+ 42 - 0
Libs/DICOM/Core/Testing/Cpp/ctkDICOMTesterTest2.cpp

@@ -0,0 +1,42 @@
+// Qt includes
+#include <QCoreApplication>
+#include <QStringList>
+
+// ctkDICOMCore includes
+#include "ctkDICOMTester.h"
+
+// STD includes
+#include <iostream>
+#include <cstdlib>
+
+void ctkDICOMTesterTest2PrintUsage()
+{
+  std::cout << " ctkDICOMTesterTest2 images" << std::endl;
+}
+
+int ctkDICOMTesterTest2(int argc, char * argv [])
+{
+  QCoreApplication app(argc, argv);
+
+  QStringList arguments = app.arguments();
+  arguments.pop_front();
+  if (!arguments.count())
+    {
+    ctkDICOMTesterTest2PrintUsage();
+    return EXIT_FAILURE;
+    }
+
+  ctkDICOMTester tester;
+  tester.startDCMQRSCP();
+
+  bool res = tester.storeData(arguments);
+
+  if (!res)
+    {
+    std::cout << "Can't store data" << std::endl;
+    return EXIT_FAILURE;
+    }
+
+  return EXIT_SUCCESS;
+}
+

+ 101 - 94
Libs/DICOM/Core/ctkDICOMQuery.cpp

@@ -60,15 +60,18 @@ class ctkDICOMQueryPrivate
 public:
   ctkDICOMQueryPrivate();
   ~ctkDICOMQueryPrivate();
-  QString CallingAETitle;
-  QString CalledAETitle;
-  QString Host;
-  int Port;
-  QMap<QString,QVariant> Filters;
-  DcmSCU SCU;
-  DcmDataset* query;
-  QStringList StudyInstanceUIDList;
 
+  /// Add a StudyInstanceUID to be queried
+  void addStudyInstanceUID(const QString& StudyInstanceUID );
+
+  QString                 CallingAETitle;
+  QString                 CalledAETitle;
+  QString                 Host;
+  int                     Port;
+  QMap<QString,QVariant>  Filters;
+  DcmSCU                  SCU;
+  DcmDataset*             Query;
+  QStringList             StudyInstanceUIDList;
 };
 
 //------------------------------------------------------------------------------
@@ -77,22 +80,29 @@ public:
 //------------------------------------------------------------------------------
 ctkDICOMQueryPrivate::ctkDICOMQueryPrivate()
 {
-  query = new DcmDataset();
+  this->Query = new DcmDataset();
+  this->Port = 0;
 }
 
 //------------------------------------------------------------------------------
 ctkDICOMQueryPrivate::~ctkDICOMQueryPrivate()
 {
-  delete query;
+  delete this->Query;
 }
 
+//------------------------------------------------------------------------------
+void ctkDICOMQueryPrivate::addStudyInstanceUID( const QString& s )
+{
+  this->StudyInstanceUIDList.append ( s );
+}
 
 //------------------------------------------------------------------------------
 // ctkDICOMQuery methods
 
 //------------------------------------------------------------------------------
-ctkDICOMQuery::ctkDICOMQuery()
-   : d_ptr(new ctkDICOMQueryPrivate)
+ctkDICOMQuery::ctkDICOMQuery(QObject* parentObject)
+  : QObject(parentObject)
+  , d_ptr(new ctkDICOMQueryPrivate)
 {
 }
 
@@ -101,53 +111,46 @@ ctkDICOMQuery::~ctkDICOMQuery()
 {
 }
 
-//------------------------------------------------------------------------------
-void ctkDICOMQuery::addStudyInstanceUID ( QString s )
-{
-  Q_D(ctkDICOMQuery);
-  d->StudyInstanceUIDList.append ( s );
-}
-
 /// Set methods for connectivity
 //------------------------------------------------------------------------------
-void ctkDICOMQuery::setCallingAETitle ( QString callingAETitle )
+void ctkDICOMQuery::setCallingAETitle( const QString& callingAETitle )
 {
   Q_D(ctkDICOMQuery);
   d->CallingAETitle = callingAETitle;
 }
 
 //------------------------------------------------------------------------------
-const QString& ctkDICOMQuery::callingAETitle() 
+QString ctkDICOMQuery::callingAETitle() const
 {
-  Q_D(ctkDICOMQuery);
+  Q_D(const ctkDICOMQuery);
   return d->CallingAETitle;
 }
 
 //------------------------------------------------------------------------------
-void ctkDICOMQuery::setCalledAETitle ( QString calledAETitle )
+void ctkDICOMQuery::setCalledAETitle( const QString& calledAETitle )
 {
   Q_D(ctkDICOMQuery);
   d->CalledAETitle = calledAETitle;
 }
 
 //------------------------------------------------------------------------------
-const QString& ctkDICOMQuery::calledAETitle()
+QString ctkDICOMQuery::calledAETitle()const
 {
-  Q_D(ctkDICOMQuery);
+  Q_D(const ctkDICOMQuery);
   return d->CalledAETitle;
 }
 
 //------------------------------------------------------------------------------
-void ctkDICOMQuery::setHost ( QString host )
+void ctkDICOMQuery::setHost( const QString& host )
 {
   Q_D(ctkDICOMQuery);
   d->Host = host;
 }
 
 //------------------------------------------------------------------------------
-const QString& ctkDICOMQuery::host()
+QString ctkDICOMQuery::host() const
 {
-  Q_D(ctkDICOMQuery);
+  Q_D(const ctkDICOMQuery);
   return d->Host;
 }
 
@@ -159,38 +162,41 @@ void ctkDICOMQuery::setPort ( int port )
 }
 
 //------------------------------------------------------------------------------
-int ctkDICOMQuery::port()
+int ctkDICOMQuery::port()const
 {
-  Q_D(ctkDICOMQuery);
+  Q_D(const ctkDICOMQuery);
   return d->Port;
 }
 
 //------------------------------------------------------------------------------
-void ctkDICOMQuery::setFilters ( QMap<QString,QVariant> filters ) 
+void ctkDICOMQuery::setFilters( const QMap<QString,QVariant>& filters ) 
 {
   Q_D(ctkDICOMQuery);
   d->Filters = filters;
 }
 
 //------------------------------------------------------------------------------
-QMap<QString,QVariant> ctkDICOMQuery::filters()
+QMap<QString,QVariant> ctkDICOMQuery::filters()const
 {
-  Q_D(ctkDICOMQuery);
+  Q_D(const ctkDICOMQuery);
   return d->Filters;
 }
 
 //------------------------------------------------------------------------------
-QStringList ctkDICOMQuery::studyInstanceUIDQueried()
+QStringList ctkDICOMQuery::studyInstanceUIDQueried()const
 {
-  Q_D(ctkDICOMQuery);
+  Q_D(const ctkDICOMQuery);
   return d->StudyInstanceUIDList;
 }
 
 //------------------------------------------------------------------------------
-void ctkDICOMQuery::query(ctkDICOMDatabase& database )
+bool ctkDICOMQuery::query(ctkDICOMDatabase& database )
 {
   // ctkDICOMDatabase::setDatabase ( database );
   Q_D(ctkDICOMQuery);
+  // In the following, we emit progress(int) after progress(QString), this
+  // is in case the connected object doesn't refresh its ui when the progress
+  // message is updated but only if the progress value is (e.g. QProgressDialog)
   if ( database.database().isOpen() )
     {
     logger.debug ( "DB open in Query" );
@@ -225,7 +231,7 @@ void ctkDICOMQuery::query(ctkDICOMDatabase& database )
     logger.error( "Error initializing the network" );
     emit progress("Error initializing the network");
     emit progress(100);
-    return;
+    return false;
     }
   logger.debug ( "Negotiating Association" );
   emit progress("Negatiating Association");
@@ -234,82 +240,82 @@ void ctkDICOMQuery::query(ctkDICOMDatabase& database )
   d->SCU.negotiateAssociation();
 
   // Clear the query
-  unsigned long elements = d->query->card();
+  unsigned long elements = d->Query->card();
   // Clean it out
   for ( unsigned long i = 0; i < elements; i++ ) 
     {
-    d->query->remove ( 0ul );
+    d->Query->remove ( 0ul );
     }
-  d->query->insertEmptyElement ( DCM_PatientID );
-  d->query->insertEmptyElement ( DCM_PatientsName );
-  d->query->insertEmptyElement ( DCM_PatientsBirthDate );
-  d->query->insertEmptyElement ( DCM_StudyID );
-  d->query->insertEmptyElement ( DCM_StudyInstanceUID );
-  d->query->insertEmptyElement ( DCM_StudyDescription );
-  d->query->insertEmptyElement ( DCM_StudyDate );
-  d->query->insertEmptyElement ( DCM_SeriesNumber );
-  d->query->insertEmptyElement ( DCM_SeriesDescription );
-  d->query->insertEmptyElement ( DCM_SeriesInstanceUID );
-  d->query->insertEmptyElement ( DCM_StudyTime );
-  d->query->insertEmptyElement ( DCM_SeriesDate );
-  d->query->insertEmptyElement ( DCM_SeriesTime );
-  d->query->insertEmptyElement ( DCM_Modality );
-  d->query->insertEmptyElement ( DCM_ModalitiesInStudy );
-  d->query->insertEmptyElement ( DCM_AccessionNumber );
-  d->query->insertEmptyElement ( DCM_NumberOfSeriesRelatedInstances ); // Number of images in the series
-  d->query->insertEmptyElement ( DCM_NumberOfStudyRelatedInstances ); // Number of images in the series
-  d->query->insertEmptyElement ( DCM_NumberOfStudyRelatedSeries ); // Number of images in the series
-
-  d->query->putAndInsertString ( DCM_QueryRetrieveLevel, "STUDY" );
+  d->Query->insertEmptyElement ( DCM_PatientID );
+  d->Query->insertEmptyElement ( DCM_PatientsName );
+  d->Query->insertEmptyElement ( DCM_PatientsBirthDate );
+  d->Query->insertEmptyElement ( DCM_StudyID );
+  d->Query->insertEmptyElement ( DCM_StudyInstanceUID );
+  d->Query->insertEmptyElement ( DCM_StudyDescription );
+  d->Query->insertEmptyElement ( DCM_StudyDate );
+  d->Query->insertEmptyElement ( DCM_SeriesNumber );
+  d->Query->insertEmptyElement ( DCM_SeriesDescription );
+  d->Query->insertEmptyElement ( DCM_SeriesInstanceUID );
+  d->Query->insertEmptyElement ( DCM_StudyTime );
+  d->Query->insertEmptyElement ( DCM_SeriesDate );
+  d->Query->insertEmptyElement ( DCM_SeriesTime );
+  d->Query->insertEmptyElement ( DCM_Modality );
+  d->Query->insertEmptyElement ( DCM_ModalitiesInStudy );
+  d->Query->insertEmptyElement ( DCM_AccessionNumber );
+  d->Query->insertEmptyElement ( DCM_NumberOfSeriesRelatedInstances ); // Number of images in the series
+  d->Query->insertEmptyElement ( DCM_NumberOfStudyRelatedInstances ); // Number of images in the series
+  d->Query->insertEmptyElement ( DCM_NumberOfStudyRelatedSeries ); // Number of images in the series
+
+  d->Query->putAndInsertString ( DCM_QueryRetrieveLevel, "STUDY" );
 
   foreach( QString key, d->Filters.keys() )
-  {
-    if ( key == QString("Name") )
     {
+    if ( key == QString("Name") )
+      {
       // make the filter a wildcard in dicom style
-      d->query->putAndInsertString( DCM_PatientsName,
+      d->Query->putAndInsertString( DCM_PatientsName,
         (QString("*") + d->Filters[key].toString() + QString("*")).toAscii().data());
-    }
+      }
     if ( key == QString("Study") )
-    {
+      {
       // make the filter a wildcard in dicom style
-      d->query->putAndInsertString( DCM_StudyDescription,
+      d->Query->putAndInsertString( DCM_StudyDescription,
         (QString("*") + d->Filters[key].toString() + QString("*")).toAscii().data());
-    }
+      }
     if ( key == QString("Series") )
-    {
+      {
       // make the filter a wildcard in dicom style
-      d->query->putAndInsertString( DCM_SeriesDescription,
+      d->Query->putAndInsertString( DCM_SeriesDescription,
         (QString("*") + d->Filters[key].toString() + QString("*")).toAscii().data());
-    }
+      }
     if ( key == QString("ID") )
-    {
+      {
       // make the filter a wildcard in dicom style
-      d->query->putAndInsertString( DCM_PatientID,
+      d->Query->putAndInsertString( DCM_PatientID,
         (QString("*") + d->Filters[key].toString() + QString("*")).toAscii().data());
-    }
+      }
     if ( key == QString("Modalities") )
-    {
+      {
       // make the filter be an "OR" of modalities using backslash (dicom-style)
       QString modalitySearch("");
-      foreach (QString modality, d->Filters[key].toStringList())
-      {
+      foreach (const QString& modality, d->Filters[key].toStringList())
+        {
         modalitySearch += modality + QString("\\");
-      }
+        }
       modalitySearch.chop(1); // remove final backslash
       logger.debug("modalitySearch " + modalitySearch);
-      d->query->putAndInsertString( DCM_ModalitiesInStudy, modalitySearch.toAscii().data() );
+      d->Query->putAndInsertString( DCM_ModalitiesInStudy, modalitySearch.toAscii().data() );
+      }
     }
-  }
 
   if ( d->Filters.keys().contains("StartDate") && d->Filters.keys().contains("EndDate") )
-  {
+    {
     QString dateRange = d->Filters["StartDate"].toString() + 
                           QString("-") + 
                               d->Filters["EndDate"].toString();
-    d->query->putAndInsertString ( DCM_StudyDate, dateRange.toAscii().data() );
+    d->Query->putAndInsertString ( DCM_StudyDate, dateRange.toAscii().data() );
     logger.debug("Query on study time " + dateRange);
-  }
+    }
 
   emit progress(30);
 
@@ -319,7 +325,7 @@ void ctkDICOMQuery::query(ctkDICOMDatabase& database )
   presentationContex = d->SCU.findPresentationContextID ( UID_FINDStudyRootQueryRetrieveInformationModel, UID_LittleEndianExplicitTransferSyntax );
   if ( presentationContex == 0 )
     {
-  presentationContex = d->SCU.findPresentationContextID ( UID_FINDStudyRootQueryRetrieveInformationModel, UID_BigEndianExplicitTransferSyntax );
+    presentationContex = d->SCU.findPresentationContextID ( UID_FINDStudyRootQueryRetrieveInformationModel, UID_BigEndianExplicitTransferSyntax );
     }
   if ( presentationContex == 0 )
     {
@@ -338,17 +344,17 @@ void ctkDICOMQuery::query(ctkDICOMDatabase& database )
     }
   emit progress(40);
 
-  OFCondition status = d->SCU.sendFINDRequest ( presentationContex, d->query, responses );
-  if ( status.good() )
-    {
-    logger.debug ( "Find succeded" );
-    emit progress("Find succeded");
-    }
-  else
+  OFCondition status = d->SCU.sendFINDRequest ( presentationContex, d->Query, responses );
+  if ( !status.good() )
     {
     logger.error ( "Find failed" );
     emit progress("Find failed");
+    d->SCU.closeAssociation ( DUL_PEERREQUESTEDRELEASE );
+    emit progress(100);
+    return false;
     }
+  logger.debug ( "Find succeded" );
+  emit progress("Find succeded");
   emit progress(50);
 
   for ( OFListIterator(FINDResponse*) it = responses->begin(); it != responses->end(); it++ )
@@ -359,13 +365,13 @@ void ctkDICOMQuery::query(ctkDICOMDatabase& database )
       database.insert ( dataset );
       OFString StudyInstanceUID;
       dataset->findAndGetOFString ( DCM_StudyInstanceUID, StudyInstanceUID );
-      this->addStudyInstanceUID ( QString ( StudyInstanceUID.c_str() ) );
+      d->addStudyInstanceUID ( QString ( StudyInstanceUID.c_str() ) );
       }
     }
   delete responses;
 
   // Now search each Study
-  d->query->putAndInsertString ( DCM_QueryRetrieveLevel, "SERIES" );
+  d->Query->putAndInsertString ( DCM_QueryRetrieveLevel, "SERIES" );
   float progressRatio = 25. / d->StudyInstanceUIDList.count();
   int i = 0;
   foreach ( QString StudyInstanceUID, d->StudyInstanceUIDList )
@@ -374,9 +380,9 @@ void ctkDICOMQuery::query(ctkDICOMDatabase& database )
     emit progress(QString("Starting Series C-FIND for Series: ") + StudyInstanceUID);
     emit progress(50 + (progressRatio * i++));
 
-    d->query->putAndInsertString ( DCM_StudyInstanceUID, StudyInstanceUID.toStdString().c_str() );
+    d->Query->putAndInsertString ( DCM_StudyInstanceUID, StudyInstanceUID.toStdString().c_str() );
     responses = new FINDResponses();
-    status = d->SCU.sendFINDRequest ( 0, d->query, responses );
+    status = d->SCU.sendFINDRequest ( 0, d->Query, responses );
     if ( status.good() )
       {
       for ( OFListIterator(FINDResponse*) it = responses->begin(); it != responses->end(); it++ )
@@ -400,5 +406,6 @@ void ctkDICOMQuery::query(ctkDICOMDatabase& database )
     }
   d->SCU.closeAssociation ( DUL_PEERREQUESTEDRELEASE );
   emit progress(100);
+  return true;
 }
 

+ 40 - 19
Libs/DICOM/Core/ctkDICOMQuery.h

@@ -39,41 +39,62 @@ class CTK_DICOM_CORE_EXPORT ctkDICOMQuery : public QObject
   Q_OBJECT
   Q_PROPERTY(QString callingAETitle READ callingAETitle WRITE setCallingAETitle);
   Q_PROPERTY(QString calledAETitle READ calledAETitle WRITE setCallingAETitle);
+  Q_PROPERTY(QString host READ host WRITE setHost);
   Q_PROPERTY(int port READ port WRITE setPort);
 
 public:
-  explicit ctkDICOMQuery();
+  explicit ctkDICOMQuery(QObject* parent = 0);
   virtual ~ctkDICOMQuery();
   
   /// Set methods for connectivity
-  void setCallingAETitle ( QString callingAETitle );
-  const QString& callingAETitle();
-  void setCalledAETitle ( QString calledAETitle );
-  const QString& calledAETitle();
-  void setHost ( QString host );
-  const QString& host();
+  /// Empty by default
+  void setCallingAETitle ( const QString& callingAETitle );
+  QString callingAETitle()const;
+  /// Empty by default
+  void setCalledAETitle ( const QString& calledAETitle );
+  QString calledAETitle()const;
+  /// Empty by default
+  void setHost ( const QString& host );
+  QString host()const;
+  /// Specify a port for the packet headers.
+  /// \a port ranges from 0 to 65535.
+  /// 0 by default.
   void setPort ( int port );
-  int port();
+  int port()const;
   
   /// Query a remote DICOM Image Store SCP
-  void query(ctkDICOMDatabase& database);
-
-  /// Add a StudyInstanceUID to be queried
-  void addStudyInstanceUID( QString StudyInstanceUID );
+  /// You must at least set the host and port before calling query()
+  bool query(ctkDICOMDatabase& database);
 
   /// Access the list of study instance UIDs from the last query
-  QStringList studyInstanceUIDQueried();
+  QStringList studyInstanceUIDQueried()const;
 
   ///
-  // Filters are keyword/value pairs as generated by
-  // the ctkDICOMWidgets in a human readable (and editable)
-  // format.  The Query is responsible for converting these
-  // into the appropriate dicom syntax for the C-Find
-  void setFilters(QMap<QString,QVariant>);
-  QMap<QString,QVariant> filters();
+  /// Filters are keyword/value pairs as generated by
+  /// the ctkDICOMWidgets in a human readable (and editable)
+  /// format.  The Query is responsible for converting these
+  /// into the appropriate dicom syntax for the C-Find
+  /// Currently supports the keys: Name, Study, Series, ID, Modalities,
+  /// StartDate and EndDate
+  /// Key         DICOM Tag                Type        Example
+  /// -----------------------------------------------------------
+  /// Name        DCM_PatientsName         QString     JOHNDOE
+  /// Study       DCM_StudyDescription     QString     
+  /// Series      DCM_SeriesDescription    QString
+  /// ID          DCM_PatientID            QString
+  /// Modalities  DCM_ModalitiesInStudy    QStringList CT, MR, MN
+  /// StartDate   DCM_StudyDate            QString     20090101
+  /// EndDate     DCM_StudyDate            QString     20091231
+  /// No filter (empty) by default.
+  void setFilters(const QMap<QString,QVariant>&);
+  QMap<QString,QVariant> filters()const;
 
 signals:
+  /// Signal is emitted inside the query() function. It ranges from 0 to 100.
+  /// In case of an error, you are assured that the progress value 100 is fired
   void progress(int progress);
+  /// Signal is emitted inside the query() function. It sends the different
+  /// the function is at.
   void progress(const QString& message);
 
 protected:

+ 308 - 0
Libs/DICOM/Core/ctkDICOMTester.cpp

@@ -0,0 +1,308 @@
+/*=========================================================================
+
+  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 <QDir>
+#include <QFileInfo>
+#include <QProcess>
+#include <QTextStream>
+
+// ctkDICOM includes
+#include "ctkDICOMTester.h"
+#include "ctkLogger.h"
+
+//------------------------------------------------------------------------------
+static ctkLogger logger("org.commontk.dicom.DICOMTester" );
+//------------------------------------------------------------------------------
+
+//------------------------------------------------------------------------------
+class ctkDICOMTesterPrivate
+{
+  Q_DECLARE_PUBLIC(ctkDICOMTester);
+protected:
+  ctkDICOMTester* const q_ptr;
+
+public:
+  ctkDICOMTesterPrivate(ctkDICOMTester&);
+  ~ctkDICOMTesterPrivate();
+
+  QString findFile(const QStringList& nameFilters, const QString& subDir)const;
+  QString findDCMQRSCPExecutable()const;
+  QString findDCMQRSCPConfigFile()const;
+  QString findStoreSCUExecutable()const;
+  void printProcessOutputs(const QString& program, QProcess* process)const;
+  
+  QProcess*   DCMQRSCPProcess;
+  QString     DCMQRSCPExecutable;
+  QString     DCMQRSCPConfigFile;
+  int         DCMQRSCPPort;
+  QString     StoreSCUExecutable;
+};
+
+//------------------------------------------------------------------------------
+// ctkDICOMTesterPrivate methods
+
+//------------------------------------------------------------------------------
+ctkDICOMTesterPrivate::ctkDICOMTesterPrivate(ctkDICOMTester& o): q_ptr(&o)
+{
+  this->DCMQRSCPProcess = 0;
+  this->DCMQRSCPExecutable = this->findDCMQRSCPExecutable();
+  this->DCMQRSCPConfigFile = this->findDCMQRSCPConfigFile();
+  this->DCMQRSCPPort = 11112;
+  this->StoreSCUExecutable = this->findStoreSCUExecutable();
+}
+
+//------------------------------------------------------------------------------
+ctkDICOMTesterPrivate::~ctkDICOMTesterPrivate()
+{
+  delete this->DCMQRSCPProcess;
+  this->DCMQRSCPProcess = 0;
+}
+
+//------------------------------------------------------------------------------
+QString ctkDICOMTesterPrivate::findFile(const QStringList& nameFilters, const QString& subDir)const
+{
+  // Search in the build tree first
+  QDir searchDir = QDir::current();
+  do
+    {
+    QFileInfo dcmExecutables(searchDir, subDir);
+    if (dcmExecutables.isDir() &&
+        dcmExecutables.exists())
+      {
+      QDir bin(dcmExecutables.absoluteFilePath());
+      QFileInfoList found = bin.entryInfoList(nameFilters, QDir::Files);
+      if (found.count() > 0)
+        {
+        return found[0].absoluteFilePath();
+        }
+      }
+    }
+  while (searchDir.cdUp())
+    ;
+  // TODO: take care of the installed case
+  return QString();
+}
+
+//------------------------------------------------------------------------------
+QString ctkDICOMTesterPrivate::findDCMQRSCPExecutable()const
+{
+  return this->findFile(QStringList("dcmqrscp*"), "CMakeExternals/Install/bin");  
+}
+
+//------------------------------------------------------------------------------
+QString ctkDICOMTesterPrivate::findDCMQRSCPConfigFile()const
+{
+  return this->findFile(QStringList("dcmqrscp.cfg"), "Libs/DICOM/Core");
+}
+
+//------------------------------------------------------------------------------
+QString ctkDICOMTesterPrivate::findStoreSCUExecutable()const
+{
+  return this->findFile(QStringList("storescu*"), "CMakeExternals/Install/bin");  
+}
+
+//------------------------------------------------------------------------------
+void ctkDICOMTesterPrivate::printProcessOutputs(const QString& program, QProcess* process)const
+{
+  QTextStream out(stdout);
+  out << program << "finished.\n";
+
+  QByteArray standardOutput = process->readAllStandardOutput();
+  if (standardOutput.count())
+    {  
+    out << "Standard Output:\n";
+    out << standardOutput;
+    }
+  QByteArray standardError = process->readAllStandardError();
+  if (standardError.count())
+    {  
+    out << "Standard Error:\n";
+    out << standardError;
+    }
+}
+
+//------------------------------------------------------------------------------
+// ctkDICOMTester methods
+
+//------------------------------------------------------------------------------
+ctkDICOMTester::ctkDICOMTester(QObject* parentObject)
+  : QObject(parentObject)
+  , d_ptr(new ctkDICOMTesterPrivate(*this))
+{
+  Q_D(ctkDICOMTester);
+}
+
+//------------------------------------------------------------------------------
+ctkDICOMTester::ctkDICOMTester(const QString& dcmqrscp,
+                               const QString& configFile,
+                               QObject* parentObject)
+  : QObject(parentObject)
+  , d_ptr(new ctkDICOMTesterPrivate(*this))
+{
+  Q_D(ctkDICOMTester);
+  this->setDCMQRSCPExecutable(dcmqrscp);
+  this->setDCMQRSCPConfigFile(configFile);
+}
+
+//------------------------------------------------------------------------------
+ctkDICOMTester::~ctkDICOMTester()
+{
+  // just in case
+  this->stopDCMQRSCP();
+}
+
+//------------------------------------------------------------------------------
+void ctkDICOMTester::setDCMQRSCPExecutable(const QString& dcmqrscp)
+{
+  Q_D(ctkDICOMTester);
+  d->DCMQRSCPExecutable = dcmqrscp;
+}
+
+//------------------------------------------------------------------------------
+QString ctkDICOMTester::dcmqrscpExecutable()const
+{
+  Q_D(const ctkDICOMTester);
+  return d->DCMQRSCPExecutable;
+}
+  
+//------------------------------------------------------------------------------
+void ctkDICOMTester::setDCMQRSCPConfigFile(const QString& configFile)
+{
+  Q_D(ctkDICOMTester);
+  d->DCMQRSCPConfigFile = configFile;
+}
+
+//------------------------------------------------------------------------------
+QString ctkDICOMTester::dcmqrscpConfigFile()const
+{
+  Q_D(const ctkDICOMTester);
+  return d->DCMQRSCPConfigFile;
+}
+//------------------------------------------------------------------------------
+void ctkDICOMTester::setStoreSCUExecutable(const QString& storeSCU)
+{
+  Q_D(ctkDICOMTester);
+  d->StoreSCUExecutable = storeSCU;
+}
+
+//------------------------------------------------------------------------------
+QString ctkDICOMTester::storeSCUExecutable()const
+{
+  Q_D(const ctkDICOMTester);
+  return d->StoreSCUExecutable;
+}
+
+//------------------------------------------------------------------------------
+void ctkDICOMTester::setDCMQRSCPPort(int port)
+{
+  Q_D(ctkDICOMTester);
+  d->DCMQRSCPPort = port;
+}
+
+//------------------------------------------------------------------------------
+int ctkDICOMTester::dcmqrscpPort()const
+{
+  Q_D(const ctkDICOMTester);
+  return d->DCMQRSCPPort;
+}
+
+//------------------------------------------------------------------------------
+QProcess* ctkDICOMTester::startDCMQRSCP()
+{
+  Q_D(ctkDICOMTester);
+  if (d->DCMQRSCPProcess)
+    {
+    return d->DCMQRSCPProcess;
+    }
+  d->DCMQRSCPProcess = new QProcess(this);
+
+  QStringList dcmqrscpArgs;
+  if (!d->DCMQRSCPConfigFile.isEmpty())
+    {
+    dcmqrscpArgs << "--config" << d->DCMQRSCPConfigFile;
+    }
+  //dcmqrscp_args << "--debug" << "--verbose";
+  dcmqrscpArgs << QString::number(d->DCMQRSCPPort);
+
+  try
+    {
+    d->DCMQRSCPProcess->start(d->DCMQRSCPExecutable, dcmqrscpArgs);
+    d->DCMQRSCPProcess->waitForStarted();
+    }
+  catch (std::exception e)
+    {
+    delete d->DCMQRSCPProcess;
+    d->DCMQRSCPProcess = 0;
+    }
+  return d->DCMQRSCPProcess;
+}
+
+//------------------------------------------------------------------------------
+bool ctkDICOMTester::stopDCMQRSCP()
+{
+  Q_D(ctkDICOMTester);
+  if (!d->DCMQRSCPProcess)
+    {
+    return false;
+    }
+
+  d->DCMQRSCPProcess->kill();
+  bool res = d->DCMQRSCPProcess->waitForFinished();
+  
+  d->printProcessOutputs("DCMQRSCP", d->DCMQRSCPProcess);
+
+  delete d->DCMQRSCPProcess;
+  d->DCMQRSCPProcess = 0;
+  return res;
+}
+
+//------------------------------------------------------------------------------
+bool ctkDICOMTester::storeData(const QStringList& data)
+{
+  Q_D(ctkDICOMTester);
+
+  if (data.count() == 0)
+    {
+    return true;
+    }
+
+  // There is no point of calling storescu if no-one is listening
+  if (!d->DCMQRSCPProcess)
+    {
+    return false;
+    }
+
+  QProcess storeSCU(this);
+  // usage of storescu:
+  // storescu -aec CTK_AE -aet CTK_AE localhost 11112 ./CMakeExternals/Source/CTKData/Data/DICOM/MRHEAD/*.IMA
+  QStringList storescuArgs;
+  storescuArgs << "-aec" << "CTK_AE";
+  storescuArgs << "-aet" << "CTK_AE";
+  storescuArgs << "localhost" <<  QString::number(d->DCMQRSCPPort);
+  storescuArgs << data;
+  
+  storeSCU.start(d->StoreSCUExecutable, storescuArgs);
+  bool res = storeSCU.waitForFinished();
+
+  d->printProcessOutputs("StoreSCU", &storeSCU);
+  return res;
+}

+ 66 - 0
Libs/DICOM/Core/ctkDICOMTester.h

@@ -0,0 +1,66 @@
+/*=========================================================================
+
+  Library:   CTK
+
+  Copyright (c) 2010
+
+  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 __ctkDICOMTester_h
+#define __ctkDICOMTester_h
+
+// Qt includes 
+#include <QObject>
+class QProcess;
+
+// CTKDICOMCore includes
+#include "ctkDICOMCoreExport.h"
+class ctkDICOMTesterPrivate;
+
+class CTK_DICOM_CORE_EXPORT ctkDICOMTester : public QObject
+{
+  Q_OBJECT
+public:
+  ctkDICOMTester(QObject* parent = 0);
+  explicit ctkDICOMTester(const QString& dcmqrscp, const QString& configFile, QObject* parent = 0);
+  virtual ~ctkDICOMTester();
+
+  void setDCMQRSCPExecutable(const QString& dcmqrscp);
+  QString dcmqrscpExecutable()const;
+  
+  void setDCMQRSCPConfigFile(const QString& configFile);
+  QString dcmqrscpConfigFile()const;
+  
+  void setStoreSCUExecutable(const QString& storescu);
+  QString storeSCUExecutable()const;
+  
+  void setDCMQRSCPPort(int port);
+  int dcmqrscpPort()const;
+  
+  QProcess* startDCMQRSCP();
+  bool stopDCMQRSCP();
+
+  /// Pushes data using storeSCU
+  bool storeData(const QStringList& data);
+
+protected:
+  QScopedPointer<ctkDICOMTesterPrivate> d_ptr;
+
+private:
+  Q_DECLARE_PRIVATE(ctkDICOMTester);
+  Q_DISABLE_COPY(ctkDICOMTester);
+};
+
+#endif