ctkDICOMDataset.cpp 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994
  1. /*=============================================================================
  2. Library: CTK
  3. Copyright (c) German Cancer Research Center,
  4. Division of Medical and Biological Informatics
  5. Licensed under the Apache License, Version 2.0 (the "License");
  6. you may not use this file except in compliance with the License.
  7. You may obtain a copy of the License at
  8. http://www.apache.org/licenses/LICENSE-2.0
  9. Unless required by applicable law or agreed to in writing, software
  10. distributed under the License is distributed on an "AS IS" BASIS,
  11. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. See the License for the specific language governing permissions and
  13. limitations under the License.
  14. =============================================================================*/
  15. #include "ctkDICOMDataset.h"
  16. #include <dctk.h>
  17. #include <dcostrmb.h>
  18. #include <dcistrmb.h>
  19. #include <stdexcept>
  20. class ctkDICOMDatasetPrivate
  21. {
  22. public:
  23. ctkDICOMDatasetPrivate() : m_DcmDataset(0), m_TakeOwnership(true) {}
  24. QString m_SpecificCharacterSet;
  25. bool m_DICOMDataSetInitialized;
  26. bool m_StrictErrorHandling;
  27. bool m_TakeOwnership;
  28. DcmDataset* m_DcmDataset;
  29. };
  30. ctkDICOMDataset::ctkDICOMDataset(bool strictErrorHandling)
  31. :d_ptr(new ctkDICOMDatasetPrivate)
  32. {
  33. Q_D(ctkDICOMDataset);
  34. d->m_DcmDataset = new DcmDataset();
  35. d->m_TakeOwnership = true;
  36. d->m_DICOMDataSetInitialized = false;
  37. d->m_StrictErrorHandling = strictErrorHandling;
  38. }
  39. ctkDICOMDataset::~ctkDICOMDataset()
  40. {
  41. Q_D(ctkDICOMDataset);
  42. if ( d->m_TakeOwnership)
  43. {
  44. delete d->m_DcmDataset;
  45. }
  46. }
  47. void ctkDICOMDataset::InitializeFromDataset(DcmDataset* dataset, bool takeOwnership)
  48. {
  49. Q_D(ctkDICOMDataset);
  50. if(d->m_DcmDataset != dataset)
  51. {
  52. if (d->m_TakeOwnership)
  53. {
  54. delete d->m_DcmDataset;
  55. }
  56. d->m_DcmDataset = NULL;
  57. }
  58. if (dataset)
  59. {
  60. d->m_DcmDataset=dataset;
  61. d->m_TakeOwnership = takeOwnership;
  62. if (!d->m_DICOMDataSetInitialized)
  63. {
  64. d->m_DICOMDataSetInitialized = true;
  65. // remember "specific character set" tag for conversion of strings to the right encoding
  66. //std::cerr << "** " << (void*)this << " ctkDICOMDataset: Initialized DcmDataset" << std::endl;
  67. if ( CopyElement( dataset, DCM_SpecificCharacterSet, 3 ) )
  68. {
  69. OFString encoding;
  70. if ( CheckCondition( dataset->findAndGetOFString(DCM_SpecificCharacterSet, encoding) ) )
  71. {
  72. d->m_SpecificCharacterSet = encoding.c_str();
  73. }
  74. else
  75. {
  76. std::cerr << "Some implementation error in DCMTK. We put something into a box and now the box is empty. This is not ok." << std::endl;
  77. //throw std::logic_error("Some implementation error in DCMTK. We put something into a box and now the box is empty. This is not ok.");
  78. }
  79. }
  80. if (d->m_SpecificCharacterSet.isEmpty())
  81. {
  82. ///
  83. /// see Bug # 6458:
  84. /// There are cases, where different studies of the same person get encoded both with and without the SpecificCharacterSet attribute set.
  85. /// DICOM says: default is ASCII / ISO_IR 6 / ISO 646
  86. /// Since we experienced such mixed data, we supplement missing characterset information with the ISO_IR 100 / Latin1 character set.
  87. /// Since Latin1 is a superset of ASCII, this will not cause problems. PLUS in most cases (Europe) we will guess right and suppress
  88. /// "double patients" in applications.
  89. ///
  90. SetElementAsString( DCM_SpecificCharacterSet, "ISO_IR 100" );
  91. }
  92. }
  93. }
  94. }
  95. void ctkDICOMDataset::InitializeFromFile(const QString& filename,
  96. const E_TransferSyntax readXfer,
  97. const E_GrpLenEncoding groupLength,
  98. const Uint32 maxReadLength,
  99. const E_FileReadMode readMode)
  100. {
  101. Q_UNUSED(maxReadLength);
  102. DcmDataset *dataset;
  103. DcmFileFormat fileformat;
  104. OFCondition status = fileformat.loadFile(filename.toAscii().data(), readXfer, groupLength, readMode);
  105. dataset = fileformat.getAndRemoveDataset();
  106. if (!status.good())
  107. {
  108. qDebug() << "Could not load " << filename << "\nDCMTK says: " << status.text();
  109. delete dataset;
  110. return;
  111. }
  112. InitializeFromDataset(dataset, true);
  113. }
  114. void ctkDICOMDataset::Serialize()
  115. {
  116. Q_D(ctkDICOMDataset);
  117. EnsureDcmDataSetIsInitialized();
  118. // store content of current DcmDataset (our parent) as QByteArray into m_ctkDICOMDataset
  119. Uint32 buffersize = 1024*1024; // reserve 1MB
  120. char* writebuffer = new char[buffersize];
  121. // write into buffer
  122. DcmOutputBufferStream dcmbuffer(writebuffer, buffersize);
  123. d->m_DcmDataset->transferInit();
  124. OFCondition condition = d->m_DcmDataset->write(dcmbuffer, EXS_LittleEndianImplicit, EET_UndefinedLength, NULL );
  125. d->m_DcmDataset->transferEnd();
  126. if ( condition.bad() )
  127. {
  128. std::cerr << "Could not DcmDataset::write(..): " << condition.text() << std::endl;
  129. }
  130. // get written contents of buffer
  131. offile_off_t datasetsize = 0;
  132. void* readbuffer = NULL;
  133. dcmbuffer.flushBuffer(readbuffer, datasetsize);
  134. //std::cerr << "** " << (void*)this << " ctkDICOMDataset: Serializing Dataset into " << datasetsize << " bytes" << std::endl;
  135. // construct Qt type from that contents
  136. QByteArray qtArray = QByteArray::fromRawData( static_cast<const char*>(readbuffer), datasetsize );
  137. //std::cerr << "** Buffer size: " << qtArray.size() << std::endl;
  138. QString stringbuffer = QString::fromAscii(qtArray.toBase64());
  139. //std::cerr << "** String of size " << stringbuffer.size() << " looks like this:\n" << stringbuffer.toStdString() << std::endl << std::endl;
  140. this->SetStoredSerialization( stringbuffer );
  141. delete[] writebuffer;
  142. }
  143. void ctkDICOMDataset::MarkForInitialization()
  144. {
  145. Q_D(ctkDICOMDataset);
  146. d->m_DICOMDataSetInitialized = false;
  147. }
  148. bool ctkDICOMDataset::IsInitialized() const
  149. {
  150. Q_D(const ctkDICOMDataset);
  151. return d->m_DICOMDataSetInitialized;
  152. }
  153. void ctkDICOMDataset::EnsureDcmDataSetIsInitialized() const
  154. {
  155. if ( ! this->IsInitialized() )
  156. {
  157. throw std::logic_error("Calling methods on uninitialized ctkDICOMDataset");
  158. }
  159. // const_cast<ctkDICOMDataset*>(this)->Deserialize();
  160. }
  161. void ctkDICOMDataset::Deserialize()
  162. {
  163. Q_D(ctkDICOMDataset);
  164. // read attribute m_ctkDICOMDataset
  165. // construct a DcmDataset from it
  166. // calls InitializeData(DcmDataset*)
  167. // this method can be called both from sub-classes when they get the InitializeData signal from the persistence framework
  168. // and from EnsureDcmDataSetIsInitialized() when a GetElement.. or SetElement.. method is called.
  169. if (d->m_DICOMDataSetInitialized) return; // only need to do this once
  170. QString stringbuffer = this->GetStoredSerialization();
  171. if ( stringbuffer.isEmpty() )
  172. {
  173. d->m_DICOMDataSetInitialized = true;
  174. return; // TODO nicer: hold three states: newly created / loaded but not initialized / restored from DB
  175. }
  176. //std::cerr << "** " << (void*)this << " ctkDICOMDataset: Deserialize Dataset from string of size " << stringbuffer.size() << "\n" << stringbuffer.toStdString() << std::endl;
  177. QByteArray qtArray = QByteArray::fromBase64( stringbuffer.toAscii() );
  178. //std::cerr << "** " << (void*)this << " ctkDICOMDataset: Deserialize Dataset from byte array of size " << qtArray.size() << std::endl;
  179. DcmInputBufferStream dcmbuffer;
  180. dcmbuffer.setBuffer( qtArray.data(), qtArray.size() );
  181. //std::cerr << "** Buffer state: " << dcmbuffer.status().code() << " " << dcmbuffer.good() << " " << dcmbuffer.eos() << " tell " << dcmbuffer.tell() << " avail " << dcmbuffer.avail() << std::endl;
  182. DcmDataset dataset;
  183. dataset.transferInit();
  184. //std::cerr << "** Dataset state: " << dataset.transferState() << std::endl << std::endl;
  185. OFCondition condition = dataset.read( dcmbuffer, EXS_LittleEndianImplicit );
  186. dataset.transferEnd();
  187. // do this in all cases, even when reading reported an error
  188. this->InitializeFromDataset(&dataset);
  189. if ( condition.bad() )
  190. {
  191. std::cerr << "** Condition code of Dataset::read() is "
  192. << condition.code() << std::endl;
  193. std::cerr << "** Buffer state: " << dcmbuffer.status().code()
  194. << " " << dcmbuffer.good()
  195. << " " << dcmbuffer.eos()
  196. << " tell " << dcmbuffer.tell()
  197. << " avail " << dcmbuffer.avail() << std::endl;
  198. std::cerr << "** Dataset state: "
  199. << static_cast<int>(dataset.transferState()) << std::endl;
  200. std::cerr << "Could not DcmDataset::read(..): "
  201. << condition.text() << std::endl;
  202. //throw std::invalid_argument( std::string("Could not DcmDataset::read(..): ") + condition.text() );
  203. }
  204. }
  205. DcmDataset& ctkDICOMDataset::GetDcmDataset() const
  206. {
  207. const Q_D(ctkDICOMDataset);
  208. return *d->m_DcmDataset;
  209. }
  210. OFCondition ctkDICOMDataset::findAndGetElement(const DcmTag& tag, DcmElement*& element, const OFBool searchIntoSub) const
  211. {
  212. EnsureDcmDataSetIsInitialized();
  213. // this one const_cast allows us to declare quite a lot of methods nicely with const
  214. return GetDcmDataset().findAndGetElement(tag, element, searchIntoSub);
  215. }
  216. OFCondition ctkDICOMDataset::findAndGetOFString(const DcmTag& tag, OFString& value, const unsigned long pos, const OFBool searchIntoSub) const
  217. {
  218. EnsureDcmDataSetIsInitialized();
  219. // this second const_cast allows us to declare quite a lot of methods nicely with const
  220. return GetDcmDataset().findAndGetOFString(tag, value, pos, searchIntoSub);
  221. }
  222. bool ctkDICOMDataset::CheckCondition(const OFCondition& condition)
  223. {
  224. if ( condition.bad() )
  225. {
  226. //std::cerr << "Bad return code (" << condition.code() << "): " << condition.text() << std::endl;
  227. }
  228. return condition.good();
  229. }
  230. bool ctkDICOMDataset::CopyElement( DcmDataset* dataset, const DcmTagKey& tag, int type )
  231. {
  232. Q_D(ctkDICOMDataset);
  233. switch (type)
  234. {
  235. case 0x1:
  236. case 0x1C:
  237. case 0x2:
  238. case 0x2C:
  239. case 0x3:
  240. // these are ok
  241. break;
  242. default:
  243. // nothing else is ok
  244. std::cerr << "Unknown attribute type. Cannot process call to ExtractElement " << TagKey(tag).toStdString() << std::endl;
  245. return false;
  246. }
  247. bool missing(false);
  248. bool copied(true);
  249. if (!dataset) return false;
  250. // type 1 or 1C must exist AND have a value
  251. if (!dataset->tagExistsWithValue( tag ))
  252. {
  253. if (type == 0x1 || type == 0x1C) missing = true;
  254. }
  255. // type 2 or 2C must exist but may have an empty value
  256. if (!dataset->tagExists( tag ))
  257. {
  258. if (type == 0x1 || type == 0x1C) missing = true;
  259. if (type == 0x2 || type == 0x2C) missing = true;
  260. }
  261. else
  262. {
  263. // we found this tag
  264. DcmElement* element(NULL);
  265. dataset->findAndGetElement( tag, element, OFFalse, OFTrue ); // OFTrue is important (copies element), DcmDataset takes ownership and deletes elements on its own destruction
  266. if (element)
  267. {
  268. copied = CheckCondition( d->m_DcmDataset->insert(element) );
  269. }
  270. }
  271. if (missing)
  272. {
  273. std::cerr << "Tag " << TagKey(tag).toStdString() << " [" << TagDescription(tag).toStdString() << "] of type " << QString("%1").arg(type,0,16).toStdString() << " missing or empty." << std::endl;
  274. }
  275. if (!copied)
  276. {
  277. std::cerr << "Tag " << TagKey(tag).toStdString() << " [" << TagDescription(tag).toStdString() << "] not copied successfully" << std::endl;
  278. }
  279. return !missing && copied;
  280. }
  281. QString ctkDICOMDataset::Decode( const DcmTag& tag, const OFString& raw ) const
  282. {
  283. Q_D(const ctkDICOMDataset);
  284. // decode for types LO, LT, PN, SH, ST, UT
  285. QString vr = TagVR(tag);
  286. if ( !d->m_SpecificCharacterSet.isEmpty()
  287. && (vr == "LO" ||
  288. vr == "LT" ||
  289. vr == "PN" ||
  290. vr == "SH" ||
  291. vr == "ST" ||
  292. vr == "UT" ) )
  293. {
  294. //std::cout << "Decode from encoding " << d->m_SpecificCharacterSet.toStdString() << std::endl;
  295. static QMap<QString, QTextDecoder*> decoders;
  296. static QMap<QString, QString> qtEncodingNamesForDICOMEncodingNames;
  297. if (qtEncodingNamesForDICOMEncodingNames.isEmpty())
  298. {
  299. // runs only once and fills up a map of encoding names that might be named in DICOM files.
  300. // for each encoding we store the name that Qt uses for the same encoding.
  301. // This is because there is not yet a standard naming scheme but lots of aliases
  302. // out in the real world: e.g. http://www.openi18n.org/subgroups/sa/locnameguide/final/CodesetAliasTable.html
  303. // DICOM Qt
  304. qtEncodingNamesForDICOMEncodingNames.insert("ISO_IR 192", "UTF-8");
  305. qtEncodingNamesForDICOMEncodingNames.insert("ISO_IR 100", "ISO-8859-1");
  306. qtEncodingNamesForDICOMEncodingNames.insert("ISO_IR 101", "ISO-8859-2");
  307. qtEncodingNamesForDICOMEncodingNames.insert("ISO_IR 109", "ISO-8859-3");
  308. qtEncodingNamesForDICOMEncodingNames.insert("ISO_IR 110", "ISO-8859-4");
  309. qtEncodingNamesForDICOMEncodingNames.insert("ISO_IR 144", "ISO-8859-5");
  310. qtEncodingNamesForDICOMEncodingNames.insert("ISO_IR 127", "ISO-8859-6");
  311. qtEncodingNamesForDICOMEncodingNames.insert("ISO_IR 126", "ISO-8859-7");
  312. qtEncodingNamesForDICOMEncodingNames.insert("ISO_IR 138", "ISO-8859-8");
  313. qtEncodingNamesForDICOMEncodingNames.insert("ISO_IR 148", "ISO-8859-9");
  314. qtEncodingNamesForDICOMEncodingNames.insert("ISO_IR 179", "ISO-8859-13");
  315. qtEncodingNamesForDICOMEncodingNames.insert("ISO 2022 IR 13", "jisx0201*-0");
  316. // use all names that Qt knows by itself
  317. foreach( QByteArray c, QTextCodec::availableCodecs() )
  318. {
  319. qtEncodingNamesForDICOMEncodingNames.insert( c.constData(), c.constData() );
  320. }
  321. }
  322. if ( qtEncodingNamesForDICOMEncodingNames.contains(d->m_SpecificCharacterSet) )
  323. {
  324. QString encodingName( qtEncodingNamesForDICOMEncodingNames[d->m_SpecificCharacterSet] );
  325. if ( !decoders.contains( encodingName ) )
  326. {
  327. QTextCodec* codec = QTextCodec::codecForName( encodingName.toAscii() );
  328. if (!codec)
  329. {
  330. std::cerr << "Could not create QTextCodec object for '" << encodingName.toStdString() << "'. Using default encoding instead." << std::endl;
  331. decoders.insert( encodingName, QTextCodec::codecForCStrings()->makeDecoder() ); // uses Latin1
  332. }
  333. else
  334. {
  335. // initialize a QTextDecoder for given encoding
  336. decoders.insert( encodingName, codec->makeDecoder() );
  337. // We are responsible for deleting the QTextDecoder objects
  338. // created by makeDecoder(). BUT as these objects are stored
  339. // in a static map that lives until application end AND
  340. // nothing application relevant happens during their
  341. // destruction, we just let them be destructed by C++ on
  342. // application exit.
  343. // Any potential leaks that are found by this behavior can
  344. // be suppressed.
  345. }
  346. }
  347. //std::cout << "Decode '" << raw.c_str() << "' to '" << decoders[encodingName]->toUnicode( raw.c_str() ).toLocal8Bit().constData() << "'" << std::endl;
  348. return decoders[encodingName]->toUnicode( raw.c_str() );
  349. }
  350. else
  351. {
  352. std::cerr << "DICOM dataset contains some encoding that we never thought we would see(" << d->m_SpecificCharacterSet.toStdString() << "). Using default encoding." << std::endl;
  353. }
  354. }
  355. return QString::fromLatin1(raw.c_str()); // Latin1 is ISO 8859, which is the default character set of DICOM (PS 3.5-2008, Page 18)
  356. }
  357. OFString ctkDICOMDataset::Encode( const DcmTag& tag, const QString& qstring ) const
  358. {
  359. // TODO: respect given character-set when encoding; see Decode()
  360. Q_UNUSED(tag);
  361. return OFString( qstring.toLatin1().data() ); // Latin1 is ISO 8859, which is the default character set of DICOM (PS 3.5-2008, Page 18)
  362. }
  363. QString ctkDICOMDataset::GetAllElementValuesAsString( const DcmTag& tag ) const
  364. {
  365. this->EnsureDcmDataSetIsInitialized();
  366. QStringList qsl;
  367. DcmElement* element(NULL);
  368. findAndGetElement(tag, element);
  369. if (!element) return QString::null;
  370. const unsigned long count = element->getVM(); // value multiplicity
  371. for (unsigned long i = 0; i < count; ++i)
  372. {
  373. OFString s;
  374. if ( CheckCondition( const_cast<ctkDICOMDataset*>(this)->findAndGetOFString(tag, s, i) ) )
  375. {
  376. qsl << Decode( tag, s );
  377. }
  378. }
  379. return qsl.join("|");
  380. }
  381. QString ctkDICOMDataset::GetElementAsString( const DcmTag& tag, unsigned long pos ) const
  382. {
  383. this->EnsureDcmDataSetIsInitialized();
  384. OFString s;
  385. if ( CheckCondition( findAndGetOFString(tag, s, pos) ) )
  386. {
  387. return Decode( tag, s );
  388. }
  389. else
  390. {
  391. return QString::null;
  392. }
  393. }
  394. QStringList ctkDICOMDataset::GetElementAsStringList( const DcmTag& tag ) const
  395. {
  396. this->EnsureDcmDataSetIsInitialized();
  397. QStringList qsl;
  398. DcmElement* element(NULL);
  399. findAndGetElement(tag, element);
  400. if (!element) return qsl;
  401. const unsigned long count = element->getVM(); // value multiplicity
  402. for (unsigned long i = 0; i < count; ++i)
  403. {
  404. qsl << GetElementAsString(tag, i);
  405. }
  406. return qsl;
  407. }
  408. ctkDICOMPersonName ctkDICOMDataset::GetElementAsPersonName( const DcmTag& tag, unsigned long pos ) const
  409. {
  410. this->EnsureDcmDataSetIsInitialized();
  411. DcmElement* element(NULL);
  412. findAndGetElement(tag, element);
  413. DcmPersonName* name = dynamic_cast<DcmPersonName*>(element);
  414. if (!name) return ctkDICOMPersonName(); // invalid
  415. OFString lastName;
  416. OFString firstName;
  417. OFString middleName;
  418. OFString namePrefix;
  419. OFString nameSuffix;
  420. if (CheckCondition( name->getNameComponents(lastName, firstName, middleName, namePrefix, nameSuffix, pos) ) )
  421. {
  422. return ctkDICOMPersonName(
  423. Decode(tag, lastName),
  424. Decode(tag, firstName),
  425. Decode(tag, middleName),
  426. Decode(tag, namePrefix),
  427. Decode(tag, nameSuffix) );
  428. }
  429. else
  430. {
  431. return ctkDICOMPersonName();
  432. }
  433. }
  434. ctkDICOMPersonNameList ctkDICOMDataset::GetElementAsPersonNameList( const DcmTag& tag ) const
  435. {
  436. this->EnsureDcmDataSetIsInitialized();
  437. ctkDICOMPersonNameList qpnl;
  438. DcmElement* element(NULL);
  439. findAndGetElement(tag, element);
  440. if (!element) return qpnl;
  441. const unsigned long count = element->getVM(); // value multiplicity
  442. for (unsigned long i = 0; i < count; ++i)
  443. {
  444. qpnl << GetElementAsPersonName(tag, i);
  445. }
  446. return qpnl;
  447. }
  448. QDate ctkDICOMDataset::GetElementAsDate( const DcmTag& tag, unsigned long pos ) const
  449. {
  450. this->EnsureDcmDataSetIsInitialized();
  451. DcmElement* element(NULL);
  452. findAndGetElement(tag, element);
  453. DcmDate* date = dynamic_cast<DcmDate*>(element);
  454. if (!date) return QDate(); // invalid
  455. OFString ofs;
  456. if (CheckCondition( date->getISOFormattedDate(ofs, pos) ) )
  457. {
  458. QString qs(ofs.c_str());
  459. return QDate::fromString(qs, "yyyy-MM-dd");
  460. }
  461. else
  462. {
  463. return QDate();
  464. }
  465. }
  466. QTime ctkDICOMDataset::GetElementAsTime( const DcmTag& tag, unsigned long pos ) const
  467. {
  468. this->EnsureDcmDataSetIsInitialized();
  469. DcmElement* element(NULL);
  470. findAndGetElement(tag, element);
  471. DcmTime* time = dynamic_cast<DcmTime*>(element);
  472. if (!time) return QTime(); // invalid
  473. OFString ofs;
  474. if (CheckCondition( time->getISOFormattedTime(ofs, pos, OFTrue, OFFalse) ) ) // true (seconds), false (fraction of a second)
  475. {
  476. QString qs(ofs.c_str());
  477. return QTime::fromString(qs, "hh:mm:ss");
  478. }
  479. else
  480. {
  481. return QTime();
  482. }
  483. }
  484. QDateTime ctkDICOMDataset::GetElementAsDateTime( const DcmTag& tag, unsigned long pos ) const
  485. {
  486. this->EnsureDcmDataSetIsInitialized();
  487. DcmElement* element(NULL);
  488. findAndGetElement(tag, element);
  489. DcmDateTime* datetime = dynamic_cast<DcmDateTime*>(element);
  490. if (!datetime) return QDateTime(); // invalid
  491. OFString ofs;
  492. if (CheckCondition( datetime->getISOFormattedDateTime(ofs, pos, OFTrue, OFFalse, OFTrue) ) ) // true (seconds), false (fraction of a second), true (time zone)
  493. {
  494. QString qs(ofs.c_str());
  495. return QDateTime::fromString(qs, "dd-MM-yyy hh:mm:ss");
  496. }
  497. else
  498. {
  499. return QDateTime();
  500. }
  501. }
  502. double ctkDICOMDataset::GetElementAsDouble( const DcmTag& tag, unsigned long pos ) const
  503. {
  504. Q_D(const ctkDICOMDataset);
  505. this->EnsureDcmDataSetIsInitialized();
  506. DcmElement* element(NULL);
  507. findAndGetElement(tag, element);
  508. DcmDecimalString* ds = dynamic_cast<DcmDecimalString*>(element);
  509. if (!ds)
  510. {
  511. if (d->m_StrictErrorHandling)
  512. {
  513. throw std::logic_error("Element not found or not a decimal number");
  514. }
  515. else
  516. {
  517. return 0.0;
  518. }
  519. }
  520. Float64 dvalue;
  521. ds->getFloat64(dvalue, pos);
  522. return dvalue;
  523. }
  524. long ctkDICOMDataset::GetElementAsInteger( const DcmTag& tag, unsigned long pos ) const
  525. {
  526. Q_D(const ctkDICOMDataset);
  527. this->EnsureDcmDataSetIsInitialized();
  528. DcmElement* element(NULL);
  529. findAndGetElement(tag, element);
  530. DcmIntegerString* is = dynamic_cast<DcmIntegerString*>(element);
  531. if (!is)
  532. {
  533. if (d->m_StrictErrorHandling)
  534. {
  535. throw std::logic_error("Element not found or not an integer");
  536. }
  537. else
  538. {
  539. return 0;
  540. }
  541. }
  542. Sint32 i = 0;
  543. is->getSint32(i, pos);
  544. return i;
  545. }
  546. int ctkDICOMDataset::GetElementAsSignedShort( const DcmTag& tag, unsigned long pos ) const // type SS
  547. {
  548. this->EnsureDcmDataSetIsInitialized();
  549. DcmElement* element(NULL);
  550. findAndGetElement(tag, element);
  551. DcmSignedShort* ss = dynamic_cast<DcmSignedShort*>(element);
  552. if (!ss) throw std::logic_error("Element not found or not a signed short integer");
  553. Sint16 i;
  554. ss->getSint16(i, pos);
  555. return i;
  556. }
  557. int ctkDICOMDataset::GetElementAsUnsignedShort( const DcmTag& tag, unsigned long pos ) const // type US
  558. {
  559. this->EnsureDcmDataSetIsInitialized();
  560. DcmElement* element(NULL);
  561. findAndGetElement(tag, element);
  562. DcmUnsignedShort* us = dynamic_cast<DcmUnsignedShort*>(element);
  563. if (!us) throw std::logic_error("Element not found or not a unsigned short integer");
  564. Uint16 i;
  565. us->getUint16(i, pos);
  566. return i;
  567. }
  568. bool ctkDICOMDataset::SetElementAsString( const DcmTag& tag, QString string )
  569. {
  570. Q_D(ctkDICOMDataset);
  571. this->EnsureDcmDataSetIsInitialized();
  572. // TODO: Evaluate DICOM tag for proper encoding (see GetElementAsString())
  573. return CheckCondition( d->m_DcmDataset->putAndInsertString( tag, string.toLatin1().data() ) );
  574. }
  575. bool ctkDICOMDataset::SetElementAsStringList( const DcmTag& /*tag*/, QStringList /*stringList*/ )
  576. {
  577. this->EnsureDcmDataSetIsInitialized();
  578. // TODO: Find out how this can be implemented with DcmDataset methods; there is no method for
  579. // setting a string at a given position
  580. return false;
  581. }
  582. bool ctkDICOMDataset::SetElementAsPersonName( const DcmTag& tag, ctkDICOMPersonName personName )
  583. {
  584. Q_D(ctkDICOMDataset);
  585. this->EnsureDcmDataSetIsInitialized();
  586. DcmPersonName* dcmPersonName = new DcmPersonName( tag ); // TODO leak?
  587. if ( CheckCondition( dcmPersonName->putNameComponents(
  588. Encode( tag, personName.lastName() ),
  589. Encode( tag, personName.firstName() ),
  590. Encode( tag, personName.middleName() ),
  591. Encode( tag, personName.namePrefix() ),
  592. Encode( tag, personName.nameSuffix() ) ) ) )
  593. {
  594. return CheckCondition( d->m_DcmDataset->insert( dcmPersonName ) );
  595. }
  596. return false;
  597. }
  598. bool ctkDICOMDataset::SetElementAsPersonNameList( const DcmTag& tag, ctkDICOMPersonNameList personNameList )
  599. {
  600. Q_UNUSED(tag);
  601. Q_UNUSED(personNameList);
  602. this->EnsureDcmDataSetIsInitialized();
  603. // TODO: Find out how this can be implemented with DcmDataset methods; there is no method for
  604. // setting an element at a given position
  605. return false;
  606. }
  607. bool ctkDICOMDataset::SetElementAsDate( const DcmTag& tag, QDate date )
  608. {
  609. Q_D(ctkDICOMDataset);
  610. this->EnsureDcmDataSetIsInitialized();
  611. OFDate ofDate( date.year(), date.month(), date.day() );
  612. DcmDate* dcmDate = new DcmDate( tag ); // TODO leak?
  613. if ( CheckCondition( dcmDate->setOFDate( ofDate ) ) )
  614. {
  615. return CheckCondition( d->m_DcmDataset->insert( dcmDate ) );
  616. }
  617. return false;
  618. }
  619. bool ctkDICOMDataset::SetElementAsTime( const DcmTag& tag, QTime time )
  620. {
  621. Q_D(ctkDICOMDataset);
  622. this->EnsureDcmDataSetIsInitialized();
  623. OFTime ofTime( time.hour(), time.minute(), time.second() );
  624. DcmTime* dcmTime = new DcmTime( tag ); // TODO leak?
  625. if ( CheckCondition( dcmTime->setOFTime( ofTime ) ) )
  626. {
  627. return CheckCondition( d->m_DcmDataset->insert( dcmTime ) );
  628. }
  629. return false;
  630. }
  631. bool ctkDICOMDataset::SetElementAsDateTime( const DcmTag& tag, QDateTime dateTime )
  632. {
  633. Q_D(ctkDICOMDataset);
  634. this->EnsureDcmDataSetIsInitialized();
  635. QDate date = dateTime.date();
  636. QTime time = dateTime.time();
  637. OFDateTime ofDateTime;
  638. ofDateTime.setDateTime( date.year(), date.month(), date.day(), time.hour(), time.minute(), time.second() );
  639. DcmDateTime* dcmDateTime = new DcmDateTime( tag ); // TODO leak?
  640. if ( CheckCondition( dcmDateTime->setOFDateTime( ofDateTime ) ) )
  641. {
  642. return CheckCondition( d->m_DcmDataset->insert( dcmDateTime ) );
  643. }
  644. return false;
  645. }
  646. bool ctkDICOMDataset::SetElementAsInteger( const DcmTag& tag, long value, unsigned long pos )
  647. {
  648. Q_D(ctkDICOMDataset);
  649. this->EnsureDcmDataSetIsInitialized();
  650. //std::cerr << "TagVR: " << TagVR( tag ).toStdString() << std::endl;
  651. return CheckCondition( d->m_DcmDataset->putAndInsertSint32( tag, value, pos ) );
  652. }
  653. bool ctkDICOMDataset::SetElementAsSignedShort( const DcmTag& tag, int value, unsigned long pos )
  654. {
  655. Q_D(ctkDICOMDataset);
  656. this->EnsureDcmDataSetIsInitialized();
  657. //std::cerr << "TagVR: " << TagVR( tag ).toStdString() << std::endl;
  658. return CheckCondition( d->m_DcmDataset->putAndInsertSint16( tag, value, pos ) );
  659. }
  660. bool ctkDICOMDataset::SetElementAsUnsignedShort( const DcmTag& tag, int value, unsigned long pos )
  661. {
  662. Q_D(ctkDICOMDataset);
  663. this->EnsureDcmDataSetIsInitialized();
  664. //std::cerr << "TagVR: " << TagVR( tag ).toStdString() << std::endl;
  665. return CheckCondition( d->m_DcmDataset->putAndInsertUint16( tag, value, pos ) );
  666. }
  667. QString ctkDICOMDataset::GetStudyInstanceUID() const
  668. {
  669. return this->GetElementAsString(DCM_StudyInstanceUID);
  670. }
  671. QString ctkDICOMDataset::GetSeriesInstanceUID() const
  672. {
  673. return this->GetElementAsString(DCM_SeriesInstanceUID);
  674. }
  675. QString ctkDICOMDataset::GetSOPInstanceUID() const
  676. {
  677. return this->GetElementAsString(DCM_SOPInstanceUID);
  678. }
  679. QString ctkDICOMDataset::TranslateDefinedTermPatientPosition( const QString& dt )
  680. {
  681. static bool initialized = false;
  682. static QMap<QString, QString> descriptionOfTerms;
  683. if (!initialized)
  684. {
  685. descriptionOfTerms.insert("HFP", "Head First - Prone");
  686. descriptionOfTerms.insert("HFDR", "Head First - Decubitus Right");
  687. descriptionOfTerms.insert("FFDR", "Feet First - Decubitus Right");
  688. descriptionOfTerms.insert("FFP", "Feet First - Prone");
  689. descriptionOfTerms.insert("HFS", "Head First - Supine");
  690. descriptionOfTerms.insert("HFDL", "Head First - Decubitus Left");
  691. descriptionOfTerms.insert("FFDL", "Feet First - Decubitus Left");
  692. descriptionOfTerms.insert("FFS", "Feet First - Supine");
  693. initialized = true;
  694. }
  695. if ( descriptionOfTerms.contains( dt.toUpper() ) )
  696. {
  697. return descriptionOfTerms.value(dt.toUpper());
  698. }
  699. else
  700. {
  701. std::cerr << "Invalid enum for patient position" << std::endl;
  702. return QString::null;
  703. }
  704. }
  705. QString ctkDICOMDataset::TranslateDefinedTermModality( const QString& dt )
  706. {
  707. static bool initialized = false;
  708. static QMap<QString, QString> descriptionOfTerms;
  709. if (!initialized)
  710. {
  711. descriptionOfTerms.insert("CR", "Computed Radiography");
  712. descriptionOfTerms.insert("CT", "Computed Tomography");
  713. descriptionOfTerms.insert("MR", "Magnetic Resonance");
  714. descriptionOfTerms.insert("NM", "Nuclear Medicine");
  715. descriptionOfTerms.insert("US", "Ultrasound");
  716. descriptionOfTerms.insert("OT", "Other");
  717. descriptionOfTerms.insert("BI", "Biomagnetic imaging");
  718. descriptionOfTerms.insert("CD", "Color flow Doppler");
  719. descriptionOfTerms.insert("DD", "Duplex Doppler");
  720. descriptionOfTerms.insert("ES", "Endoscopy");
  721. descriptionOfTerms.insert("LS", "Laser surface scan");
  722. descriptionOfTerms.insert("PT", "Positron emission tomography (PET)");
  723. descriptionOfTerms.insert("RG", "Radiographic imaging (conventional film/screen)");
  724. descriptionOfTerms.insert("ST", "Single-photon emission computed tomograpy (SPECT)");
  725. descriptionOfTerms.insert("TG", "Thermography");
  726. descriptionOfTerms.insert("XA", "X-Ray Aniography");
  727. descriptionOfTerms.insert("RF", "Radio Fluoroscopy");
  728. descriptionOfTerms.insert("RTIMAGE", "Radiotherapy Image");
  729. descriptionOfTerms.insert("RTDOSE", "Radiotherapy Dose");
  730. descriptionOfTerms.insert("RTSTRUCT", "Radiotherapy Structure Set");
  731. descriptionOfTerms.insert("RTPLAN", "Radiotherapy Plan");
  732. descriptionOfTerms.insert("RTRECORD", "RT Treatment Record");
  733. descriptionOfTerms.insert("HC", "Hard Copy");
  734. descriptionOfTerms.insert("DX", "Digital Radiography");
  735. descriptionOfTerms.insert("MG", "Mammography");
  736. descriptionOfTerms.insert("IO", "Intra-oral Radiography");
  737. descriptionOfTerms.insert("PX", "Panoramic X-Ray");
  738. descriptionOfTerms.insert("GM", "General Microscopy");
  739. descriptionOfTerms.insert("SM", "Slide Microscopy");
  740. descriptionOfTerms.insert("XC", "External-camera Photography");
  741. descriptionOfTerms.insert("PR", "Presentation state");
  742. descriptionOfTerms.insert("AU", "Audio");
  743. descriptionOfTerms.insert("ECG", "Electrocardiography");
  744. descriptionOfTerms.insert("EPS", "Cardiac Electrophysiology");
  745. descriptionOfTerms.insert("HD", "Hemodynamic Waveform");
  746. descriptionOfTerms.insert("SR", "SR Document");
  747. descriptionOfTerms.insert("IVUS", "Intravascular Ultrasound");
  748. descriptionOfTerms.insert("OP", "Ophthalmic Photography");
  749. descriptionOfTerms.insert("SMR", "Stereometric Relationship");
  750. descriptionOfTerms.insert("OCT", "Optical Coherence Tomography (non-Ophthalmic)");
  751. descriptionOfTerms.insert("OPR", "Ophthalmic Refraction");
  752. descriptionOfTerms.insert("OPV", "Ophthalmic Visual Field");
  753. descriptionOfTerms.insert("OPM", "Ophthalmic Mapping");
  754. descriptionOfTerms.insert("KO", "Key Object Selection");
  755. descriptionOfTerms.insert("SEG", "Segmentation");
  756. descriptionOfTerms.insert("REG", "Registration");
  757. descriptionOfTerms.insert("OPT", "Ophthalmic Tomography");
  758. descriptionOfTerms.insert("BDUS", "Bone Densitometry (ultrasound)");
  759. descriptionOfTerms.insert("BMD", "Bone Densitometry (X-Ray)");
  760. descriptionOfTerms.insert("DOC", "Document");
  761. // retired terms (but probably still in use)
  762. descriptionOfTerms.insert("DS", "Digital Subtraction Angiography");
  763. descriptionOfTerms.insert("CF", "Cinefluorography");
  764. descriptionOfTerms.insert("DF", "Digital fluoroscopy");
  765. descriptionOfTerms.insert("VF", "Videofluorography");
  766. descriptionOfTerms.insert("AS", "Angioscopy");
  767. descriptionOfTerms.insert("CS", "Cystoscopy");
  768. descriptionOfTerms.insert("EC", "Echocardiography");
  769. descriptionOfTerms.insert("LP", "Laparoscopy");
  770. descriptionOfTerms.insert("FA", "Fluorescein angiography ");
  771. descriptionOfTerms.insert("CP", "Culposcopy");
  772. descriptionOfTerms.insert("DM", "Digital microscopy");
  773. descriptionOfTerms.insert("FS", "Fundoscopy");
  774. descriptionOfTerms.insert("MA", "Magnetic resonance angiography");
  775. descriptionOfTerms.insert("MS", "Magnetic resonance spectroscopy");
  776. initialized = true;
  777. }
  778. if ( descriptionOfTerms.contains( dt.toUpper() ) )
  779. {
  780. return descriptionOfTerms.value(dt.toUpper());
  781. }
  782. else
  783. {
  784. std::cerr << "Invalid enum for patient position" << std::endl;
  785. return QString::null;
  786. }
  787. }
  788. QString ctkDICOMDataset::TagKey( const DcmTag& tag )
  789. {
  790. return QString("(%1,%2)").arg( tag.getGroup(), 4, 16, QLatin1Char('0')).arg( tag.getElement(), 4, 16, QLatin1Char('0') );
  791. }
  792. QString ctkDICOMDataset::TagDescription( const DcmTag& tag )
  793. {
  794. if (!dcmDataDict.isDictionaryLoaded())
  795. return QString("<no DICOM dictionary loaded. application broken>");
  796. const DcmDataDictionary& globalDict = dcmDataDict.rdlock();
  797. const DcmDictEntry* entry = globalDict.findEntry(tag, NULL);
  798. QString returnName("Unknown");
  799. if (entry)
  800. {
  801. returnName = entry->getTagName();
  802. }
  803. dcmDataDict.unlock();
  804. return returnName;
  805. }
  806. QString ctkDICOMDataset::TagVR( const DcmTag& tag )
  807. {
  808. if (!dcmDataDict.isDictionaryLoaded()) return QString("<no DICOM dictionary loaded. application broken>");
  809. const DcmDataDictionary& globalDataDict = dcmDataDict.rdlock();
  810. const DcmDictEntry* entry = globalDataDict.findEntry(tag, NULL);
  811. QString returnVR("UN");
  812. if (entry)
  813. {
  814. returnVR = entry->getVR().getVRName();
  815. }
  816. dcmDataDict.unlock();
  817. return returnVR;
  818. }
  819. QString ctkDICOMDataset::GetStoredSerialization()
  820. {
  821. throw std::runtime_error("No serialization implemented for this object!");
  822. }
  823. void ctkDICOMDataset::SetStoredSerialization(QString serializedDataset)
  824. {
  825. Q_UNUSED(serializedDataset);
  826. throw std::runtime_error("No serialization implemented for this object!");
  827. }
  828. bool ctkDICOMDataset::SaveToFile(const QString& filePath) const
  829. {
  830. Q_D(const ctkDICOMDataset);
  831. DcmFileFormat* fileformat = new DcmFileFormat ( d->m_DcmDataset );
  832. OFCondition status = fileformat->saveFile ( QDir::toNativeSeparators( filePath).toAscii() );
  833. delete fileformat;
  834. return status.good();
  835. }