ctkDICOMRetrieve.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410
  1. /*=========================================================================
  2. Library: CTK
  3. Copyright (c) Kitware Inc.
  4. Licensed under the Apache License, Version 2.0 (the "License");
  5. you may not use this file except in compliance with the License.
  6. You may obtain a copy of the License at
  7. http://www.apache.org/licenses/LICENSE-2.0.txt
  8. Unless required by applicable law or agreed to in writing, software
  9. distributed under the License is distributed on an "AS IS" BASIS,
  10. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  11. See the License for the specific language governing permissions and
  12. limitations under the License.
  13. =========================================================================*/
  14. #include <stdexcept>
  15. // Qt includes
  16. #include <QSqlQuery>
  17. #include <QSqlRecord>
  18. #include <QVariant>
  19. #include <QDate>
  20. #include <QStringList>
  21. #include <QSet>
  22. #include <QFile>
  23. #include <QDirIterator>
  24. #include <QFileInfo>
  25. #include <QDebug>
  26. // ctkDICOMCore includes
  27. #include "ctkDICOMRetrieve.h"
  28. #include "ctkLogger.h"
  29. // DCMTK includes
  30. #include "dcmtk/dcmnet/dimse.h"
  31. #include "dcmtk/dcmnet/diutil.h"
  32. #include <dcmtk/dcmdata/dcfilefo.h>
  33. #include <dcmtk/dcmdata/dcfilefo.h>
  34. #include <dcmtk/dcmdata/dcdeftag.h>
  35. #include <dcmtk/dcmdata/dcdatset.h>
  36. #include <dcmtk/ofstd/ofcond.h>
  37. #include <dcmtk/ofstd/ofstring.h>
  38. #include <dcmtk/ofstd/ofstd.h> /* for class OFStandard */
  39. #include <dcmtk/dcmdata/dcddirif.h> /* for class DicomDirInterface */
  40. #include <dcmtk/dcmjpeg/djdecode.h> /* for dcmjpeg decoders */
  41. #include <dcmtk/dcmjpeg/djencode.h> /* for dcmjpeg encoders */
  42. #include <dcmtk/dcmdata/dcrledrg.h> /* for DcmRLEDecoderRegistration */
  43. #include <dcmtk/dcmdata/dcrleerg.h> /* for DcmRLEEncoderRegistration */
  44. // NOTE: using ctk stand-in class for now - switch back
  45. // to dcmtk's scu.h when cget support is in a release version
  46. //#include <dcmtk/dcmnet/scu.h>
  47. #include <ctkDcmSCU.h>
  48. #include "dcmtk/oflog/oflog.h"
  49. static ctkLogger logger("org.commontk.dicom.DICOMRetrieve");
  50. //------------------------------------------------------------------------------
  51. class ctkDICOMRetrievePrivate
  52. {
  53. public:
  54. ctkDICOMRetrievePrivate();
  55. ~ctkDICOMRetrievePrivate();
  56. bool KeepAssociationOpen;
  57. bool ConnectionParamsChanged;
  58. QSharedPointer<ctkDICOMDatabase> RetrieveDatabase;
  59. ctkDcmSCU SCU;
  60. QString MoveDestinationAETitle;
  61. // do the retrieve, handling both series and study retrieves
  62. enum RetrieveType { RetrieveSeries, RetrieveStudy };
  63. bool retrieve ( const QString& studyInstanceUID,
  64. const QString& seriesInstanceUID,
  65. const RetrieveType rType );
  66. };
  67. //------------------------------------------------------------------------------
  68. // ctkDICOMRetrievePrivate methods
  69. //------------------------------------------------------------------------------
  70. ctkDICOMRetrievePrivate::ctkDICOMRetrievePrivate()
  71. {
  72. this->RetrieveDatabase = QSharedPointer<ctkDICOMDatabase> (0);
  73. this->KeepAssociationOpen = true;
  74. this->ConnectionParamsChanged = false;
  75. logger.info ( "Setting Transfer Syntaxes" );
  76. OFList<OFString> transferSyntaxes;
  77. transferSyntaxes.push_back ( UID_LittleEndianExplicitTransferSyntax );
  78. transferSyntaxes.push_back ( UID_BigEndianExplicitTransferSyntax );
  79. transferSyntaxes.push_back ( UID_LittleEndianImplicitTransferSyntax );
  80. this->SCU.addPresentationContext ( UID_MOVEStudyRootQueryRetrieveInformationModel, transferSyntaxes );
  81. }
  82. //------------------------------------------------------------------------------
  83. ctkDICOMRetrievePrivate::~ctkDICOMRetrievePrivate()
  84. {
  85. // At least now be kind to the server and release association
  86. this->SCU.closeAssociation(DCMSCU_RELEASE_ASSOCIATION);
  87. }
  88. //------------------------------------------------------------------------------
  89. bool ctkDICOMRetrievePrivate::retrieve ( const QString& studyInstanceUID,
  90. const QString& seriesInstanceUID,
  91. const RetrieveType rType )
  92. {
  93. if ( !this->RetrieveDatabase )
  94. {
  95. logger.error ( "No RetrieveDatabase for retrieve transaction" );
  96. return false;
  97. }
  98. // Register the JPEG libraries in case we need them
  99. // (registration only happens once, so it's okay to call repeatedly)
  100. // register global JPEG decompression codecs
  101. DJDecoderRegistration::registerCodecs();
  102. // register global JPEG compression codecs
  103. DJEncoderRegistration::registerCodecs();
  104. // register RLE compression codec
  105. DcmRLEEncoderRegistration::registerCodecs();
  106. // register RLE decompression codec
  107. DcmRLEDecoderRegistration::registerCodecs();
  108. // If we like to query another server than before, be sure to disconnect first
  109. if (this->SCU.isConnected() && this->ConnectionParamsChanged)
  110. {
  111. this->SCU.closeAssociation(DCMSCU_RELEASE_ASSOCIATION);
  112. }
  113. // Connect to server if not already connected
  114. if (!this->SCU.isConnected())
  115. {
  116. // Check and initialize networking parameters in DCMTK
  117. if ( !this->SCU.initNetwork().good() )
  118. {
  119. logger.error ( "Error initializing the network" );
  120. return false;
  121. }
  122. // Negotiate (i.e. start the) association
  123. logger.debug ( "Negotiating Association" );
  124. if ( !this->SCU.negotiateAssociation().good() )
  125. {
  126. logger.error ( "Error negotiating association" );
  127. return false;;
  128. }
  129. }
  130. this->ConnectionParamsChanged = false;
  131. // Setup query about what to be received from the PACS
  132. logger.debug ( "Setting Retrieve Parameters" );
  133. DcmDataset *retrieveParameters = new DcmDataset();
  134. if ( rType == RetrieveSeries )
  135. {
  136. retrieveParameters->putAndInsertString ( DCM_QueryRetrieveLevel, "SERIES" );
  137. retrieveParameters->putAndInsertString ( DCM_SeriesInstanceUID, seriesInstanceUID.toStdString().c_str() );
  138. // Always required to send all highler level unique keys, so add study here (we are in Study Root)
  139. retrieveParameters->putAndInsertString ( DCM_StudyInstanceUID, studyInstanceUID.toStdString().c_str() ); //TODO
  140. }
  141. else
  142. {
  143. retrieveParameters->putAndInsertString ( DCM_QueryRetrieveLevel, "STUDY" );
  144. retrieveParameters->putAndInsertString ( DCM_StudyInstanceUID, studyInstanceUID.toStdString().c_str() );
  145. }
  146. // Issue request
  147. logger.debug ( "Sending Move Request" );
  148. OFList<RetrieveResponse*> responses;
  149. T_ASC_PresentationContextID presID = this->SCU.findPresentationContextID(UID_MOVEStudyRootQueryRetrieveInformationModel, "" /* don't care about transfer syntax */ );
  150. if (presID == 0)
  151. {
  152. logger.error ( "MOVE Request failed: No valid Study Root MOVE Presentation Context available" );
  153. if (!this->KeepAssociationOpen)
  154. {
  155. this->SCU.closeAssociation(DCMSCU_RELEASE_ASSOCIATION);
  156. }
  157. return false;
  158. }
  159. OFCondition status = this->SCU.sendMOVERequest ( presID, this->MoveDestinationAETitle.toStdString().c_str(), retrieveParameters, &responses );
  160. // Close association if we do not want to explicitly keep it open
  161. if (!this->KeepAssociationOpen)
  162. {
  163. this->SCU.closeAssociation(DCMSCU_RELEASE_ASSOCIATION);
  164. }
  165. // Free some (little) memory
  166. delete retrieveParameters;
  167. // If we do not receive a single response, something is fishy
  168. if ( responses.begin() == responses.end() )
  169. {
  170. logger.error ( "No responses received at all! (at least one empty response always expected)" );
  171. throw std::runtime_error( std::string("No responses received from server!") );
  172. }
  173. /* The server is permitted to acknowledge every image that was received, or
  174. * to send a single move response.
  175. * If there is only a single response, this can mean the following:
  176. * 1) No images to transfer (Status Success and Number of Completed Subops = 0)
  177. * 2) All images transferred (Status Success and Number of Completed Subops > 0)
  178. * 3) Error code, i.e. no images transferred
  179. * 4) Warning (one or more failures, i.e. some images transferred)
  180. */
  181. if ( responses.size() == 1 )
  182. {
  183. RetrieveResponse* rsp = *responses.begin();
  184. logger.debug ( "MOVE response receveid with status: " + QString(DU_cmoveStatusString(rsp->m_status)) );
  185. if ((rsp->m_status == STATUS_Success) || (rsp->m_status == STATUS_MOVE_Warning_SubOperationsCompleteOneOrMoreFailures))
  186. {
  187. if (rsp->m_numberOfCompletedSubops == 0)
  188. {
  189. logger.error ( "No images transferred by PACS!" );
  190. throw std::runtime_error( std::string("No images transferred by PACS!") );
  191. }
  192. }
  193. else
  194. {
  195. logger.error("MOVE request failed, server does report error");
  196. QString statusDetail("No details");
  197. if (rsp->m_statusDetail != NULL)
  198. {
  199. std::ostringstream out;
  200. rsp->m_statusDetail->print(out);
  201. statusDetail = "Status Detail: " + statusDetail.fromStdString(out.str());
  202. }
  203. statusDetail.prepend("MOVE request failed: ");
  204. logger.error(statusDetail);
  205. throw std::runtime_error( statusDetail.toStdString() );
  206. }
  207. }
  208. // Select the last MOVE response to output meaningful status information
  209. OFIterator<RetrieveResponse*> it = responses.begin();
  210. Uint32 numResults = responses.size();
  211. for (Uint32 i = 1; i < numResults; i++)
  212. {
  213. it++;
  214. }
  215. logger.debug ( "MOVE responses report for study: " + studyInstanceUID +"\n"
  216. + QString::number(static_cast<unsigned int>((*it)->m_numberOfCompletedSubops)) + " images transferred, and\n"
  217. + QString::number(static_cast<unsigned int>((*it)->m_numberOfWarningSubops)) + " images transferred with warning, and\n"
  218. + QString::number(static_cast<unsigned int>((*it)->m_numberOfFailedSubops)) + " images transfers failed");
  219. return true;
  220. }
  221. //------------------------------------------------------------------------------
  222. // ctkDICOMRetrieve methods
  223. //------------------------------------------------------------------------------
  224. ctkDICOMRetrieve::ctkDICOMRetrieve()
  225. : d_ptr(new ctkDICOMRetrievePrivate)
  226. {
  227. }
  228. //------------------------------------------------------------------------------
  229. ctkDICOMRetrieve::~ctkDICOMRetrieve()
  230. {
  231. }
  232. //------------------------------------------------------------------------------
  233. /// Set methods for connectivity
  234. void ctkDICOMRetrieve::setCallingAETitle( const QString& callingAETitle )
  235. {
  236. Q_D(ctkDICOMRetrieve);
  237. if (strcmp(callingAETitle.toStdString().c_str(), d->SCU.getAETitle().c_str()))
  238. {
  239. d->SCU.setAETitle(callingAETitle.toStdString().c_str());
  240. d->ConnectionParamsChanged = true;
  241. }
  242. }
  243. //------------------------------------------------------------------------------
  244. QString ctkDICOMRetrieve::callingAETitle() const
  245. {
  246. Q_D(const ctkDICOMRetrieve);
  247. return d->SCU.getAETitle().c_str();
  248. }
  249. //------------------------------------------------------------------------------
  250. void ctkDICOMRetrieve::setCalledAETitle( const QString& calledAETitle )
  251. {
  252. Q_D(ctkDICOMRetrieve);
  253. if (strcmp(calledAETitle.toStdString().c_str(),d->SCU.getPeerAETitle().c_str()))
  254. {
  255. d->SCU.setPeerAETitle(calledAETitle.toStdString().c_str());
  256. d->ConnectionParamsChanged = true;
  257. }
  258. }
  259. //------------------------------------------------------------------------------
  260. QString ctkDICOMRetrieve::calledAETitle()const
  261. {
  262. Q_D(const ctkDICOMRetrieve);
  263. return d->SCU.getPeerAETitle().c_str();
  264. }
  265. //------------------------------------------------------------------------------
  266. void ctkDICOMRetrieve::setHost( const QString& host )
  267. {
  268. Q_D(ctkDICOMRetrieve);
  269. if (strcmp(host.toStdString().c_str(), d->SCU.getPeerHostName().c_str()))
  270. {
  271. d->SCU.setPeerHostName(host.toStdString().c_str());
  272. d->ConnectionParamsChanged = true;
  273. }
  274. }
  275. //------------------------------------------------------------------------------
  276. QString ctkDICOMRetrieve::host()const
  277. {
  278. Q_D(const ctkDICOMRetrieve);
  279. return d->SCU.getPeerHostName().c_str();
  280. }
  281. //------------------------------------------------------------------------------
  282. void ctkDICOMRetrieve::setCalledPort( int port )
  283. {
  284. Q_D(ctkDICOMRetrieve);
  285. if (d->SCU.getPeerPort() != port)
  286. {
  287. d->SCU.setPeerPort(port);
  288. d->ConnectionParamsChanged = true;
  289. }
  290. }
  291. //------------------------------------------------------------------------------
  292. int ctkDICOMRetrieve::calledPort()const
  293. {
  294. Q_D(const ctkDICOMRetrieve);
  295. return d->SCU.getPeerPort();
  296. }
  297. //------------------------------------------------------------------------------
  298. void ctkDICOMRetrieve::setMoveDestinationAETitle( const QString& moveDestinationAETitle )
  299. {
  300. Q_D(ctkDICOMRetrieve);
  301. if (moveDestinationAETitle != d->MoveDestinationAETitle)
  302. {
  303. d->MoveDestinationAETitle = moveDestinationAETitle;
  304. d->ConnectionParamsChanged = true;
  305. }
  306. }
  307. //------------------------------------------------------------------------------
  308. QString ctkDICOMRetrieve::moveDestinationAETitle()const
  309. {
  310. Q_D(const ctkDICOMRetrieve);
  311. return d->MoveDestinationAETitle;
  312. }
  313. //------------------------------------------------------------------------------
  314. void ctkDICOMRetrieve::setRetrieveDatabase(QSharedPointer<ctkDICOMDatabase> dicomDatabase)
  315. {
  316. Q_D(ctkDICOMRetrieve);
  317. d->RetrieveDatabase = dicomDatabase;
  318. // (server parameters do not have to be changed)
  319. }
  320. //------------------------------------------------------------------------------
  321. QSharedPointer<ctkDICOMDatabase> ctkDICOMRetrieve::retrieveDatabase()const
  322. {
  323. Q_D(const ctkDICOMRetrieve);
  324. return d->RetrieveDatabase;
  325. }
  326. //------------------------------------------------------------------------------
  327. void ctkDICOMRetrieve::setKeepAssociationOpen(const bool keepOpen)
  328. {
  329. Q_D(ctkDICOMRetrieve);
  330. d->KeepAssociationOpen = keepOpen;
  331. }
  332. //------------------------------------------------------------------------------
  333. bool ctkDICOMRetrieve::keepAssociationOpen()
  334. {
  335. Q_D(const ctkDICOMRetrieve);
  336. return d->KeepAssociationOpen;
  337. }
  338. //------------------------------------------------------------------------------
  339. bool ctkDICOMRetrieve::retrieveSeries(const QString& studyInstanceUID,
  340. const QString& seriesInstanceUID)
  341. {
  342. if (studyInstanceUID.isEmpty() || seriesInstanceUID.isEmpty())
  343. {
  344. logger.error("Cannot receive series: Either Study or Series Instance UID empty.");
  345. return false;
  346. }
  347. Q_D(ctkDICOMRetrieve);
  348. logger.info ( "Starting retrieveSeries" );
  349. return d->retrieve ( studyInstanceUID, seriesInstanceUID, ctkDICOMRetrievePrivate::RetrieveSeries );
  350. }
  351. //------------------------------------------------------------------------------
  352. bool ctkDICOMRetrieve::retrieveStudy( const QString& studyInstanceUID )
  353. {
  354. if (studyInstanceUID.isEmpty())
  355. {
  356. logger.error("Cannot receive series: Either Study or Series Instance UID empty.");
  357. return false;
  358. }
  359. Q_D(ctkDICOMRetrieve);
  360. logger.info ( "Starting retrieveStudy" );
  361. return d->retrieve ( studyInstanceUID, "", ctkDICOMRetrievePrivate::RetrieveStudy );
  362. }