Browse Source

Add classes for DICOM SCP implementation

Nicholas Herlambang 12 years ago
parent
commit
ff63ede310

+ 56 - 0
Libs/DICOM/Core/Testing/Cpp/ctkDICOMSCPTest1.cpp

@@ -0,0 +1,56 @@
+/*=============================================================================
+
+  Library: CTK
+
+  Copyright (c) German Cancer Research Center,
+    Division of Medical and Biological Informatics
+
+  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.apache.org/licenses/LICENSE-2.0
+
+  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 <QTextStream>
+
+// ctkDICOMCore includes
+#include "ctkDICOMSCP.h"
+#include "ctkDICOMEchoSCP.h"
+#include "ctkDICOMFindSCP.h"
+
+// STD includes
+#include <iostream>
+#include <cstdlib>
+
+int ctkDICOMSCPTest1(int argc, char * argv []) {
+  ctkDICOMSCP* scp = new ctkDICOMSCP();
+  
+  // set parameters
+  scp->setAETitle("CTK");
+  scp->setPort(12345);
+  scp->setEnableThreading(false);
+
+  // register SCPs
+  scp->registerSCP(new ctkDICOMEchoSCP());
+  scp->registerSCP(new ctkDICOMFindSCP());
+  //scp->registerSCP(new ctkDICOMMoveSCP());
+  //scp->registerSCP(new ctkDICOMStoreSCP());
+
+  // start the server
+  scp->start();
+
+  scp->deleteLater();
+
+  return 0;
+}
+

+ 33 - 0
Libs/DICOM/Core/ctkDICOMEchoSCP.cpp

@@ -0,0 +1,33 @@
+#include "ctkDICOMEchoSCP.h"
+
+// CTK includes
+#include "ctkLogger.h"
+
+static ctkLogger logger ( "org.commontk.dicom.DcmEchoSCP" );
+
+//------------------------------------------------------------------------------
+ctkDICOMEchoSCP::ctkDICOMEchoSCP(QObject *parent) :
+  ctkDICOMSCP(parent)
+{
+  this->dimseCommand = DIMSE_C_ECHO_RQ;
+}
+
+//------------------------------------------------------------------------------
+OFCondition ctkDICOMEchoSCP::handleRequest(T_ASC_Association *assoc, T_DIMSE_Message& message, T_ASC_PresentationContextID presId)
+{
+  OFCondition cond = EC_Normal;
+
+  T_DIMSE_C_EchoRQ * req = &message.msg.CEchoRQ;
+
+  logger.info(QString("Received Echo SCP RQ: MsgID ") + req->MessageID);
+  /* we send an echo response back */
+  cond = DIMSE_sendEchoResponse(assoc, presId,
+                                req, STATUS_Success, NULL);
+
+  if (cond.bad()) {
+    OFString temp_str;
+    DimseCondition::dump(temp_str, cond);
+    logger.info(QString("echoSCP: Echo Response Failed: ") + temp_str.c_str());
+  }
+  return cond;
+}

+ 20 - 0
Libs/DICOM/Core/ctkDICOMEchoSCP.h

@@ -0,0 +1,20 @@
+#ifndef CTKDICOMECHOSCP_H
+#define CTKDICOMECHOSCP_H
+
+#include "ctkDICOMCoreExport.h"
+#include "ctkDICOMSCP.h"
+
+class CTK_DICOM_CORE_EXPORT ctkDICOMEchoSCP : public ctkDICOMSCP
+{
+  Q_OBJECT
+public:
+  explicit ctkDICOMEchoSCP(QObject *parent = 0);
+  
+  virtual OFCondition handleRequest(T_ASC_Association *assoc, T_DIMSE_Message& message, T_ASC_PresentationContextID presId);
+signals:
+  
+public slots:
+  
+};
+
+#endif // CTKDICOMECHOSCP_H

+ 87 - 0
Libs/DICOM/Core/ctkDICOMFindContext.cpp

@@ -0,0 +1,87 @@
+#include "ctkDICOMFindContext.h"
+
+#include "dcmtk/config/osconfig.h"    /* make sure OS specific configuration is included first */
+#include "dcmtk/dcmqrdb/dcmqrcbf.h"
+#include "dcmtk/dcmqrdb/dcmqrcnf.h"
+#include "dcmtk/dcmdata/dcdeftag.h"
+#include "dcmtk/dcmqrdb/dcmqropt.h"
+#include "dcmtk/dcmnet/diutil.h"
+#include "dcmtk/dcmdata/dcfilefo.h"
+#include "dcmtk/dcmqrdb/dcmqrdbs.h"
+#include "dcmtk/dcmqrdb/dcmqrdbi.h"
+
+// CTK includes
+#include "ctkLogger.h"
+
+static ctkLogger logger ("org.commontk.dicom.DcmFindContext");
+
+//------------------------------------------------------------------------------
+ctkDICOMFindContext::ctkDICOMFindContext(DIC_US priorStat, QObject* parent):
+  QObject(parent),
+  priorStatus(priorStat),
+  ourAETitle()
+{
+}
+
+//------------------------------------------------------------------------------
+void ctkDICOMFindContext::callbackHandler(
+    /* in */
+    OFBool cancelled, T_DIMSE_C_FindRQ *request,
+    DcmDataset *requestIdentifiers, int responseCount,
+    /* out */
+    T_DIMSE_C_FindRSP *response,
+    DcmDataset **responseIdentifiers,
+    DcmDataset **stDetail)
+{
+  OFCondition dbcond = EC_Normal;
+  DcmQueryRetrieveDatabaseStatus dbStatus(priorStatus);
+
+  if (responseCount == 1) {
+    /* start the database search */
+    DCMQRDB_INFO("Find SCP Request Identifiers:" << OFendl << DcmObject::PrintHelper(*requestIdentifiers));
+    dbcond = this->startFindRequest(
+          request->AffectedSOPClassUID, requestIdentifiers, &dbStatus);
+    if (dbcond.bad()) {
+      DCMQRDB_ERROR("findSCP: Database: startFindRequest Failed ("
+                    << DU_cfindStatusString(dbStatus.status()) << "):");
+    }
+  }
+
+  /* only cancel if we have pending responses */
+  if (cancelled && DICOM_PENDING_STATUS(dbStatus.status())) {
+    this->cancelFindRequest(&dbStatus);
+  }
+
+  if (DICOM_PENDING_STATUS(dbStatus.status())) {
+    dbcond = this->nextFindResponse(responseIdentifiers, &dbStatus);
+    if (dbcond.bad()) {
+      DCMQRDB_ERROR("findSCP: Database: nextFindResponse Failed ("
+                    << DU_cfindStatusString(dbStatus.status()) << "):");
+    }
+  }
+
+  if (*responseIdentifiers != NULL)
+  {
+
+    if (! DU_putStringDOElement(*responseIdentifiers, DCM_RetrieveAETitle, ourAETitle.c_str())) {
+      DCMQRDB_ERROR("DO: adding Retrieve AE Title");
+    }
+  }
+
+  /* set response status */
+  response->DimseStatus = dbStatus.status();
+  *stDetail = dbStatus.extractStatusDetail();
+
+  OFString str;
+  logger.info(QString("Find SCP Response ") + QString::number(responseCount)
+              + "[status:" + DU_cfindStatusString(dbStatus.status()) + "]");
+  DIMSE_dumpMessage(str, *response, DIMSE_OUTGOING);
+  logger.debug(str.c_str());
+
+  if (DICOM_PENDING_STATUS(dbStatus.status()) && (*responseIdentifiers != NULL))
+    //DCMQRDB_DEBUG("Find SCP Response Identifiers:" << OFendl << DcmObject::PrintHelper(**responseIdentifiers));
+  if (*stDetail)
+    //DCMQRDB_DEBUG("  Status detail:" << OFendl << DcmObject::PrintHelper(**stDetail));
+
+  this->priorStatus = dbStatus.status();
+}

