QtDcmMoveScu.cpp
Go to the documentation of this file.
1 /*
2  QtDcm is a C++ Qt based library for communication and conversion of Dicom images.
3  Copyright (C) 2011 Alexandre Abadie <Alexandre.Abadie@univ-rennes1.fr>
4 
5  This library is free software; you can redistribute it and/or
6  modify it under the terms of the GNU Lesser General Public
7  License as published by the Free Software Foundation; either
8  version 2.1 of the License, or (at your option) any later version.
9 
10  This library is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13  Lesser General Public License for more details.
14 
15  You should have received a copy of the GNU Lesser General Public
16  License along with this library; if not, write to the Free Software
17  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18 */
19 
20 #define QT_NO_CAST_TO_ASCII
21 
22 #ifdef WITH_OPENSSL
23 #include "dcmtk/dcmtls/tlstrans.h"
24 #include "dcmtk/dcmtls/tlslayer.h"
25 #endif
26 
27 #include <QtDcmPreferences.h>
28 #include <QtDcmServer.h>
29 #include <QtDcmManager.h>
30 #include <QtDcmMoveScu.h>
31 
33 {
34 
35 public:
36  QStringList series;
37  QStringList filenames;
38  QString outputDir;
39  QString importDir;
40  QString currentSerie;
41 
43  QString imageId;
44 
45 
46 
47  T_ASC_Network* net; /* the global DICOM network */
48  T_ASC_Parameters* params;
49  T_ASC_Association* assoc;
50  T_ASC_PresentationContextID presId;
51 
52  DcmFileFormat* file;
53  char* imageFile;
54 
56  T_DIMSE_BlockingMode blockMode;
57  OFBool useMetaheader;
58  OFBool bitPreserving;
59  OFBool ignore;
60  E_TransferSyntax writeTransferSyntax;
62  E_GrpLenEncoding groupLength;
63  E_EncodingType sequenceType;
64  E_PaddingEncoding paddingType;
65  OFCmdUnsignedInt filepad;
66  OFCmdUnsignedInt itempad;
67  const char *moveDestination;
70  OFCmdUnsignedInt maxPDU;
71  E_TransferSyntax networkTransferSyntax;
72  OFCmdUnsignedInt repeatCount;
74  OFCmdSignedInt cancelAfterNResponses;
76  DcmDataset overrideKeys;
77  OFString outputDirectory;
78 
82  int step;
83 };
84 
85 QtDcmMoveScu::QtDcmMoveScu ( QObject * parent )
86  : QThread(parent),
87  d ( new QtDcmMoveScu::Private )
88 {
89  d->progressTotal = 0;
90  d->progressSerie = 0;
91  d->step = 0;
92 
93  d->net = 0;
94  d->assoc = 0;
95  d->params = 0;
96  d->file = 0;
97  d->maxPDU = ASC_DEFAULTMAXPDU;
98  d->useMetaheader = OFTrue;
99  d->networkTransferSyntax = EXS_Unknown;
100  d->writeTransferSyntax = EXS_Unknown;
101  d->groupLength = EGL_recalcGL;
102  d->sequenceType = EET_ExplicitLength;
103  d->paddingType = EPD_withoutPadding;
104  d->filepad = 0;
105  d->itempad = 0;
106  d->bitPreserving = OFFalse;
107  d->ignore = OFFalse;
108  d->correctUIDPadding = OFFalse;
109  d->repeatCount = 1;
110  d->abortAssociation = OFFalse;
111  d->moveDestination = NULL;
112  d->cancelAfterNResponses = -1;
114  d->ignorePendingDatasets = OFTrue;
115  d->outputDirectory = ".";
116  d->acseTimeout = 30;
117  d->blockMode = DIMSE_BLOCKING;
118 
120 }
121 
123 {
124  delete d;
125  d = NULL;
126 }
127 
129 {
130  d->mode = mode;
131 }
132 
133 void QtDcmMoveScu::setImageId ( const QString & id )
134 {
135  d->imageId = id;
136 }
137 
138 void QtDcmMoveScu::setSeries ( const QStringList & series )
139 {
140  d->series = series;
141 }
142 
143 void QtDcmMoveScu::setOutputDir ( const QString & dir )
144 {
145  d->outputDir = dir;
146 }
147 
148 void QtDcmMoveScu::setImportDir ( const QString & dir )
149 {
150  d->importDir = dir;
151 }
152 
154 {
155  if ( this->isRunning() ) {
156  ASC_dropNetwork ( &d->net );
157  }
158 }
159 
161 {
162  OFCondition cond;
163  d->step = ( int ) ( 100.0 / d->series.size() );
164  d->progressTotal = 0;
165 
166  for ( int i = 0; i < d->series.size(); i++ ) {
167  d->currentSerie = d->series.at ( i );
168  const QDir serieDir ( d->outputDir + QDir::separator() + d->series.at ( i ) );
169 
170  if ( !serieDir.exists() ) {
171  QDir ( d->outputDir ).mkdir ( d->series.at ( i ) );
172  }
173 
174  d->outputDirectory = QString ( d->outputDir + QDir::separator() + d->currentSerie ).toUtf8().constData();
175 
176  if ( d->mode == IMPORT ) {
177  cond = this->move ( d->series.at ( i ) );
178  emit updateProgress ( ( int ) ( 100.0 * ( i+1 ) / d->series.size() ) );
179  d->progressTotal += d->step;
180  emit serieMoved ( serieDir.absolutePath(), d->series.at ( i ), i );
181  }
182  else {
183  cond = this->move ( d->imageId );
184  }
185  }
186 }
187 
188 OFCondition QtDcmMoveScu::move ( const QString & uid )
189 {
190  OFString temp_str;
191  d->params = NULL;
192  d->assoc = NULL;
193  d->net = NULL;
194 
195  QuerySyntax querySyntax[3] = {
196  { UID_FINDPatientRootQueryRetrieveInformationModel,
197  UID_MOVEPatientRootQueryRetrieveInformationModel },
198  { UID_FINDStudyRootQueryRetrieveInformationModel,
199  UID_MOVEStudyRootQueryRetrieveInformationModel },
200  { UID_RETIRED_FINDPatientStudyOnlyQueryRetrieveInformationModel,
201  UID_RETIRED_MOVEPatientStudyOnlyQueryRetrieveInformationModel }
202  };
203 
204  OFCondition cond;
205 
206  if ( d->mode == IMPORT ) {
207  this->addOverrideKey ( QString ( "QueryRetrieveLevel=" ) + QString ( "" "SERIES" "" ) );
208  this->addOverrideKey ( QString ( "SeriesInstanceUID=" + uid ) );
209  }
210  else {
211  this->addOverrideKey ( QString ( "QueryRetrieveLevel=" ) + QString ( "" "IMAGE" "" ) );
212  this->addOverrideKey ( QString ( "SOPInstanceUID=" + uid ) );
213  }
214 
215  cond = ASC_initializeNetwork ( NET_ACCEPTORREQUESTOR, QtDcmPreferences::instance()->port().toInt(), d->acseTimeout, &d->net );
216 
217  if ( cond.bad() ) {
218  qDebug() << "Cannot create network: " << DimseCondition::dump ( temp_str, cond ).c_str();
219  return cond;
220  }
221 
222  cond = ASC_createAssociationParameters ( &d->params, d->maxPDU );
223 
224  if ( cond.bad() ) {
225  qDebug() << "Cannot create association: " << DimseCondition::dump ( temp_str, cond ).c_str();
226  return cond;
227  }
228 
229  ASC_setAPTitles ( d->params,
230  QtDcmPreferences::instance()->aetitle().toUtf8().data(),
231  QtDcmManager::instance()->currentPacs().aetitle().toUtf8().data(),
232  QtDcmManager::instance()->currentPacs().aetitle().toUtf8().data() );
233 
234  ASC_setPresentationAddresses ( d->params,
235  QtDcmPreferences::instance()->hostname().toUtf8().data(),
236  QString ( QtDcmManager::instance()->currentPacs().address() + ":" + QtDcmManager::instance()->currentPacs().port() ).toUtf8().data() );
237 
238  cond = addPresentationContext ( d->params, 1, querySyntax[d->queryModel].findSyntax, d->networkTransferSyntax );
239  cond = addPresentationContext ( d->params, 3, querySyntax[d->queryModel].moveSyntax, d->networkTransferSyntax );
240 
241  if ( cond.bad() ) {
242  qDebug() << "Wrong presentation context:" << DimseCondition::dump ( temp_str, cond ).c_str();
243  T_ASC_RejectParameters rej;
244  ASC_getRejectParameters ( d->params, &rej );
245  ASC_printRejectParameters ( temp_str, &rej );
246 
247  ASC_dropNetwork ( &d->net );
248  return cond;
249  }
250 
251  cond = ASC_requestAssociation ( d->net, d->params, &d->assoc );
252 
253  if ( cond.bad() ) {
254  if ( cond == DUL_ASSOCIATIONREJECTED ) {
255  T_ASC_RejectParameters rej;
256  ASC_getRejectParameters ( d->params, &rej );
257  ASC_printRejectParameters ( temp_str, &rej );
258  }
259  else {
260  qDebug() << "Association Request Failed:" << DimseCondition::dump ( temp_str,cond ).c_str();
261  }
262 
263  ASC_abortAssociation ( d->assoc );
264  ASC_dropNetwork ( &d->net );
265 
266  qDebug() << "Association Rejected:" << QString ( temp_str.c_str() );
267  return cond;
268  }
269 
270  if ( ASC_countAcceptedPresentationContexts ( d->params ) == 0 ) {
271  qDebug() << "No Acceptable Presentation Contexts";
272 
273  ASC_abortAssociation ( d->assoc );
274  ASC_dropNetwork ( &d->net );
275 
276  return cond;
277  }
278 
279  cond = EC_Normal;
280 
281  cond = this->cmove ( d->assoc, NULL );
282 
283  d->overrideKeys.clear();
284  this->addOverrideKey ( QString ( "QueryRetrieveLevel=" ) + QString ( "" "STUDY" "" ) );
285 
286  if ( cond == EC_Normal ) {
287  if ( d->abortAssociation ) {
288  qDebug() << "Aborting Association";
289  cond = ASC_abortAssociation ( d->assoc );
290  ASC_dropNetwork ( &d->net );
291 
292  if ( cond.bad() ) {
293  qDebug() << "Association Abort Failed: " << DimseCondition::dump ( temp_str,cond ).c_str();
294  return cond;
295  }
296  }
297  else {
298  /* release association */
299  qDebug() << "Releasing Association";
300 // cond = ASC_releaseAssociation(assoc); //Problem with error message Illegal Key
301  cond = ASC_dropNetwork ( &d->net );
302  if (cond.bad()) {
303  qDebug() << "Drop Network Failed:" << DimseCondition::dump ( temp_str,cond ).c_str();
304  return cond;
305  }
306  }
307  }
308  else if ( cond == DUL_PEERREQUESTEDRELEASE ) {
309  qDebug() << "Protocol Error: Peer requested release (Aborting)";
310  cond = ASC_abortAssociation ( d->assoc );
311  ASC_dropNetwork ( &d->net );
312 
313  if ( cond.bad() ) {
314  qDebug() << "Association Abort Failed: " << DimseCondition::dump ( temp_str,cond ).c_str();
315  return cond;
316  }
317 
318  return cond;
319  }
320  else if ( cond == DUL_PEERABORTEDASSOCIATION ) {
321  qDebug() << "Peer Aborted Association";
322  ASC_abortAssociation ( d->assoc );
323  ASC_dropNetwork ( &d->net );
324 
325  return cond;
326  }
327  else {
328  qDebug() << "Move SCU Failed: Aborting Association";
329  cond = ASC_abortAssociation ( d->assoc );
330  ASC_dropNetwork ( &d->net );
331 
332  if ( cond.bad() ) {
333  qDebug() << "Association Abort Failed: " << DimseCondition::dump ( temp_str,cond ).c_str();
334  return cond;
335  }
336  return cond;
337  }
338 
339  return cond;
340 }
341 
342 void QtDcmMoveScu::addOverrideKey ( const QString & key )
343 {
344  unsigned int g = 0xffff;
345  unsigned int e = 0xffff;
346  int n = 0;
347  OFString dicName, valStr;
348  OFString msg;
349  char msg2[200];
350 
351  // try to parse group and element number
352  OFString toParse(key.toLatin1().data());
353  n = sscanf ( key.toLatin1().data(), "%x,%x=", &g, &e );
354 
355  size_t eqPos = toParse.find ( '=' );
356 
357  if ( n < 2 ) { // if at least no tag could be parsed
358  // if value is given, extract it (and extrect dictname)
359  if ( eqPos != OFString_npos ) {
360  dicName = toParse.substr ( 0, eqPos ).c_str();
361  valStr = toParse.substr ( eqPos + 1, toParse.length() );
362  }
363  else {
364  // no value given, just dictionary name
365  dicName = key.toLatin1().data(); // only dictionary name given (without value)
366  }
367 
368  // try to lookup in dictionary
369  DcmTagKey key ( 0xffff, 0xffff );
370  const DcmDataDictionary& globalDataDict = dcmDataDict.rdlock();
371  const DcmDictEntry *dicent = globalDataDict.findEntry ( dicName.c_str() );
372 
373  dcmDataDict.unlock();
374 
375  if ( dicent != NULL ) {
376  // found dictionary name, copy group and element number
377  key = dicent->getKey();
378  g = key.getGroup();
379  e = key.getElement();
380  }
381  else {
382  // not found in dictionary
383  msg = "bad key format or dictionary name not found in dictionary: ";
384  msg += dicName;
385  qDebug() << QString ( msg.c_str() );
386  }
387  } // tag could be parsed, copy value if it exists
388  else {
389  if ( eqPos != OFString_npos )
390  valStr = toParse.substr ( eqPos + 1, toParse.length() );
391  }
392 
393  DcmTag tag ( g, e );
394  if ( tag.error() != EC_Normal ) {
395  sprintf ( msg2, "unknown tag: (%04x,%04x)", g, e );
396  qDebug() << QString ( msg2 );
397  }
398 
399  DcmElement *elem = newDicomElement ( tag );
400  if ( elem == NULL ) {
401  sprintf ( msg2, "cannot create element for tag: (%04x,%04x)", g, e );
402  qDebug() << QString ( msg2 );
403  }
404 
405  if ( valStr.length() > 0 ) {
406  if ( elem->putString ( valStr.c_str() ).bad() ) {
407  sprintf ( msg2, "cannot put tag value: (%04x,%04x)=\"", g, e );
408  msg = msg2;
409  msg += valStr;
410  msg += "\"";
411  qDebug() << QString ( msg.c_str() );
412  }
413  }
414 
415  if ( d->overrideKeys.insert ( elem, OFTrue ).bad() ) {
416  sprintf ( msg2, "cannot insert tag: (%04x,%04x)", g, e );
417  qDebug() << QString ( msg2 );
418  }
419 }
420 
421 OFCondition QtDcmMoveScu::addPresentationContext ( T_ASC_Parameters *params, T_ASC_PresentationContextID pid, const char* abstractSyntax, E_TransferSyntax preferredTransferSyntax )
422 {
423  const char* transferSyntaxes[] = { NULL, NULL, NULL };
424  int numTransferSyntaxes = 0;
425 
426  switch ( preferredTransferSyntax )
427  {
428  case EXS_LittleEndianImplicit:
429  /* we only support Little Endian Implicit */
430  transferSyntaxes[0] = UID_LittleEndianImplicitTransferSyntax;
431  numTransferSyntaxes = 1;
432  break;
433 
434  case EXS_LittleEndianExplicit:
435  /* we prefer Little Endian Explicit */
436  transferSyntaxes[0] = UID_LittleEndianExplicitTransferSyntax;
437  transferSyntaxes[1] = UID_BigEndianExplicitTransferSyntax;
438  transferSyntaxes[2] = UID_LittleEndianImplicitTransferSyntax;
439  numTransferSyntaxes = 3;
440  break;
441 
442  case EXS_BigEndianExplicit:
443  /* we prefer Big Endian Explicit */
444  transferSyntaxes[0] = UID_BigEndianExplicitTransferSyntax;
445  transferSyntaxes[1] = UID_LittleEndianExplicitTransferSyntax;
446  transferSyntaxes[2] = UID_LittleEndianImplicitTransferSyntax;
447  numTransferSyntaxes = 3;
448  break;
449 
450  default:
451  /* We prefer explicit transfer syntaxes.
452  * If we are running on a Little Endian machine we prefer
453  * LittleEndianExplicitTransferSyntax to BigEndianTransferSyntax.
454  */
455 
456  if ( gLocalByteOrder == EBO_LittleEndian ) /* defined in dcxfer.h */ {
457  transferSyntaxes[0] = UID_LittleEndianExplicitTransferSyntax;
458  transferSyntaxes[1] = UID_BigEndianExplicitTransferSyntax;
459  }
460  else {
461  transferSyntaxes[0] = UID_BigEndianExplicitTransferSyntax;
462  transferSyntaxes[1] = UID_LittleEndianExplicitTransferSyntax;
463  }
464 
465  transferSyntaxes[2] = UID_LittleEndianImplicitTransferSyntax;
466 
467  numTransferSyntaxes = 3;
468  break;
469  }
470 
471  return ASC_addPresentationContext (
472  params, pid, abstractSyntax,
473  transferSyntaxes, numTransferSyntaxes );
474 }
475 
476 OFCondition QtDcmMoveScu::acceptSubAssoc ( T_ASC_Network * aNet, T_ASC_Association ** assoc )
477 {
478  const char* knownAbstractSyntaxes[] = { UID_VerificationSOPClass };
479  const char* transferSyntaxes[] = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL };
480  int numTransferSyntaxes;
481 
482  OFCondition cond = ASC_receiveAssociation ( aNet, assoc, ASC_DEFAULTMAXPDU );
483 
484  if ( cond.good() ) {
485  switch ( EXS_Unknown ) {
486 
487  case EXS_LittleEndianImplicit:
488  /* we only support Little Endian Implicit */
489  transferSyntaxes[0] = UID_LittleEndianImplicitTransferSyntax;
490  numTransferSyntaxes = 1;
491  break;
492 
493  case EXS_LittleEndianExplicit:
494  /* we prefer Little Endian Explicit */
495  transferSyntaxes[0] = UID_LittleEndianExplicitTransferSyntax;
496  transferSyntaxes[1] = UID_BigEndianExplicitTransferSyntax;
497  transferSyntaxes[2] = UID_LittleEndianImplicitTransferSyntax;
498  numTransferSyntaxes = 3;
499  break;
500 
501  case EXS_BigEndianExplicit:
502  /* we prefer Big Endian Explicit */
503  transferSyntaxes[0] = UID_BigEndianExplicitTransferSyntax;
504  transferSyntaxes[1] = UID_LittleEndianExplicitTransferSyntax;
505  transferSyntaxes[2] = UID_LittleEndianImplicitTransferSyntax;
506  numTransferSyntaxes = 3;
507  break;
508 
509  case EXS_JPEGProcess14SV1:
510  /* we prefer JPEGLossless:Hierarchical-1stOrderPrediction (default lossless) */
511  transferSyntaxes[0] = UID_JPEGProcess14SV1TransferSyntax;
512  transferSyntaxes[1] = UID_LittleEndianExplicitTransferSyntax;
513  transferSyntaxes[2] = UID_BigEndianExplicitTransferSyntax;
514  transferSyntaxes[3] = UID_LittleEndianImplicitTransferSyntax;
515  numTransferSyntaxes = 4;
516  break;
517 
518  case EXS_JPEGProcess1:
519  /* we prefer JPEGBaseline (default lossy for 8 bit images) */
520  transferSyntaxes[0] = UID_JPEGProcess1TransferSyntax;
521  transferSyntaxes[1] = UID_LittleEndianExplicitTransferSyntax;
522  transferSyntaxes[2] = UID_BigEndianExplicitTransferSyntax;
523  transferSyntaxes[3] = UID_LittleEndianImplicitTransferSyntax;
524  numTransferSyntaxes = 4;
525  break;
526 
527  case EXS_JPEGProcess2_4:
528  /* we prefer JPEGExtended (default lossy for 12 bit images) */
529  transferSyntaxes[0] = UID_JPEGProcess2_4TransferSyntax;
530  transferSyntaxes[1] = UID_LittleEndianExplicitTransferSyntax;
531  transferSyntaxes[2] = UID_BigEndianExplicitTransferSyntax;
532  transferSyntaxes[3] = UID_LittleEndianImplicitTransferSyntax;
533  numTransferSyntaxes = 4;
534  break;
535 
536  case EXS_JPEG2000LosslessOnly:
537  /* we prefer JPEG2000 Lossless */
538  transferSyntaxes[0] = UID_JPEG2000LosslessOnlyTransferSyntax;
539  transferSyntaxes[1] = UID_LittleEndianExplicitTransferSyntax;
540  transferSyntaxes[2] = UID_BigEndianExplicitTransferSyntax;
541  transferSyntaxes[3] = UID_LittleEndianImplicitTransferSyntax;
542  numTransferSyntaxes = 4;
543  break;
544 
545  case EXS_JPEG2000:
546  /* we prefer JPEG2000 Lossy */
547  transferSyntaxes[0] = UID_JPEG2000TransferSyntax;
548  transferSyntaxes[1] = UID_LittleEndianExplicitTransferSyntax;
549  transferSyntaxes[2] = UID_BigEndianExplicitTransferSyntax;
550  transferSyntaxes[3] = UID_LittleEndianImplicitTransferSyntax;
551  numTransferSyntaxes = 4;
552  break;
553 
554  case EXS_JPEGLSLossless:
555  /* we prefer JPEG-LS Lossless */
556  transferSyntaxes[0] = UID_JPEGLSLosslessTransferSyntax;
557  transferSyntaxes[1] = UID_LittleEndianExplicitTransferSyntax;
558  transferSyntaxes[2] = UID_BigEndianExplicitTransferSyntax;
559  transferSyntaxes[3] = UID_LittleEndianImplicitTransferSyntax;
560  numTransferSyntaxes = 4;
561  break;
562 
563  case EXS_JPEGLSLossy:
564  /* we prefer JPEG-LS Lossy */
565  transferSyntaxes[0] = UID_JPEGLSLossyTransferSyntax;
566  transferSyntaxes[1] = UID_LittleEndianExplicitTransferSyntax;
567  transferSyntaxes[2] = UID_BigEndianExplicitTransferSyntax;
568  transferSyntaxes[3] = UID_LittleEndianImplicitTransferSyntax;
569  numTransferSyntaxes = 4;
570  break;
571 
572  case EXS_MPEG2MainProfileAtMainLevel:
573  /* we prefer MPEG2 MP@ML */
574  transferSyntaxes[0] = UID_MPEG2MainProfileAtMainLevelTransferSyntax;
575  transferSyntaxes[1] = UID_LittleEndianExplicitTransferSyntax;
576  transferSyntaxes[2] = UID_BigEndianExplicitTransferSyntax;
577  transferSyntaxes[3] = UID_LittleEndianImplicitTransferSyntax;
578  numTransferSyntaxes = 4;
579  break;
580 
581  case EXS_RLELossless:
582  /* we prefer RLE Lossless */
583  transferSyntaxes[0] = UID_RLELosslessTransferSyntax;
584  transferSyntaxes[1] = UID_LittleEndianExplicitTransferSyntax;
585  transferSyntaxes[2] = UID_BigEndianExplicitTransferSyntax;
586  transferSyntaxes[3] = UID_LittleEndianImplicitTransferSyntax;
587  numTransferSyntaxes = 4;
588  break;
589 #ifdef WITH_ZLIB
590 
591  case EXS_DeflatedLittleEndianExplicit:
592  /* we prefer Deflated Explicit VR Little Endian */
593  transferSyntaxes[0] = UID_DeflatedExplicitVRLittleEndianTransferSyntax;
594  transferSyntaxes[1] = UID_LittleEndianExplicitTransferSyntax;
595  transferSyntaxes[2] = UID_BigEndianExplicitTransferSyntax;
596  transferSyntaxes[3] = UID_LittleEndianImplicitTransferSyntax;
597  numTransferSyntaxes = 4;
598  break;
599 #endif
600  default:
601  if ( OFFalse ) {
602  /* we accept all supported transfer syntaxes
603  * (similar to "AnyTransferSyntax" in "storescp.cfg")
604  */
605  transferSyntaxes[0] = UID_JPEG2000TransferSyntax;
606  transferSyntaxes[1] = UID_JPEG2000LosslessOnlyTransferSyntax;
607  transferSyntaxes[2] = UID_JPEGProcess2_4TransferSyntax;
608  transferSyntaxes[3] = UID_JPEGProcess1TransferSyntax;
609  transferSyntaxes[4] = UID_JPEGProcess14SV1TransferSyntax;
610  transferSyntaxes[5] = UID_JPEGLSLossyTransferSyntax;
611  transferSyntaxes[6] = UID_JPEGLSLosslessTransferSyntax;
612  transferSyntaxes[7] = UID_RLELosslessTransferSyntax;
613  transferSyntaxes[8] = UID_MPEG2MainProfileAtMainLevelTransferSyntax;
614  transferSyntaxes[9] = UID_DeflatedExplicitVRLittleEndianTransferSyntax;
615 
616  if ( gLocalByteOrder == EBO_LittleEndian ) {
617  transferSyntaxes[10] = UID_LittleEndianExplicitTransferSyntax;
618  transferSyntaxes[11] = UID_BigEndianExplicitTransferSyntax;
619  }
620  else {
621  transferSyntaxes[10] = UID_BigEndianExplicitTransferSyntax;
622  transferSyntaxes[11] = UID_LittleEndianExplicitTransferSyntax;
623  }
624 
625  transferSyntaxes[12] = UID_LittleEndianImplicitTransferSyntax;
626 
627  numTransferSyntaxes = 13;
628  }
629  else {
630  /* We prefer explicit transfer syntaxes.
631  * If we are running on a Little Endian machine we prefer
632  * LittleEndianExplicitTransferSyntax to BigEndianTransferSyntax.
633  */
634  if ( gLocalByteOrder == EBO_LittleEndian ) /* defined in dcxfer.h */ {
635  transferSyntaxes[0] = UID_LittleEndianExplicitTransferSyntax;
636  transferSyntaxes[1] = UID_BigEndianExplicitTransferSyntax;
637  }
638  else {
639  transferSyntaxes[0] = UID_BigEndianExplicitTransferSyntax;
640  transferSyntaxes[1] = UID_LittleEndianExplicitTransferSyntax;
641  }
642 
643  transferSyntaxes[2] = UID_LittleEndianImplicitTransferSyntax;
644 
645  numTransferSyntaxes = 3;
646  }
647 
648  break;
649 
650  }
651 
652  /* accept the Verification SOP Class if presented */
653  cond = ASC_acceptContextsWithPreferredTransferSyntaxes (
654  ( *assoc )->params,
655  knownAbstractSyntaxes, DIM_OF ( knownAbstractSyntaxes ),
656  transferSyntaxes, numTransferSyntaxes );
657 
658  if ( cond.good() ) {
659  /* the array of Storage SOP Class UIDs comes from dcuid.h */
660  cond = ASC_acceptContextsWithPreferredTransferSyntaxes (
661  ( *assoc )->params,
662  dcmAllStorageSOPClassUIDs, numberOfAllDcmStorageSOPClassUIDs,
663  transferSyntaxes, numTransferSyntaxes );
664  }
665  }
666 
667  if ( cond.good() ) cond = ASC_acknowledgeAssociation ( *assoc );
668 
669  if ( cond.bad() ) {
670  ASC_dropAssociation ( *assoc );
671  ASC_destroyAssociation ( assoc );
672  }
673 
674  return cond;
675 }
676 
677 OFCondition QtDcmMoveScu::echoSCP ( T_ASC_Association * assoc, T_DIMSE_Message * msg, T_ASC_PresentationContextID presID )
678 {
679  OFCondition cond = DIMSE_sendEchoResponse ( assoc, presID, &msg->msg.CEchoRQ, STATUS_Success, NULL );
680 
681  if ( cond.bad() ) {
682  qDebug() << "Echo SCP Failed";
683  }
684 
685  return cond;
686 }
687 
688 OFCondition QtDcmMoveScu::storeSCP ( T_ASC_Association *assoc, T_DIMSE_Message *msg, T_ASC_PresentationContextID presID, void* subOpCallbackData )
689 {
690  QtDcmMoveScu * self = static_cast<QtDcmMoveScu * >(subOpCallbackData);
691  if (!self) {
692  qWarning() << "Cannot cast caller";
693  return EC_IllegalCall;
694  }
695 
696  OFCondition cond = EC_Normal;
697  T_DIMSE_C_StoreRQ *req;
698  char imageFile[2048];
699 
700  req = &msg->msg.CStoreRQ;
701 
702  if ( OFFalse ) {
703 #ifdef _WIN32
704  tmpnam ( imageFile );
705 #else
706  strcpy ( imageFile, NULL_DEVICE_NAME );
707 #endif
708  }
709  else {
710  sprintf ( imageFile, "%s.%s",
711  dcmSOPClassUIDToModality ( req->AffectedSOPClassUID ),
712  req->AffectedSOPInstanceUID );
713  }
714 
715  self->d->assoc = assoc;
716 
717  self->d->imageFile = imageFile;
718  DcmFileFormat dcmff;
719  self->d->file = &dcmff;
720 
721  // store SourceApplicationEntityTitle in metaheader
722 
723  if ( assoc && assoc->params ) {
724  const char *aet = assoc->params->DULparams.callingAPTitle;
725 
726  if ( aet ) dcmff.getMetaInfo()->putAndInsertString ( DCM_SourceApplicationEntityTitle, aet );
727  }
728 
729  DcmDataset *dset = dcmff.getDataset();
730 
731  if ( OFFalse ) {
732  cond = DIMSE_storeProvider ( assoc, presID, req, imageFile, OFFalse,
733  NULL, storeSCPCallback, ( void * ) subOpCallbackData, DIMSE_BLOCKING, 0 );
734  }
735  else {
736  cond = DIMSE_storeProvider ( assoc, presID, req, ( char * ) NULL, OFFalse,
737  &dset, storeSCPCallback, ( void * ) subOpCallbackData, DIMSE_BLOCKING, 0 );
738  }
739 
740  return cond;
741 }
742 
743 void QtDcmMoveScu::storeSCPCallback ( void *callbackData, T_DIMSE_StoreProgress *progress, T_DIMSE_C_StoreRQ *req, char *imageFile, DcmDataset **imageDataSet, T_DIMSE_C_StoreRSP *rsp, DcmDataset **statusDetail )
744 {
745  QtDcmMoveScu * self = static_cast<QtDcmMoveScu * >(callbackData);
746  if (!self) {
747  qWarning() << "Cannot cast caller";
748  return;
749  }
750 
751  DIC_UI sopClass;
752  DIC_UI sopInstance;
753 
754  if ( progress->state == DIMSE_StoreEnd ) {
755  *statusDetail = NULL;
756  if ( ( imageDataSet != NULL ) && ( *imageDataSet != NULL ) && !self->d->bitPreserving && !self->d->ignore ) {
757  /* create full path name for the output file */
758  OFString ofname;
759  OFStandard::combineDirAndFilename ( ofname, self->d->outputDirectory, self->d->imageFile, OFTrue /* allowEmptyDirName */ );
760 
761  E_TransferSyntax xfer = self->d->writeTransferSyntax;
762 
763  if ( xfer == EXS_Unknown ) xfer = ( *imageDataSet )->getOriginalXfer();
764 
765  OFCondition cond = self->d->file->saveFile ( ofname.c_str(), xfer, self->d->sequenceType, self->d->groupLength,
766  self->d->paddingType, OFstatic_cast ( Uint32, self->d->filepad ), OFstatic_cast ( Uint32, self->d->itempad ),
767  ( self->d->useMetaheader ) ? EWM_fileformat : EWM_dataset );
768 
769  if ( QFile ( ofname.c_str() ).exists() ) {
770  emit self->previewSlice ( QString ( ofname.c_str() ) );
771  }
772 
773  if ( ( rsp->DimseStatus == STATUS_Success ) && !self->d->ignore ) {
774  /* which SOP class and SOP instance ? */
775  if ( !DU_findSOPClassAndInstanceInDataSet ( *imageDataSet, sopClass, sopInstance, self->d->correctUIDPadding ) ) {
776  rsp->DimseStatus = STATUS_STORE_Error_CannotUnderstand;
777  }
778  else if ( strcmp ( sopClass, req->AffectedSOPClassUID ) != 0 ) {
779  rsp->DimseStatus = STATUS_STORE_Error_DataSetDoesNotMatchSOPClass;
780  }
781  else if ( strcmp ( sopInstance, req->AffectedSOPInstanceUID ) != 0 ) {
782  rsp->DimseStatus = STATUS_STORE_Error_DataSetDoesNotMatchSOPClass;
783  }
784  }
785  }
786  }
787 
788  return;
789 }
790 
791 OFCondition QtDcmMoveScu::subOpSCP ( T_ASC_Association **subAssoc, void * subOpCallbackData )
792 {
793  T_DIMSE_Message msg;
794  T_ASC_PresentationContextID presID;
795 
796  if ( !ASC_dataWaiting ( *subAssoc, 0 ) ) { /* just in case */
797  return DIMSE_NODATAAVAILABLE;
798  }
799 
800  OFCondition cond = DIMSE_receiveCommand ( *subAssoc, DIMSE_BLOCKING, 0, &presID, &msg, NULL );
801 
802  if ( cond == EC_Normal ) {
803  switch ( msg.CommandField )
804  {
805  case DIMSE_C_STORE_RQ:
806  cond = storeSCP ( *subAssoc, &msg, presID, subOpCallbackData );
807  break;
808 
809  case DIMSE_C_ECHO_RQ:
810  cond = echoSCP ( *subAssoc, &msg, presID );
811  break;
812 
813  default:
814  cond = DIMSE_BADCOMMANDTYPE;
815  break;
816  }
817  }
818 
819  /* clean up on association termination */
820  if ( cond == DUL_PEERREQUESTEDRELEASE ) {
821  cond = ASC_acknowledgeRelease ( *subAssoc );
822  ASC_dropSCPAssociation ( *subAssoc );
823  ASC_destroyAssociation ( subAssoc );
824  return cond;
825  }
826  else if ( cond == DUL_PEERABORTEDASSOCIATION ) {
827  }
828  else if ( cond != EC_Normal ) {
829  OFString temp_str;
830  qDebug() << "DIMSE failure (aborting sub-association): " << DimseCondition::dump ( temp_str, cond ).c_str();
831  /* some kind of error so abort the association */
832  cond = ASC_abortAssociation ( *subAssoc );
833  }
834 
835  if ( cond != EC_Normal ) {
836  ASC_dropAssociation ( *subAssoc );
837  ASC_destroyAssociation ( subAssoc );
838  }
839 
840  return cond;
841 }
842 
843 void QtDcmMoveScu::subOpCallback ( void * caller, T_ASC_Network *aNet, T_ASC_Association **subAssoc )
844 {
845  if ( !caller )
846  return;
847 
848  QtDcmMoveScu * self = static_cast<QtDcmMoveScu * >(caller);
849  if (!self) {
850  qWarning() << "Cannot cast caller";
851  return;
852  }
853 
854  if ( self->d->slicesCount ) {
855  emit self->updateProgress ( self->d->progressTotal + ( int ) ( ( ( float ) ( self->d->step * ( self->d->progressSerie ) / self->d->slicesCount ) ) ) );
856  }
857 
858  if ( aNet == NULL ) return; /* help no net ! */
859 
860  if ( *subAssoc == NULL ) {
861  /* negotiate association */
862  acceptSubAssoc ( aNet, subAssoc );
863  }
864  else {
865  /* be a service class provider */
866  subOpSCP ( subAssoc, caller );
867  }
868 
869  self->d->progressSerie ++;
870 }
871 
872 void QtDcmMoveScu::moveCallback ( void *caller, T_DIMSE_C_MoveRQ * req, int responseCount, T_DIMSE_C_MoveRSP * rsp )
873 {
874  if ( !caller )
875  return;
876 
877  QtDcmMoveScu * self = static_cast<QtDcmMoveScu * >(caller);
878  if (!self) {
879  qWarning() << "Cannot cast caller";
880  return;
881  }
882 
883  if ( responseCount == 1 ) {
884  self->d->progressSerie = 0;
885  }
886 
887  self->d->slicesCount = rsp->NumberOfRemainingSubOperations + rsp->NumberOfFailedSubOperations + rsp->NumberOfWarningSubOperations + rsp->NumberOfCompletedSubOperations;
888 
889  OFString temp_str;
890 
891  DIMSE_dumpMessage ( temp_str, *rsp, DIMSE_INCOMING );
892 
893  qDebug() << "Move Response " << responseCount << ":";
894  foreach (const QString &msg, QString ( temp_str.c_str() ).split('\n')) {
895  qDebug() << msg;
896  }
897 }
898 
899 void QtDcmMoveScu::substituteOverrideKeys ( DcmDataset & dset )
900 {
901  if ( d->overrideKeys.isEmpty()) {
902  return; /* nothing to do */
903  }
904 
905  /* copy the override keys */
906  DcmDataset keys ( d->overrideKeys );
907 
908  /* put the override keys into dset replacing existing tags */
909  unsigned long elemCount = keys.card();
910 
911  for ( unsigned long i = 0; i < elemCount; i++ ) {
912  DcmElement *elem = keys.remove ( ( unsigned long ) 0 );
913  dset.insert ( elem, OFTrue );
914  }
915 }
916 
917 OFCondition QtDcmMoveScu::moveSCU ( T_ASC_Association * assoc, const char *fname )
918 {
919  T_DIMSE_C_MoveRQ req;
920  T_DIMSE_C_MoveRSP rsp;
921  DIC_US msgId = assoc->nextMsgID++;
922  DcmDataset *rspIds = NULL;
923  const char *sopClass;
924  DcmDataset *statusDetail = NULL;
925 
926  QuerySyntax querySyntax[3] =
927  {
928  { UID_FINDPatientRootQueryRetrieveInformationModel,
929  UID_MOVEPatientRootQueryRetrieveInformationModel },
930  { UID_FINDStudyRootQueryRetrieveInformationModel,
931  UID_MOVEStudyRootQueryRetrieveInformationModel },
932  { UID_RETIRED_FINDPatientStudyOnlyQueryRetrieveInformationModel,
933  UID_RETIRED_MOVEPatientStudyOnlyQueryRetrieveInformationModel }
934  };
935 
936 
937  DcmFileFormat file;
938  if ( fname != NULL ) {
939  if ( file.loadFile ( fname ).bad() ) {
940  qDebug() << "bad DICOM file: " << QString ( fname );
941  return DIMSE_BADDATA;
942  }
943  }
944 
945  /* replace specific keys by those in overrideKeys */
946  substituteOverrideKeys ( *file.getDataset() );
947 
948  sopClass = querySyntax[d->queryModel].moveSyntax;
949 
950  /* which presentation context should be used */
951  d->presId = ASC_findAcceptedPresentationContextID ( d->assoc, sopClass );
952 
953  if ( d->presId == 0 ) return DIMSE_NOVALIDPRESENTATIONCONTEXTID;
954 
955  req.MessageID = msgId;
956 
957  strcpy ( req.AffectedSOPClassUID, sopClass );
958 
959  req.Priority = DIMSE_PRIORITY_MEDIUM;
960 
961  req.DataSetType = DIMSE_DATASET_PRESENT;
962 
963  if ( d->moveDestination == NULL ) {
964  /* set the destination to be me */
965  ASC_getAPTitles ( assoc->params, req.MoveDestination,
966  NULL, NULL );
967  }
968  else {
969  strcpy( req.MoveDestination, d->moveDestination );
970  }
971 
972 
973  const OFCondition cond = DIMSE_moveUser ( assoc,
974  d->presId,
975  &req,
976  file.getDataset(),
977  moveCallback,
978  ( void* ) this,
979  d->blockMode,
980  d->dimseTimeout,
981  d->net,
982  subOpCallback,
983  ( void* ) this,
984  &rsp,
985  &statusDetail,
986  &rspIds,
988 
989  if ( rspIds != NULL ) delete rspIds;
990 
991  return cond;
992 }
993 
994 
995 OFCondition QtDcmMoveScu::cmove ( T_ASC_Association * assoc, const char *fname )
996 {
997  OFCondition cond = EC_Normal;
998  int n = ( int ) d->repeatCount;
999 
1000  while ( cond.good() && n-- ) {
1001  cond = moveSCU ( assoc, fname );
1002  }
1003 
1004  return cond;
1005 }
static OFCondition storeSCP(T_ASC_Association *assoc, T_DIMSE_Message *msg, T_ASC_PresentationContextID presID, void *subOpCallbackData)
virtual ~QtDcmMoveScu()
static QtDcmPreferences * instance()
void substituteOverrideKeys(DcmDataset &dset)
E_PaddingEncoding paddingType
QtDcmMoveScu(QObject *parent=0)
static OFCondition echoSCP(T_ASC_Association *assoc, T_DIMSE_Message *msg, T_ASC_PresentationContextID presID)
QtDcmServer currentPacs() const
QString aetitle() const
PACS AETitle getter.
Definition: QtDcmServer.h:49
E_TransferSyntax networkTransferSyntax
void setImportDir(const QString &dir)
QtDcmMoveScu::QueryModel queryModel
static OFCondition acceptSubAssoc(T_ASC_Network *aNet, T_ASC_Association **assoc)
static QtDcmManager * instance()
OFCmdUnsignedInt filepad
T_ASC_Network * net
OFCmdUnsignedInt itempad
OFCondition move(const QString &uid)
OFCondition cmove(T_ASC_Association *assoc, const char *fname)
QString hostname() const
DcmFileFormat * file
void setImageId(const QString &id)
const char * moveDestination
void updateProgress(int i)
E_TransferSyntax writeTransferSyntax
OFCmdUnsignedInt maxPDU
OFCondition moveSCU(T_ASC_Association *assoc, const char *fname)
OFCondition addPresentationContext(T_ASC_Parameters *params, T_ASC_PresentationContextID pid, const char *abstractSyntax, E_TransferSyntax preferredTransferSyntax)
T_ASC_Association * assoc
T_ASC_Parameters * params
static void moveCallback(void *caller, T_DIMSE_C_MoveRQ *req, int responseCount, T_DIMSE_C_MoveRSP *rsp)
QString aetitle() const
QtDcm local AETitle getter.
T_ASC_PresentationContextID presId
OFCmdSignedInt cancelAfterNResponses
void serieMoved(const QString &directory, const QString &uid, int number)
static OFCondition subOpSCP(T_ASC_Association **subAssoc, void *subOpCallbackData)
void setSeries(const QStringList &series)
E_EncodingType sequenceType
OFCmdUnsignedInt repeatCount
static void storeSCPCallback(void *caller, T_DIMSE_StoreProgress *progress, T_DIMSE_C_StoreRQ *req, char *imageFile, DcmDataset **imageDataSet, T_DIMSE_C_StoreRSP *rsp, DcmDataset **statusDetail)
void setMode(eMoveMode eMoveMode)
QtDcmMoveScu::eMoveMode mode
void setOutputDir(const QString &dir)
void addOverrideKey(const QString &key)
T_DIMSE_BlockingMode blockMode
static void subOpCallback(void *, T_ASC_Network *aNet, T_ASC_Association **subAssoc)
E_GrpLenEncoding groupLength