Просмотр исходного кода

Add ctkDICOMTester to provide testing tools for DICOM

Mainly inspired from ctkDICOMApplicationTest1
Julien Finet лет назад: 14
Родитель
Сommit
10b27c2710

+ 8 - 0
Libs/DICOM/Core/CMakeLists.txt

@@ -25,6 +25,8 @@ SET(KIT_SRCS
   ctkDICOMQuery.h
   ctkDICOMRetrieve.cpp
   ctkDICOMRetrieve.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

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

@@ -9,6 +9,8 @@ CREATE_TEST_SOURCELIST(Tests ${KIT}CppTests.cpp
   ctkDICOMQueryTest1.cpp
   ctkDICOMPersonNameTest1.cpp
   ctkDICOMTest1.cpp
+  ctkDICOMTesterTest1.cpp
+  ctkDICOMTesterTest2.cpp
   )
 
 SET (TestsToRun ${Tests})
@@ -50,3 +52,9 @@ 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})

+ 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;
+}
+

+ 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