+ 73 - 0
Libs/DICOM/Core/ctkDICOMFindContext.h

@@ -0,0 +1,73 @@
+#ifndef CTKDICOMFINDCONTEXT_H
+#define CTKDICOMFINDCONTEXT_H
+
+// DCMTK includes
+#include <dcmtk/config/osconfig.h>    /* make sure OS specific configuration is included first */
+#include <dcmtk/dcmnet/dimse.h>
+#include <dcmtk/dcmqrdb/qrdefine.h>
+#include <dcmtk/dcmqrdb/dcmqrdba.h>
+
+// Qt includes
+#include <QDebug>
+#include <QObject>
+
+#include "ctkDICOMCoreExport.h"
+
+class CTK_DICOM_CORE_EXPORT ctkDICOMFindContext : public QObject
+{
+  Q_OBJECT
+public:
+    /** constructor
+     *  @param options options for the Q/R service
+     *  @param priorStat prior DIMSE status
+     */
+    ctkDICOMFindContext(DIC_US priorStat, QObject *parent = 0);
+
+    /** set the AEtitle under which this application operates
+     *  @param ae AEtitle, is copied into this object.
+     */
+    void setOurAETitle(const char *ae)
+    {
+      if (ae) ourAETitle = ae; else ourAETitle.clear();
+    }
+
+    /** callback handler called by the DIMSE_storeProvider callback function.
+     *  @param cancelled (in) flag indicating whether a C-CANCEL was received
+     *  @param request original find request (in)
+     *  @param requestIdentifiers original find request identifiers (in)
+     *  @param responseCount find response count (in)
+     *  @param response find response (out)
+     *  @param responseIdentifiers find response identifiers (out)
+     *  @param stDetail status detail for find response (out)
+     */
+    void callbackHandler(
+        OFBool cancelled, T_DIMSE_C_FindRQ *request,
+        DcmDataset *requestIdentifiers, int responseCount,
+        T_DIMSE_C_FindRSP *response,
+        DcmDataset **responseIdentifiers,
+        DcmDataset **stDetail);
+
+    virtual OFCondition startFindRequest (const char *SOPClassUID, DcmDataset *findRequestIdentifiers, DcmQueryRetrieveDatabaseStatus *status)
+    {
+      return OFCondition(OFM_dcmdata, 0x001, OF_error, "DcmQR Index Database Error");
+    }
+
+    virtual OFCondition nextFindResponse (DcmDataset **findResponseIdentifiers, DcmQueryRetrieveDatabaseStatus *status)
+    {
+      return OFCondition(OFM_dcmdata, 0x001, OF_error, "DcmQR Index Database Error");
+    }
+
+    virtual OFCondition cancelFindRequest (DcmQueryRetrieveDatabaseStatus *status)
+    {
+      return OFCondition(OFM_dcmdata, 0x001, OF_error, "DcmQR Index Database Error");
+    }
+
+private:
+    /// prior DIMSE status
+    DIC_US priorStatus;
+
+    /// our current title
+    OFString ourAETitle;
+};
+
+#endif // CTKDCMFINDCONTEXT_H

+ 209 - 0
Libs/DICOM/Core/ctkDICOMFindSCP.cpp

