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