ctkDICOMRetrieve.cpp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432
  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. #ifndef WIN32
  31. #define HAVE_CONFIG_H
  32. #endif
  33. #include "dcmtk/dcmnet/dimse.h"
  34. #include "dcmtk/dcmnet/diutil.h"
  35. #include <dcmtk/dcmdata/dcfilefo.h>
  36. #include <dcmtk/dcmdata/dcfilefo.h>
  37. #include <dcmtk/dcmdata/dcdeftag.h>
  38. #include <dcmtk/dcmdata/dcdatset.h>
  39. #include <dcmtk/ofstd/ofcond.h>
  40. #include <dcmtk/ofstd/ofstring.h>
  41. #include <dcmtk/ofstd/ofstd.h> /* for class OFStandard */
  42. #include <dcmtk/dcmdata/dcddirif.h> /* for class DicomDirInterface */
  43. #include <dcmtk/dcmjpeg/djdecode.h> /* for dcmjpeg decoders */
  44. #include <dcmtk/dcmjpeg/djencode.h> /* for dcmjpeg encoders */
  45. #include <dcmtk/dcmdata/dcrledrg.h> /* for DcmRLEDecoderRegistration */
  46. #include <dcmtk/dcmdata/dcrleerg.h> /* for DcmRLEEncoderRegistration */
  47. #include <dcmtk/dcmnet/scu.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. QString CallingAETitle;
  57. QString CalledAETitle;
  58. QString Host;
  59. int CallingPort; // TODO: Not used yet since C-STORE receiver must be run separately so far
  60. int CalledPort;
  61. DcmSCU SCU; // TODO: not used yet.
  62. DcmDataset* parameters;
  63. QString MoveDestinationAETitle;
  64. QSharedPointer<ctkDICOMDatabase> RetrieveDatabase;
  65. // do the retrieve, handling both series and study retrieves
  66. enum RetrieveType { RetrieveSeries, RetrieveStudy };
  67. bool retrieve ( const QString& studyInstanceUID,
  68. const QString& seriesInstanceUID,
  69. const RetrieveType rType );
  70. };
  71. //------------------------------------------------------------------------------
  72. // ctkDICOMRetrievePrivate methods
  73. //------------------------------------------------------------------------------
  74. ctkDICOMRetrievePrivate::ctkDICOMRetrievePrivate()
  75. {
  76. this->parameters = new DcmDataset();
  77. this->RetrieveDatabase = QSharedPointer<ctkDICOMDatabase> (0);
  78. this->CallingPort = 0;
  79. this->CalledPort = 0;
  80. }
  81. //------------------------------------------------------------------------------
  82. ctkDICOMRetrievePrivate::~ctkDICOMRetrievePrivate()
  83. {
  84. delete this->parameters;
  85. }
  86. //------------------------------------------------------------------------------
  87. bool ctkDICOMRetrievePrivate::retrieve ( const QString& studyInstanceUID,
  88. const QString& seriesInstanceUID,
  89. const RetrieveType rType )
  90. {
  91. if ( !this->RetrieveDatabase )
  92. {
  93. logger.error ( "No RetrieveDatabase for retrieve transaction" );
  94. return false;
  95. }
  96. // Register the JPEG libraries in case we need them
  97. // (registration only happens once, so it's okay to call repeatedly)
  98. // register global JPEG decompression codecs
  99. DJDecoderRegistration::registerCodecs();
  100. // register global JPEG compression codecs
  101. DJEncoderRegistration::registerCodecs();
  102. // register RLE compression codec
  103. DcmRLEEncoderRegistration::registerCodecs();
  104. // register RLE decompression codec
  105. DcmRLEDecoderRegistration::registerCodecs();
  106. // Set the DCMTK log level
  107. dcmtk::log4cplus::Logger rootLogger = dcmtk::log4cplus::Logger::getRoot();
  108. rootLogger.setLogLevel(dcmtk::log4cplus::DEBUG_LOG_LEVEL);
  109. // TODO: use this->SCU instead?
  110. DcmSCU scu;
  111. scu.setAETitle ( OFString(this->CallingAETitle.toStdString().c_str()) );
  112. scu.setPeerAETitle ( OFString(this->CalledAETitle.toStdString().c_str()) );
  113. scu.setPeerHostName ( OFString(this->Host.toStdString().c_str()) );
  114. scu.setPeerPort ( this->CalledPort );
  115. logger.info ( "Setting Transfer Syntaxes" );
  116. OFList<OFString> transferSyntaxes;
  117. transferSyntaxes.push_back ( UID_LittleEndianExplicitTransferSyntax );
  118. transferSyntaxes.push_back ( UID_BigEndianExplicitTransferSyntax );
  119. transferSyntaxes.push_back ( UID_LittleEndianImplicitTransferSyntax );
  120. scu.addPresentationContext ( UID_MOVEStudyRootQueryRetrieveInformationModel, transferSyntaxes );
  121. // Check and initialize networking parameters in DCMTK
  122. if ( !scu.initNetwork().good() )
  123. {
  124. logger.error ( "Error initializing the network" );
  125. return false;
  126. }
  127. // Negotiate (i.e. start the) association
  128. logger.debug ( "Negotiating Association" );
  129. if ( !scu.negotiateAssociation().good() )
  130. {
  131. logger.error ( "Error negotiating association" );
  132. return false;;
  133. }
  134. // Setup query what to be received from the PACS
  135. logger.debug ( "Setting Parameters" );
  136. // Clear the query
  137. parameters->clear();
  138. if ( rType == RetrieveSeries )
  139. {
  140. this->parameters->putAndInsertString ( DCM_QueryRetrieveLevel, "SERIES" );
  141. this->parameters->putAndInsertString ( DCM_SeriesInstanceUID, seriesInstanceUID.toStdString().c_str() );
  142. // Always required to send all highler level unique keys, so add study here (we are in Study Root)
  143. this->parameters->putAndInsertString ( DCM_StudyInstanceUID, studyInstanceUID.toStdString().c_str() ); //TODO
  144. }
  145. else
  146. {
  147. this->parameters->putAndInsertString ( DCM_QueryRetrieveLevel, "STUDY" );
  148. this->parameters->putAndInsertString ( DCM_StudyInstanceUID, studyInstanceUID.toStdString().c_str() );
  149. }
  150. // Issue request
  151. logger.debug ( "Sending Move Request" );
  152. MOVEResponses responses;
  153. T_ASC_PresentationContextID presID = scu.findPresentationContextID(UID_MOVEStudyRootQueryRetrieveInformationModel, "" /* don't care about transfer syntax */ );
  154. if (presID == 0)
  155. {
  156. logger.error ( "MOVE Request failed: No valid Study Root MOVE Presentation Context available" );
  157. scu.closeAssociation(DCMSCU_RELEASE_ASSOCIATION);
  158. return false;
  159. }
  160. OFCondition status = scu.sendMOVERequest ( presID, this->MoveDestinationAETitle.toStdString().c_str(), this->parameters, &responses );
  161. // Close association, no need to keep it open
  162. scu.closeAssociation(DCMSCU_RELEASE_ASSOCIATION);
  163. // If we do not receive a single response, something is fishy
  164. if ( responses.begin() == responses.end() )
  165. {
  166. logger.error ( "No responses received at all! (at least one empty response always expected)" );
  167. throw std::runtime_error( std::string("No responses received from server!") );
  168. }
  169. /* The server is permitted to acknowledge every image that was received, or
  170. * to send a single move response.
  171. * If there is only a single response, this can mean the following:
  172. * 1) No images to transfer (Status Success and Number of Completed Subops = 0)
  173. * 2) All images transferred (Status Success and Number of Completed Subops > 0)
  174. * 3) Error code, i.e. no images transferred
  175. * 4) Warning (one or more failures, i.e. some images transferred)
  176. */
  177. if ( responses.numResults() == 1 )
  178. {
  179. MOVEResponse* rsp = *responses.begin();
  180. logger.debug ( "MOVE response receveid with status: " + QString(DU_cmoveStatusString(rsp->m_status)) );
  181. if ((rsp->m_status == STATUS_Success) || (rsp->m_status == STATUS_MOVE_Warning_SubOperationsCompleteOneOrMoreFailures))
  182. {
  183. if (rsp->m_numberOfCompletedSubops == 0)
  184. {
  185. logger.error ( "No images transferred by PACS!" );
  186. throw std::runtime_error( std::string("No images transferred by PACS!") );
  187. }
  188. }
  189. else
  190. {
  191. logger.error("MOVE request failed, server does report error");
  192. QString statusDetail("No details");
  193. if (rsp->m_statusDetail != NULL)
  194. {
  195. std::ostringstream out;
  196. rsp->m_statusDetail->print(out);
  197. statusDetail = "Status Detail: " + statusDetail.fromStdString(out.str());
  198. }
  199. statusDetail.prepend("MOVE request failed: ");
  200. logger.error(statusDetail);
  201. throw std::runtime_error( statusDetail.toStdString() );
  202. }
  203. }
  204. // Select the last MOVE response to output meaningful status information
  205. OFListIterator(MOVEResponse*) it = responses.begin();
  206. Uint32 numResults = responses.numResults();
  207. for (Uint32 i = 1; i < numResults; i++)
  208. {
  209. it++;
  210. }
  211. logger.debug ( "MOVE responses report for study: " + studyInstanceUID +"\n"
  212. + QString::number((*it)->m_numberOfCompletedSubops) + " images transferred, and\n"
  213. + QString::number((*it)->m_numberOfWarningSubops) + " images transferred with warning, and\n"
  214. + QString::number((*it)->m_numberOfFailedSubops) + " images transfers failed");
  215. /* Comment from Michael: The code below does not make sense. Using MOVE you never
  216. * receive the image here but only status information; thus, rsp->m_dataset is _not_
  217. * an image. I leave it inside since it might be moved to a location which makes more
  218. * sense.
  219. */
  220. // for ( OFListIterator(MOVEResponse*) it = responses.begin(); it != responses.end(); it++ )
  221. // {
  222. // DcmDataset *dataset = (*it)->m_dataset;
  223. // if ( dataset != NULL )
  224. // {
  225. // logger.debug ( "Got a valid dataset" );
  226. // // Save in correct directory
  227. // E_TransferSyntax output_transfersyntax = dataset->getOriginalXfer();
  228. // dataset->chooseRepresentation( output_transfersyntax, NULL );
  229. //
  230. // if ( !dataset->canWriteXfer( output_transfersyntax ) )
  231. // {
  232. // // Pick EXS_LittleEndianExplicit as our default
  233. // output_transfersyntax = EXS_LittleEndianExplicit;
  234. // }
  235. //
  236. // DcmXfer opt_oxferSyn( output_transfersyntax );
  237. // if ( !dataset->chooseRepresentation( opt_oxferSyn.getXfer(), NULL ).bad() )
  238. // {
  239. // DcmFileFormat fileformat( dataset );
  240. //
  241. // // Follow dcmdjpeg example
  242. // OFString SOPInstanceUID;
  243. // dataset->findAndGetOFString ( DCM_SOPInstanceUID, SOPInstanceUID );
  244. // QFileInfo fi ( directory, QString ( SOPInstanceUID.c_str() ) );
  245. // logger.debug ( "Saving file: " + fi.absoluteFilePath() );
  246. // status = fileformat.saveFile ( fi.absoluteFilePath().toStdString().c_str(), opt_oxferSyn.getXfer() );
  247. // if ( !status.good() )
  248. // {
  249. // logger.error ( "Error saving file: " + fi.absoluteFilePath() + " Error is " + status.text() );
  250. // }
  251. // // Insert into our local database
  252. // RetrieveDatabase->insert( dataset, true );
  253. // }
  254. // }
  255. // }
  256. return true;
  257. }
  258. //------------------------------------------------------------------------------
  259. // ctkDICOMRetrieve methods
  260. //------------------------------------------------------------------------------
  261. ctkDICOMRetrieve::ctkDICOMRetrieve()
  262. : d_ptr(new ctkDICOMRetrievePrivate)
  263. {
  264. }
  265. //------------------------------------------------------------------------------
  266. ctkDICOMRetrieve::~ctkDICOMRetrieve()
  267. {
  268. }
  269. //------------------------------------------------------------------------------
  270. /// Set methods for connectivity
  271. void ctkDICOMRetrieve::setCallingAETitle( const QString& callingAETitle )
  272. {
  273. Q_D(ctkDICOMRetrieve);
  274. d->CallingAETitle = callingAETitle;
  275. }
  276. //------------------------------------------------------------------------------
  277. QString ctkDICOMRetrieve::callingAETitle() const
  278. {
  279. Q_D(const ctkDICOMRetrieve);
  280. return d->CallingAETitle;
  281. }
  282. //------------------------------------------------------------------------------
  283. void ctkDICOMRetrieve::setCalledAETitle( const QString& calledAETitle )
  284. {
  285. Q_D(ctkDICOMRetrieve);
  286. d->CalledAETitle = calledAETitle;
  287. }
  288. //------------------------------------------------------------------------------
  289. QString ctkDICOMRetrieve::calledAETitle()const
  290. {
  291. Q_D(const ctkDICOMRetrieve);
  292. return d->CalledAETitle;
  293. }
  294. //------------------------------------------------------------------------------
  295. void ctkDICOMRetrieve::setHost( const QString& host )
  296. {
  297. Q_D(ctkDICOMRetrieve);
  298. d->Host = host;
  299. }
  300. //------------------------------------------------------------------------------
  301. QString ctkDICOMRetrieve::host()const
  302. {
  303. Q_D(const ctkDICOMRetrieve);
  304. return d->Host;
  305. }
  306. //------------------------------------------------------------------------------
  307. void ctkDICOMRetrieve::setCallingPort( int port )
  308. {
  309. Q_D(ctkDICOMRetrieve);
  310. d->CallingPort = port;
  311. }
  312. //------------------------------------------------------------------------------
  313. int ctkDICOMRetrieve::callingPort()const
  314. {
  315. Q_D(const ctkDICOMRetrieve);
  316. return d->CallingPort;
  317. }
  318. //------------------------------------------------------------------------------
  319. void ctkDICOMRetrieve::setCalledPort( int port )
  320. {
  321. Q_D(ctkDICOMRetrieve);
  322. d->CalledPort = port;
  323. }
  324. //------------------------------------------------------------------------------
  325. int ctkDICOMRetrieve::calledPort()const
  326. {
  327. Q_D(const ctkDICOMRetrieve);
  328. return d->CalledPort;
  329. }
  330. //------------------------------------------------------------------------------
  331. void ctkDICOMRetrieve::setMoveDestinationAETitle( const QString& moveDestinationAETitle )
  332. {
  333. Q_D(ctkDICOMRetrieve);
  334. d->MoveDestinationAETitle = moveDestinationAETitle;
  335. }
  336. //------------------------------------------------------------------------------
  337. QString ctkDICOMRetrieve::moveDestinationAETitle()const
  338. {
  339. Q_D(const ctkDICOMRetrieve);
  340. return d->MoveDestinationAETitle;
  341. }
  342. //------------------------------------------------------------------------------
  343. void ctkDICOMRetrieve::setRetrieveDatabase(QSharedPointer<ctkDICOMDatabase> dicomDatabase)
  344. {
  345. Q_D(ctkDICOMRetrieve);
  346. d->RetrieveDatabase = dicomDatabase;
  347. }
  348. //------------------------------------------------------------------------------
  349. QSharedPointer<ctkDICOMDatabase> ctkDICOMRetrieve::retrieveDatabase()const
  350. {
  351. Q_D(const ctkDICOMRetrieve);
  352. return d->RetrieveDatabase;
  353. }
  354. //------------------------------------------------------------------------------
  355. bool ctkDICOMRetrieve::retrieveSeries(const QString& studyInstanceUID,
  356. const QString& seriesInstanceUID )
  357. {
  358. if (studyInstanceUID.isEmpty() || seriesInstanceUID.isEmpty())
  359. {
  360. logger.error("Cannot receive series: Either Study or Series Instance UID empty.");
  361. return false;
  362. }
  363. Q_D(ctkDICOMRetrieve);
  364. logger.info ( "Starting retrieveSeries" );
  365. return d->retrieve ( studyInstanceUID, seriesInstanceUID, ctkDICOMRetrievePrivate::RetrieveSeries );
  366. }
  367. //------------------------------------------------------------------------------
  368. bool ctkDICOMRetrieve::retrieveStudy( const QString& studyInstanceUID )
  369. {
  370. if (studyInstanceUID.isEmpty())
  371. {
  372. logger.error("Cannot receive series: Either Study or Series Instance UID empty.");
  373. return false;
  374. }
  375. Q_D(ctkDICOMRetrieve);
  376. logger.info ( "Starting retrieveStudy" );
  377. return d->retrieve ( studyInstanceUID, "", ctkDICOMRetrievePrivate::RetrieveStudy );
  378. }