@@ -0,0 +1,209 @@
+#include "ctkDICOMFindSCP.h"
+
+// DCMTK includes
+#include <dcmtk/dcmdata/dcdatset.h>
+#include <dcmtk/dcmdata/dcdeftag.h>
+#include <dcmtk/dcmnet/diutil.h>
+#include <dcmtk/dcmqrdb/dcmqrcbf.h>
+#include <dcmtk/dcmqrdb/dcmqrdbs.h>
+
+// ctk includes
+#include "ctkDICOMFindContext.h"
+#include "ctkLogger.h"
+
+static ctkLogger logger ("org.commontk.dicom.DcmFindSCP");
+
+class ctkDICOMFindSCPPrivate{
+public:
+  ctkDICOMFindSCPPrivate(ctkDICOMFindSCP* parent);
+  ~ctkDICOMFindSCPPrivate();
+
+  OFCondition findProvider(T_ASC_Association *assoc,
+                           T_ASC_PresentationContextID presIdCmd,
+                           T_DIMSE_C_FindRQ *request,
+                           ctkDICOMFindContext* context,
+                           T_DIMSE_BlockingMode blockMode, int timeout);
+
+private:
+  ctkDICOMFindSCP* q_ptr;
+  Q_DECLARE_PUBLIC(ctkDICOMFindSCP);
+};
+
+//------------------------------------------------------------------------------
+// ctkDICOMFindSCPPrivate class methods
+//------------------------------------------------------------------------------
+//------------------------------------------------------------------------------
+ctkDICOMFindSCPPrivate::ctkDICOMFindSCPPrivate(ctkDICOMFindSCP *parent):
+  q_ptr(parent)
+{
+
+}
+
+//------------------------------------------------------------------------------
+OFCondition ctkDICOMFindSCPPrivate::findProvider(T_ASC_Association *assoc,
+                                               T_ASC_PresentationContextID presIdCmd,
+                                               T_DIMSE_C_FindRQ *request,
+                                               ctkDICOMFindContext *context,
+                                               T_DIMSE_BlockingMode blockMode,
+                                               int timeout)
+{
+  T_ASC_PresentationContextID presIdData;
+  T_DIMSE_C_FindRSP rsp;
+  DcmDataset *statusDetail = NULL;
+  DcmDataset *reqIds = NULL;
+  DcmDataset *rspIds = NULL;
+  OFBool cancelled = OFFalse;
+  OFBool normal = OFTrue;
+  int responseCount = 0;
+
+  /* receive data (i.e. the search mask) and store it in memory */
+  OFCondition cond = DIMSE_receiveDataSetInMemory(assoc, blockMode, timeout, &presIdData, &reqIds, NULL, NULL);
+
+  /* if no error occured while receiving data */
+  if (cond.good())
+  {
+    /* check if the presentation context IDs of the C-FIND-RQ and */
+    /* the search mask data are the same; if not, return an error */
+    if (presIdData != presIdCmd)
+    {
+      cond = makeDcmnetCondition(DIMSEC_INVALIDPRESENTATIONCONTEXTID, OF_error, "DIMSE: Presentation Contexts of Command and Data Differ");
+    }
+    else
+    {
+      /* if the IDs are the same go ahead */
+      /* initialize the C-FIND-RSP message variable */
+      bzero((char*)&rsp, sizeof(rsp));
+      rsp.DimseStatus = STATUS_Pending;
+
+      /* as long as no error occured and the status of the C-FIND-RSP message which will */
+      /* be/was sent is pending, perform this loop in which records that match the search */
+      /* mask are selected (whithin the execution of the callback function) and sent over */
+      /* the network to the other DICOM application using C-FIND-RSP messages. */
+      while (cond.good() && DICOM_PENDING_STATUS(rsp.DimseStatus) && normal)
+      {
+        /* increase the counter that counts the number of response messages */
+        responseCount++;
+
+        /* check if a C-CANCEL-RQ is received */
+        cond = DIMSE_checkForCancelRQ(assoc, presIdCmd, request->MessageID);
+        if (cond.good())
+        {
+          /* if a C-CANCEL-RQ was received, we need to set status and an indicator variable */
+          rsp.DimseStatus = STATUS_FIND_Cancel_MatchingTerminatedDueToCancelRequest;
+          cancelled = OFTrue;
+        } else if (cond == DIMSE_NODATAAVAILABLE)
+        {
+          /* timeout */
+        }
+        else
+        {
+          /* some execption condition occured, bail out */
+          normal = OFFalse;
+        }
+
+        /* if everything is still ok */
+        if (normal)
+        {
+          /* execute callback function (note that this function always determines the next record */
+          /* which matches the search mask. This record will be available here through rspIds) */
+          context->callbackHandler(cancelled, request, reqIds,responseCount, &rsp, &rspIds, &statusDetail);
+
+          /* if we encountered a C-CANCEL-RQ earlier, set a variable and possibly delete the search mask */
+          if (cancelled) {
+            /* make sure */
+            rsp.DimseStatus =
+                STATUS_FIND_Cancel_MatchingTerminatedDueToCancelRequest;
+            if (rspIds != NULL) {
+              delete reqIds;
+              reqIds = NULL;
+            }
+          }
+
+          /* send a C-FIND-RSP message over the network to the other DICOM application */
+          cond = DIMSE_sendFindResponse(assoc, presIdCmd, request,
+                                        &rsp, rspIds, statusDetail);
+
+          /* if there are search results, delete them */
+          if (rspIds != NULL) {
+            delete rspIds;
+            rspIds = NULL;
+          }
+
+          /* if there is status detail information, delete it */
+          if (statusDetail != NULL) {
+            delete statusDetail;
+            statusDetail = NULL;
+          }
+        }
+      }
+    }
+  }
+
+  /* delete search mask */
+  delete reqIds;
+
+  /* delete latest search result */
+  delete rspIds;
+
+  /* return result value */
+  return cond;
+}
+
+//------------------------------------------------------------------------------
+ctkDICOMFindSCPPrivate::~ctkDICOMFindSCPPrivate()
+{
+
+}
+
+//------------------------------------------------------------------------------
+// ctkDICOMFindSCP class methods
+//------------------------------------------------------------------------------
+//------------------------------------------------------------------------------
+ctkDICOMFindSCP::ctkDICOMFindSCP(QObject *parent) :
+  ctkDICOMSCP(parent),
+  d_ptr(new ctkDICOMFindSCPPrivate(this))
+{
+  this->dimseCommand = DIMSE_C_FIND_RQ;
+}
+
+//------------------------------------------------------------------------------
+ctkDICOMFindSCP::~ctkDICOMFindSCP()
+{
+  delete d_ptr;
+}
+
+//------------------------------------------------------------------------------
+OFCondition ctkDICOMFindSCP::handleRequest(T_ASC_Association *assoc, T_DIMSE_Message &message, T_ASC_PresentationContextID presId)
+{
+  Q_D(ctkDICOMFindSCP);
+
+  OFCondition cond = EC_Normal;
+  T_DIMSE_C_FindRQ * request = &message.msg.CFindRQ;
+
+  ctkDICOMFindContext* context = this->createContext();
+
+  DIC_AE aeTitle;
+  aeTitle[0] = '\0';
+  ASC_getAPTitles(assoc->params, NULL, aeTitle, NULL);
+  context->setOurAETitle(aeTitle);
+
+  OFString temp_str;
+  DIMSE_dumpMessage(temp_str, *request, DIMSE_INCOMING);
+  logger.info(QString("Received Find SCP:\n") + temp_str.c_str());
+
+  cond = d->findProvider(assoc, presId, request,
+                         context, DIMSE_BLOCKING, 30);
+  if (cond.bad()) {
+    DimseCondition::dump(temp_str, cond);
+    logger.error(QString("Find SCP Failed: ") + temp_str.c_str());
+  }
+
+  context->deleteLater();
+  return cond;
+}
+
+//------------------------------------------------------------------------------
+ctkDICOMFindContext* ctkDICOMFindSCP::createContext()
+{
+  return new ctkDICOMFindContext(STATUS_Pending);
+}

+ 32 - 0
Libs/DICOM/Core/ctkDICOMFindSCP.h

