1 /**
2  * Orthanc - A Lightweight, RESTful DICOM Store
3  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
4  * Department, University Hospital of Liege, Belgium
5  * Copyright (C) 2017-2021 Osimis S.A., Belgium
6  *
7  * This program is free software: you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public License
9  * as published by the Free Software Foundation, either version 3 of
10  * the License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful, but
13  * WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with this program. If not, see
19  * <http://www.gnu.org/licenses/>.
20  **/
21 
22 
23 #if ORTHANC_UNIT_TESTS_LINK_FRAMEWORK == 1
24 // Must be the first to be sure to use the Orthanc framework shared library
25 #  include <OrthancFramework.h>
26 #endif
27 
28 #if !defined(ORTHANC_ENABLE_DCMTK_TRANSCODING)
29 #  error ORTHANC_ENABLE_DCMTK_TRANSCODING is not defined
30 #endif
31 
32 #if !defined(ORTHANC_ENABLE_PUGIXML)
33 #  error ORTHANC_ENABLE_PUGIXML is not defined
34 #endif
35 
36 #include <gtest/gtest.h>
37 
38 #include "../Sources/Compatibility.h"
39 #include "../Sources/DicomNetworking/DicomFindAnswers.h"
40 #include "../Sources/DicomParsing/DicomModification.h"
41 #include "../Sources/DicomParsing/DicomWebJsonVisitor.h"
42 #include "../Sources/DicomParsing/FromDcmtkBridge.h"
43 #include "../Sources/DicomParsing/ToDcmtkBridge.h"
44 #include "../Sources/DicomParsing/ParsedDicomCache.h"
45 #include "../Sources/Endianness.h"
46 #include "../Sources/Images/Image.h"
47 #include "../Sources/Images/ImageBuffer.h"
48 #include "../Sources/Images/ImageProcessing.h"
49 #include "../Sources/Images/PngReader.h"
50 #include "../Sources/Images/PngWriter.h"
51 #include "../Sources/Logging.h"
52 #include "../Sources/OrthancException.h"
53 #include "../Resources/CodeGeneration/EncodingTests.h"
54 
55 #if ORTHANC_SANDBOXED != 1
56 #  include "../Sources/SystemToolbox.h"
57 #endif
58 
59 #include <dcmtk/dcmdata/dcdeftag.h>
60 #include <dcmtk/dcmdata/dcelem.h>
61 #include <dcmtk/dcmdata/dcvrat.h>
62 
63 #include <boost/algorithm/string/predicate.hpp>
64 #include <boost/lexical_cast.hpp>
65 
66 #if ORTHANC_ENABLE_PUGIXML == 1
67 #  include <pugixml.hpp>
68 #  if !defined(PUGIXML_VERSION)
69 #    error PUGIXML_VERSION is not available
70 #  endif
71 #endif
72 
73 using namespace Orthanc;
74 
TEST(DicomFormat,Tag)75 TEST(DicomFormat, Tag)
76 {
77   ASSERT_EQ("PatientName", FromDcmtkBridge::GetTagName(DicomTag(0x0010, 0x0010), ""));
78 
79   DicomTag t = FromDcmtkBridge::ParseTag("SeriesDescription");
80   ASSERT_EQ(0x0008, t.GetGroup());
81   ASSERT_EQ(0x103E, t.GetElement());
82 
83   t = FromDcmtkBridge::ParseTag("0020-e040");
84   ASSERT_EQ(0x0020, t.GetGroup());
85   ASSERT_EQ(0xe040, t.GetElement());
86 
87   // Test ==() and !=() operators
88   ASSERT_TRUE(DICOM_TAG_PATIENT_ID == DicomTag(0x0010, 0x0020));
89   ASSERT_FALSE(DICOM_TAG_PATIENT_ID != DicomTag(0x0010, 0x0020));
90 }
91 
92 
93 #if ORTHANC_SANDBOXED != 1
TEST(DicomModification,Basic)94 TEST(DicomModification, Basic)
95 {
96   DicomModification m;
97   m.SetupAnonymization(DicomVersion_2008);
98   //m.SetLevel(DicomRootLevel_Study);
99   //m.ReplacePlainString(DICOM_TAG_PATIENT_ID, "coucou");
100   //m.ReplacePlainString(DICOM_TAG_PATIENT_NAME, "coucou");
101 
102   ParsedDicomFile o(true);
103   o.SaveToFile("UnitTestsResults/anon.dcm");
104 
105   for (int i = 0; i < 10; i++)
106   {
107     char b[1024];
108     sprintf(b, "UnitTestsResults/anon%06d.dcm", i);
109     std::unique_ptr<ParsedDicomFile> f(o.Clone(false));
110     if (i > 4)
111       o.ReplacePlainString(DICOM_TAG_SERIES_INSTANCE_UID, "coucou");
112     m.Apply(*f);
113     f->SaveToFile(b);
114   }
115 }
116 #endif
117 
118 
TEST(DicomModification,Anonymization)119 TEST(DicomModification, Anonymization)
120 {
121   ASSERT_EQ(DICOM_TAG_PATIENT_NAME, FromDcmtkBridge::ParseTag("PatientName"));
122 
123   const DicomTag privateTag(0x0045, 0x1010);
124   const DicomTag privateTag2(FromDcmtkBridge::ParseTag("0031-1020"));
125   ASSERT_TRUE(privateTag.IsPrivate());
126   ASSERT_TRUE(privateTag2.IsPrivate());
127   ASSERT_EQ(0x0031, privateTag2.GetGroup());
128   ASSERT_EQ(0x1020, privateTag2.GetElement());
129 
130   std::string s;
131   ParsedDicomFile o(true);
132   o.ReplacePlainString(DICOM_TAG_PATIENT_NAME, "coucou");
133   ASSERT_FALSE(o.GetTagValue(s, privateTag));
134   o.Insert(privateTag, "private tag", false, "OrthancCreator");
135   ASSERT_TRUE(o.GetTagValue(s, privateTag));
136   ASSERT_STREQ("private tag", s.c_str());
137 
138   ASSERT_FALSE(o.GetTagValue(s, privateTag2));
139   ASSERT_THROW(o.Replace(privateTag2, std::string("hello"), false, DicomReplaceMode_ThrowIfAbsent, "OrthancCreator"), OrthancException);
140   ASSERT_FALSE(o.GetTagValue(s, privateTag2));
141   o.Replace(privateTag2, std::string("hello"), false, DicomReplaceMode_IgnoreIfAbsent, "OrthancCreator");
142   ASSERT_FALSE(o.GetTagValue(s, privateTag2));
143   o.Replace(privateTag2, std::string("hello"), false, DicomReplaceMode_InsertIfAbsent, "OrthancCreator");
144   ASSERT_TRUE(o.GetTagValue(s, privateTag2));
145   ASSERT_STREQ("hello", s.c_str());
146   o.Replace(privateTag2, std::string("hello world"), false, DicomReplaceMode_InsertIfAbsent, "OrthancCreator");
147   ASSERT_TRUE(o.GetTagValue(s, privateTag2));
148   ASSERT_STREQ("hello world", s.c_str());
149 
150   ASSERT_TRUE(o.GetTagValue(s, DICOM_TAG_PATIENT_NAME));
151   ASSERT_FALSE(Toolbox::IsUuid(s));
152 
153   DicomModification m;
154   m.SetupAnonymization(DicomVersion_2008);
155   m.Keep(privateTag);
156 
157   m.Apply(o);
158 
159   ASSERT_TRUE(o.GetTagValue(s, DICOM_TAG_PATIENT_NAME));
160   ASSERT_TRUE(Toolbox::IsUuid(s));
161   ASSERT_TRUE(o.GetTagValue(s, privateTag));
162   ASSERT_STREQ("private tag", s.c_str());
163 
164   m.SetupAnonymization(DicomVersion_2008);
165   m.Apply(o);
166   ASSERT_FALSE(o.GetTagValue(s, privateTag));
167 }
168 
169 
170 #include <dcmtk/dcmdata/dcuid.h>
171 
TEST(DicomModification,Png)172 TEST(DicomModification, Png)
173 {
174   // Red dot in http://en.wikipedia.org/wiki/Data_URI_scheme (RGBA image)
175   std::string s = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==";
176 
177   std::string m, cc;
178   ASSERT_TRUE(Toolbox::DecodeDataUriScheme(m, cc, s));
179 
180   ASSERT_EQ("image/png", m);
181 
182   PngReader reader;
183   reader.ReadFromMemory(cc);
184 
185   ASSERT_EQ(5u, reader.GetHeight());
186   ASSERT_EQ(5u, reader.GetWidth());
187   ASSERT_EQ(PixelFormat_RGBA32, reader.GetFormat());
188 
189   ParsedDicomFile o(true);
190   o.EmbedContent(s);
191 
192 #if ORTHANC_SANDBOXED != 1
193   o.SaveToFile("UnitTestsResults/png1.dcm");
194 #endif
195 
196   // Red dot, without alpha channel
197   s = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAIAAAACDbGyAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3gUGDTcIn2+8BgAAACJJREFUCNdj/P//PwMjIwME/P/P+J8BBTAxEOL/R9Lx/z8AynoKAXOeiV8AAAAASUVORK5CYII=";
198   o.EmbedContent(s);
199 
200 #if ORTHANC_SANDBOXED != 1
201   o.SaveToFile("UnitTestsResults/png2.dcm");
202 #endif
203 
204   // Check box in Graylevel8
205   s = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAAAAAA6mKC9AAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3gUGDDcB53FulQAAAElJREFUGNNtj0sSAEEEQ1+U+185s1CtmRkblQ9CZldsKHJDk6DLGLJa6chjh0ooQmpjXMM86zPwydGEj6Ed/UGykkEM8X+p3u8/8LcOJIWLGeMAAAAASUVORK5CYII=";
206   o.EmbedContent(s);
207   //o.ReplacePlainString(DICOM_TAG_SOP_CLASS_UID, UID_DigitalXRayImageStorageForProcessing);
208 
209 #if ORTHANC_SANDBOXED != 1
210   o.SaveToFile("UnitTestsResults/png3.dcm");
211 #endif
212 
213 
214   {
215     // Gradient in Graylevel16
216 
217     ImageBuffer img;
218     img.SetWidth(256);
219     img.SetHeight(256);
220     img.SetFormat(PixelFormat_Grayscale16);
221 
222     ImageAccessor accessor;
223     img.GetWriteableAccessor(accessor);
224 
225     uint16_t v = 0;
226     for (unsigned int y = 0; y < img.GetHeight(); y++)
227     {
228       uint16_t *p = reinterpret_cast<uint16_t*>(accessor.GetRow(y));
229       for (unsigned int x = 0; x < img.GetWidth(); x++, p++, v++)
230       {
231         *p = v;
232       }
233     }
234 
235     o.EmbedImage(accessor);
236 
237 #if ORTHANC_SANDBOXED != 1
238     o.SaveToFile("UnitTestsResults/png4.dcm");
239 #endif
240   }
241 }
242 
243 
TEST(FromDcmtkBridge,Encodings1)244 TEST(FromDcmtkBridge, Encodings1)
245 {
246   for (unsigned int i = 0; i < testEncodingsCount; i++)
247   {
248     std::string source(testEncodingsEncoded[i]);
249     std::string expected(testEncodingsExpected[i]);
250     std::string s = Toolbox::ConvertToUtf8(source, testEncodings[i], false);
251     //std::cout << EnumerationToString(testEncodings[i]) << std::endl;
252     EXPECT_EQ(expected, s);
253   }
254 }
255 
256 
TEST(FromDcmtkBridge,Enumerations)257 TEST(FromDcmtkBridge, Enumerations)
258 {
259   // http://dicom.nema.org/medical/dicom/current/output/html/part03.html#sect_C.12.1.1.2
260   Encoding e;
261 
262   ASSERT_FALSE(GetDicomEncoding(e, ""));
263   ASSERT_TRUE(GetDicomEncoding(e, "ISO_IR 6"));  ASSERT_EQ(Encoding_Ascii, e);
264 
265   // http://dicom.nema.org/medical/dicom/current/output/html/part03.html#table_C.12-2
266   ASSERT_TRUE(GetDicomEncoding(e, "ISO_IR 100"));  ASSERT_EQ(Encoding_Latin1, e);
267   ASSERT_TRUE(GetDicomEncoding(e, "ISO_IR 101"));  ASSERT_EQ(Encoding_Latin2, e);
268   ASSERT_TRUE(GetDicomEncoding(e, "ISO_IR 109"));  ASSERT_EQ(Encoding_Latin3, e);
269   ASSERT_TRUE(GetDicomEncoding(e, "ISO_IR 110"));  ASSERT_EQ(Encoding_Latin4, e);
270   ASSERT_TRUE(GetDicomEncoding(e, "ISO_IR 144"));  ASSERT_EQ(Encoding_Cyrillic, e);
271   ASSERT_TRUE(GetDicomEncoding(e, "ISO_IR 127"));  ASSERT_EQ(Encoding_Arabic, e);
272   ASSERT_TRUE(GetDicomEncoding(e, "ISO_IR 126"));  ASSERT_EQ(Encoding_Greek, e);
273   ASSERT_TRUE(GetDicomEncoding(e, "ISO_IR 138"));  ASSERT_EQ(Encoding_Hebrew, e);
274   ASSERT_TRUE(GetDicomEncoding(e, "ISO_IR 148"));  ASSERT_EQ(Encoding_Latin5, e);
275   ASSERT_TRUE(GetDicomEncoding(e, "ISO_IR 13"));   ASSERT_EQ(Encoding_Japanese, e);
276   ASSERT_TRUE(GetDicomEncoding(e, "ISO_IR 166"));  ASSERT_EQ(Encoding_Thai, e);
277 
278   // http://dicom.nema.org/medical/dicom/current/output/html/part03.html#table_C.12-3
279   ASSERT_TRUE(GetDicomEncoding(e, "ISO 2022 IR 6"));    ASSERT_EQ(Encoding_Ascii, e);
280   ASSERT_TRUE(GetDicomEncoding(e, "ISO 2022 IR 100"));  ASSERT_EQ(Encoding_Latin1, e);
281   ASSERT_TRUE(GetDicomEncoding(e, "ISO 2022 IR 101"));  ASSERT_EQ(Encoding_Latin2, e);
282   ASSERT_TRUE(GetDicomEncoding(e, "ISO 2022 IR 109"));  ASSERT_EQ(Encoding_Latin3, e);
283   ASSERT_TRUE(GetDicomEncoding(e, "ISO 2022 IR 110"));  ASSERT_EQ(Encoding_Latin4, e);
284   ASSERT_TRUE(GetDicomEncoding(e, "ISO 2022 IR 144"));  ASSERT_EQ(Encoding_Cyrillic, e);
285   ASSERT_TRUE(GetDicomEncoding(e, "ISO 2022 IR 127"));  ASSERT_EQ(Encoding_Arabic, e);
286   ASSERT_TRUE(GetDicomEncoding(e, "ISO 2022 IR 126"));  ASSERT_EQ(Encoding_Greek, e);
287   ASSERT_TRUE(GetDicomEncoding(e, "ISO 2022 IR 138"));  ASSERT_EQ(Encoding_Hebrew, e);
288   ASSERT_TRUE(GetDicomEncoding(e, "ISO 2022 IR 148"));  ASSERT_EQ(Encoding_Latin5, e);
289   ASSERT_TRUE(GetDicomEncoding(e, "ISO 2022 IR 13"));   ASSERT_EQ(Encoding_Japanese, e);
290   ASSERT_TRUE(GetDicomEncoding(e, "ISO 2022 IR 166"));  ASSERT_EQ(Encoding_Thai, e);
291 
292   // http://dicom.nema.org/medical/dicom/current/output/html/part03.html#table_C.12-4
293   ASSERT_TRUE(GetDicomEncoding(e, "ISO 2022 IR 87"));    ASSERT_EQ(Encoding_JapaneseKanji, e);
294   ASSERT_FALSE(GetDicomEncoding(e, "ISO 2022 IR 159"));  //ASSERT_EQ(Encoding_JapaneseKanjiSupplementary, e);
295   ASSERT_TRUE(GetDicomEncoding(e, "ISO 2022 IR 149"));   ASSERT_EQ(Encoding_Korean, e);
296   ASSERT_TRUE(GetDicomEncoding(e, "ISO 2022 IR 58"));    ASSERT_EQ(Encoding_SimplifiedChinese, e);
297 
298   // http://dicom.nema.org/medical/dicom/current/output/html/part03.html#table_C.12-5
299   ASSERT_TRUE(GetDicomEncoding(e, "ISO_IR 192"));  ASSERT_EQ(Encoding_Utf8, e);
300   ASSERT_TRUE(GetDicomEncoding(e, "GB18030"));     ASSERT_EQ(Encoding_Chinese, e);
301   ASSERT_TRUE(GetDicomEncoding(e, "GBK"));         ASSERT_EQ(Encoding_Chinese, e);
302 }
303 
304 
TEST(FromDcmtkBridge,Encodings3)305 TEST(FromDcmtkBridge, Encodings3)
306 {
307   for (unsigned int i = 0; i < testEncodingsCount; i++)
308   {
309     //std::cout << EnumerationToString(testEncodings[i]) << std::endl;
310     std::string dicom;
311 
312     {
313       ParsedDicomFile f(true);
314       f.SetEncoding(testEncodings[i]);
315 
316       std::string s = Toolbox::ConvertToUtf8(testEncodingsEncoded[i], testEncodings[i], false);
317       f.Insert(DICOM_TAG_PATIENT_NAME, s, false, "");
318       f.SaveToMemoryBuffer(dicom);
319     }
320 
321     if (testEncodings[i] != Encoding_Windows1251)
322     {
323       ParsedDicomFile g(dicom);
324 
325       if (testEncodings[i] != Encoding_Ascii)
326       {
327         bool hasCodeExtensions;
328         ASSERT_EQ(testEncodings[i], g.DetectEncoding(hasCodeExtensions));
329         ASSERT_FALSE(hasCodeExtensions);
330       }
331 
332       std::string tag;
333       ASSERT_TRUE(g.GetTagValue(tag, DICOM_TAG_PATIENT_NAME));
334       ASSERT_EQ(std::string(testEncodingsExpected[i]), tag);
335     }
336   }
337 }
338 
339 
TEST(FromDcmtkBridge,ValueRepresentation)340 TEST(FromDcmtkBridge, ValueRepresentation)
341 {
342   ASSERT_EQ(ValueRepresentation_PersonName,
343             FromDcmtkBridge::LookupValueRepresentation(DICOM_TAG_PATIENT_NAME));
344   ASSERT_EQ(ValueRepresentation_Date,
345             FromDcmtkBridge::LookupValueRepresentation(DicomTag(0x0008, 0x0020) /* StudyDate */));
346   ASSERT_EQ(ValueRepresentation_Time,
347             FromDcmtkBridge::LookupValueRepresentation(DicomTag(0x0008, 0x0030) /* StudyTime */));
348   ASSERT_EQ(ValueRepresentation_DateTime,
349             FromDcmtkBridge::LookupValueRepresentation(DicomTag(0x0008, 0x002a) /* AcquisitionDateTime */));
350   ASSERT_EQ(ValueRepresentation_NotSupported,
351             FromDcmtkBridge::LookupValueRepresentation(DicomTag(0x0001, 0x0001) /* some private tag */));
352 }
353 
354 
355 
356 static const DicomTag REFERENCED_STUDY_SEQUENCE(0x0008, 0x1110);
357 static const DicomTag REFERENCED_PATIENT_SEQUENCE(0x0008, 0x1120);
358 
CreateSampleJson(Json::Value & a)359 static void CreateSampleJson(Json::Value& a)
360 {
361   {
362     Json::Value b = Json::objectValue;
363     b["PatientName"] = "Hello";
364     b["PatientID"] = "World";
365     b["StudyDescription"] = "Toto";
366     a.append(b);
367   }
368 
369   {
370     Json::Value b = Json::objectValue;
371     b["PatientName"] = "data:application/octet-stream;base64,SGVsbG8y";  // echo -n "Hello2" | base64
372     b["PatientID"] = "World2";
373     a.append(b);
374   }
375 }
376 
377 
378 
TEST(ParsedDicomFile,InsertReplaceStrings)379 TEST(ParsedDicomFile, InsertReplaceStrings)
380 {
381   ParsedDicomFile f(true);
382 
383   f.Insert(DICOM_TAG_PATIENT_NAME, "World", false, "");
384   ASSERT_THROW(f.Insert(DICOM_TAG_PATIENT_ID, "Hello", false, ""), OrthancException);  // Already existing tag
385   f.ReplacePlainString(DICOM_TAG_SOP_INSTANCE_UID, "Toto");  // (*)
386   f.ReplacePlainString(DICOM_TAG_SOP_CLASS_UID, "Tata");  // (**)
387 
388   DicomTransferSyntax syntax;
389   ASSERT_TRUE(f.LookupTransferSyntax(syntax));
390   // The default transfer syntax depends on the OS endianness
391   ASSERT_TRUE(syntax == DicomTransferSyntax_LittleEndianExplicit ||
392               syntax == DicomTransferSyntax_BigEndianExplicit);
393 
394   ASSERT_THROW(f.Replace(DICOM_TAG_ACCESSION_NUMBER, std::string("Accession"),
395                          false, DicomReplaceMode_ThrowIfAbsent, ""), OrthancException);
396   f.Replace(DICOM_TAG_ACCESSION_NUMBER, std::string("Accession"), false, DicomReplaceMode_IgnoreIfAbsent, "");
397 
398   std::string s;
399   ASSERT_FALSE(f.GetTagValue(s, DICOM_TAG_ACCESSION_NUMBER));
400   f.Replace(DICOM_TAG_ACCESSION_NUMBER, std::string("Accession"), false, DicomReplaceMode_InsertIfAbsent, "");
401   ASSERT_TRUE(f.GetTagValue(s, DICOM_TAG_ACCESSION_NUMBER));
402   ASSERT_EQ(s, "Accession");
403   f.Replace(DICOM_TAG_ACCESSION_NUMBER, std::string("Accession2"), false, DicomReplaceMode_IgnoreIfAbsent, "");
404   ASSERT_TRUE(f.GetTagValue(s, DICOM_TAG_ACCESSION_NUMBER));
405   ASSERT_EQ(s, "Accession2");
406   f.Replace(DICOM_TAG_ACCESSION_NUMBER, std::string("Accession3"), false, DicomReplaceMode_ThrowIfAbsent, "");
407   ASSERT_TRUE(f.GetTagValue(s, DICOM_TAG_ACCESSION_NUMBER));
408   ASSERT_EQ(s, "Accession3");
409 
410   ASSERT_TRUE(f.GetTagValue(s, DICOM_TAG_PATIENT_NAME));
411   ASSERT_EQ(s, "World");
412   ASSERT_TRUE(f.GetTagValue(s, DICOM_TAG_SOP_INSTANCE_UID));
413   ASSERT_EQ(s, "Toto");
414   ASSERT_TRUE(f.GetTagValue(s, DICOM_TAG_MEDIA_STORAGE_SOP_INSTANCE_UID));  // Implicitly modified by (*)
415   ASSERT_EQ(s, "Toto");
416   ASSERT_TRUE(f.GetTagValue(s, DICOM_TAG_SOP_CLASS_UID));
417   ASSERT_EQ(s, "Tata");
418   ASSERT_TRUE(f.GetTagValue(s, DICOM_TAG_MEDIA_STORAGE_SOP_CLASS_UID));  // Implicitly modified by (**)
419   ASSERT_EQ(s, "Tata");
420 }
421 
422 
423 
424 
TEST(ParsedDicomFile,InsertReplaceJson)425 TEST(ParsedDicomFile, InsertReplaceJson)
426 {
427   ParsedDicomFile f(true);
428 
429   Json::Value a;
430   CreateSampleJson(a);
431 
432   ASSERT_FALSE(f.HasTag(REFERENCED_STUDY_SEQUENCE));
433   f.Remove(REFERENCED_STUDY_SEQUENCE);  // No effect
434   f.Insert(REFERENCED_STUDY_SEQUENCE, a, true, "");
435   ASSERT_TRUE(f.HasTag(REFERENCED_STUDY_SEQUENCE));
436   ASSERT_THROW(f.Insert(REFERENCED_STUDY_SEQUENCE, a, true, ""), OrthancException);
437   f.Remove(REFERENCED_STUDY_SEQUENCE);
438   ASSERT_FALSE(f.HasTag(REFERENCED_STUDY_SEQUENCE));
439   f.Insert(REFERENCED_STUDY_SEQUENCE, a, true, "");
440   ASSERT_TRUE(f.HasTag(REFERENCED_STUDY_SEQUENCE));
441 
442   ASSERT_FALSE(f.HasTag(REFERENCED_PATIENT_SEQUENCE));
443   ASSERT_THROW(f.Replace(REFERENCED_PATIENT_SEQUENCE, a, false, DicomReplaceMode_ThrowIfAbsent, ""), OrthancException);
444   ASSERT_FALSE(f.HasTag(REFERENCED_PATIENT_SEQUENCE));
445   f.Replace(REFERENCED_PATIENT_SEQUENCE, a, false, DicomReplaceMode_IgnoreIfAbsent, "");
446   ASSERT_FALSE(f.HasTag(REFERENCED_PATIENT_SEQUENCE));
447   f.Replace(REFERENCED_PATIENT_SEQUENCE, a, false, DicomReplaceMode_InsertIfAbsent, "");
448   ASSERT_TRUE(f.HasTag(REFERENCED_PATIENT_SEQUENCE));
449 
450   {
451     Json::Value b;
452     f.DatasetToJson(b, DicomToJsonFormat_Full, DicomToJsonFlags_Default, 0);
453 
454     Json::Value c;
455     Toolbox::SimplifyDicomAsJson(c, b, DicomToJsonFormat_Human);
456 
457     ASSERT_EQ(0, c["ReferencedPatientSequence"].compare(a));
458     ASSERT_NE(0, c["ReferencedStudySequence"].compare(a));  // Because Data URI Scheme decoding was enabled
459   }
460 
461   a = "data:application/octet-stream;base64,VGF0YQ==";   // echo -n "Tata" | base64
462   f.Replace(DICOM_TAG_SOP_INSTANCE_UID, a, false, DicomReplaceMode_InsertIfAbsent, "");  // (*)
463   f.Replace(DICOM_TAG_SOP_CLASS_UID, a, true, DicomReplaceMode_InsertIfAbsent, "");  // (**)
464 
465   std::string s;
466   ASSERT_TRUE(f.GetTagValue(s, DICOM_TAG_SOP_INSTANCE_UID));
467   ASSERT_EQ(s, a.asString());
468   ASSERT_TRUE(f.GetTagValue(s, DICOM_TAG_MEDIA_STORAGE_SOP_INSTANCE_UID));  // Implicitly modified by (*)
469   ASSERT_EQ(s, a.asString());
470   ASSERT_TRUE(f.GetTagValue(s, DICOM_TAG_SOP_CLASS_UID));
471   ASSERT_EQ(s, "Tata");
472   ASSERT_TRUE(f.GetTagValue(s, DICOM_TAG_MEDIA_STORAGE_SOP_CLASS_UID));  // Implicitly modified by (**)
473   ASSERT_EQ(s, "Tata");
474 }
475 
476 
TEST(ParsedDicomFile,JsonEncoding)477 TEST(ParsedDicomFile, JsonEncoding)
478 {
479   ParsedDicomFile f(true);
480 
481   for (unsigned int i = 0; i < testEncodingsCount; i++)
482   {
483     if (testEncodings[i] != Encoding_Windows1251)
484     {
485       //std::cout << EnumerationToString(testEncodings[i]) << std::endl;
486       f.SetEncoding(testEncodings[i]);
487 
488       if (testEncodings[i] != Encoding_Ascii)
489       {
490         bool hasCodeExtensions;
491         ASSERT_EQ(testEncodings[i], f.DetectEncoding(hasCodeExtensions));
492         ASSERT_FALSE(hasCodeExtensions);
493       }
494 
495       Json::Value s = Toolbox::ConvertToUtf8(testEncodingsEncoded[i], testEncodings[i], false);
496       f.Replace(DICOM_TAG_PATIENT_NAME, s, false, DicomReplaceMode_InsertIfAbsent, "");
497 
498       Json::Value v;
499       f.DatasetToJson(v, DicomToJsonFormat_Human, DicomToJsonFlags_Default, 0);
500       ASSERT_EQ(v["PatientName"].asString(), std::string(testEncodingsExpected[i]));
501     }
502   }
503 }
504 
505 
TEST(ParsedDicomFile,ToJsonFlags1)506 TEST(ParsedDicomFile, ToJsonFlags1)
507 {
508   FromDcmtkBridge::RegisterDictionaryTag(DicomTag(0x7053, 0x1000), ValueRepresentation_OtherByte, "MyPrivateTag", 1, 1, "OrthancCreator");
509   FromDcmtkBridge::RegisterDictionaryTag(DicomTag(0x7050, 0x1000), ValueRepresentation_PersonName, "Declared public tag", 1, 1, "");
510 
511   ParsedDicomFile f(true);
512   f.Insert(DicomTag(0x7050, 0x1000), "Some public tag", false, "");  // Even group => public tag
513   f.Insert(DicomTag(0x7052, 0x1000), "Some unknown tag", false, "");  // Even group => public, unknown tag
514   f.Insert(DicomTag(0x7053, 0x1000), "Some private tag", false, "OrthancCreator");  // Odd group => private tag
515 
516   Json::Value v;
517   f.DatasetToJson(v, DicomToJsonFormat_Short, DicomToJsonFlags_None, 0);
518   ASSERT_EQ(Json::objectValue, v.type());
519   ASSERT_EQ(6u, v.getMemberNames().size());
520   ASSERT_FALSE(v.isMember("7052,1000"));
521   ASSERT_FALSE(v.isMember("7053,1000"));
522   ASSERT_TRUE(v.isMember("7050,1000"));
523   ASSERT_EQ(Json::stringValue, v["7050,1000"].type());
524   ASSERT_EQ("Some public tag", v["7050,1000"].asString());
525 
526   f.DatasetToJson(v, DicomToJsonFormat_Short, static_cast<DicomToJsonFlags>(DicomToJsonFlags_IncludePrivateTags | DicomToJsonFlags_IncludeBinary | DicomToJsonFlags_ConvertBinaryToNull), 0);
527   ASSERT_EQ(Json::objectValue, v.type());
528   ASSERT_EQ(7u, v.getMemberNames().size());
529   ASSERT_FALSE(v.isMember("7052,1000"));
530   ASSERT_TRUE(v.isMember("7050,1000"));
531   ASSERT_TRUE(v.isMember("7053,1000"));
532   ASSERT_EQ("Some public tag", v["7050,1000"].asString());
533   ASSERT_EQ(Json::nullValue, v["7053,1000"].type());
534 
535   f.DatasetToJson(v, DicomToJsonFormat_Short, static_cast<DicomToJsonFlags>(DicomToJsonFlags_IncludePrivateTags), 0);
536   ASSERT_EQ(Json::objectValue, v.type());
537   ASSERT_EQ(6u, v.getMemberNames().size());
538   ASSERT_FALSE(v.isMember("7052,1000"));
539   ASSERT_TRUE(v.isMember("7050,1000"));
540   ASSERT_FALSE(v.isMember("7053,1000"));
541 
542   f.DatasetToJson(v, DicomToJsonFormat_Short, static_cast<DicomToJsonFlags>(DicomToJsonFlags_IncludePrivateTags | DicomToJsonFlags_IncludeBinary), 0);
543   ASSERT_EQ(Json::objectValue, v.type());
544   ASSERT_EQ(7u, v.getMemberNames().size());
545   ASSERT_FALSE(v.isMember("7052,1000"));
546   ASSERT_TRUE(v.isMember("7050,1000"));
547   ASSERT_TRUE(v.isMember("7053,1000"));
548   ASSERT_EQ("Some public tag", v["7050,1000"].asString());
549   std::string mime, content;
550   ASSERT_EQ(Json::stringValue, v["7053,1000"].type());
551   ASSERT_TRUE(Toolbox::DecodeDataUriScheme(mime, content, v["7053,1000"].asString()));
552   ASSERT_EQ("application/octet-stream", mime);
553   ASSERT_EQ("Some private tag", content);
554 
555   f.DatasetToJson(v, DicomToJsonFormat_Short, static_cast<DicomToJsonFlags>(DicomToJsonFlags_IncludeUnknownTags | DicomToJsonFlags_IncludeBinary | DicomToJsonFlags_ConvertBinaryToNull), 0);
556   ASSERT_EQ(Json::objectValue, v.type());
557   ASSERT_EQ(7u, v.getMemberNames().size());
558   ASSERT_TRUE(v.isMember("7050,1000"));
559   ASSERT_TRUE(v.isMember("7052,1000"));
560   ASSERT_FALSE(v.isMember("7053,1000"));
561   ASSERT_EQ("Some public tag", v["7050,1000"].asString());
562   ASSERT_EQ(Json::nullValue, v["7052,1000"].type());
563 
564   f.DatasetToJson(v, DicomToJsonFormat_Short, static_cast<DicomToJsonFlags>(DicomToJsonFlags_IncludeUnknownTags | DicomToJsonFlags_IncludeBinary), 0);
565   ASSERT_EQ(Json::objectValue, v.type());
566   ASSERT_EQ(7u, v.getMemberNames().size());
567   ASSERT_TRUE(v.isMember("7050,1000"));
568   ASSERT_TRUE(v.isMember("7052,1000"));
569   ASSERT_FALSE(v.isMember("7053,1000"));
570   ASSERT_EQ("Some public tag", v["7050,1000"].asString());
571   ASSERT_EQ(Json::stringValue, v["7052,1000"].type());
572   ASSERT_TRUE(Toolbox::DecodeDataUriScheme(mime, content, v["7052,1000"].asString()));
573   ASSERT_EQ("application/octet-stream", mime);
574   ASSERT_EQ("Some unknown tag", content);
575 
576   f.DatasetToJson(v, DicomToJsonFormat_Short, static_cast<DicomToJsonFlags>(DicomToJsonFlags_IncludeUnknownTags | DicomToJsonFlags_IncludePrivateTags | DicomToJsonFlags_IncludeBinary | DicomToJsonFlags_ConvertBinaryToNull), 0);
577   ASSERT_EQ(Json::objectValue, v.type());
578   ASSERT_EQ(8u, v.getMemberNames().size());
579   ASSERT_TRUE(v.isMember("7050,1000"));
580   ASSERT_TRUE(v.isMember("7052,1000"));
581   ASSERT_TRUE(v.isMember("7053,1000"));
582   ASSERT_EQ("Some public tag", v["7050,1000"].asString());
583   ASSERT_EQ(Json::nullValue, v["7052,1000"].type());
584   ASSERT_EQ(Json::nullValue, v["7053,1000"].type());
585 }
586 
587 
TEST(ParsedDicomFile,ToJsonFlags2)588 TEST(ParsedDicomFile, ToJsonFlags2)
589 {
590   ParsedDicomFile f(true);
591 
592   {
593     // "ParsedDicomFile" uses Little Endian => 'B' (least significant
594     // byte) will be stored first in the memory buffer and in the
595     // file, then 'A'. Hence the expected "BA" value below.
596     Uint16 v[] = { 'A' * 256 + 'B', 0 };
597     ASSERT_TRUE(f.GetDcmtkObject().getDataset()->putAndInsertUint16Array(DCM_PixelData, v, 2).good());
598   }
599 
600   Json::Value v;
601   f.DatasetToJson(v, DicomToJsonFormat_Short, DicomToJsonFlags_None, 0);
602   ASSERT_EQ(Json::objectValue, v.type());
603   ASSERT_EQ(5u, v.getMemberNames().size());
604   ASSERT_FALSE(v.isMember("7fe0,0010"));
605 
606   f.DatasetToJson(v, DicomToJsonFormat_Short, static_cast<DicomToJsonFlags>(DicomToJsonFlags_IncludePixelData | DicomToJsonFlags_ConvertBinaryToNull), 0);
607   ASSERT_EQ(Json::objectValue, v.type());
608   ASSERT_EQ(6u, v.getMemberNames().size());
609   ASSERT_TRUE(v.isMember("7fe0,0010"));
610   ASSERT_EQ(Json::nullValue, v["7fe0,0010"].type());
611 
612   f.DatasetToJson(v, DicomToJsonFormat_Short, static_cast<DicomToJsonFlags>(DicomToJsonFlags_IncludePixelData | DicomToJsonFlags_ConvertBinaryToAscii), 0);
613   ASSERT_EQ(Json::objectValue, v.type());
614   ASSERT_EQ(6u, v.getMemberNames().size());
615   ASSERT_TRUE(v.isMember("7fe0,0010"));
616   ASSERT_EQ(Json::stringValue, v["7fe0,0010"].type());
617   ASSERT_EQ("BA", v["7fe0,0010"].asString().substr(0, 2));
618 
619   f.DatasetToJson(v, DicomToJsonFormat_Short, DicomToJsonFlags_IncludePixelData, 0);
620   ASSERT_EQ(Json::objectValue, v.type());
621   ASSERT_EQ(6u, v.getMemberNames().size());
622   ASSERT_TRUE(v.isMember("7fe0,0010"));
623   ASSERT_EQ(Json::stringValue, v["7fe0,0010"].type());
624   std::string mime, content;
625   ASSERT_TRUE(Toolbox::DecodeDataUriScheme(mime, content, v["7fe0,0010"].asString()));
626   ASSERT_EQ("application/octet-stream", mime);
627   ASSERT_EQ("BA", content.substr(0, 2));
628 }
629 
630 
TEST(ParsedDicomFile,ToJsonFlags3)631 TEST(ParsedDicomFile, ToJsonFlags3)
632 {
633   ParsedDicomFile f(false);
634 
635   {
636     Uint8 v[2] = { 0, 0 };
637     ASSERT_TRUE(f.GetDcmtkObject().getDataset()->putAndInsertString(DCM_PatientName, "HELLO^").good());
638     ASSERT_TRUE(f.GetDcmtkObject().getDataset()->putAndInsertUint32(DcmTag(0x4000, 0x0000), 42).good());
639     ASSERT_TRUE(f.GetDcmtkObject().getDataset()->putAndInsertUint8Array(DCM_PixelData, v, 2).good());
640     ASSERT_TRUE(f.GetDcmtkObject().getDataset()->putAndInsertString(DcmTag(0x07fe1, 0x0010), "WORLD^").good());
641   }
642 
643   std::string s;
644   Toolbox::EncodeDataUriScheme(s, "application/octet-stream", std::string(2, '\0'));
645 
646   {
647     Json::Value v;
648     f.DatasetToJson(v, DicomToJsonFormat_Short, static_cast<DicomToJsonFlags>(DicomToJsonFlags_IncludePrivateTags | DicomToJsonFlags_IncludePixelData | DicomToJsonFlags_StopAfterPixelData), 0);
649     ASSERT_EQ(Json::objectValue, v.type());
650     ASSERT_EQ(3u, v.size());
651     ASSERT_EQ("HELLO^", v["0010,0010"].asString());
652     ASSERT_EQ("42", v["4000,0000"].asString());
653     ASSERT_EQ(s, v["7fe0,0010"].asString());
654   }
655 
656   {
657     Json::Value v;
658     f.DatasetToJson(v, DicomToJsonFormat_Short, static_cast<DicomToJsonFlags>(DicomToJsonFlags_IncludePrivateTags | DicomToJsonFlags_SkipGroupLengths), 0);
659     ASSERT_EQ(Json::objectValue, v.type());
660     ASSERT_EQ(2u, v.size());
661     ASSERT_EQ("HELLO^", v["0010,0010"].asString());
662     ASSERT_EQ("WORLD^", v["7fe1,0010"].asString());
663   }
664 }
665 
666 
TEST(DicomFindAnswers,Basic)667 TEST(DicomFindAnswers, Basic)
668 {
669   DicomFindAnswers a(false);
670 
671   {
672     DicomMap m;
673     m.SetValue(DICOM_TAG_PATIENT_ID, "hello", false);
674     a.Add(m);
675   }
676 
677   {
678     ParsedDicomFile d(true);
679     d.ReplacePlainString(DICOM_TAG_PATIENT_ID, "my");
680     a.Add(d);
681   }
682 
683   {
684     DicomMap m;
685     m.SetValue(DICOM_TAG_PATIENT_ID, "world", false);
686     a.Add(m);
687   }
688 
689   Json::Value j;
690   a.ToJson(j, true);
691   ASSERT_EQ(3u, j.size());
692 
693   //std::cout << j;
694 }
695 
696 
TEST(ParsedDicomFile,FromJson)697 TEST(ParsedDicomFile, FromJson)
698 {
699   FromDcmtkBridge::RegisterDictionaryTag(DicomTag(0x7057, 0x1000), ValueRepresentation_OtherByte, "MyPrivateTag2", 1, 1, "ORTHANC");
700   FromDcmtkBridge::RegisterDictionaryTag(DicomTag(0x7059, 0x1000), ValueRepresentation_OtherByte, "MyPrivateTag3", 1, 1, "");
701   FromDcmtkBridge::RegisterDictionaryTag(DicomTag(0x7050, 0x1000), ValueRepresentation_PersonName, "Declared public tag2", 1, 1, "");
702 
703   Json::Value v;
704   const std::string sopClassUid = "1.2.840.10008.5.1.4.1.1.1";  // CR Image Storage:
705 
706   // Test the private creator
707   ASSERT_EQ(DcmTag_ERROR_TagName, FromDcmtkBridge::GetTagName(DicomTag(0x7057, 0x1000), "NOPE"));
708   ASSERT_EQ("MyPrivateTag2", FromDcmtkBridge::GetTagName(DicomTag(0x7057, 0x1000), "ORTHANC"));
709 
710   {
711     v["SOPClassUID"] = sopClassUid;
712     v["SpecificCharacterSet"] = "ISO_IR 148";    // This is latin-5
713     v["PatientName"] = "Sébastien";
714     v["7050-1000"] = "Some public tag";  // Even group => public tag
715     v["7052-1000"] = "Some unknown tag";  // Even group => public, unknown tag
716     v["7057-1000"] = "Some private tag";  // Odd group => private tag
717     v["7059-1000"] = "Some private tag2";  // Odd group => private tag, with an odd length to test padding
718 
719     std::string s;
720     Toolbox::EncodeDataUriScheme(s, "application/octet-stream", "Sebastien");
721     v["StudyDescription"] = s;
722 
723     v["PixelData"] = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==";  // A red dot of 5x5 pixels
724     v["0040,0100"] = Json::arrayValue;  // ScheduledProcedureStepSequence
725 
726     Json::Value vv;
727     vv["Modality"] = "MR";
728     v["0040,0100"].append(vv);
729 
730     vv["Modality"] = "CT";
731     v["0040,0100"].append(vv);
732   }
733 
734   const DicomToJsonFlags toJsonFlags = static_cast<DicomToJsonFlags>(DicomToJsonFlags_IncludeBinary |
735                                                                      DicomToJsonFlags_IncludePixelData |
736                                                                      DicomToJsonFlags_IncludePrivateTags |
737                                                                      DicomToJsonFlags_IncludeUnknownTags |
738                                                                      DicomToJsonFlags_ConvertBinaryToAscii);
739 
740 
741   {
742     std::unique_ptr<ParsedDicomFile> dicom
743       (ParsedDicomFile::CreateFromJson(v, static_cast<DicomFromJsonFlags>(DicomFromJsonFlags_GenerateIdentifiers), ""));
744 
745     Json::Value vv;
746     dicom->DatasetToJson(vv, DicomToJsonFormat_Human, toJsonFlags, 0);
747 
748     ASSERT_EQ(vv["SOPClassUID"].asString(), sopClassUid);
749     ASSERT_EQ(vv["MediaStorageSOPClassUID"].asString(), sopClassUid);
750     ASSERT_TRUE(vv.isMember("SOPInstanceUID"));
751     ASSERT_TRUE(vv.isMember("SeriesInstanceUID"));
752     ASSERT_TRUE(vv.isMember("StudyInstanceUID"));
753     ASSERT_TRUE(vv.isMember("PatientID"));
754   }
755 
756 
757   {
758     std::unique_ptr<ParsedDicomFile> dicom
759       (ParsedDicomFile::CreateFromJson(v, static_cast<DicomFromJsonFlags>(DicomFromJsonFlags_GenerateIdentifiers), ""));
760 
761     Json::Value vv;
762     dicom->DatasetToJson(vv, DicomToJsonFormat_Human, static_cast<DicomToJsonFlags>(DicomToJsonFlags_IncludePixelData), 0);
763 
764     std::string mime, content;
765     ASSERT_TRUE(Toolbox::DecodeDataUriScheme(mime, content, vv["PixelData"].asString()));
766     ASSERT_EQ("application/octet-stream", mime);
767     ASSERT_EQ(5u * 5u * 3u /* the red dot is 5x5 pixels in RGB24 */ + 1 /* for padding */, content.size());
768   }
769 
770 
771   {
772     std::unique_ptr<ParsedDicomFile> dicom
773       (ParsedDicomFile::CreateFromJson(v, static_cast<DicomFromJsonFlags>(DicomFromJsonFlags_DecodeDataUriScheme), ""));
774 
775     Json::Value vv;
776     dicom->DatasetToJson(vv, DicomToJsonFormat_Short, toJsonFlags, 0);
777 
778     ASSERT_FALSE(vv.isMember("SOPInstanceUID"));
779     ASSERT_FALSE(vv.isMember("SeriesInstanceUID"));
780     ASSERT_FALSE(vv.isMember("StudyInstanceUID"));
781     ASSERT_FALSE(vv.isMember("PatientID"));
782     ASSERT_EQ(2u, vv["0040,0100"].size());
783     ASSERT_EQ("MR", vv["0040,0100"][0]["0008,0060"].asString());
784     ASSERT_EQ("CT", vv["0040,0100"][1]["0008,0060"].asString());
785     ASSERT_EQ("Some public tag", vv["7050,1000"].asString());
786     ASSERT_EQ("Some unknown tag", vv["7052,1000"].asString());
787     ASSERT_EQ("Some private tag", vv["7057,1000"].asString());
788     ASSERT_EQ("Some private tag2", vv["7059,1000"].asString());
789     ASSERT_EQ("Sébastien", vv["0010,0010"].asString());
790     ASSERT_EQ("Sebastien", vv["0008,1030"].asString());
791     ASSERT_EQ("ISO_IR 148", vv["0008,0005"].asString());
792     ASSERT_EQ("5", vv[DICOM_TAG_ROWS.Format()].asString());
793     ASSERT_EQ("5", vv[DICOM_TAG_COLUMNS.Format()].asString());
794     ASSERT_TRUE(vv[DICOM_TAG_PIXEL_DATA.Format()].asString().empty());
795   }
796 }
797 
798 
799 
TEST(TestImages,PatternGrayscale8)800 TEST(TestImages, PatternGrayscale8)
801 {
802   Orthanc::Image image(Orthanc::PixelFormat_Grayscale8, 256, 256, false);
803 
804   for (int y = 0; y < 256; y++)
805   {
806     uint8_t *p = reinterpret_cast<uint8_t*>(image.GetRow(y));
807     for (int x = 0; x < 256; x++, p++)
808     {
809       *p = y;
810     }
811   }
812 
813   Orthanc::ImageAccessor r;
814 
815   image.GetRegion(r, 32, 32, 64, 192);
816   Orthanc::ImageProcessing::Set(r, 0);
817 
818   image.GetRegion(r, 160, 32, 64, 192);
819   Orthanc::ImageProcessing::Set(r, 255);
820 
821   std::string saved;
822 
823   {
824     ParsedDicomFile f(true);
825     f.ReplacePlainString(DICOM_TAG_SOP_CLASS_UID, "1.2.840.10008.5.1.4.1.1.7");
826     f.ReplacePlainString(DICOM_TAG_STUDY_INSTANCE_UID, "1.2.276.0.7230010.3.1.2.2831176407.321.1458901422.884998");
827     f.ReplacePlainString(DICOM_TAG_PATIENT_ID, "ORTHANC");
828     f.ReplacePlainString(DICOM_TAG_PATIENT_NAME, "Orthanc");
829     f.ReplacePlainString(DICOM_TAG_STUDY_DESCRIPTION, "Patterns");
830     f.ReplacePlainString(DICOM_TAG_SERIES_DESCRIPTION, "Grayscale8");
831     f.EmbedImage(image);
832 
833     f.SaveToMemoryBuffer(saved);
834   }
835 
836   {
837     Orthanc::ParsedDicomFile f(saved);
838 
839     std::unique_ptr<Orthanc::ImageAccessor> decoded(f.DecodeFrame(0));
840     ASSERT_EQ(256u, decoded->GetWidth());
841     ASSERT_EQ(256u, decoded->GetHeight());
842     ASSERT_EQ(Orthanc::PixelFormat_Grayscale8, decoded->GetFormat());
843 
844     for (int y = 0; y < 256; y++)
845     {
846       const void* a = image.GetConstRow(y);
847       const void* b = decoded->GetConstRow(y);
848       ASSERT_EQ(0, memcmp(a, b, 256));
849     }
850   }
851 }
852 
853 
TEST(TestImages,PatternRGB)854 TEST(TestImages, PatternRGB)
855 {
856   Orthanc::Image image(Orthanc::PixelFormat_RGB24, 384, 256, false);
857 
858   for (int y = 0; y < 256; y++)
859   {
860     uint8_t *p = reinterpret_cast<uint8_t*>(image.GetRow(y));
861     for (int x = 0; x < 128; x++, p += 3)
862     {
863       p[0] = y;
864       p[1] = 0;
865       p[2] = 0;
866     }
867     for (int x = 128; x < 128 * 2; x++, p += 3)
868     {
869       p[0] = 0;
870       p[1] = 255 - y;
871       p[2] = 0;
872     }
873     for (int x = 128 * 2; x < 128 * 3; x++, p += 3)
874     {
875       p[0] = 0;
876       p[1] = 0;
877       p[2] = y;
878     }
879   }
880 
881   std::string saved;
882 
883   {
884     ParsedDicomFile f(true);
885     f.ReplacePlainString(DICOM_TAG_SOP_CLASS_UID, "1.2.840.10008.5.1.4.1.1.7");
886     f.ReplacePlainString(DICOM_TAG_STUDY_INSTANCE_UID, "1.2.276.0.7230010.3.1.2.2831176407.321.1458901422.884998");
887     f.ReplacePlainString(DICOM_TAG_PATIENT_ID, "ORTHANC");
888     f.ReplacePlainString(DICOM_TAG_PATIENT_NAME, "Orthanc");
889     f.ReplacePlainString(DICOM_TAG_STUDY_DESCRIPTION, "Patterns");
890     f.ReplacePlainString(DICOM_TAG_SERIES_DESCRIPTION, "RGB24");
891     f.EmbedImage(image);
892 
893     f.SaveToMemoryBuffer(saved);
894   }
895 
896   {
897     Orthanc::ParsedDicomFile f(saved);
898 
899     std::unique_ptr<Orthanc::ImageAccessor> decoded(f.DecodeFrame(0));
900     ASSERT_EQ(384u, decoded->GetWidth());
901     ASSERT_EQ(256u, decoded->GetHeight());
902     ASSERT_EQ(Orthanc::PixelFormat_RGB24, decoded->GetFormat());
903 
904     for (int y = 0; y < 256; y++)
905     {
906       const void* a = image.GetConstRow(y);
907       const void* b = decoded->GetConstRow(y);
908       ASSERT_EQ(0, memcmp(a, b, 3 * 384));
909     }
910   }
911 }
912 
913 
TEST(TestImages,PatternUint16)914 TEST(TestImages, PatternUint16)
915 {
916   Orthanc::Image image(Orthanc::PixelFormat_Grayscale16, 256, 256, false);
917 
918   uint16_t v = 0;
919   for (int y = 0; y < 256; y++)
920   {
921     uint16_t *p = reinterpret_cast<uint16_t*>(image.GetRow(y));
922     for (int x = 0; x < 256; x++, v++, p++)
923     {
924       *p = v;
925     }
926   }
927 
928   Orthanc::ImageAccessor r;
929 
930   image.GetRegion(r, 32, 32, 64, 192);
931   Orthanc::ImageProcessing::Set(r, 0);
932 
933   image.GetRegion(r, 160, 32, 64, 192);
934   Orthanc::ImageProcessing::Set(r, 65535);
935 
936   std::string saved;
937 
938   {
939     ParsedDicomFile f(true);
940     f.ReplacePlainString(DICOM_TAG_SOP_CLASS_UID, "1.2.840.10008.5.1.4.1.1.7");
941     f.ReplacePlainString(DICOM_TAG_STUDY_INSTANCE_UID, "1.2.276.0.7230010.3.1.2.2831176407.321.1458901422.884998");
942     f.ReplacePlainString(DICOM_TAG_PATIENT_ID, "ORTHANC");
943     f.ReplacePlainString(DICOM_TAG_PATIENT_NAME, "Orthanc");
944     f.ReplacePlainString(DICOM_TAG_STUDY_DESCRIPTION, "Patterns");
945     f.ReplacePlainString(DICOM_TAG_SERIES_DESCRIPTION, "Grayscale16");
946     f.EmbedImage(image);
947 
948     f.SaveToMemoryBuffer(saved);
949   }
950 
951   {
952     Orthanc::ParsedDicomFile f(saved);
953 
954     std::unique_ptr<Orthanc::ImageAccessor> decoded(f.DecodeFrame(0));
955     ASSERT_EQ(256u, decoded->GetWidth());
956     ASSERT_EQ(256u, decoded->GetHeight());
957     ASSERT_EQ(Orthanc::PixelFormat_Grayscale16, decoded->GetFormat());
958 
959     for (int y = 0; y < 256; y++)
960     {
961       const void* a = image.GetConstRow(y);
962       const void* b = decoded->GetConstRow(y);
963       ASSERT_EQ(0, memcmp(a, b, 512));
964     }
965   }
966 }
967 
968 
TEST(TestImages,PatternInt16)969 TEST(TestImages, PatternInt16)
970 {
971   Orthanc::Image image(Orthanc::PixelFormat_SignedGrayscale16, 256, 256, false);
972 
973   int16_t v = -32768;
974   for (int y = 0; y < 256; y++)
975   {
976     int16_t *p = reinterpret_cast<int16_t*>(image.GetRow(y));
977     for (int x = 0; x < 256; x++, v++, p++)
978     {
979       *p = v;
980     }
981   }
982 
983   Orthanc::ImageAccessor r;
984   image.GetRegion(r, 32, 32, 64, 192);
985   Orthanc::ImageProcessing::Set(r, -32768);
986 
987   image.GetRegion(r, 160, 32, 64, 192);
988   Orthanc::ImageProcessing::Set(r, 32767);
989 
990   std::string saved;
991 
992   {
993     ParsedDicomFile f(true);
994     f.ReplacePlainString(DICOM_TAG_SOP_CLASS_UID, "1.2.840.10008.5.1.4.1.1.7");
995     f.ReplacePlainString(DICOM_TAG_STUDY_INSTANCE_UID, "1.2.276.0.7230010.3.1.2.2831176407.321.1458901422.884998");
996     f.ReplacePlainString(DICOM_TAG_PATIENT_ID, "ORTHANC");
997     f.ReplacePlainString(DICOM_TAG_PATIENT_NAME, "Orthanc");
998     f.ReplacePlainString(DICOM_TAG_STUDY_DESCRIPTION, "Patterns");
999     f.ReplacePlainString(DICOM_TAG_SERIES_DESCRIPTION, "SignedGrayscale16");
1000     f.EmbedImage(image);
1001 
1002     f.SaveToMemoryBuffer(saved);
1003   }
1004 
1005   {
1006     Orthanc::ParsedDicomFile f(saved);
1007 
1008     std::unique_ptr<Orthanc::ImageAccessor> decoded(f.DecodeFrame(0));
1009     ASSERT_EQ(256u, decoded->GetWidth());
1010     ASSERT_EQ(256u, decoded->GetHeight());
1011     ASSERT_EQ(Orthanc::PixelFormat_SignedGrayscale16, decoded->GetFormat());
1012 
1013     for (int y = 0; y < 256; y++)
1014     {
1015       const void* a = image.GetConstRow(y);
1016       const void* b = decoded->GetConstRow(y);
1017       ASSERT_EQ(0, memcmp(a, b, 512));
1018     }
1019   }
1020 }
1021 
1022 
1023 
CheckEncoding(ParsedDicomFile & dicom,Encoding expected)1024 static void CheckEncoding(ParsedDicomFile& dicom,
1025                           Encoding expected)
1026 {
1027   const char* value = NULL;
1028   ASSERT_TRUE(dicom.GetDcmtkObject().getDataset()->findAndGetString(DCM_SpecificCharacterSet, value).good());
1029 
1030   Encoding encoding;
1031   ASSERT_TRUE(GetDicomEncoding(encoding, value));
1032   ASSERT_EQ(expected, encoding);
1033 }
1034 
1035 
TEST(ParsedDicomFile,DicomMapEncodings1)1036 TEST(ParsedDicomFile, DicomMapEncodings1)
1037 {
1038   SetDefaultDicomEncoding(Encoding_Ascii);
1039   ASSERT_EQ(Encoding_Ascii, GetDefaultDicomEncoding());
1040 
1041   {
1042     DicomMap m;
1043     ParsedDicomFile dicom(m, GetDefaultDicomEncoding(), false);
1044     ASSERT_EQ(1u, dicom.GetDcmtkObject().getDataset()->card());
1045     CheckEncoding(dicom, Encoding_Ascii);
1046   }
1047 
1048   {
1049     DicomMap m;
1050     ParsedDicomFile dicom(m, Encoding_Latin4, false);
1051     ASSERT_EQ(1u, dicom.GetDcmtkObject().getDataset()->card());
1052     CheckEncoding(dicom, Encoding_Latin4);
1053   }
1054 
1055   {
1056     DicomMap m;
1057     m.SetValue(DICOM_TAG_SPECIFIC_CHARACTER_SET, "ISO_IR 148", false);
1058     ParsedDicomFile dicom(m, GetDefaultDicomEncoding(), false);
1059     ASSERT_EQ(1u, dicom.GetDcmtkObject().getDataset()->card());
1060     CheckEncoding(dicom, Encoding_Latin5);
1061   }
1062 
1063   {
1064     DicomMap m;
1065     m.SetValue(DICOM_TAG_SPECIFIC_CHARACTER_SET, "ISO_IR 148", false);
1066     ParsedDicomFile dicom(m, Encoding_Latin1, false);
1067     ASSERT_EQ(1u, dicom.GetDcmtkObject().getDataset()->card());
1068     CheckEncoding(dicom, Encoding_Latin5);
1069   }
1070 }
1071 
1072 
TEST(ParsedDicomFile,DicomMapEncodings2)1073 TEST(ParsedDicomFile, DicomMapEncodings2)
1074 {
1075   const char* utf8 = NULL;
1076   for (unsigned int i = 0; i < testEncodingsCount; i++)
1077   {
1078     if (testEncodings[i] == Encoding_Utf8)
1079     {
1080       utf8 = testEncodingsEncoded[i];
1081       break;
1082     }
1083   }
1084 
1085   ASSERT_TRUE(utf8 != NULL);
1086 
1087   for (unsigned int i = 0; i < testEncodingsCount; i++)
1088   {
1089     // 1251 codepage is not supported by the core DICOM standard, ignore it
1090     if (testEncodings[i] != Encoding_Windows1251)
1091     {
1092       {
1093         // Sanity check to test the proper behavior of "EncodingTests.py"
1094         std::string encoded = Toolbox::ConvertFromUtf8(testEncodingsExpected[i], testEncodings[i]);
1095         ASSERT_STREQ(testEncodingsEncoded[i], encoded.c_str());
1096         std::string decoded = Toolbox::ConvertToUtf8(encoded, testEncodings[i], false);
1097         ASSERT_STREQ(testEncodingsExpected[i], decoded.c_str());
1098 
1099         if (testEncodings[i] != Encoding_Chinese)
1100         {
1101           // A specific source string is used in "EncodingTests.py" to
1102           // test against Chinese, it is normal that it does not correspond to UTF8
1103 
1104           const std::string tmp = Toolbox::ConvertToUtf8(
1105             Toolbox::ConvertFromUtf8(utf8, testEncodings[i]), testEncodings[i], false);
1106           ASSERT_STREQ(testEncodingsExpected[i], tmp.c_str());
1107         }
1108       }
1109 
1110 
1111       Json::Value v;
1112 
1113       {
1114         DicomMap m;
1115         m.SetValue(DICOM_TAG_PATIENT_NAME, testEncodingsExpected[i], false);
1116 
1117         ParsedDicomFile dicom(m, testEncodings[i], false);
1118 
1119         const char* encoded = NULL;
1120         ASSERT_TRUE(dicom.GetDcmtkObject().getDataset()->findAndGetString(DCM_PatientName, encoded).good());
1121         ASSERT_STREQ(testEncodingsEncoded[i], encoded);
1122 
1123         dicom.DatasetToJson(v, DicomToJsonFormat_Human, DicomToJsonFlags_Default, 0);
1124 
1125         Encoding encoding;
1126         ASSERT_TRUE(GetDicomEncoding(encoding, v["SpecificCharacterSet"].asCString()));
1127         ASSERT_EQ(encoding, testEncodings[i]);
1128         ASSERT_STREQ(testEncodingsExpected[i], v["PatientName"].asCString());
1129       }
1130 
1131 
1132       {
1133         DicomMap m;
1134         m.SetValue(DICOM_TAG_SPECIFIC_CHARACTER_SET, GetDicomSpecificCharacterSet(testEncodings[i]), false);
1135         m.SetValue(DICOM_TAG_PATIENT_NAME, testEncodingsExpected[i], false);
1136 
1137         ParsedDicomFile dicom(m, testEncodings[i], false);
1138 
1139         Json::Value v2;
1140         dicom.DatasetToJson(v2, DicomToJsonFormat_Human, DicomToJsonFlags_Default, 0);
1141 
1142         ASSERT_EQ(v2["PatientName"].asString(), v["PatientName"].asString());
1143         ASSERT_EQ(v2["SpecificCharacterSet"].asString(), v["SpecificCharacterSet"].asString());
1144       }
1145     }
1146   }
1147 }
1148 
1149 
TEST(ParsedDicomFile,ChangeEncoding)1150 TEST(ParsedDicomFile, ChangeEncoding)
1151 {
1152   for (unsigned int i = 0; i < testEncodingsCount; i++)
1153   {
1154     // 1251 codepage is not supported by the core DICOM standard, ignore it
1155     if (testEncodings[i] != Encoding_Windows1251)
1156     {
1157       DicomMap m;
1158       m.SetValue(DICOM_TAG_PATIENT_NAME, testEncodingsExpected[i], false);
1159 
1160       std::string tag;
1161 
1162       ParsedDicomFile dicom(m, Encoding_Utf8, false);
1163       bool hasCodeExtensions;
1164       ASSERT_EQ(Encoding_Utf8, dicom.DetectEncoding(hasCodeExtensions));
1165       ASSERT_FALSE(hasCodeExtensions);
1166       ASSERT_TRUE(dicom.GetTagValue(tag, DICOM_TAG_PATIENT_NAME));
1167       ASSERT_EQ(tag, testEncodingsExpected[i]);
1168 
1169       {
1170         Json::Value v;
1171         dicom.DatasetToJson(v, DicomToJsonFormat_Human, DicomToJsonFlags_Default, 0);
1172         ASSERT_STREQ(v["SpecificCharacterSet"].asCString(), "ISO_IR 192");
1173         ASSERT_STREQ(v["PatientName"].asCString(), testEncodingsExpected[i]);
1174       }
1175 
1176       dicom.ChangeEncoding(testEncodings[i]);
1177 
1178       ASSERT_EQ(testEncodings[i], dicom.DetectEncoding(hasCodeExtensions));
1179       ASSERT_FALSE(hasCodeExtensions);
1180 
1181       const char* c = NULL;
1182       ASSERT_TRUE(dicom.GetDcmtkObject().getDataset()->findAndGetString(DCM_PatientName, c).good());
1183       EXPECT_STREQ(c, testEncodingsEncoded[i]);
1184 
1185       ASSERT_TRUE(dicom.GetTagValue(tag, DICOM_TAG_PATIENT_NAME));  // Decodes to UTF-8
1186       EXPECT_EQ(tag, testEncodingsExpected[i]);
1187 
1188       {
1189         Json::Value v;
1190         dicom.DatasetToJson(v, DicomToJsonFormat_Human, DicomToJsonFlags_Default, 0);
1191         ASSERT_STREQ(v["SpecificCharacterSet"].asCString(), GetDicomSpecificCharacterSet(testEncodings[i]));
1192         ASSERT_STREQ(v["PatientName"].asCString(), testEncodingsExpected[i]);
1193       }
1194     }
1195   }
1196 }
1197 
1198 
TEST(Toolbox,CaseWithAccents)1199 TEST(Toolbox, CaseWithAccents)
1200 {
1201   ASSERT_EQ(toUpperResult, Toolbox::ToUpperCaseWithAccents(toUpperSource));
1202 }
1203 
1204 
1205 
TEST(ParsedDicomFile,InvalidCharacterSets)1206 TEST(ParsedDicomFile, InvalidCharacterSets)
1207 {
1208   {
1209     // No encoding provided, fallback to default encoding
1210     DicomMap m;
1211     m.SetValue(DICOM_TAG_PATIENT_NAME, "HELLO", false);
1212 
1213     ParsedDicomFile d(m, Encoding_Latin3 /* default encoding */, false);
1214 
1215     bool hasCodeExtensions;
1216     ASSERT_EQ(Encoding_Latin3, d.DetectEncoding(hasCodeExtensions));
1217     ASSERT_FALSE(hasCodeExtensions);
1218   }
1219 
1220   {
1221     // Valid encoding, "ISO_IR 13" is Japanese
1222     DicomMap m;
1223     m.SetValue(DICOM_TAG_SPECIFIC_CHARACTER_SET, "ISO_IR 13", false);
1224     m.SetValue(DICOM_TAG_PATIENT_NAME, "HELLO", false);
1225 
1226     ParsedDicomFile d(m, Encoding_Latin3 /* default encoding */, false);
1227 
1228     bool hasCodeExtensions;
1229     ASSERT_EQ(Encoding_Japanese, d.DetectEncoding(hasCodeExtensions));
1230     ASSERT_FALSE(hasCodeExtensions);
1231   }
1232 
1233   {
1234     // Invalid value for an encoding ("nope" is not in the DICOM standard)
1235     DicomMap m;
1236     m.SetValue(DICOM_TAG_SPECIFIC_CHARACTER_SET, "nope", false);
1237     m.SetValue(DICOM_TAG_PATIENT_NAME, "HELLO", false);
1238 
1239     ASSERT_THROW(ParsedDicomFile d(m, Encoding_Latin3, false),
1240                  OrthancException);
1241   }
1242 
1243   {
1244     // Invalid encoding, as provided as a binary string
1245     DicomMap m;
1246     m.SetValue(DICOM_TAG_SPECIFIC_CHARACTER_SET, "ISO_IR 13", true);
1247     m.SetValue(DICOM_TAG_PATIENT_NAME, "HELLO", false);
1248 
1249     ASSERT_THROW(ParsedDicomFile d(m, Encoding_Latin3, false),
1250                  OrthancException);
1251   }
1252 
1253   {
1254     // Encoding provided as an empty string, fallback to default encoding
1255     // In Orthanc <= 1.3.1, this test was throwing an exception
1256     DicomMap m;
1257     m.SetValue(DICOM_TAG_SPECIFIC_CHARACTER_SET, "", false);
1258     m.SetValue(DICOM_TAG_PATIENT_NAME, "HELLO", false);
1259 
1260     ParsedDicomFile d(m, Encoding_Latin3 /* default encoding */, false);
1261 
1262     bool hasCodeExtensions;
1263     ASSERT_EQ(Encoding_Latin3, d.DetectEncoding(hasCodeExtensions));
1264     ASSERT_FALSE(hasCodeExtensions);
1265   }
1266 }
1267 
1268 
1269 
TEST(ParsedDicomFile,FloatPrecision)1270 TEST(ParsedDicomFile, FloatPrecision)
1271 {
1272   Float32 v;
1273 
1274   switch (Toolbox::DetectEndianness())
1275   {
1276     case Endianness_Little:
1277       reinterpret_cast<uint8_t*>(&v)[3] = 0x4E;
1278       reinterpret_cast<uint8_t*>(&v)[2] = 0x9C;
1279       reinterpret_cast<uint8_t*>(&v)[1] = 0xAD;
1280       reinterpret_cast<uint8_t*>(&v)[0] = 0x8F;
1281       break;
1282 
1283     case Endianness_Big:
1284       reinterpret_cast<uint8_t*>(&v)[0] = 0x4E;
1285       reinterpret_cast<uint8_t*>(&v)[1] = 0x9C;
1286       reinterpret_cast<uint8_t*>(&v)[2] = 0xAD;
1287       reinterpret_cast<uint8_t*>(&v)[3] = 0x8F;
1288       break;
1289 
1290     default:
1291       throw OrthancException(ErrorCode_InternalError);
1292   }
1293 
1294   ParsedDicomFile f(false);
1295   ASSERT_TRUE(f.GetDcmtkObject().getDataset()->putAndInsertFloat32(DCM_ExaminedBodyThickness /* VR: FL */, v).good());
1296 
1297   {
1298     Float32 u;
1299     ASSERT_TRUE(f.GetDcmtkObject().getDataset()->findAndGetFloat32(DCM_ExaminedBodyThickness, u).good());
1300     ASSERT_FLOAT_EQ(u, v);
1301     ASSERT_TRUE(memcmp(&u, &v, 4) == 0);
1302   }
1303 
1304   {
1305     Json::Value json;
1306     f.DatasetToJson(json, DicomToJsonFormat_Short, DicomToJsonFlags_None, 256);
1307     ASSERT_EQ("1314310016", json["0010,9431"].asString());
1308   }
1309 
1310   {
1311     DicomMap summary;
1312     f.ExtractDicomSummary(summary, 256);
1313     ASSERT_EQ("1314310016", summary.GetStringValue(DicomTag(0x0010, 0x9431), "nope", false));
1314   }
1315 
1316   {
1317     // This flavor uses "Json::Value" serialization
1318     DicomWebJsonVisitor visitor;
1319     f.Apply(visitor);
1320     Float32 u = visitor.GetResult() ["00109431"]["Value"][0].asFloat();
1321     ASSERT_FLOAT_EQ(u, v);
1322     ASSERT_TRUE(memcmp(&u, &v, 4) == 0);
1323   }
1324 }
1325 
1326 
1327 
TEST(Toolbox,RemoveIso2022EscapeSequences)1328 TEST(Toolbox, RemoveIso2022EscapeSequences)
1329 {
1330   // +----------------------------------+
1331   // | one-byte control messages        |
1332   // +----------------------------------+
1333 
1334   static const uint8_t iso2022_cstr_oneByteControl[] = {
1335     0x0f, 0x41,
1336     0x0e, 0x42,
1337     0x8e, 0x1b, 0x4e, 0x43,
1338     0x8f, 0x1b, 0x4f, 0x44,
1339     0x8e, 0x1b, 0x4a, 0x45,
1340     0x8f, 0x1b, 0x4a, 0x46,
1341     0x50, 0x51, 0x52, 0x00
1342   };
1343 
1344   static const uint8_t iso2022_cstr_oneByteControl_ref[] = {
1345     0x41,
1346     0x42,
1347     0x43,
1348     0x44,
1349     0x8e, 0x1b, 0x4a, 0x45,
1350     0x8f, 0x1b, 0x4a, 0x46,
1351     0x50, 0x51, 0x52, 0x00
1352   };
1353 
1354   // +----------------------------------+
1355   // | two-byte control messages        |
1356   // +----------------------------------+
1357 
1358   static const uint8_t iso2022_cstr_twoByteControl[] = {
1359     0x1b, 0x6e, 0x41,
1360     0x1b, 0x6f, 0x42,
1361     0x1b, 0x4e, 0x43,
1362     0x1b, 0x4f, 0x44,
1363     0x1b, 0x7e, 0x45,
1364     0x1b, 0x7d, 0x46,
1365     0x1b, 0x7c, 0x47, 0x00
1366   };
1367 
1368   static const uint8_t iso2022_cstr_twoByteControl_ref[] = {
1369     0x41,
1370     0x42,
1371     0x43,
1372     0x44,
1373     0x45,
1374     0x46,
1375     0x47, 0x00
1376   };
1377 
1378   // +----------------------------------+
1379   // | various-length escape sequences  |
1380   // +----------------------------------+
1381 
1382   static const uint8_t iso2022_cstr_escapeSequence[] = {
1383     0x1b, 0x40, 0x41, // 1b and 40 should not be removed (invalid esc seq)
1384     0x1b, 0x50, 0x42, // ditto
1385     0x1b, 0x7f, 0x43, // ditto
1386     0x1b, 0x21, 0x4a, 0x44, // this will match
1387     0x1b, 0x20, 0x21, 0x2f, 0x40, 0x45, // this will match
1388     0x1b, 0x20, 0x21, 0x2f, 0x2f, 0x40, 0x46, // this will match too
1389     0x1b, 0x20, 0x21, 0x2f, 0x1f, 0x47, 0x48, 0x00 // this will NOT match!
1390   };
1391 
1392   static const uint8_t iso2022_cstr_escapeSequence_ref[] = {
1393     0x1b, 0x40, 0x41, // 1b and 40 should not be removed (invalid esc seq)
1394     0x1b, 0x50, 0x42, // ditto
1395     0x1b, 0x7f, 0x43, // ditto
1396     0x44, // this will match
1397     0x45, // this will match
1398     0x46, // this will match too
1399     0x1b, 0x20, 0x21, 0x2f, 0x1f, 0x47, 0x48, 0x00 // this will NOT match!
1400   };
1401 
1402 
1403   // +----------------------------------+
1404   // | a real-world japanese sample     |
1405   // +----------------------------------+
1406 
1407   static const uint8_t iso2022_cstr_real_ir13[] = {
1408     0xd4, 0xcf, 0xc0, 0xde, 0x5e, 0xc0, 0xdb, 0xb3,
1409     0x3d, 0x1b, 0x24, 0x42, 0x3b, 0x33, 0x45, 0x44,
1410     0x1b, 0x28, 0x4a, 0x5e, 0x1b, 0x24, 0x42, 0x42,
1411     0x40, 0x4f, 0x3a, 0x1b, 0x28, 0x4a, 0x3d, 0x1b,
1412     0x24, 0x42, 0x24, 0x64, 0x24, 0x5e, 0x24, 0x40,
1413     0x1b, 0x28, 0x4a, 0x5e, 0x1b, 0x24, 0x42, 0x24,
1414     0x3f, 0x24, 0x6d, 0x24, 0x26, 0x1b, 0x28, 0x4a, 0x00
1415   };
1416 
1417   static const uint8_t iso2022_cstr_real_ir13_ref[] = {
1418     0xd4, 0xcf, 0xc0, 0xde, 0x5e, 0xc0, 0xdb, 0xb3,
1419     0x3d,
1420     0x3b, 0x33, 0x45, 0x44,
1421     0x5e,
1422     0x42,
1423     0x40, 0x4f, 0x3a,
1424     0x3d,
1425     0x24, 0x64, 0x24, 0x5e, 0x24, 0x40,
1426     0x5e,
1427     0x24,
1428     0x3f, 0x24, 0x6d, 0x24, 0x26, 0x00
1429   };
1430 
1431 
1432 
1433   // +----------------------------------+
1434   // | the actual test                  |
1435   // +----------------------------------+
1436 
1437   std::string iso2022_str_oneByteControl(
1438     reinterpret_cast<const char*>(iso2022_cstr_oneByteControl));
1439   std::string iso2022_str_oneByteControl_ref(
1440     reinterpret_cast<const char*>(iso2022_cstr_oneByteControl_ref));
1441   std::string iso2022_str_twoByteControl(
1442     reinterpret_cast<const char*>(iso2022_cstr_twoByteControl));
1443   std::string iso2022_str_twoByteControl_ref(
1444     reinterpret_cast<const char*>(iso2022_cstr_twoByteControl_ref));
1445   std::string iso2022_str_escapeSequence(
1446     reinterpret_cast<const char*>(iso2022_cstr_escapeSequence));
1447   std::string iso2022_str_escapeSequence_ref(
1448     reinterpret_cast<const char*>(iso2022_cstr_escapeSequence_ref));
1449   std::string iso2022_str_real_ir13(
1450     reinterpret_cast<const char*>(iso2022_cstr_real_ir13));
1451   std::string iso2022_str_real_ir13_ref(
1452     reinterpret_cast<const char*>(iso2022_cstr_real_ir13_ref));
1453 
1454   std::string dest;
1455 
1456   Toolbox::RemoveIso2022EscapeSequences(dest, iso2022_str_oneByteControl);
1457   ASSERT_EQ(dest, iso2022_str_oneByteControl_ref);
1458 
1459   Toolbox::RemoveIso2022EscapeSequences(dest, iso2022_str_twoByteControl);
1460   ASSERT_EQ(dest, iso2022_str_twoByteControl_ref);
1461 
1462   Toolbox::RemoveIso2022EscapeSequences(dest, iso2022_str_escapeSequence);
1463   ASSERT_EQ(dest, iso2022_str_escapeSequence_ref);
1464 
1465   Toolbox::RemoveIso2022EscapeSequences(dest, iso2022_str_real_ir13);
1466   ASSERT_EQ(dest, iso2022_str_real_ir13_ref);
1467 }
1468 
1469 
1470 
DecodeFromSpecification(const std::string & s)1471 static std::string DecodeFromSpecification(const std::string& s)
1472 {
1473   std::vector<std::string> tokens;
1474   Toolbox::TokenizeString(tokens, s, ' ');
1475 
1476   std::string result;
1477   result.resize(tokens.size());
1478 
1479   for (size_t i = 0; i < tokens.size(); i++)
1480   {
1481     std::vector<std::string> components;
1482     Toolbox::TokenizeString(components, tokens[i], '/');
1483 
1484     if (components.size() != 2)
1485     {
1486       throw;
1487     }
1488 
1489     int a = boost::lexical_cast<int>(components[0]);
1490     int b = boost::lexical_cast<int>(components[1]);
1491     if (a < 0 || a > 15 ||
1492         b < 0 || b > 15 ||
1493         (a == 0 && b == 0))
1494     {
1495       throw;
1496     }
1497 
1498     result[i] = static_cast<uint8_t>(a * 16 + b);
1499   }
1500 
1501   return result;
1502 }
1503 
1504 
1505 
1506 // Compatibility wrapper
SelectNode(const pugi::xml_document & doc,const char * xpath)1507 static pugi::xpath_node SelectNode(const pugi::xml_document& doc,
1508                                    const char* xpath)
1509 {
1510 #if PUGIXML_VERSION <= 140
1511   return doc.select_single_node(xpath);  // Deprecated in pugixml 1.5
1512 #else
1513   return doc.select_node(xpath);
1514 #endif
1515 }
1516 
1517 
TEST(Toolbox,EncodingsKorean)1518 TEST(Toolbox, EncodingsKorean)
1519 {
1520   // http://dicom.nema.org/MEDICAL/dicom/current/output/chtml/part05/sect_I.2.html
1521 
1522   std::string korean = DecodeFromSpecification(
1523     "04/08 06/15 06/14 06/07 05/14 04/07 06/09 06/12 06/04 06/15 06/14 06/07 03/13 "
1524     "01/11 02/04 02/09 04/03 15/11 15/03 05/14 01/11 02/04 02/09 04/03 13/01 12/14 "
1525     "13/04 13/07 03/13 01/11 02/04 02/09 04/03 12/08 10/11 05/14 01/11 02/04 02/09 "
1526     "04/03 11/01 14/06 11/05 11/15");
1527 
1528   // This array can be re-generated using command-line:
1529   // echo -n "Hong^Gildong=..." | hexdump -v -e '14/1 "0x%02x, "' -e '"\n"'
1530   static const uint8_t utf8raw[] = {
1531     0x48, 0x6f, 0x6e, 0x67, 0x5e, 0x47, 0x69, 0x6c, 0x64, 0x6f, 0x6e, 0x67, 0x3d, 0xe6,
1532     0xb4, 0xaa, 0x5e, 0xe5, 0x90, 0x89, 0xe6, 0xb4, 0x9e, 0x3d, 0xed, 0x99, 0x8d, 0x5e,
1533     0xea, 0xb8, 0xb8, 0xeb, 0x8f, 0x99
1534   };
1535 
1536   std::string utf8(reinterpret_cast<const char*>(utf8raw), sizeof(utf8raw));
1537 
1538   ParsedDicomFile dicom(false);
1539   dicom.ReplacePlainString(DICOM_TAG_SPECIFIC_CHARACTER_SET, "\\ISO 2022 IR 149");
1540   ASSERT_TRUE(dicom.GetDcmtkObject().getDataset()->putAndInsertString
1541               (DCM_PatientName, korean.c_str(), OFBool(true)).good());
1542 
1543   bool hasCodeExtensions;
1544   Encoding encoding = dicom.DetectEncoding(hasCodeExtensions);
1545   ASSERT_EQ(Encoding_Korean, encoding);
1546   ASSERT_TRUE(hasCodeExtensions);
1547 
1548   std::string value;
1549   ASSERT_TRUE(dicom.GetTagValue(value, DICOM_TAG_PATIENT_NAME));
1550   ASSERT_EQ(utf8, value);
1551 
1552   DicomWebJsonVisitor visitor;
1553   dicom.Apply(visitor);
1554   ASSERT_EQ(utf8.substr(0, 12), visitor.GetResult()["00100010"]["Value"][0]["Alphabetic"].asString());
1555   ASSERT_EQ(utf8.substr(13, 10), visitor.GetResult()["00100010"]["Value"][0]["Ideographic"].asString());
1556   ASSERT_EQ(utf8.substr(24), visitor.GetResult()["00100010"]["Value"][0]["Phonetic"].asString());
1557 
1558 #if ORTHANC_ENABLE_PUGIXML == 1
1559   // http://dicom.nema.org/medical/dicom/current/output/chtml/part18/sect_F.3.html#table_F.3.1-1
1560   std::string xml;
1561   visitor.FormatXml(xml);
1562 
1563   pugi::xml_document doc;
1564   doc.load_buffer(xml.c_str(), xml.size());
1565 
1566   pugi::xpath_node node = SelectNode(doc, "//NativeDicomModel/DicomAttribute[@tag=\"00080005\"]/Value");
1567   ASSERT_STREQ("ISO 2022 IR 149", node.node().text().as_string());
1568 
1569   node = SelectNode(doc, "//NativeDicomModel/DicomAttribute[@tag=\"00080005\"]");
1570   ASSERT_STREQ("CS", node.node().attribute("vr").value());
1571 
1572   node = SelectNode(doc, "//NativeDicomModel/DicomAttribute[@tag=\"00100010\"]");
1573   ASSERT_STREQ("PN", node.node().attribute("vr").value());
1574 
1575   node = SelectNode(doc, "//NativeDicomModel/DicomAttribute[@tag=\"00100010\"]/PersonName/Alphabetic/FamilyName");
1576   ASSERT_STREQ("Hong", node.node().text().as_string());
1577 
1578   node = SelectNode(doc, "//NativeDicomModel/DicomAttribute[@tag=\"00100010\"]/PersonName/Alphabetic/GivenName");
1579   ASSERT_STREQ("Gildong", node.node().text().as_string());
1580 
1581   node = SelectNode(doc, "//NativeDicomModel/DicomAttribute[@tag=\"00100010\"]/PersonName/Ideographic/FamilyName");
1582   ASSERT_EQ(utf8.substr(13, 3), node.node().text().as_string());
1583 
1584   node = SelectNode(doc, "//NativeDicomModel/DicomAttribute[@tag=\"00100010\"]/PersonName/Ideographic/GivenName");
1585   ASSERT_EQ(utf8.substr(17, 6), node.node().text().as_string());
1586 
1587   node = SelectNode(doc, "//NativeDicomModel/DicomAttribute[@tag=\"00100010\"]/PersonName/Phonetic/FamilyName");
1588   ASSERT_EQ(utf8.substr(24, 3), node.node().text().as_string());
1589 
1590   node = SelectNode(doc, "//NativeDicomModel/DicomAttribute[@tag=\"00100010\"]/PersonName/Phonetic/GivenName");
1591   ASSERT_EQ(utf8.substr(28), node.node().text().as_string());
1592 #endif
1593 
1594   {
1595     DicomMap m;
1596     m.FromDicomWeb(visitor.GetResult());
1597     ASSERT_EQ(2u, m.GetSize());
1598 
1599     std::string s;
1600     ASSERT_TRUE(m.LookupStringValue(s, DICOM_TAG_SPECIFIC_CHARACTER_SET, false));
1601     ASSERT_EQ("ISO 2022 IR 149", s);
1602 
1603     ASSERT_TRUE(m.LookupStringValue(s, DICOM_TAG_PATIENT_NAME, false));
1604     std::vector<std::string> v;
1605     Toolbox::TokenizeString(v, s, '=');
1606     ASSERT_EQ(3u, v.size());
1607     ASSERT_EQ("Hong^Gildong", v[0]);
1608     ASSERT_EQ(utf8, s);
1609   }
1610 }
1611 
1612 
TEST(Toolbox,EncodingsJapaneseKanji)1613 TEST(Toolbox, EncodingsJapaneseKanji)
1614 {
1615   // http://dicom.nema.org/MEDICAL/dicom/current/output/chtml/part05/sect_H.3.html
1616 
1617   std::string japanese = DecodeFromSpecification(
1618     "05/09 06/01 06/13 06/01 06/04 06/01 05/14 05/04 06/01 07/02 06/15 07/05 03/13 "
1619     "01/11 02/04 04/02 03/11 03/03 04/05 04/04 01/11 02/08 04/02 05/14 01/11 02/04 "
1620     "04/02 04/02 04/00 04/15 03/10 01/11 02/08 04/02 03/13 01/11 02/04 04/02 02/04 "
1621     "06/04 02/04 05/14 02/04 04/00 01/11 02/08 04/02 05/14 01/11 02/04 04/02 02/04 "
1622     "03/15 02/04 06/13 02/04 02/06 01/11 02/08 04/02");
1623 
1624   // This array can be re-generated using command-line:
1625   // echo -n "Yamada^Tarou=..." | hexdump -v -e '14/1 "0x%02x, "' -e '"\n"'
1626   static const uint8_t utf8raw[] = {
1627     0x59, 0x61, 0x6d, 0x61, 0x64, 0x61, 0x5e, 0x54, 0x61, 0x72, 0x6f, 0x75, 0x3d, 0xe5,
1628     0xb1, 0xb1, 0xe7, 0x94, 0xb0, 0x5e, 0xe5, 0xa4, 0xaa, 0xe9, 0x83, 0x8e, 0x3d, 0xe3,
1629     0x82, 0x84, 0xe3, 0x81, 0xbe, 0xe3, 0x81, 0xa0, 0x5e, 0xe3, 0x81, 0x9f, 0xe3, 0x82,
1630     0x8d, 0xe3, 0x81, 0x86
1631   };
1632 
1633   std::string utf8(reinterpret_cast<const char*>(utf8raw), sizeof(utf8raw));
1634 
1635   ParsedDicomFile dicom(false);
1636   dicom.ReplacePlainString(DICOM_TAG_SPECIFIC_CHARACTER_SET, "\\ISO 2022 IR 87");
1637   ASSERT_TRUE(dicom.GetDcmtkObject().getDataset()->putAndInsertString
1638               (DCM_PatientName, japanese.c_str(), OFBool(true)).good());
1639 
1640   bool hasCodeExtensions;
1641   Encoding encoding = dicom.DetectEncoding(hasCodeExtensions);
1642   ASSERT_EQ(Encoding_JapaneseKanji, encoding);
1643   ASSERT_TRUE(hasCodeExtensions);
1644 
1645   std::string value;
1646   ASSERT_TRUE(dicom.GetTagValue(value, DICOM_TAG_PATIENT_NAME));
1647   ASSERT_EQ(utf8, value);
1648 
1649   DicomWebJsonVisitor visitor;
1650   dicom.Apply(visitor);
1651   ASSERT_EQ(utf8.substr(0, 12), visitor.GetResult()["00100010"]["Value"][0]["Alphabetic"].asString());
1652   ASSERT_EQ(utf8.substr(13, 13), visitor.GetResult()["00100010"]["Value"][0]["Ideographic"].asString());
1653   ASSERT_EQ(utf8.substr(27), visitor.GetResult()["00100010"]["Value"][0]["Phonetic"].asString());
1654 
1655 #if ORTHANC_ENABLE_PUGIXML == 1
1656   // http://dicom.nema.org/medical/dicom/current/output/chtml/part18/sect_F.3.html#table_F.3.1-1
1657   std::string xml;
1658   visitor.FormatXml(xml);
1659 
1660   pugi::xml_document doc;
1661   doc.load_buffer(xml.c_str(), xml.size());
1662 
1663   pugi::xpath_node node = SelectNode(doc, "//NativeDicomModel/DicomAttribute[@tag=\"00080005\"]/Value");
1664   ASSERT_STREQ("ISO 2022 IR 87", node.node().text().as_string());
1665 
1666   node = SelectNode(doc, "//NativeDicomModel/DicomAttribute[@tag=\"00080005\"]");
1667   ASSERT_STREQ("CS", node.node().attribute("vr").value());
1668 
1669   node = SelectNode(doc, "//NativeDicomModel/DicomAttribute[@tag=\"00100010\"]");
1670   ASSERT_STREQ("PN", node.node().attribute("vr").value());
1671 
1672   node = SelectNode(doc, "//NativeDicomModel/DicomAttribute[@tag=\"00100010\"]/PersonName/Alphabetic/FamilyName");
1673   ASSERT_STREQ("Yamada", node.node().text().as_string());
1674 
1675   node = SelectNode(doc, "//NativeDicomModel/DicomAttribute[@tag=\"00100010\"]/PersonName/Alphabetic/GivenName");
1676   ASSERT_STREQ("Tarou", node.node().text().as_string());
1677 
1678   node = SelectNode(doc, "//NativeDicomModel/DicomAttribute[@tag=\"00100010\"]/PersonName/Ideographic/FamilyName");
1679   ASSERT_EQ(utf8.substr(13, 6), node.node().text().as_string());
1680 
1681   node = SelectNode(doc, "//NativeDicomModel/DicomAttribute[@tag=\"00100010\"]/PersonName/Ideographic/GivenName");
1682   ASSERT_EQ(utf8.substr(20, 6), node.node().text().as_string());
1683 
1684   node = SelectNode(doc, "//NativeDicomModel/DicomAttribute[@tag=\"00100010\"]/PersonName/Phonetic/FamilyName");
1685   ASSERT_EQ(utf8.substr(27, 9), node.node().text().as_string());
1686 
1687   node = SelectNode(doc, "//NativeDicomModel/DicomAttribute[@tag=\"00100010\"]/PersonName/Phonetic/GivenName");
1688   ASSERT_EQ(utf8.substr(37), node.node().text().as_string());
1689 #endif
1690 
1691   {
1692     DicomMap m;
1693     m.FromDicomWeb(visitor.GetResult());
1694     ASSERT_EQ(2u, m.GetSize());
1695 
1696     std::string s;
1697     ASSERT_TRUE(m.LookupStringValue(s, DICOM_TAG_SPECIFIC_CHARACTER_SET, false));
1698     ASSERT_EQ("ISO 2022 IR 87", s);
1699 
1700     ASSERT_TRUE(m.LookupStringValue(s, DICOM_TAG_PATIENT_NAME, false));
1701     std::vector<std::string> v;
1702     Toolbox::TokenizeString(v, s, '=');
1703     ASSERT_EQ(3u, v.size());
1704     ASSERT_EQ("Yamada^Tarou", v[0]);
1705     ASSERT_EQ(utf8, s);
1706   }
1707 }
1708 
1709 
1710 
TEST(Toolbox,EncodingsChinese3)1711 TEST(Toolbox, EncodingsChinese3)
1712 {
1713   // http://dicom.nema.org/MEDICAL/dicom/current/output/chtml/part05/sect_J.3.html
1714 
1715   static const uint8_t chinese[] = {
1716     0x57, 0x61, 0x6e, 0x67, 0x5e, 0x58, 0x69, 0x61, 0x6f, 0x44, 0x6f,
1717     0x6e, 0x67, 0x3d, 0xcd, 0xf5, 0x5e, 0xd0, 0xa1, 0xb6, 0xab, 0x3d, 0x00
1718   };
1719 
1720   ParsedDicomFile dicom(false);
1721   dicom.ReplacePlainString(DICOM_TAG_SPECIFIC_CHARACTER_SET, "GB18030");
1722   ASSERT_TRUE(dicom.GetDcmtkObject().getDataset()->putAndInsertString
1723               (DCM_PatientName, reinterpret_cast<const char*>(chinese), OFBool(true)).good());
1724 
1725   bool hasCodeExtensions;
1726   Encoding encoding = dicom.DetectEncoding(hasCodeExtensions);
1727   ASSERT_EQ(Encoding_Chinese, encoding);
1728   ASSERT_FALSE(hasCodeExtensions);
1729 
1730   std::string value;
1731   ASSERT_TRUE(dicom.GetTagValue(value, DICOM_TAG_PATIENT_NAME));
1732 
1733   std::vector<std::string> tokens;
1734   Orthanc::Toolbox::TokenizeString(tokens, value, '=');
1735   ASSERT_EQ(3u, tokens.size());
1736   ASSERT_EQ("Wang^XiaoDong", tokens[0]);
1737   ASSERT_TRUE(tokens[2].empty());
1738 
1739   std::vector<std::string> middle;
1740   Orthanc::Toolbox::TokenizeString(middle, tokens[1], '^');
1741   ASSERT_EQ(2u, middle.size());
1742   ASSERT_EQ(3u, middle[0].size());
1743   ASSERT_EQ(6u, middle[1].size());
1744 
1745   // CDF5 in GB18030
1746   ASSERT_EQ(static_cast<char>(0xe7), middle[0][0]);
1747   ASSERT_EQ(static_cast<char>(0x8e), middle[0][1]);
1748   ASSERT_EQ(static_cast<char>(0x8b), middle[0][2]);
1749 
1750   // D0A1 in GB18030
1751   ASSERT_EQ(static_cast<char>(0xe5), middle[1][0]);
1752   ASSERT_EQ(static_cast<char>(0xb0), middle[1][1]);
1753   ASSERT_EQ(static_cast<char>(0x8f), middle[1][2]);
1754 
1755   // B6AB in GB18030
1756   ASSERT_EQ(static_cast<char>(0xe4), middle[1][3]);
1757   ASSERT_EQ(static_cast<char>(0xb8), middle[1][4]);
1758   ASSERT_EQ(static_cast<char>(0x9c), middle[1][5]);
1759 }
1760 
1761 
TEST(Toolbox,EncodingsChinese4)1762 TEST(Toolbox, EncodingsChinese4)
1763 {
1764   // http://dicom.nema.org/MEDICAL/dicom/current/output/chtml/part05/sect_J.4.html
1765 
1766   static const uint8_t chinese[] = {
1767     0x54, 0x68, 0x65, 0x20, 0x66, 0x69, 0x72, 0x73, 0x74, 0x20, 0x6c, 0x69, 0x6e,
1768     0x65, 0x20, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x73, 0xd6, 0xd0, 0xce,
1769     0xc4, 0x2e, 0x0d, 0x0a, 0x54, 0x68, 0x65, 0x20, 0x73, 0x65, 0x63, 0x6f, 0x6e,
1770     0x64, 0x20, 0x6c, 0x69, 0x6e, 0x65, 0x20, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64,
1771     0x65, 0x73, 0xd6, 0xd0, 0xce, 0xc4, 0x2c, 0x20, 0x74, 0x6f, 0x6f, 0x2e, 0x0d,
1772     0x0a, 0x54, 0x68, 0x65, 0x20, 0x74, 0x68, 0x69, 0x72, 0x64, 0x20, 0x6c, 0x69,
1773     0x6e, 0x65, 0x2e, 0x0d, 0x0a, 0x00
1774   };
1775 
1776   static const uint8_t patternRaw[] = {
1777     0xe4, 0xb8, 0xad, 0xe6, 0x96, 0x87
1778   };
1779 
1780   const std::string pattern(reinterpret_cast<const char*>(patternRaw), sizeof(patternRaw));
1781 
1782   ParsedDicomFile dicom(false);
1783   dicom.ReplacePlainString(DICOM_TAG_SPECIFIC_CHARACTER_SET, "GB18030");
1784   ASSERT_TRUE(dicom.GetDcmtkObject().getDataset()->putAndInsertString
1785               (DCM_PatientComments, reinterpret_cast<const char*>(chinese), OFBool(true)).good());
1786 
1787   bool hasCodeExtensions;
1788   Encoding encoding = dicom.DetectEncoding(hasCodeExtensions);
1789   ASSERT_EQ(Encoding_Chinese, encoding);
1790   ASSERT_FALSE(hasCodeExtensions);
1791 
1792   std::string value;
1793   ASSERT_TRUE(dicom.GetTagValue(value, DICOM_TAG_PATIENT_COMMENTS));
1794 
1795   std::vector<std::string> lines;
1796   Orthanc::Toolbox::TokenizeString(lines, value, '\n');
1797   ASSERT_EQ(4u, lines.size());
1798   ASSERT_TRUE(boost::starts_with(lines[0], "The first line includes"));
1799   ASSERT_TRUE(boost::ends_with(lines[0], ".\r"));
1800   ASSERT_TRUE(lines[0].find(pattern) != std::string::npos);
1801   ASSERT_TRUE(boost::starts_with(lines[1], "The second line includes"));
1802   ASSERT_TRUE(boost::ends_with(lines[1], ", too.\r"));
1803   ASSERT_TRUE(lines[1].find(pattern) != std::string::npos);
1804   ASSERT_EQ("The third line.\r", lines[2]);
1805   ASSERT_FALSE(lines[1].find(pattern) == std::string::npos);
1806   ASSERT_TRUE(lines[3].empty());
1807 }
1808 
1809 
TEST(Toolbox,EncodingsSimplifiedChinese2)1810 TEST(Toolbox, EncodingsSimplifiedChinese2)
1811 {
1812   // http://dicom.nema.org/MEDICAL/dicom/current/output/chtml/part05/sect_K.2.html
1813 
1814   static const uint8_t chinese[] = {
1815     0x5a, 0x68, 0x61, 0x6e, 0x67, 0x5e, 0x58, 0x69, 0x61, 0x6f, 0x44, 0x6f,
1816     0x6e, 0x67, 0x3d, 0x1b, 0x24, 0x29, 0x41, 0xd5, 0xc5, 0x5e, 0x1b, 0x24,
1817     0x29, 0x41, 0xd0, 0xa1, 0xb6, 0xab, 0x3d, 0x20, 0x00
1818   };
1819 
1820   // echo -n "Zhang^XiaoDong=..." | hexdump -v -e '14/1 "0x%02x, "' -e '"\n"'
1821   static const uint8_t utf8[] = {
1822     0x5a, 0x68, 0x61, 0x6e, 0x67, 0x5e, 0x58, 0x69, 0x61, 0x6f, 0x44, 0x6f, 0x6e, 0x67,
1823     0x3d, 0xe5, 0xbc, 0xa0, 0x5e, 0xe5, 0xb0, 0x8f, 0xe4, 0xb8, 0x9c, 0x3d
1824   };
1825 
1826   ParsedDicomFile dicom(false);
1827   dicom.ReplacePlainString(DICOM_TAG_SPECIFIC_CHARACTER_SET, "\\ISO 2022 IR 58");
1828   ASSERT_TRUE(dicom.GetDcmtkObject().getDataset()->putAndInsertString
1829               (DCM_PatientName, reinterpret_cast<const char*>(chinese), OFBool(true)).good());
1830 
1831   bool hasCodeExtensions;
1832   Encoding encoding = dicom.DetectEncoding(hasCodeExtensions);
1833   ASSERT_EQ(Encoding_SimplifiedChinese, encoding);
1834   ASSERT_TRUE(hasCodeExtensions);
1835 
1836   std::string value;
1837   ASSERT_TRUE(dicom.GetTagValue(value, DICOM_TAG_PATIENT_NAME));
1838   ASSERT_EQ(value, std::string(reinterpret_cast<const char*>(utf8), sizeof(utf8)));
1839 }
1840 
1841 
TEST(Toolbox,EncodingsSimplifiedChinese3)1842 TEST(Toolbox, EncodingsSimplifiedChinese3)
1843 {
1844   // http://dicom.nema.org/MEDICAL/dicom/current/output/chtml/part05/sect_K.2.html
1845 
1846   static const uint8_t chinese[] = {
1847     0x31, 0x2e, 0x1b, 0x24, 0x29, 0x41, 0xb5, 0xda, 0xd2, 0xbb, 0xd0, 0xd0, 0xce, 0xc4, 0xd7, 0xd6, 0xa1, 0xa3, 0x0d, 0x0a,
1848     0x32, 0x2e, 0x1b, 0x24, 0x29, 0x41, 0xb5, 0xda, 0xb6, 0xfe, 0xd0, 0xd0, 0xce, 0xc4, 0xd7, 0xd6, 0xa1, 0xa3, 0x0d, 0x0a,
1849     0x33, 0x2e, 0x1b, 0x24, 0x29, 0x41, 0xb5, 0xda, 0xc8, 0xfd, 0xd0, 0xd0, 0xce, 0xc4, 0xd7, 0xd6, 0xa1, 0xa3, 0x0d, 0x0a, 0x00
1850   };
1851 
1852   static const uint8_t line1[] = {
1853     0x31, 0x2e, 0xe7, 0xac, 0xac, 0xe4, 0xb8, 0x80, 0xe8, 0xa1, 0x8c, 0xe6, 0x96, 0x87,
1854     0xe5, 0xad, 0x97, 0xe3, 0x80, 0x82, '\r'
1855   };
1856 
1857   static const uint8_t line2[] = {
1858     0x32, 0x2e, 0xe7, 0xac, 0xac, 0xe4, 0xba, 0x8c, 0xe8, 0xa1, 0x8c, 0xe6, 0x96, 0x87,
1859     0xe5, 0xad, 0x97, 0xe3, 0x80, 0x82, '\r'
1860   };
1861 
1862   static const uint8_t line3[] = {
1863     0x33, 0x2e, 0xe7, 0xac, 0xac, 0xe4, 0xb8, 0x89, 0xe8, 0xa1, 0x8c, 0xe6, 0x96, 0x87,
1864     0xe5, 0xad, 0x97, 0xe3, 0x80, 0x82, '\r'
1865   };
1866 
1867   ParsedDicomFile dicom(false);
1868   dicom.ReplacePlainString(DICOM_TAG_SPECIFIC_CHARACTER_SET, "\\ISO 2022 IR 58");
1869   ASSERT_TRUE(dicom.GetDcmtkObject().getDataset()->putAndInsertString
1870               (DCM_PatientName, reinterpret_cast<const char*>(chinese), OFBool(true)).good());
1871 
1872   bool hasCodeExtensions;
1873   Encoding encoding = dicom.DetectEncoding(hasCodeExtensions);
1874   ASSERT_EQ(Encoding_SimplifiedChinese, encoding);
1875   ASSERT_TRUE(hasCodeExtensions);
1876 
1877   std::string value;
1878   ASSERT_TRUE(dicom.GetTagValue(value, DICOM_TAG_PATIENT_NAME));
1879 
1880   std::vector<std::string> lines;
1881   Toolbox::TokenizeString(lines, value, '\n');
1882   ASSERT_EQ(4u, lines.size());
1883   ASSERT_EQ(std::string(reinterpret_cast<const char*>(line1), sizeof(line1)), lines[0]);
1884   ASSERT_EQ(std::string(reinterpret_cast<const char*>(line2), sizeof(line2)), lines[1]);
1885   ASSERT_EQ(std::string(reinterpret_cast<const char*>(line3), sizeof(line3)), lines[2]);
1886   ASSERT_TRUE(lines[3].empty());
1887 }
1888 
1889 
SetTagKey(ParsedDicomFile & dicom,const DicomTag & tag,const DicomTag & value)1890 static void SetTagKey(ParsedDicomFile& dicom,
1891                       const DicomTag& tag,
1892                       const DicomTag& value)
1893 {
1894   // This function emulates a call to function
1895   // "dicom.GetDcmtkObject().getDataset()->putAndInsertTagKey(tag,
1896   // value)" that was not available in DCMTK 3.6.0
1897 
1898   std::unique_ptr<DcmAttributeTag> element(new DcmAttributeTag(ToDcmtkBridge::Convert(tag)));
1899 
1900   DcmTagKey v = ToDcmtkBridge::Convert(value);
1901   if (!element->putTagVal(v).good())
1902   {
1903     throw OrthancException(ErrorCode_InternalError);
1904   }
1905 
1906   dicom.GetDcmtkObject().getDataset()->insert(element.release());
1907 }
1908 
1909 
TEST(DicomWebJson,ValueRepresentation)1910 TEST(DicomWebJson, ValueRepresentation)
1911 {
1912   // http://dicom.nema.org/medical/dicom/current/output/chtml/part18/sect_F.2.3.html
1913 
1914   ParsedDicomFile dicom(false);
1915   dicom.ReplacePlainString(DicomTag(0x0040, 0x0241), "AE");
1916   dicom.ReplacePlainString(DicomTag(0x0010, 0x1010), "AS");
1917   SetTagKey(dicom, DicomTag(0x0020, 0x9165), DicomTag(0x0010, 0x0020));
1918   dicom.ReplacePlainString(DicomTag(0x0008, 0x0052), "CS");
1919   dicom.ReplacePlainString(DicomTag(0x0008, 0x0012), "DA");
1920   dicom.ReplacePlainString(DicomTag(0x0010, 0x1020), "42");  // DS
1921   dicom.ReplacePlainString(DicomTag(0x0008, 0x002a), "DT");
1922   dicom.ReplacePlainString(DicomTag(0x0010, 0x9431), "43");  // FL
1923   dicom.ReplacePlainString(DicomTag(0x0008, 0x1163), "44");  // FD
1924   dicom.ReplacePlainString(DicomTag(0x0008, 0x1160), "45");  // IS
1925   dicom.ReplacePlainString(DicomTag(0x0008, 0x0070), "LO");
1926   dicom.ReplacePlainString(DicomTag(0x0010, 0x4000), "LT");
1927   dicom.ReplacePlainString(DicomTag(0x0028, 0x2000), "OB");
1928   dicom.ReplacePlainString(DicomTag(0x7fe0, 0x0009), "3.14159");  // OD (other double)
1929   dicom.ReplacePlainString(DicomTag(0x0064, 0x0009), "2.71828");  // OF (other float)
1930   dicom.ReplacePlainString(DicomTag(0x0066, 0x0040), "46");  // OL (other long)
1931   ASSERT_THROW(dicom.ReplacePlainString(DicomTag(0x0028, 0x1201), "O"), OrthancException);
1932   dicom.ReplacePlainString(DicomTag(0x0028, 0x1201), "OWOW");
1933   dicom.ReplacePlainString(DicomTag(0x0010, 0x0010), "PN");
1934   dicom.ReplacePlainString(DicomTag(0x0008, 0x0050), "SH");
1935   dicom.ReplacePlainString(DicomTag(0x0018, 0x6020), "-15");  // SL
1936   dicom.ReplacePlainString(DicomTag(0x0018, 0x9219), "-16");  // SS
1937   dicom.ReplacePlainString(DicomTag(0x0008, 0x0081), "ST");
1938   dicom.ReplacePlainString(DicomTag(0x0008, 0x0013), "TM");
1939   dicom.ReplacePlainString(DicomTag(0x0008, 0x0119), "UC");
1940   dicom.ReplacePlainString(DicomTag(0x0008, 0x0016), "UI");
1941   dicom.ReplacePlainString(DicomTag(0x0008, 0x1161), "128");  // UL
1942   dicom.ReplacePlainString(DicomTag(0x4342, 0x1234), "UN");   // Inexistent tag
1943   dicom.ReplacePlainString(DicomTag(0x0008, 0x0120), "UR");
1944   dicom.ReplacePlainString(DicomTag(0x0008, 0x0301), "17");   // US
1945   dicom.ReplacePlainString(DicomTag(0x0040, 0x0031), "UT");
1946 
1947   DicomWebJsonVisitor visitor;
1948   dicom.Apply(visitor);
1949 
1950   std::string s;
1951 
1952   // The tag (0002,0002) is "Media Storage SOP Class UID" and is
1953   // automatically copied by DCMTK from tag (0008,0016)
1954   ASSERT_EQ("UI", visitor.GetResult() ["00020002"]["vr"].asString());
1955   ASSERT_EQ("UI", visitor.GetResult() ["00020002"]["Value"][0].asString());
1956   ASSERT_EQ("AE", visitor.GetResult() ["00400241"]["vr"].asString());
1957   ASSERT_EQ("AE", visitor.GetResult() ["00400241"]["Value"][0].asString());
1958   ASSERT_EQ("AS", visitor.GetResult() ["00101010"]["vr"].asString());
1959   ASSERT_EQ("AS", visitor.GetResult() ["00101010"]["Value"][0].asString());
1960   ASSERT_EQ("AT", visitor.GetResult() ["00209165"]["vr"].asString());
1961   ASSERT_EQ("00100020", visitor.GetResult() ["00209165"]["Value"][0].asString());
1962   ASSERT_EQ("CS", visitor.GetResult() ["00080052"]["vr"].asString());
1963   ASSERT_EQ("CS", visitor.GetResult() ["00080052"]["Value"][0].asString());
1964   ASSERT_EQ("DA", visitor.GetResult() ["00080012"]["vr"].asString());
1965   ASSERT_EQ("DA", visitor.GetResult() ["00080012"]["Value"][0].asString());
1966   ASSERT_EQ("DS", visitor.GetResult() ["00101020"]["vr"].asString());
1967   ASSERT_FLOAT_EQ(42.0f, visitor.GetResult() ["00101020"]["Value"][0].asFloat());
1968   ASSERT_EQ("DT", visitor.GetResult() ["0008002A"]["vr"].asString());
1969   ASSERT_EQ("DT", visitor.GetResult() ["0008002A"]["Value"][0].asString());
1970   ASSERT_EQ("FL", visitor.GetResult() ["00109431"]["vr"].asString());
1971   ASSERT_FLOAT_EQ(43.0f, visitor.GetResult() ["00109431"]["Value"][0].asFloat());
1972   ASSERT_EQ("FD", visitor.GetResult() ["00081163"]["vr"].asString());
1973   ASSERT_FLOAT_EQ(44.0f, visitor.GetResult() ["00081163"]["Value"][0].asFloat());
1974   ASSERT_EQ("IS", visitor.GetResult() ["00081160"]["vr"].asString());
1975   ASSERT_FLOAT_EQ(45.0f, visitor.GetResult() ["00081160"]["Value"][0].asFloat());
1976   ASSERT_EQ("LO", visitor.GetResult() ["00080070"]["vr"].asString());
1977   ASSERT_EQ("LO", visitor.GetResult() ["00080070"]["Value"][0].asString());
1978   ASSERT_EQ("LT", visitor.GetResult() ["00104000"]["vr"].asString());
1979   ASSERT_EQ("LT", visitor.GetResult() ["00104000"]["Value"][0].asString());
1980 
1981   ASSERT_EQ("OB", visitor.GetResult() ["00282000"]["vr"].asString());
1982   Toolbox::DecodeBase64(s, visitor.GetResult() ["00282000"]["InlineBinary"].asString());
1983   ASSERT_EQ("OB", s);
1984 
1985 #if DCMTK_VERSION_NUMBER >= 361
1986   ASSERT_EQ("OD", visitor.GetResult() ["7FE00009"]["vr"].asString());
1987   ASSERT_FLOAT_EQ(3.14159f, boost::lexical_cast<float>(visitor.GetResult() ["7FE00009"]["Value"][0].asString()));
1988 #else
1989   ASSERT_EQ("UN", visitor.GetResult() ["7FE00009"]["vr"].asString());
1990   Toolbox::DecodeBase64(s, visitor.GetResult() ["7FE00009"]["InlineBinary"].asString());
1991   ASSERT_EQ(8u, s.size()); // Because of padding
1992   ASSERT_EQ(0, s[7]);
1993   ASSERT_EQ("3.14159", s.substr(0, 7));
1994 #endif
1995 
1996   ASSERT_EQ("OF", visitor.GetResult() ["00640009"]["vr"].asString());
1997   ASSERT_FLOAT_EQ(2.71828f, boost::lexical_cast<float>(visitor.GetResult() ["00640009"]["Value"][0].asString()));
1998 
1999 #if DCMTK_VERSION_NUMBER < 361
2000   ASSERT_EQ("UN", visitor.GetResult() ["00660040"]["vr"].asString());
2001   Toolbox::DecodeBase64(s, visitor.GetResult() ["00660040"]["InlineBinary"].asString());
2002   ASSERT_EQ("46", s);
2003 #elif DCMTK_VERSION_NUMBER == 361
2004   ASSERT_EQ("UL", visitor.GetResult() ["00660040"]["vr"].asString());
2005   ASSERT_EQ(46, visitor.GetResult() ["00660040"]["Value"][0].asInt());
2006 #else
2007   ASSERT_EQ("OL", visitor.GetResult() ["00660040"]["vr"].asString());
2008   ASSERT_EQ(46, visitor.GetResult() ["00660040"]["Value"][0].asInt());
2009 #endif
2010 
2011   ASSERT_EQ("OW", visitor.GetResult() ["00281201"]["vr"].asString());
2012   Toolbox::DecodeBase64(s, visitor.GetResult() ["00281201"]["InlineBinary"].asString());
2013   ASSERT_EQ("OWOW", s);
2014 
2015   ASSERT_EQ("PN", visitor.GetResult() ["00100010"]["vr"].asString());
2016   ASSERT_EQ("PN", visitor.GetResult() ["00100010"]["Value"][0]["Alphabetic"].asString());
2017 
2018   ASSERT_EQ("SH", visitor.GetResult() ["00080050"]["vr"].asString());
2019   ASSERT_EQ("SH", visitor.GetResult() ["00080050"]["Value"][0].asString());
2020 
2021   ASSERT_EQ("SL", visitor.GetResult() ["00186020"]["vr"].asString());
2022   ASSERT_EQ(-15, visitor.GetResult() ["00186020"]["Value"][0].asInt());
2023 
2024   ASSERT_EQ("SS", visitor.GetResult() ["00189219"]["vr"].asString());
2025   ASSERT_EQ(-16, visitor.GetResult() ["00189219"]["Value"][0].asInt());
2026 
2027   ASSERT_EQ("ST", visitor.GetResult() ["00080081"]["vr"].asString());
2028   ASSERT_EQ("ST", visitor.GetResult() ["00080081"]["Value"][0].asString());
2029 
2030   ASSERT_EQ("TM", visitor.GetResult() ["00080013"]["vr"].asString());
2031   ASSERT_EQ("TM", visitor.GetResult() ["00080013"]["Value"][0].asString());
2032 
2033 #if DCMTK_VERSION_NUMBER >= 361
2034   ASSERT_EQ("UC", visitor.GetResult() ["00080119"]["vr"].asString());
2035   ASSERT_EQ("UC", visitor.GetResult() ["00080119"]["Value"][0].asString());
2036 #else
2037   ASSERT_EQ("UN", visitor.GetResult() ["00080119"]["vr"].asString());
2038   Toolbox::DecodeBase64(s, visitor.GetResult() ["00080119"]["InlineBinary"].asString());
2039   ASSERT_EQ("UC", s);
2040 #endif
2041 
2042   ASSERT_EQ("UI", visitor.GetResult() ["00080016"]["vr"].asString());
2043   ASSERT_EQ("UI", visitor.GetResult() ["00080016"]["Value"][0].asString());
2044 
2045   ASSERT_EQ("UL", visitor.GetResult() ["00081161"]["vr"].asString());
2046   ASSERT_EQ(128u, visitor.GetResult() ["00081161"]["Value"][0].asUInt());
2047 
2048   ASSERT_EQ("UN", visitor.GetResult() ["43421234"]["vr"].asString());
2049   Toolbox::DecodeBase64(s, visitor.GetResult() ["43421234"]["InlineBinary"].asString());
2050   ASSERT_EQ("UN", s);
2051 
2052 #if DCMTK_VERSION_NUMBER >= 361
2053   ASSERT_EQ("UR", visitor.GetResult() ["00080120"]["vr"].asString());
2054   ASSERT_EQ("UR", visitor.GetResult() ["00080120"]["Value"][0].asString());
2055 #else
2056   ASSERT_EQ("UN", visitor.GetResult() ["00080120"]["vr"].asString());
2057   Toolbox::DecodeBase64(s, visitor.GetResult() ["00080120"]["InlineBinary"].asString());
2058   ASSERT_EQ("UR", s);
2059 #endif
2060 
2061 #if DCMTK_VERSION_NUMBER >= 361
2062   ASSERT_EQ("US", visitor.GetResult() ["00080301"]["vr"].asString());
2063   ASSERT_EQ(17u, visitor.GetResult() ["00080301"]["Value"][0].asUInt());
2064 #else
2065   ASSERT_EQ("UN", visitor.GetResult() ["00080301"]["vr"].asString());
2066   Toolbox::DecodeBase64(s, visitor.GetResult() ["00080301"]["InlineBinary"].asString());
2067   ASSERT_EQ("17", s);
2068 #endif
2069 
2070   ASSERT_EQ("UT", visitor.GetResult() ["00400031"]["vr"].asString());
2071   ASSERT_EQ("UT", visitor.GetResult() ["00400031"]["Value"][0].asString());
2072 
2073   std::string xml;
2074   visitor.FormatXml(xml);
2075 
2076   {
2077     DicomMap m;
2078     m.FromDicomWeb(visitor.GetResult());
2079     ASSERT_EQ(31u, m.GetSize());
2080 
2081     ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0002, 0x0002), false));  ASSERT_EQ("UI", s);
2082     ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0040, 0x0241), false));  ASSERT_EQ("AE", s);
2083     ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0010, 0x1010), false));  ASSERT_EQ("AS", s);
2084     ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0020, 0x9165), false));  ASSERT_EQ("00100020", s);
2085     ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0008, 0x0052), false));  ASSERT_EQ("CS", s);
2086     ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0008, 0x0012), false));  ASSERT_EQ("DA", s);
2087     ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0010, 0x1020), false));  ASSERT_EQ("42", s);
2088     ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0008, 0x002a), false));  ASSERT_EQ("DT", s);
2089     ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0010, 0x9431), false));  ASSERT_EQ("43", s);
2090     ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0008, 0x1163), false));  ASSERT_EQ("44", s);
2091     ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0008, 0x1160), false));  ASSERT_EQ("45", s);
2092     ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0008, 0x0070), false));  ASSERT_EQ("LO", s);
2093     ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0010, 0x4000), false));  ASSERT_EQ("LT", s);
2094     ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0028, 0x2000), true));   ASSERT_EQ("OB", s);
2095     ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x7fe0, 0x0009), true));
2096 
2097 #if DCMTK_VERSION_NUMBER >= 361
2098     ASSERT_FLOAT_EQ(3.14159f, boost::lexical_cast<float>(s));
2099 #else
2100     ASSERT_EQ(8u, s.size()); // Because of padding
2101     ASSERT_EQ(0, s[7]);
2102     ASSERT_EQ("3.14159", s.substr(0, 7));
2103 #endif
2104 
2105     ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0064, 0x0009), true));
2106     ASSERT_FLOAT_EQ(2.71828f, boost::lexical_cast<float>(s));
2107     ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0028, 0x1201), true));   ASSERT_EQ("OWOW", s);
2108     ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0010, 0x0010), false));  ASSERT_EQ("PN", s);
2109     ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0008, 0x0050), false));  ASSERT_EQ("SH", s);
2110     ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0018, 0x6020), false));  ASSERT_EQ("-15", s);
2111     ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0018, 0x9219), false));  ASSERT_EQ("-16", s);
2112     ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0008, 0x0081), false));  ASSERT_EQ("ST", s);
2113     ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0008, 0x0013), false));  ASSERT_EQ("TM", s);
2114     ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0008, 0x0016), false));  ASSERT_EQ("UI", s);
2115     ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0008, 0x1161), false));  ASSERT_EQ("128", s);
2116     ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x4342, 0x1234), true));   ASSERT_EQ("UN", s);
2117     ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0040, 0x0031), false));  ASSERT_EQ("UT", s);
2118 
2119 #if DCMTK_VERSION_NUMBER >= 361
2120     ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0066, 0x0040), false));  ASSERT_EQ("46", s);
2121     ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0008, 0x0119), false));  ASSERT_EQ("UC", s);
2122     ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0008, 0x0120), false));  ASSERT_EQ("UR", s);
2123     ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0008, 0x0301), false));  ASSERT_EQ("17", s);
2124 #else
2125     ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0066, 0x0040), true));  ASSERT_EQ("46", s);  // OL
2126     ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0008, 0x0119), true));  ASSERT_EQ("UC", s);
2127     ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0008, 0x0120), true));  ASSERT_EQ("UR", s);
2128     ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0008, 0x0301), true));  ASSERT_EQ("17", s);  // US (but tag unknown to DCMTK 3.6.0)
2129 #endif
2130   }
2131 }
2132 
2133 
TEST(DicomWebJson,Sequence)2134 TEST(DicomWebJson, Sequence)
2135 {
2136   ParsedDicomFile dicom(false);
2137 
2138   {
2139     std::unique_ptr<DcmSequenceOfItems> sequence(new DcmSequenceOfItems(DCM_ReferencedSeriesSequence));
2140 
2141     for (unsigned int i = 0; i < 3; i++)
2142     {
2143       std::unique_ptr<DcmItem> item(new DcmItem);
2144       std::string s = "item" + boost::lexical_cast<std::string>(i);
2145       item->putAndInsertString(DCM_ReferencedSOPInstanceUID, s.c_str(), OFFalse);
2146       ASSERT_TRUE(sequence->insert(item.release(), false, false).good());
2147     }
2148 
2149     ASSERT_TRUE(dicom.GetDcmtkObject().getDataset()->insert(sequence.release(), false, false).good());
2150   }
2151 
2152   DicomWebJsonVisitor visitor;
2153   dicom.Apply(visitor);
2154 
2155   ASSERT_EQ("SQ", visitor.GetResult() ["00081115"]["vr"].asString());
2156   ASSERT_EQ(3u, visitor.GetResult() ["00081115"]["Value"].size());
2157 
2158   std::set<std::string> items;
2159 
2160   for (Json::Value::ArrayIndex i = 0; i < 3; i++)
2161   {
2162     ASSERT_EQ(1u, visitor.GetResult() ["00081115"]["Value"][i].size());
2163     ASSERT_EQ(1u, visitor.GetResult() ["00081115"]["Value"][i]["00081155"]["Value"].size());
2164     ASSERT_EQ("UI", visitor.GetResult() ["00081115"]["Value"][i]["00081155"]["vr"].asString());
2165     items.insert(visitor.GetResult() ["00081115"]["Value"][i]["00081155"]["Value"][0].asString());
2166   }
2167 
2168   ASSERT_EQ(3u, items.size());
2169   ASSERT_TRUE(items.find("item0") != items.end());
2170   ASSERT_TRUE(items.find("item1") != items.end());
2171   ASSERT_TRUE(items.find("item2") != items.end());
2172 
2173   std::string xml;
2174   visitor.FormatXml(xml);
2175 
2176   {
2177     DicomMap m;
2178     m.FromDicomWeb(visitor.GetResult());
2179     ASSERT_EQ(0u, m.GetSize());  // Sequences are not handled by DicomMap
2180   }
2181 }
2182 
2183 
TEST(ParsedDicomCache,Basic)2184 TEST(ParsedDicomCache, Basic)
2185 {
2186   ParsedDicomCache cache(10);
2187   ASSERT_EQ(0u, cache.GetCurrentSize());
2188   ASSERT_EQ(0u, cache.GetNumberOfItems());
2189 
2190   DicomMap tags;
2191   tags.SetValue(DICOM_TAG_PATIENT_ID, "patient1", false);
2192   cache.Acquire("a", new ParsedDicomFile(tags, Encoding_Latin1, true), 20);
2193   ASSERT_EQ(20u, cache.GetCurrentSize());
2194   ASSERT_EQ(1u, cache.GetNumberOfItems());
2195 
2196   {
2197     ParsedDicomCache::Accessor accessor(cache, "b");
2198     ASSERT_FALSE(accessor.IsValid());
2199     ASSERT_THROW(accessor.GetDicom(), OrthancException);
2200     ASSERT_THROW(accessor.GetFileSize(), OrthancException);
2201   }
2202 
2203   {
2204     ParsedDicomCache::Accessor accessor(cache, "a");
2205     ASSERT_TRUE(accessor.IsValid());
2206     std::string s;
2207     ASSERT_TRUE(accessor.GetDicom().GetTagValue(s, DICOM_TAG_PATIENT_ID));
2208     ASSERT_EQ("patient1", s);
2209     ASSERT_EQ(20u, accessor.GetFileSize());
2210   }
2211 
2212   tags.SetValue(DICOM_TAG_PATIENT_ID, "patient2", false);
2213   cache.Acquire("b", new ParsedDicomFile(tags, Encoding_Latin1, true), 5);
2214   ASSERT_EQ(5u, cache.GetCurrentSize());
2215   ASSERT_EQ(1u, cache.GetNumberOfItems());
2216 
2217   cache.Acquire("c", new ParsedDicomFile(true), 5);
2218   ASSERT_EQ(10u, cache.GetCurrentSize());
2219   ASSERT_EQ(2u, cache.GetNumberOfItems());
2220 
2221   {
2222     ParsedDicomCache::Accessor accessor(cache, "b");
2223     ASSERT_TRUE(accessor.IsValid());
2224     std::string s;
2225     ASSERT_TRUE(accessor.GetDicom().GetTagValue(s, DICOM_TAG_PATIENT_ID));
2226     ASSERT_EQ("patient2", s);
2227     ASSERT_EQ(5u, accessor.GetFileSize());
2228   }
2229 
2230   cache.Acquire("d", new ParsedDicomFile(true), 5);
2231   ASSERT_EQ(10u, cache.GetCurrentSize());
2232   ASSERT_EQ(2u, cache.GetNumberOfItems());
2233 
2234   ASSERT_TRUE(ParsedDicomCache::Accessor(cache, "b").IsValid());
2235   ASSERT_FALSE(ParsedDicomCache::Accessor(cache, "c").IsValid());  // recycled by LRU
2236   ASSERT_TRUE(ParsedDicomCache::Accessor(cache, "d").IsValid());
2237 
2238   cache.Invalidate("d");
2239   ASSERT_EQ(5u, cache.GetCurrentSize());
2240   ASSERT_EQ(1u, cache.GetNumberOfItems());
2241   ASSERT_TRUE(ParsedDicomCache::Accessor(cache, "b").IsValid());
2242   ASSERT_FALSE(ParsedDicomCache::Accessor(cache, "d").IsValid());
2243 
2244   cache.Acquire("e", new ParsedDicomFile(true), 15);
2245   ASSERT_EQ(15u, cache.GetCurrentSize());
2246   ASSERT_EQ(1u, cache.GetNumberOfItems());
2247 
2248   ASSERT_FALSE(ParsedDicomCache::Accessor(cache, "c").IsValid());
2249   ASSERT_FALSE(ParsedDicomCache::Accessor(cache, "d").IsValid());
2250   ASSERT_TRUE(ParsedDicomCache::Accessor(cache, "e").IsValid());
2251 
2252   cache.Invalidate("e");
2253   ASSERT_EQ(0u, cache.GetCurrentSize());
2254   ASSERT_EQ(0u, cache.GetNumberOfItems());
2255   ASSERT_FALSE(ParsedDicomCache::Accessor(cache, "e").IsValid());
2256 }
2257 
2258 
2259 
2260 
2261 #if ORTHANC_ENABLE_DCMTK_TRANSCODING == 1
2262 
2263 #include "../Sources/DicomNetworking/DicomStoreUserConnection.h"
2264 #include "../Sources/DicomParsing/DcmtkTranscoder.h"
2265 
TEST(Toto,DISABLED_Transcode3)2266 TEST(Toto, DISABLED_Transcode3)
2267 {
2268   DicomAssociationParameters p;
2269   p.SetRemotePort(2000);
2270 
2271   DicomStoreUserConnection scu(p);
2272   scu.SetCommonClassesProposed(false);
2273   scu.SetRetiredBigEndianProposed(true);
2274 
2275   DcmtkTranscoder transcoder;
2276 
2277   for (int j = 0; j < 2; j++)
2278     for (int i = 0; i <= DicomTransferSyntax_XML; i++)
2279     {
2280       DicomTransferSyntax a = (DicomTransferSyntax) i;
2281 
2282       std::string path = ("/home/jodogne/Subversion/orthanc-tests/Database/TransferSyntaxes/" +
2283                           std::string(GetTransferSyntaxUid(a)) + ".dcm");
2284       if (Orthanc::SystemToolbox::IsRegularFile(path))
2285       {
2286         printf("\n======= %s\n", GetTransferSyntaxUid(a));
2287 
2288         std::string source;
2289         Orthanc::SystemToolbox::ReadFile(source, path);
2290 
2291         std::string c, k;
2292         try
2293         {
2294           scu.Transcode(c, k, transcoder, source.c_str(), source.size(),
2295                         DicomTransferSyntax_LittleEndianExplicit, false, "", 0);
2296         }
2297         catch (OrthancException& e)
2298         {
2299           if (e.GetErrorCode() == ErrorCode_NotImplemented)
2300           {
2301             LOG(ERROR) << "cannot transcode " << GetTransferSyntaxUid(a);
2302           }
2303           else
2304           {
2305             throw;
2306           }
2307         }
2308       }
2309     }
2310 }
2311 
2312 
TEST(Toto,DISABLED_Transcode4)2313 TEST(Toto, DISABLED_Transcode4)
2314 {
2315   std::unique_ptr<DcmFileFormat> toto;
2316 
2317   {
2318     std::string source;
2319     Orthanc::SystemToolbox::ReadFile(source, "/home/jodogne/Subversion/orthanc-tests/Database/KarstenHilbertRF.dcm");
2320     toto.reset(FromDcmtkBridge::LoadFromMemoryBuffer(source.c_str(), source.size()));
2321   }
2322 
2323   const std::string sourceUid = IDicomTranscoder::GetSopInstanceUid(*toto);
2324 
2325   DicomTransferSyntax sourceSyntax;
2326   ASSERT_TRUE(FromDcmtkBridge::LookupOrthancTransferSyntax(sourceSyntax, *toto));
2327 
2328   DcmtkTranscoder transcoder;
2329 
2330   for (int i = 0; i <= DicomTransferSyntax_XML; i++)
2331   {
2332     DicomTransferSyntax a = (DicomTransferSyntax) i;
2333 
2334     std::set<DicomTransferSyntax> s;
2335     s.insert(a);
2336 
2337     std::string t;
2338 
2339     IDicomTranscoder::DicomImage source, target;
2340     source.AcquireParsed(dynamic_cast<DcmFileFormat*>(toto->clone()));
2341 
2342     if (!transcoder.Transcode(target, source, s, true))
2343     {
2344       printf("**************** CANNOT: [%s] => [%s]\n",
2345              GetTransferSyntaxUid(sourceSyntax), GetTransferSyntaxUid(a));
2346     }
2347     else
2348     {
2349       DicomTransferSyntax targetSyntax;
2350       ASSERT_TRUE(FromDcmtkBridge::LookupOrthancTransferSyntax(targetSyntax, target.GetParsed()));
2351 
2352       ASSERT_EQ(targetSyntax, a);
2353       bool lossy = (a == DicomTransferSyntax_JPEGProcess1 ||
2354                     a == DicomTransferSyntax_JPEGProcess2_4 ||
2355                     a == DicomTransferSyntax_JPEGLSLossy);
2356 
2357       printf("SIZE: %d\n", static_cast<int>(t.size()));
2358       if (sourceUid == IDicomTranscoder::GetSopInstanceUid(target.GetParsed()))
2359       {
2360         ASSERT_FALSE(lossy);
2361       }
2362       else
2363       {
2364         ASSERT_TRUE(lossy);
2365       }
2366     }
2367   }
2368 }
2369 
2370 #endif
2371