@@ -0,0 +1,32 @@
+#ifndef CTKDICOMFINDSCP_H
+#define CTKDICOMFINDSCP_H
+
+// DCMTK includes
+#include <dcmtk/dcmqrdb/dcmqrdbs.h>
+
+// ctk includes
+#include "ctkDICOMCoreExport.h"
+#include "ctkDICOMSCP.h"
+
+class ctkDICOMFindSCPPrivate;
+class ctkDICOMFindContext;
+
+class CTK_DICOM_CORE_EXPORT ctkDICOMFindSCP : public ctkDICOMSCP
+{
+  Q_OBJECT
+public:
+  explicit ctkDICOMFindSCP(QObject *parent = 0);
+  ~ctkDICOMFindSCP();
+  
+  virtual OFCondition handleRequest(T_ASC_Association *assoc, T_DIMSE_Message &message, T_ASC_PresentationContextID presId);
+
+protected:
+  virtual ctkDICOMFindContext* createContext();
+
+private:
+  ctkDICOMFindSCPPrivate* d_ptr;
+  Q_DECLARE_PRIVATE(ctkDICOMFindSCP);
+  Q_DISABLE_COPY(ctkDICOMFindSCP);
+};
+
+#endif // CTKDCMFINDSCP_H

+ 747 - 0
Libs/DICOM/Core/ctkDICOMSCP.cpp

@@ -0,0 +1,747 @@
+#include "ctkDICOMSCP.h"
+
+// DCMTK includes
+#include <dcmtk/dcmqrdb/dcmqrsrv.h>
+
+// Qt includes
+#include <QtConcurrentRun>
+
+// CTK includes
+#include "ctkLogger.h"
+
+static ctkLogger logger ( "org.commontk.dicom.DcmSCP" );
+
+class ctkDICOMSCPPrivate
+{
+public:
+  ctkDICOMSCPPrivate(ctkDICOMSCP* parent);
+  ~ctkDICOMSCPPrivate();
+
+  QString aeTitle;
+  int port;
+  int maxPDU;
+  int maxAssociation;
+  int acseTimeout;
+  bool isGetEnabled;
+  bool isPatientRootEnabled;
+  bool isStudyRootEnabled;
+  bool isPatientStudyOnlyEnabled;
+  bool isShutdownAllowed;
+  bool isThreadingEnabled;
+  bool terminateRequested;
+
+  /** wait for incoming A-ASSOCIATE requests, perform association negotiation
+   *  and serve the requests. May fork child processes depending on availability
+   *  of the fork() system function and configuration options.
+   *  @param theNet network structure for listen socket
+   *  @return EC_Normal if successful, an error code otherwise
+   */
+  OFCondition waitForAssociation(T_ASC_Network *theNet);
+
+  OFCondition negotiateAssociation(T_ASC_Association * assoc);
+  OFCondition refuseAssociation(T_ASC_Association ** assoc, CTN_RefuseReason reason);
+  OFCondition handleAssociation(T_ASC_Association * assoc, OFBool correctUIDPadding);
+  OFCondition dispatch(T_ASC_Association *assoc, OFBool correctUIDPadding);
+
+  void threadedStartServer();
+  void threadedHandleAssociation(T_ASC_Association * assoc,
+                                 OFBool correctUIDPadding);
+
+  QMap<T_DIMSE_Command, ctkDICOMSCP*> registeredSCPs;
+  T_ASC_Network* network;
+
+private:
+  ctkDICOMSCP* q_ptr;
+  Q_DECLARE_PUBLIC(ctkDICOMSCP);
+};
+
+//------------------------------------------------------------------------------
+// ctkDICOMSCPPrivate class methods
+//------------------------------------------------------------------------------
+//------------------------------------------------------------------------------
+ctkDICOMSCPPrivate::ctkDICOMSCPPrivate(ctkDICOMSCP *parent):
+  q_ptr(parent)
+{
+  this->aeTitle = "AETitle";
+  this->port = 104;
+  this->maxPDU = 100000;
+  this->maxAssociation = 20;
+  this->acseTimeout = 30;
+  this->isGetEnabled = false;
+  this->isPatientRootEnabled = false;
+  this->isStudyRootEnabled = true;
+  this->isPatientStudyOnlyEnabled = false;
+  this->isShutdownAllowed = true;
+  this->terminateRequested = false;
+  this->isThreadingEnabled = true;
+
+  this->network = new T_ASC_Network();
+}
+
+//------------------------------------------------------------------------------
+ctkDICOMSCPPrivate::~ctkDICOMSCPPrivate()
+{
+  delete this->network;
+}
+
+//------------------------------------------------------------------------------
+OFCondition ctkDICOMSCPPrivate::waitForAssociation(T_ASC_Network * theNet)
+{
+  Q_Q(ctkDICOMSCP);
+
+  OFCondition cond = EC_Normal;
+  OFString temp_str;
+
+  T_ASC_Association  *assoc;
+  char                buf[BUFSIZ];
+  int timeout = 1000;
+  OFBool go_cleanup = OFFalse;
+
+  if (ASC_associationWaiting(theNet, timeout))
+  {
+    cond = ASC_receiveAssociation(theNet, &assoc, this->maxPDU);
+    if (cond.bad())
+    {
+      DimseCondition::dump(temp_str, cond);
+      logger.debug(QString("Failed to receive association: ") + temp_str.c_str());
+      go_cleanup = OFTrue;
+    }
+  } else return EC_Normal;
+
+  if (! go_cleanup)
+  {
+    logger.info(QString("Association Received (") + assoc->params->DULparams.callingPresentationAddress
+                + ":" + assoc->params->DULparams.callingAPTitle + " -> "
+                + assoc->params->DULparams.calledAPTitle + ")");
+
+    ASC_dumpParameters(temp_str, assoc->params, ASC_ASSOC_RQ);
+    logger.debug(QString("Parameters:") + temp_str.c_str());
+  }
+
+  if (! go_cleanup)
+  {
+    /* Application Context Name */
+    cond = ASC_getApplicationContextName(assoc->params, buf);
+    if (cond.bad() || strcmp(buf, DICOM_STDAPPLICATIONCONTEXT) != 0)
+    {
+      /* reject: the application context name is not supported */
+      logger.info(QString("Bad AppContextName: ") + buf);
+      cond = this->refuseAssociation(&assoc, CTN_BadAppContext);
+      go_cleanup = OFTrue;
+    }
+  }
+
+  if (! go_cleanup)
+  {
+    /* Implementation Class UID */
+    if (strlen(assoc->params->theirImplementationClassUID) == 0)
+    {
+      /* reject: no implementation Class UID provided */
+      logger.info(QString("No implementation Class UID provided"));
+      cond = this->refuseAssociation(&assoc, CTN_NoReason);
+      go_cleanup = OFTrue;
+    }
+  }
+
+  // TODO: Check whether peer is in AETitle list
+  /*
+  if (! go_cleanup)
+  {
+    // Does peer AE have access to required service ??
+    if (! config_->peerInAETitle(assoc->params->DULparams.calledAPTitle,
+                                 assoc->params->DULparams.callingAPTitle,
+                                 assoc->params->DULparams.callingPresentationAddress))
+    {
+      DCMQRDB_DEBUG("Peer "
+                    << assoc->params->DULparams.callingPresentationAddress << ":"
+                    << assoc->params->DULparams.callingAPTitle << " is not not permitted to access "
+                    << assoc->params->DULparams.calledAPTitle << " (see configuration file)");
+      cond = refuseAssociation(&assoc, CTN_BadAEService);
+      go_cleanup = OFTrue;
+    }
+  }
+  */
+
+  // TODO: Check if the number of associations exceeds the maximum number of associations
+  /*
+  if (! go_cleanup)
+  {
+    // too many concurrent associations ??
+    if (processtable_.countChildProcesses() >= OFstatic_cast(size_t, d->maxAssociation))
+    {
+      cond = d->refuseAssociation(&assoc, CTN_TooManyAssociations);
+      go_cleanup = OFTrue;
+    }
+  }
+  */
+
+  if (! go_cleanup)
+  {
+    cond = this->negotiateAssociation(assoc);
+    if (cond.bad()) go_cleanup = OFTrue;
+  }
+
+  if (! go_cleanup)
+  {
+    cond = ASC_acknowledgeAssociation(assoc);
+    if (cond.bad())
+    {
+      DimseCondition::dump(temp_str, cond);
+      logger.error(QString(temp_str.c_str()));
+      go_cleanup = OFTrue;
+    }
+  }
+
+  if (! go_cleanup)
+  {
+    logger.debug(QString("Association Acknowledged (Max Send PDV: ") + QString::number(assoc->sendPDVLength) + ")");
+
+    QtConcurrent::run(this, &ctkDICOMSCPPrivate::threadedHandleAssociation, assoc, false);
+    return EC_Normal;
+  }
+
+  // cleanup code
+  OFCondition oldcond = cond;    /* store condition flag for later use */
+  if (cond != ASC_SHUTDOWNAPPLICATION)
+  {
+    /* the child will handle the association, we can drop it */
+    cond = ASC_dropAssociation(assoc);
+    if (cond.bad())
+    {
+      DimseCondition::dump(temp_str, cond);
+      logger.error(QString("Cannot Drop Association: ") + temp_str.c_str());
+    }
+    cond = ASC_destroyAssociation(&assoc);
+    if (cond.bad())
+    {
+      DimseCondition::dump(temp_str, cond);
+      logger.error(QString("Cannot Destroy Association: ") + temp_str.c_str());
+    }
+  }
+
+  if (oldcond == ASC_SHUTDOWNAPPLICATION) cond = oldcond; /* abort flag is reported to top-level wait loop */
+  return cond;
+}
+
+//------------------------------------------------------------------------------
+OFCondition ctkDICOMSCPPrivate::negotiateAssociation(T_ASC_Association * assoc)
+{
+  OFCondition cond = EC_Normal;
+  int i;
+  OFString temp_str;
+  struct { const char *moveSyntax, *findSyntax; } queryRetrievePairs[] =
+  {
+  { UID_MOVEPatientRootQueryRetrieveInformationModel,
+        UID_FINDPatientRootQueryRetrieveInformationModel },
+  { UID_MOVEStudyRootQueryRetrieveInformationModel,
+        UID_FINDStudyRootQueryRetrieveInformationModel },
+  { UID_RETIRED_MOVEPatientStudyOnlyQueryRetrieveInformationModel,
+        UID_RETIRED_FINDPatientStudyOnlyQueryRetrieveInformationModel }
+};
+
+  DIC_AE calledAETitle;
+  ASC_getAPTitles(assoc->params, NULL, calledAETitle, NULL);
+
+  const char* transferSyntaxes[] = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+                                     NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL };
+
+  transferSyntaxes[0] = UID_LittleEndianExplicitTransferSyntax;
+  transferSyntaxes[1] = UID_BigEndianExplicitTransferSyntax;
+  transferSyntaxes[2] = UID_LittleEndianImplicitTransferSyntax;
+  int numTransferSyntaxes = 3;
+
+  const char * const nonStorageSyntaxes[] =
+  {
+    UID_VerificationSOPClass,
+    UID_FINDPatientRootQueryRetrieveInformationModel,
+    UID_MOVEPatientRootQueryRetrieveInformationModel,
+    UID_GETPatientRootQueryRetrieveInformationModel,
+  #ifndef NO_PATIENTSTUDYONLY_SUPPORT
+    UID_RETIRED_FINDPatientStudyOnlyQueryRetrieveInformationModel,
+    UID_RETIRED_MOVEPatientStudyOnlyQueryRetrieveInformationModel,
+    UID_RETIRED_GETPatientStudyOnlyQueryRetrieveInformationModel,
+  #endif
+    UID_FINDStudyRootQueryRetrieveInformationModel,
+    UID_MOVEStudyRootQueryRetrieveInformationModel,
+    UID_GETStudyRootQueryRetrieveInformationModel,
+    UID_PrivateShutdownSOPClass
+  };
+
+  const int numberOfNonStorageSyntaxes = DIM_OF(nonStorageSyntaxes);
+  const char *selectedNonStorageSyntaxes[DIM_OF(nonStorageSyntaxes)];
+  int numberOfSelectedNonStorageSyntaxes = 0;
+  for (i = 0; i < numberOfNonStorageSyntaxes; i++)
+  {
+    if (0 == strcmp(nonStorageSyntaxes[i], UID_FINDPatientRootQueryRetrieveInformationModel))
+    {
+      if (this->isPatientRootEnabled) selectedNonStorageSyntaxes[numberOfSelectedNonStorageSyntaxes++] = nonStorageSyntaxes[i];
+    }
+    else if (0 == strcmp(nonStorageSyntaxes[i], UID_MOVEPatientRootQueryRetrieveInformationModel))
+    {
+      if (this->isPatientRootEnabled) selectedNonStorageSyntaxes[numberOfSelectedNonStorageSyntaxes++] = nonStorageSyntaxes[i];
+    }
+    else if (0 == strcmp(nonStorageSyntaxes[i], UID_GETPatientRootQueryRetrieveInformationModel))
+    {
+      if (this->isPatientRootEnabled && (this->isGetEnabled)) selectedNonStorageSyntaxes[numberOfSelectedNonStorageSyntaxes++] = nonStorageSyntaxes[i];
+    }
+    else if (0 == strcmp(nonStorageSyntaxes[i], UID_RETIRED_FINDPatientStudyOnlyQueryRetrieveInformationModel))
+    {
+      if (this->isPatientStudyOnlyEnabled) selectedNonStorageSyntaxes[numberOfSelectedNonStorageSyntaxes++] = nonStorageSyntaxes[i];
+    }
+    else if (0 == strcmp(nonStorageSyntaxes[i], UID_RETIRED_MOVEPatientStudyOnlyQueryRetrieveInformationModel))
+    {
+      if (this->isPatientStudyOnlyEnabled) selectedNonStorageSyntaxes[numberOfSelectedNonStorageSyntaxes++] = nonStorageSyntaxes[i];
+    }
+    else if (0 == strcmp(nonStorageSyntaxes[i], UID_RETIRED_GETPatientStudyOnlyQueryRetrieveInformationModel))
+    {
+      if (this->isPatientStudyOnlyEnabled && (this->isGetEnabled)) selectedNonStorageSyntaxes[numberOfSelectedNonStorageSyntaxes++] = nonStorageSyntaxes[i];
+    }
+    else if (0 == strcmp(nonStorageSyntaxes[i], UID_FINDStudyRootQueryRetrieveInformationModel))
+    {
+      if (this->isStudyRootEnabled) selectedNonStorageSyntaxes[numberOfSelectedNonStorageSyntaxes++] = nonStorageSyntaxes[i];
+    }
+    else if (0 == strcmp(nonStorageSyntaxes[i], UID_MOVEStudyRootQueryRetrieveInformationModel))
+    {
+      if (this->isStudyRootEnabled) selectedNonStorageSyntaxes[numberOfSelectedNonStorageSyntaxes++] = nonStorageSyntaxes[i];
+    }
+    else if (0 == strcmp(nonStorageSyntaxes[i], UID_GETStudyRootQueryRetrieveInformationModel))
+    {
+      if (this->isStudyRootEnabled && (this->isGetEnabled)) selectedNonStorageSyntaxes[numberOfSelectedNonStorageSyntaxes++] = nonStorageSyntaxes[i];
+    }
+    else if (0 == strcmp(nonStorageSyntaxes[i], UID_PrivateShutdownSOPClass))
+    {
+      if (this->isShutdownAllowed) selectedNonStorageSyntaxes[numberOfSelectedNonStorageSyntaxes++] = nonStorageSyntaxes[i];
+    } else {
+      selectedNonStorageSyntaxes[numberOfSelectedNonStorageSyntaxes++] = nonStorageSyntaxes[i];
+    }
+  }
+
+  /*  accept any of the non-storage syntaxes */
+  cond = ASC_acceptContextsWithPreferredTransferSyntaxes(
+        assoc->params,
+        (const char**)selectedNonStorageSyntaxes, numberOfSelectedNonStorageSyntaxes,
+        (const char**)transferSyntaxes, numTransferSyntaxes);
+  if (cond.bad()) {
+    DimseCondition::dump(temp_str, cond);
+    logger.error(QString("Cannot accept presentation contexts: ") + temp_str.c_str());
+  }
+
+  /*  accept any of the storage syntaxes */
+  if (!this->isGetEnabled)
+  {
+    /* accept storage syntaxes with default role only */
+    cond = ASC_acceptContextsWithPreferredTransferSyntaxes(
+          assoc->params,
+          dcmAllStorageSOPClassUIDs, numberOfAllDcmStorageSOPClassUIDs,
+          (const char**)transferSyntaxes, DIM_OF(transferSyntaxes));
+    if (cond.bad()) {
+      DimseCondition::dump(temp_str, cond);
+      logger.error(QString("Cannot accept presentation contexts: ") + temp_str.c_str());
+    }
+  } else {
+    /* accept storage syntaxes with proposed role */
+    T_ASC_PresentationContext pc;
+    T_ASC_SC_ROLE role;
+    int npc = ASC_countPresentationContexts(assoc->params);
+    for (i = 0; i < npc; i++)
+    {
+      ASC_getPresentationContext(assoc->params, i, &pc);
+      if (dcmIsaStorageSOPClassUID(pc.abstractSyntax))
+      {
+        /*
+          ** We are prepared to accept whatever role he proposes.
+          ** Normally we can be the SCP of the Storage Service Class.
+          ** When processing the C-GET operation we can be the SCU of the Storage Service Class.
+          */
+        role = pc.proposedRole;
+
+        /*
+          ** Accept in the order "least wanted" to "most wanted" transfer
+          ** syntax.  Accepting a transfer syntax will override previously
+          ** accepted transfer syntaxes.
+          */
+        for (int k = numTransferSyntaxes - 1; k >= 0; k--)
+        {
+          for (int j = 0; j < (int)pc.transferSyntaxCount; j++)
+          {
+            /* if the transfer syntax was proposed then we can accept it
+               * appears in our supported list of transfer syntaxes
+               */
+            if (strcmp(pc.proposedTransferSyntaxes[j], transferSyntaxes[k]) == 0)
+            {
+              cond = ASC_acceptPresentationContext(
+                    assoc->params, pc.presentationContextID, transferSyntaxes[k], role);
+              if (cond.bad()) return cond;
+            }
+          }
+        }
+      }
+    } /* for */
+  } /* else */
+
+  /*
+     * check if we have negotiated the private "shutdown" SOP Class
+     */
+  if (0 != ASC_findAcceptedPresentationContextID(assoc, UID_PrivateShutdownSOPClass))
+  {
+    logger.info(QString("Shutting down server ... (negotiated private \"shut down\" SOP class)"));
+    this->refuseAssociation(&assoc, CTN_NoReason);
+    return ASC_SHUTDOWNAPPLICATION;
+  }
+
+  return cond;
+}
+
+//------------------------------------------------------------------------------
+void ctkDICOMSCPPrivate::threadedStartServer()
+{
+  OFCondition cond = ASC_initializeNetwork(NET_ACCEPTORREQUESTOR, this->port, this->acseTimeout, &this->network);
+  if(cond.bad()) {
+    logger.error(QString("Failed to initialize network"));
+  }
+
+  while(!this->terminateRequested && cond.good())
+  {
+    cond = this->waitForAssociation(this->network);
+  }
+
+  cond = ASC_dropNetwork(&this->network);
+  if(cond.bad()) {
+    logger.debug(QString("Cannot drop network"));
+  }
+
+  this->terminateRequested = false;
+}
+
+//------------------------------------------------------------------------------
+void ctkDICOMSCPPrivate::threadedHandleAssociation(T_ASC_Association *assoc, bool correctUIDPadding)
+{
+  this->handleAssociation(assoc, correctUIDPadding);
+}
+
+//------------------------------------------------------------------------------
+OFCondition ctkDICOMSCPPrivate::refuseAssociation(T_ASC_Association ** assoc, CTN_RefuseReason reason)
+{
+  OFCondition cond = EC_Normal;
+  T_ASC_RejectParameters rej;
+  OFString temp_str;
+
+  const char *reason_string;
+  switch (reason)
+  {
+  case CTN_TooManyAssociations:
+    reason_string = "TooManyAssociations";
+    break;
+  case CTN_CannotFork:
+    reason_string = "CannotFork";
+    break;
+  case CTN_BadAppContext:
+    reason_string = "BadAppContext";
+    break;
+  case CTN_BadAEPeer:
+    reason_string = "BadAEPeer";
+    break;
+  case CTN_BadAEService:
+    reason_string = "BadAEService";
+    break;
+  case CTN_NoReason:
+    reason_string = "NoReason";
+    break;
+  default:
+    reason_string = "???";
+    break;
+  }
+  logger.info(QString("Refusing Association (") + reason_string + ")");
+
+  switch (reason)
+  {
+  case CTN_TooManyAssociations:
+    rej.result = ASC_RESULT_REJECTEDTRANSIENT;
+    rej.source = ASC_SOURCE_SERVICEPROVIDER_PRESENTATION_RELATED;
+    rej.reason = ASC_REASON_SP_PRES_LOCALLIMITEXCEEDED;
+    break;
+  case CTN_CannotFork:
+    rej.result = ASC_RESULT_REJECTEDPERMANENT;
+    rej.source = ASC_SOURCE_SERVICEPROVIDER_PRESENTATION_RELATED;
+    rej.reason = ASC_REASON_SP_PRES_TEMPORARYCONGESTION;
+    break;
+  case CTN_BadAppContext:
+    rej.result = ASC_RESULT_REJECTEDTRANSIENT;
+    rej.source = ASC_SOURCE_SERVICEUSER;
+    rej.reason = ASC_REASON_SU_APPCONTEXTNAMENOTSUPPORTED;
+    break;
+  case CTN_BadAEPeer:
+    rej.result = ASC_RESULT_REJECTEDPERMANENT;
+    rej.source = ASC_SOURCE_SERVICEUSER;
+    rej.reason = ASC_REASON_SU_CALLINGAETITLENOTRECOGNIZED;
+    break;
+  case CTN_BadAEService:
+    rej.result = ASC_RESULT_REJECTEDPERMANENT;
+    rej.source = ASC_SOURCE_SERVICEUSER;
+    rej.reason = ASC_REASON_SU_CALLEDAETITLENOTRECOGNIZED;
+    break;
+  case CTN_NoReason:
+  default:
+    rej.result = ASC_RESULT_REJECTEDPERMANENT;
+    rej.source = ASC_SOURCE_SERVICEUSER;
+    rej.reason = ASC_REASON_SU_NOREASON;
+    break;
+  }
+
+  cond = ASC_rejectAssociation(*assoc, &rej);
+
+  if (cond.bad())
+  {
+    DimseCondition::dump(temp_str, cond);
+    logger.error(QString("Association Reject Failed: ") + temp_str.c_str());
+  }
+
+  cond = ASC_dropAssociation(*assoc);
+  if (cond.bad())
+  {
+    DimseCondition::dump(temp_str, cond);
+    logger.error(QString("Cannot Drop Association: ") + temp_str.c_str());
+  }
+  cond = ASC_destroyAssociation(assoc);
+  if (cond.bad())
+  {
+    DimseCondition::dump(temp_str, cond);
+    logger.error(QString("Cannot Destroy Association: ") + temp_str.c_str());
+  }
+
+  return cond;
+}
+
+//------------------------------------------------------------------------------
+OFCondition ctkDICOMSCPPrivate::handleAssociation(T_ASC_Association * assoc, OFBool correctUIDPadding)
+{
+  OFCondition         cond = EC_Normal;
+  DIC_NODENAME        peerHostName;
+  DIC_AE              peerAETitle;
+  DIC_AE              myAETitle;
+  OFString            temp_str;
+
+  ASC_getPresentationAddresses(assoc->params, peerHostName, NULL);
+  ASC_getAPTitles(assoc->params, peerAETitle, myAETitle, NULL);
+
+  /* now do the real work */
+  cond = dispatch(assoc, correctUIDPadding);
+
+  /* clean up on association termination */
+  if (cond == DUL_PEERREQUESTEDRELEASE) {
+    logger.info("Association Release");
+    cond = ASC_acknowledgeRelease(assoc);
+    ASC_dropSCPAssociation(assoc);
+  } else if (cond == DUL_PEERABORTEDASSOCIATION) {
+    logger.info("Association Aborted");
+  } else {
+    DimseCondition::dump(temp_str, cond);
+    logger.error(QString("DIMSE Failure (aborting association): ") + temp_str.c_str());
+    /* some kind of error so abort the association */
+    cond = ASC_abortAssociation(assoc);
+  }
+
+  cond = ASC_dropAssociation(assoc);
+  if (cond.bad()) {
+    DimseCondition::dump(temp_str, cond);
+    logger.error(QString("Cannot Drop Association: ") + temp_str.c_str());
+  }
+  cond = ASC_destroyAssociation(&assoc);
+  if (cond.bad()) {
+    DimseCondition::dump(temp_str, cond);
+    logger.error(QString("Cannot Destroy Association: ") + temp_str.c_str());
+  }
+
+  return cond;
+}
+
+//------------------------------------------------------------------------------
+OFCondition ctkDICOMSCPPrivate::dispatch(T_ASC_Association *assoc, OFBool correctUIDPadding)
+{
+  OFCondition cond = EC_Normal;
+  T_DIMSE_Message msg;
+  T_ASC_PresentationContextID presID;
+
+  // this while loop is executed exactly once unless the "keepDBHandleDuringAssociation_"
+  // flag is not set, in which case the inner loop is executed only once and this loop
+  // repeats for each incoming DIMSE command. In this case, the DB handle is created
+  // and released for each DIMSE command.
+  while (cond.good())
+  {
+    // this while loop is executed exactly once unless the "keepDBHandleDuringAssociation_"
+    // flag is set, in which case the DB handle remains open until something goes wrong
+    // or the remote peer closes the association
+    while (cond.good())
+    {
+      cond = DIMSE_receiveCommand(assoc, DIMSE_BLOCKING, 0, &presID, &msg, NULL);
+
+      /* did peer release, abort, or do we have a valid message ? */
+      if (cond.good())
+      {
+        /* process command */
+
+        if(msg.CommandField == DIMSE_C_CANCEL_RQ)
+        {
+          logger.info("dispatch: late C-CANCEL-RQ, ignoring");
+        }
+        else
+        {
+          if(this->registeredSCPs.contains(msg.CommandField))
+          {
+            cond = this->registeredSCPs[msg.CommandField]->handleRequest(assoc, msg, presID);
+          }
+          else
+          {
+            /* we cannot handle this kind of message */
+            cond = DIMSE_BADCOMMANDTYPE;
+            logger.error(QString("Cannot handle command: ") + (unsigned)msg.CommandField);
+          }
+        }
+      }
+      else if ((cond == DUL_PEERREQUESTEDRELEASE)||(cond == DUL_PEERABORTEDASSOCIATION))
+      {
+        // association gone
+      }
+      else
+      {
+        // the condition will be returned, the caller will abort the assosiation.
+      }
+    }
+  }
+
+  // Association done
+  return cond;
+}
+
+//------------------------------------------------------------------------------
+// ctkDICOMSCP class methods
+//------------------------------------------------------------------------------
+//------------------------------------------------------------------------------
+ctkDICOMSCP::ctkDICOMSCP(QObject *parent) :
+  QObject(parent),
+  d_ptr(new ctkDICOMSCPPrivate(this))
+{
+  this->dimseCommand = DIMSE_NOTHING;
+}
+
+//------------------------------------------------------------------------------
+ctkDICOMSCP::~ctkDICOMSCP()
+{
+  delete d_ptr;
+}
+
+//------------------------------------------------------------------------------
+T_DIMSE_Command ctkDICOMSCP::getDimseCommand()
+{
+  return this->dimseCommand;
+}
+
+//------------------------------------------------------------------------------
+bool ctkDICOMSCP::registerSCP(ctkDICOMSCP *scp)
+{
+  Q_D(ctkDICOMSCP);
+
+  if(!scp)
+  {
+    return false;
+  }
+
+  T_DIMSE_Command command = scp->getDimseCommand();
+
+  if(d->registeredSCPs.contains(command))
+  {
+    return false;
+  }
+
+  scp->setParent(this);
+  d->registeredSCPs[command] = scp;
+
+  return true;
+}
+
+//------------------------------------------------------------------------------
+void ctkDICOMSCP::setAETitle(const QString& aeTitle)
+{
+  Q_D(ctkDICOMSCP);
+
+  d->aeTitle = aeTitle;
+}
+
+//------------------------------------------------------------------------------
+void ctkDICOMSCP::setPort(int port)
+{
+  Q_D(ctkDICOMSCP);
+
+  d->port = port;
+}
+
+//------------------------------------------------------------------------------
+void ctkDICOMSCP::setEnableThreading(bool flag)
+{
+  Q_D(ctkDICOMSCP);
+
+  d->isThreadingEnabled = flag;
+}
+
+//------------------------------------------------------------------------------
+void ctkDICOMSCP::setMaxPDU(int size)
+{
+  Q_D(ctkDICOMSCP);
+
+  d->maxPDU = size;
+}
+
+//------------------------------------------------------------------------------
+void ctkDICOMSCP::setMaxAssociations(int count)
+{
+  Q_D(ctkDICOMSCP);
+
+  d->maxAssociation = count;
+}
+
+//------------------------------------------------------------------------------
+void ctkDICOMSCP::setEnableGetSupport(bool flag)
+{
+  Q_D(ctkDICOMSCP);
+  
+  d->isGetEnabled = flag;
+}
+
+//------------------------------------------------------------------------------
+void ctkDICOMSCP::setEnablePatientRoot(bool flag)
+{
+  Q_D(ctkDICOMSCP);
+
+  d->isPatientRootEnabled = flag;
+}
+
+//------------------------------------------------------------------------------
+void ctkDICOMSCP::setEnableStudyRoot(bool flag)
+{
+  Q_D(ctkDICOMSCP);
+
+  d->isStudyRootEnabled = flag;
+}
+
+//------------------------------------------------------------------------------
+void ctkDICOMSCP::start()
+{
+  Q_D(ctkDICOMSCP);
+
+  if(d->isThreadingEnabled)
+  {
+    QtConcurrent::run(d, &ctkDICOMSCPPrivate::threadedStartServer);
+  }
+  else
+  {
+    d->threadedStartServer();
+  }
+}
+
+//------------------------------------------------------------------------------
+void ctkDICOMSCP::stop()
+{
+  Q_D(ctkDICOMSCP);
+
+  d->terminateRequested = true;
+}

+ 58 - 0
Libs/DICOM/Core/ctkDICOMSCP.h

@@ -0,0 +1,58 @@
+#ifndef CTKDCMSCP_H
+#define CTKDCMSCP_H
+
+// DCMTK includes
+#include <dcmtk/dcmnet/assoc.h>
+#include <dcmtk/dcmnet/dimse.h>
+
+// Qt includes
+#include <QObject>
+
+// ctk includes
+#include "ctkDICOMCoreExport.h"
+
+class ctkDICOMSCPPrivate;
+
+class CTK_DICOM_CORE_EXPORT ctkDICOMSCP : public QObject
+{
+  Q_OBJECT
+public:
+  explicit ctkDICOMSCP(QObject *parent = 0);
+  ~ctkDICOMSCP();
+
+  void setAETitle(const QString& aeTitle);
+  void setPort(int port);
+  void setMaxPDU(int size);
+  void setMaxAssociations(int count);
+  void setEnableGetSupport(bool flag);
+  void setEnablePatientRoot(bool flag);
+  void setEnableStudyRoot(bool flag);
+  void setEctnablePatientStudyOnly(bool flag);
+  void setAllowShutdown(bool flag);
+  void setEnableThreading(bool flag);
+
+  bool registerSCP(ctkDICOMSCP* scp);
+  T_DIMSE_Command getDimseCommand();
+
+  void start();
+  void stop();
+
+  virtual OFCondition handleRequest(T_ASC_Association* assoc, T_DIMSE_Message& message, T_ASC_PresentationContextID presId)
+  {
+    Q_UNUSED(assoc);
+    Q_UNUSED(message);
+    Q_UNUSED(presId);
+
+    return DIMSE_BADCOMMANDTYPE;
+  }
+
+protected:
+  T_DIMSE_Command dimseCommand;
+
+private:
+  ctkDICOMSCPPrivate* d_ptr;
+  Q_DECLARE_PRIVATE(ctkDICOMSCP);
+  Q_DISABLE_COPY(ctkDICOMSCP);
+};
+
+#endif // CTKDCMSCP_H