Orthanc/OrthancFramework/UnitTestsSources/FromDcmtkTests.cpp
2025-06-23 19:07:37 +05:30

3667 lines
143 KiB
C++

/**
* Orthanc - A Lightweight, RESTful DICOM Store
* Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
* Department, University Hospital of Liege, Belgium
* Copyright (C) 2017-2023 Osimis S.A., Belgium
* Copyright (C) 2024-2025 Orthanc Team SRL, Belgium
* Copyright (C) 2021-2025 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
*
* This program is free software: you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this program. If not, see
* <http://www.gnu.org/licenses/>.
**/
#if ORTHANC_UNIT_TESTS_LINK_FRAMEWORK == 1
// Must be the first to be sure to use the Orthanc framework shared library
# include <OrthancFramework.h>
#endif
#if !defined(ORTHANC_ENABLE_DCMTK_TRANSCODING)
# error ORTHANC_ENABLE_DCMTK_TRANSCODING is not defined
#endif
#if !defined(ORTHANC_ENABLE_PUGIXML)
# error ORTHANC_ENABLE_PUGIXML is not defined
#endif
#include <gtest/gtest.h>
#include "../Sources/Compatibility.h"
#include "../Sources/DicomFormat/DicomImageInformation.h"
#include "../Sources/DicomFormat/DicomPath.h"
#include "../Sources/DicomNetworking/DicomFindAnswers.h"
#include "../Sources/DicomParsing/DicomModification.h"
#include "../Sources/DicomParsing/DicomWebJsonVisitor.h"
#include "../Sources/DicomParsing/FromDcmtkBridge.h"
#include "../Sources/DicomParsing/ParsedDicomCache.h"
#include "../Sources/DicomParsing/ToDcmtkBridge.h"
#include "../Sources/Endianness.h"
#include "../Sources/Images/Image.h"
#include "../Sources/Images/ImageBuffer.h"
#include "../Sources/Images/ImageProcessing.h"
#include "../Sources/Images/PngReader.h"
#include "../Sources/Logging.h"
#include "../Sources/OrthancException.h"
#include "../Resources/CodeGeneration/EncodingTests.h"
#if ORTHANC_SANDBOXED != 1
# include "../Sources/SystemToolbox.h"
#endif
#include <dcmtk/dcmdata/dcdeftag.h>
#include <dcmtk/dcmdata/dcelem.h>
#include <dcmtk/dcmdata/dcvrat.h>
#include <dcmtk/dcmdata/dcpxitem.h>
#include <dcmtk/dcmdata/dcvrss.h>
#include <dcmtk/dcmdata/dcvrfl.h>
#include <boost/algorithm/string/predicate.hpp>
#include <boost/lexical_cast.hpp>
#if ORTHANC_ENABLE_PUGIXML == 1
# include <pugixml.hpp>
# if !defined(PUGIXML_VERSION)
# error PUGIXML_VERSION is not available
# endif
#endif
using namespace Orthanc;
TEST(DicomFormat, Tag)
{
ASSERT_EQ("PatientName", FromDcmtkBridge::GetTagName(DicomTag(0x0010, 0x0010), ""));
DicomTag t = FromDcmtkBridge::ParseTag("SeriesDescription");
ASSERT_EQ(0x0008, t.GetGroup());
ASSERT_EQ(0x103E, t.GetElement());
t = FromDcmtkBridge::ParseTag("0020-e040");
ASSERT_EQ(0x0020, t.GetGroup());
ASSERT_EQ(0xe040, t.GetElement());
// Test ==() and !=() operators
ASSERT_TRUE(DICOM_TAG_PATIENT_ID == DicomTag(0x0010, 0x0020));
ASSERT_FALSE(DICOM_TAG_PATIENT_ID != DicomTag(0x0010, 0x0020));
}
#if ORTHANC_SANDBOXED != 1
TEST(DicomModification, Basic)
{
DicomModification m;
m.SetupAnonymization(DicomVersion_2008);
//m.SetLevel(DicomRootLevel_Study);
//m.ReplacePlainString(DICOM_TAG_PATIENT_ID, "coucou");
//m.ReplacePlainString(DICOM_TAG_PATIENT_NAME, "coucou");
ParsedDicomFile o(true);
o.SaveToFile("UnitTestsResults/anon.dcm");
for (int i = 0; i < 10; i++)
{
char b[1024];
sprintf(b, "UnitTestsResults/anon%06d.dcm", i);
std::unique_ptr<ParsedDicomFile> f(o.Clone(false));
if (i > 4)
o.ReplacePlainString(DICOM_TAG_SERIES_INSTANCE_UID, "coucou");
m.Apply(*f);
f->SaveToFile(b);
}
}
#endif
TEST(DicomModification, Anonymization)
{
ASSERT_EQ(DICOM_TAG_PATIENT_NAME, FromDcmtkBridge::ParseTag("PatientName"));
const DicomTag privateTag(0x0045, 0x1010);
const DicomTag privateTag2(FromDcmtkBridge::ParseTag("0031-1020"));
ASSERT_TRUE(privateTag.IsPrivate());
ASSERT_TRUE(privateTag2.IsPrivate());
ASSERT_EQ(0x0031, privateTag2.GetGroup());
ASSERT_EQ(0x1020, privateTag2.GetElement());
std::string s;
ParsedDicomFile o(true);
o.ReplacePlainString(DICOM_TAG_PATIENT_NAME, "coucou");
ASSERT_FALSE(o.GetTagValue(s, privateTag));
o.Insert(privateTag, "private tag", false, "OrthancCreator");
ASSERT_TRUE(o.GetTagValue(s, privateTag));
ASSERT_STREQ("private tag", s.c_str());
ASSERT_FALSE(o.GetTagValue(s, privateTag2));
ASSERT_THROW(o.Replace(privateTag2, std::string("hello"), false, DicomReplaceMode_ThrowIfAbsent, "OrthancCreator"), OrthancException);
ASSERT_FALSE(o.GetTagValue(s, privateTag2));
o.Replace(privateTag2, std::string("hello"), false, DicomReplaceMode_IgnoreIfAbsent, "OrthancCreator");
ASSERT_FALSE(o.GetTagValue(s, privateTag2));
o.Replace(privateTag2, std::string("hello"), false, DicomReplaceMode_InsertIfAbsent, "OrthancCreator");
ASSERT_TRUE(o.GetTagValue(s, privateTag2));
ASSERT_STREQ("hello", s.c_str());
o.Replace(privateTag2, std::string("hello world"), false, DicomReplaceMode_InsertIfAbsent, "OrthancCreator");
ASSERT_TRUE(o.GetTagValue(s, privateTag2));
ASSERT_STREQ("hello world", s.c_str());
ASSERT_TRUE(o.GetTagValue(s, DICOM_TAG_PATIENT_NAME));
ASSERT_FALSE(Toolbox::IsUuid(s));
DicomModification m;
m.SetupAnonymization(DicomVersion_2008);
m.Keep(privateTag);
m.Apply(o);
ASSERT_TRUE(o.GetTagValue(s, DICOM_TAG_PATIENT_NAME));
ASSERT_TRUE(Toolbox::IsUuid(s));
ASSERT_TRUE(o.GetTagValue(s, privateTag));
ASSERT_STREQ("private tag", s.c_str());
m.SetupAnonymization(DicomVersion_2008);
m.Apply(o);
ASSERT_FALSE(o.GetTagValue(s, privateTag));
}
#include <dcmtk/dcmdata/dcuid.h>
TEST(DicomModification, Png)
{
// Red dot in http://en.wikipedia.org/wiki/Data_URI_scheme (RGBA image)
std::string s = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==";
std::string m, cc;
ASSERT_TRUE(Toolbox::DecodeDataUriScheme(m, cc, s));
ASSERT_EQ("image/png", m);
PngReader reader;
reader.ReadFromMemory(cc);
ASSERT_EQ(5u, reader.GetHeight());
ASSERT_EQ(5u, reader.GetWidth());
ASSERT_EQ(PixelFormat_RGBA32, reader.GetFormat());
ParsedDicomFile o(true);
o.EmbedContent(s);
#if ORTHANC_SANDBOXED != 1
o.SaveToFile("UnitTestsResults/png1.dcm");
#endif
// Red dot, without alpha channel
s = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAIAAAACDbGyAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3gUGDTcIn2+8BgAAACJJREFUCNdj/P//PwMjIwME/P/P+J8BBTAxEOL/R9Lx/z8AynoKAXOeiV8AAAAASUVORK5CYII=";
o.EmbedContent(s);
#if ORTHANC_SANDBOXED != 1
o.SaveToFile("UnitTestsResults/png2.dcm");
#endif
// Check box in Graylevel8
s = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAAAAAA6mKC9AAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3gUGDDcB53FulQAAAElJREFUGNNtj0sSAEEEQ1+U+185s1CtmRkblQ9CZldsKHJDk6DLGLJa6chjh0ooQmpjXMM86zPwydGEj6Ed/UGykkEM8X+p3u8/8LcOJIWLGeMAAAAASUVORK5CYII=";
o.EmbedContent(s);
//o.ReplacePlainString(DICOM_TAG_SOP_CLASS_UID, UID_DigitalXRayImageStorageForProcessing);
#if ORTHANC_SANDBOXED != 1
o.SaveToFile("UnitTestsResults/png3.dcm");
#endif
{
// Gradient in Graylevel16
ImageBuffer img;
img.SetWidth(256);
img.SetHeight(256);
img.SetFormat(PixelFormat_Grayscale16);
ImageAccessor accessor;
img.GetWriteableAccessor(accessor);
uint16_t v = 0;
for (unsigned int y = 0; y < img.GetHeight(); y++)
{
uint16_t *p = reinterpret_cast<uint16_t*>(accessor.GetRow(y));
for (unsigned int x = 0; x < img.GetWidth(); x++, p++, v++)
{
*p = v;
}
}
o.EmbedImage(accessor);
#if ORTHANC_SANDBOXED != 1
o.SaveToFile("UnitTestsResults/png4.dcm");
#endif
// From http://www.schaik.com/pngsuite/pngsuite_bas_png.html
// 16Bit RGBA PNG
// License http://www.schaik.com/pngsuite/PngSuite.LICENSE
s = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgEAYAAAAj6qa3AAAABGdBTUEAAYagMeiWXwAADSJJREFUeJzdmV9sHNd1xn/zj7NLck0u5VqOSwSgrIcEkQDKtNvYxlKJAstNEIgWIFkuUtQyWsCQW8mKlAJecf1iLLUGWsmKDCgwUMByigC25UKh0SaIXNMpiSiJHZoERAN+kEQ0lR1LkLhLL8nd4fzrwzl3qVVVNI9BHhbfzp07d+537r3nfOeMlaZpCtB8FwCaE+3YmLh9+x/LfStNG/8hfzPfgN6x5iZ98P/B5ubfr98fWn/TD5rvZrbVRt01W/AsQGYuMwf5clqWxnRMMDH4N4LxccFI28O/F3T12tHnnW8JWj9U1PvsUjTv2aL41zr+TxT1fvT0Le97RPGQYPBrRb3fHFU013/ZIr4pc6FaguZIZhxuMkCqNhLq2VK2BL3ldFiJTynerxM7rBPSdm9SJ6SjuM8I2nrf1vvWvYpP6du0PTXj36P4RPv4kRm/T3FECU+1YzOr+KhgY8oQb5Szo7USNDdl5gCCCX8buGunJDmmU1GbCfXO4c5hyJfTfu31VTWArmD0r4rzOrFP1AC2oPNFNcBDSvwLOp8HFHUnpfp8ohj/VsdNdNw/FVz9MyX8J4rPKuHLSlOfX5k3xFcmOwvVEjTHMqMAzdHMGEDwqv9U2w5IdO1am11tJ9S7NnRtgN5yuqh3/0snWteJXtGJfqQTm1FD/LsaYlYNoe2WYqrtiV7HipHBh5W4XgerSvi6Eo6V5oLgcov48uWugVoJGlPZAqwZINjgXwZYnejY1maAeJ9ORU+52exmzYV695buLZAvpz/Vu6d1ohU1gK5EcF7Q03ZH0VaXy48Uv6Pj6P34Ax1Hr1cVAzV88w0lrO3LvxNcmjXEl2a6B6slWFno7ANoTmaGAYLf+PcDBL/2/xwg/IG3r90ApxR1U5pTbja7WXOhnjuSOwK95eTv1AA6wXDrLRP+J0FXr+29gtb7OpoeheRVHUfPcHj4lnH+Qonr9fK/CNY/N8TrR3PFWgmW7+76DKARZx2AYMovAATH/MMA4WbvAkD4Je/jNh8QbVfUI9ByP3rKzWY3ay7Ue3p6eiBfTvSsRpHgqtmqDUHPE3QcNYClBkh1dN3KYajEA8GGPr+8rDR1Fost4ouLPXdUS7Bc6SoCrOztPA3QzGXqNxsgHPHGAcJN3hxAeM7b3rYDIvUBJqAZv27cmznlZrObNRfq+Xw+D73l5EkdRb10U3FF0VW0dqoBduhoxqvr8w29XlJcVKyOGOLVar63VoJ6PZcDWKl0FgGab2T2AAT9/hWA1cmOYYBwzBsFiH7ufg0gmnDbfYBjApiJtMYg6teNezOn3Gx2s+ZCfd3b696GfDk6p4ReVAPoynlK0Nb7iXr18DUl/leC9ecEa9rvRov4jR3rxqslWDzRcxBgebmrC6BZyRQBgkW/B2B1taMDIAw8HyCaczcBRE+7rwDEE067AWxdCyNhTCQ3Ac34dePezOk0m92suVBfv339dugth3NqAPXStf/Ut9zicZpNJa5xfuFTwavvGOJXz61/pFaC2kjvOMBStTsP0GhkswDBUf8IwOpDHecBoofdXwBEl9x7AaJT7j6AuMf5HCD5Z/tv23yArafYaDcjYUwkNwHN+HXj3swpN5vdrLlQ7z/Ufwjy5aYepKoqPrdXzVUTNF78+lnBKy8Z4leO93+3WoKFT/vuAag/l3sRoDGTHQQIAt8HCCteESB8wXseIJp3BwDi3wqD5An7dYD4J8IwmbBv2QH7BY1oNdrNSBgTyU1AM37duDdzys1mN2su1De+vPFl6C0vTkvrfz+m5lLC1+4QvHjAEL+4f+PJWgmun71zJ0C9L7cA0Phl9kGA4Fn/BEAYygyiilsEiD9wHgCIv+x8DJCctx8ESMbsUYB0s3UBIHnZ3t9mAEvPvFHrLdGq2s1IGBPJTUAzft24N3PKzWY3ay7Uh4aGhiBfvnxJ71YFpz80xKenh+6rluDagbtOAiwVuicBmlszkwDha/KGqOrmAeJYZpB83z4IkKyzbwAkX7V/BZBOWcMA6SVhlG6y5gDSCat9B/BNRY37Rq0b0Wq0m5EwJpKbgGb8unFv5pSbzW7WXKjvfnz349BbfnyPIX7mzd27aiWoVvN5gODH/k6AqCYjxIGTAUhG7HGA9Kz1GECaygzSilUE4Dsyw3TeGgBgigJA+qoyeksZ/pRvtvkAVNu18jM9Ai21brSWajcjYUwkNwHN+PVWvqin3Gx2s+Zr1GWE3bvOvAmQz0sP/0BwUgwoIzin4mcA7HJSArAeS88CWJY8bx1NjwAwyyCAVUnFIJMMA1hPpSK2dvEWAP/AP94+GzSJqckFTJqiO8CIVqPdjIQxkdwENOPXjXszp1w2+5k3YfeuWmn3Lvl/5q0zb+1+HGDovulpgLveu7YNoHtwaQYgM9ncCuA9KW9wq1EewHFkBnYlKQLYM8kWAHs+GQCwptICgDWWjgJYYeoBWBNp+xFIjDTRjLyVmJr8zKQrqtZbotVoNyNhTCTXgGb8uri36WkYuq9aqlYhn5dftQpD98m96Q+nPxwaAth48uJ+gDt3ygi5BRkxW2/kAPwTkgl6nszArURFAOcBmaEzHw8A2JNJAcB+XVJs64fC0H4lebrdAForMaUIk5G3ElM1gElTWmrdiFbdAUbCmEguAe3ifth4sla6dgDuOgnBj8HfCf4BCE7CXe/BtW2w8aT0vXjg4oGNLwM05zMDAH33yIi5F+UN2cHGDIDvywy8Y+H3ALznwxcA3MvRBgAnit2bDeBsji8A2Elit9cDDiphU4MxuYBR+SYxvSU/M2rdiFaj3UTCXDkO/d+tlq6fhTt3wlIBuifFM7i98otq0D0ISzPS5/pZCZ6ZAbjy0pWX+g8BhI945wCCEX8coHt2aRAgm5UZ+JWgCNAxu7oFwJ2PBgBcWxi4+6JTAM6meA7APpEcbM8G1Qe0ik+mBmNKEUYJ3pKfmTTFqHURrVfPwfpHaqWFT6HvHjkQuQVoboXMJMQBOBmJFfEz0tbcKn3qffLMwqdS+vLOwdV3rr6zfjtANO7uAAjf874Oa5I3c7R5BMDvkRl2fLT6FQDvE2HgjkUlAPcVYehMxO0+IPq2oskF9Ay3ajAmvzMZuRrC5GeSptzYAevGq6XaCPSOy4HIvQiNX0L2QXGR3pOQjIA9DnYZkpK0ha9Btg6NnDxTfw6CEfDHIRoHdwfcGLkxsu5tgKTXrgJEkevCTUpwj/cGgH8l6AfoeF8YeOMi1t2vRT8HcP8t+nabAUI9u61yo5G2WnwyNZhWKUIzcklMq1XI99ZKiyeg5yAsVaE7D40ZyA5K6co/AVEV3DykZ8F6TH7pWXCrEOWlT/CsPNOYge5ZWBqE8D3wvg5JL9hVqNaqNdELSWLbsKYEo9PuXoCo7uYAokl3+GYDeL8LvwDgHQqPt/mA8EuKps5qyo1adTPFJ1ODkVLE4iL03FEt1euQy8mB6OoSz5DNiov0fYkVnidB03Ek9luW/NJU2uJY+oShPBMEMkajISlXR4fknq4rSbhtw+Lni5/39AAkFbsIkHxm3w0QO04MEE25BQD/cHAMIDruHoLbpMPhbiVsCsymzqrpr9H2EtfrRyFXrJWWK9BVlH3RWYRmBTJFCI6Cf0RihleUKoJbhOT7YB+EtAJWEayjkB4BuwJJEdwKREXwjkH4PfArEBQhcxSatxuvAnYR6pV6JXcEIB0UzZr02QsA8ZRTAIjLTgnA3xxcAIj3OT9oM8CqOsFWZV3jvKmzSrlxaQa6B6ul5buh6zNY2Qudp8UzZPaIi/R7YPUh6DgP4QvgPS/qwXkAknVg30D05I+AWWBQMo1ki/SJP5BnwhegYxZWt8iYwSKEe8B7A6LT4O6F5DOw74Z0UMT60uzSbPcWgHTAugyQTNrDAMnP7EcBkk32HNymHhBoXG99UtDKuhSYly9D10CttLIAnX1yILIONHOQqUPQD/6Vm7bqw+D+QupJ7gDEXwbnYymx2r8SfWkNgFWBtAj2PCQD4MxDPADuZYg2gDsP0QB0fASrX5F3BP0Q1cHNQeyAE0PSB/YCpANgXYbl+eX5rg0A6ZRVaDOAZoXJMftwmw8ItOhpvqXIJ4WVSegsVEuNKcgWoDkJmWE5IH5hDVcnoWMYwgA8H6JL4N4rMsr5IiTnwX5QBLY1DEwBBWASGAZrCtKCJOFJAZwIYlfKMVEC3icSkDvel7gUTYI7LGrFLUA8BU4Bkkmwh/U9BViZWpnqlGxwzJJ0WLPB/1UPMAUN+YjUKEN2tFZqjkFmVMySGYXgN+DfD8Ex8A9LrPDGIRwDbxSiOXA3QXQK3H2iJ+3X5WuDPQrpJUm001cl37Se0v9jkI5q3yfW0N2nY41BVNJ3jayhf1jmEpfBKUHyM7AfXcN0DKxRaIw1xrIlgPSCJP7puDUCVppmtinxCfNxNHNBPiZm5/5vbG7+/fr9ofVvbgb5NJbZ1ny3NmqZZLb5LmS2iRluxsYEZG/T/kdx/xvwP2XY7MOt27XzAAAAAElFTkSuQmCC";
o.EmbedContent(s);
#if ORTHANC_SANDBOXED != 1
o.SaveToFile("UnitTestsResults/png5.dcm");
#endif
}
}
TEST(FromDcmtkBridge, Encodings1)
{
for (unsigned int i = 0; i < testEncodingsCount; i++)
{
std::string source(testEncodingsEncoded[i]);
std::string expected(testEncodingsExpected[i]);
std::string s = Toolbox::ConvertToUtf8(source, testEncodings[i], false);
//std::cout << EnumerationToString(testEncodings[i]) << std::endl;
EXPECT_EQ(expected, s);
}
}
TEST(FromDcmtkBridge, Enumerations)
{
// http://dicom.nema.org/medical/dicom/current/output/html/part03.html#sect_C.12.1.1.2
Encoding e;
ASSERT_FALSE(GetDicomEncoding(e, ""));
ASSERT_TRUE(GetDicomEncoding(e, "ISO_IR 6")); ASSERT_EQ(Encoding_Ascii, e);
// http://dicom.nema.org/medical/dicom/current/output/html/part03.html#table_C.12-2
ASSERT_TRUE(GetDicomEncoding(e, "ISO_IR 100")); ASSERT_EQ(Encoding_Latin1, e);
ASSERT_TRUE(GetDicomEncoding(e, "ISO_IR 101")); ASSERT_EQ(Encoding_Latin2, e);
ASSERT_TRUE(GetDicomEncoding(e, "ISO_IR 109")); ASSERT_EQ(Encoding_Latin3, e);
ASSERT_TRUE(GetDicomEncoding(e, "ISO_IR 110")); ASSERT_EQ(Encoding_Latin4, e);
ASSERT_TRUE(GetDicomEncoding(e, "ISO_IR 144")); ASSERT_EQ(Encoding_Cyrillic, e);
ASSERT_TRUE(GetDicomEncoding(e, "ISO_IR 127")); ASSERT_EQ(Encoding_Arabic, e);
ASSERT_TRUE(GetDicomEncoding(e, "ISO_IR 126")); ASSERT_EQ(Encoding_Greek, e);
ASSERT_TRUE(GetDicomEncoding(e, "ISO_IR 138")); ASSERT_EQ(Encoding_Hebrew, e);
ASSERT_TRUE(GetDicomEncoding(e, "ISO_IR 148")); ASSERT_EQ(Encoding_Latin5, e);
ASSERT_TRUE(GetDicomEncoding(e, "ISO_IR 13")); ASSERT_EQ(Encoding_Japanese, e);
ASSERT_TRUE(GetDicomEncoding(e, "ISO_IR 166")); ASSERT_EQ(Encoding_Thai, e);
// http://dicom.nema.org/medical/dicom/current/output/html/part03.html#table_C.12-3
ASSERT_TRUE(GetDicomEncoding(e, "ISO 2022 IR 6")); ASSERT_EQ(Encoding_Ascii, e);
ASSERT_TRUE(GetDicomEncoding(e, "ISO 2022 IR 100")); ASSERT_EQ(Encoding_Latin1, e);
ASSERT_TRUE(GetDicomEncoding(e, "ISO 2022 IR 101")); ASSERT_EQ(Encoding_Latin2, e);
ASSERT_TRUE(GetDicomEncoding(e, "ISO 2022 IR 109")); ASSERT_EQ(Encoding_Latin3, e);
ASSERT_TRUE(GetDicomEncoding(e, "ISO 2022 IR 110")); ASSERT_EQ(Encoding_Latin4, e);
ASSERT_TRUE(GetDicomEncoding(e, "ISO 2022 IR 144")); ASSERT_EQ(Encoding_Cyrillic, e);
ASSERT_TRUE(GetDicomEncoding(e, "ISO 2022 IR 127")); ASSERT_EQ(Encoding_Arabic, e);
ASSERT_TRUE(GetDicomEncoding(e, "ISO 2022 IR 126")); ASSERT_EQ(Encoding_Greek, e);
ASSERT_TRUE(GetDicomEncoding(e, "ISO 2022 IR 138")); ASSERT_EQ(Encoding_Hebrew, e);
ASSERT_TRUE(GetDicomEncoding(e, "ISO 2022 IR 148")); ASSERT_EQ(Encoding_Latin5, e);
ASSERT_TRUE(GetDicomEncoding(e, "ISO 2022 IR 13")); ASSERT_EQ(Encoding_Japanese, e);
ASSERT_TRUE(GetDicomEncoding(e, "ISO 2022 IR 166")); ASSERT_EQ(Encoding_Thai, e);
// http://dicom.nema.org/medical/dicom/current/output/html/part03.html#table_C.12-4
ASSERT_TRUE(GetDicomEncoding(e, "ISO 2022 IR 87")); ASSERT_EQ(Encoding_JapaneseKanji, e);
ASSERT_FALSE(GetDicomEncoding(e, "ISO 2022 IR 159")); //ASSERT_EQ(Encoding_JapaneseKanjiSupplementary, e);
ASSERT_TRUE(GetDicomEncoding(e, "ISO 2022 IR 149")); ASSERT_EQ(Encoding_Korean, e);
ASSERT_TRUE(GetDicomEncoding(e, "ISO 2022 IR 58")); ASSERT_EQ(Encoding_SimplifiedChinese, e);
// http://dicom.nema.org/medical/dicom/current/output/html/part03.html#table_C.12-5
ASSERT_TRUE(GetDicomEncoding(e, "ISO_IR 192")); ASSERT_EQ(Encoding_Utf8, e);
ASSERT_TRUE(GetDicomEncoding(e, "GB18030")); ASSERT_EQ(Encoding_Chinese, e);
ASSERT_TRUE(GetDicomEncoding(e, "GBK")); ASSERT_EQ(Encoding_Chinese, e);
// common spelling mistakes
ASSERT_TRUE(GetDicomEncoding(e, "ISO_IR_100")); ASSERT_EQ(Encoding_Latin1, e);
ASSERT_TRUE(GetDicomEncoding(e, "ISO_2022_IR_6")); ASSERT_EQ(Encoding_Ascii, e);
}
TEST(FromDcmtkBridge, Encodings3)
{
for (unsigned int i = 0; i < testEncodingsCount; i++)
{
//std::cout << EnumerationToString(testEncodings[i]) << std::endl;
std::string dicom;
{
ParsedDicomFile f(true);
f.SetEncoding(testEncodings[i]);
std::string s = Toolbox::ConvertToUtf8(testEncodingsEncoded[i], testEncodings[i], false);
f.Insert(DICOM_TAG_PATIENT_NAME, s, false, "");
f.SaveToMemoryBuffer(dicom);
}
if (testEncodings[i] != Encoding_Windows1251)
{
ParsedDicomFile g(dicom);
if (testEncodings[i] != Encoding_Ascii)
{
bool hasCodeExtensions;
ASSERT_EQ(testEncodings[i], g.DetectEncoding(hasCodeExtensions));
ASSERT_FALSE(hasCodeExtensions);
}
std::string tag;
ASSERT_TRUE(g.GetTagValue(tag, DICOM_TAG_PATIENT_NAME));
ASSERT_EQ(std::string(testEncodingsExpected[i]), tag);
}
}
}
TEST(FromDcmtkBridge, ValueRepresentation)
{
ASSERT_EQ(ValueRepresentation_PersonName,
FromDcmtkBridge::LookupValueRepresentation(DICOM_TAG_PATIENT_NAME));
ASSERT_EQ(ValueRepresentation_Date,
FromDcmtkBridge::LookupValueRepresentation(DicomTag(0x0008, 0x0020) /* StudyDate */));
ASSERT_EQ(ValueRepresentation_Time,
FromDcmtkBridge::LookupValueRepresentation(DicomTag(0x0008, 0x0030) /* StudyTime */));
ASSERT_EQ(ValueRepresentation_DateTime,
FromDcmtkBridge::LookupValueRepresentation(DicomTag(0x0008, 0x002a) /* AcquisitionDateTime */));
ASSERT_EQ(ValueRepresentation_NotSupported,
FromDcmtkBridge::LookupValueRepresentation(DicomTag(0x0001, 0x0001) /* some private tag */));
}
TEST(FromDcmtkBridge, ParseListOfTags)
{
{// nominal test
std::string source = "0010,0010;PatientBirthDate;0020,0020";
std::set<DicomTag> result;
FromDcmtkBridge::ParseListOfTags(result, source);
ASSERT_TRUE(result.find(DICOM_TAG_PATIENT_NAME) != result.end());
ASSERT_TRUE(result.find(DICOM_TAG_PATIENT_BIRTH_DATE) != result.end());
ASSERT_TRUE(result.find(DICOM_TAG_PATIENT_ORIENTATION) != result.end());
ASSERT_TRUE(result.find(DICOM_TAG_PATIENT_ID) == result.end());
// serialize to string
std::string serialized;
FromDcmtkBridge::FormatListOfTags(serialized, result);
ASSERT_EQ("0010,0010;0010,0030;0020,0020", serialized);
}
{// no tag
std::string source = "";
std::set<DicomTag> result;
FromDcmtkBridge::ParseListOfTags(result, source);
ASSERT_EQ(0u, result.size());
}
{// invalid tag
std::string source = "0010,0010;Patient-BirthDate;0020,0020";
std::set<DicomTag> result;
ASSERT_THROW(FromDcmtkBridge::ParseListOfTags(result, source), OrthancException);
}
{// duplicate tag only once
std::string source = "0010,0010;PatientName";
std::set<DicomTag> result;
FromDcmtkBridge::ParseListOfTags(result, source);
ASSERT_EQ(1u, result.size());
}
{// Json
Json::Value source = Json::arrayValue;
source.append("0010,0010");
source.append("PatientBirthDate");
source.append("0020,0020");
std::set<DicomTag> result;
FromDcmtkBridge::ParseListOfTags(result, source);
ASSERT_TRUE(result.find(DICOM_TAG_PATIENT_NAME) != result.end());
ASSERT_TRUE(result.find(DICOM_TAG_PATIENT_BIRTH_DATE) != result.end());
ASSERT_TRUE(result.find(DICOM_TAG_PATIENT_ORIENTATION) != result.end());
ASSERT_TRUE(result.find(DICOM_TAG_PATIENT_ID) == result.end());
}
}
static const DicomTag REFERENCED_STUDY_SEQUENCE(0x0008, 0x1110);
static const DicomTag REFERENCED_PATIENT_SEQUENCE(0x0008, 0x1120);
static void CreateSampleJson(Json::Value& a)
{
{
Json::Value b = Json::objectValue;
b["PatientName"] = "Hello";
b["PatientID"] = "World";
b["StudyDescription"] = "Toto";
a.append(b);
}
{
Json::Value b = Json::objectValue;
b["PatientName"] = "data:application/octet-stream;base64,SGVsbG8y"; // echo -n "Hello2" | base64
b["PatientID"] = "World2";
a.append(b);
}
}
TEST(ParsedDicomFile, InsertReplaceStrings)
{
ParsedDicomFile f(true);
f.Insert(DICOM_TAG_PATIENT_NAME, "World", false, "");
ASSERT_THROW(f.Insert(DICOM_TAG_PATIENT_ID, "Hello", false, ""), OrthancException); // Already existing tag
f.ReplacePlainString(DICOM_TAG_SOP_INSTANCE_UID, "Toto"); // (*)
f.ReplacePlainString(DICOM_TAG_SOP_CLASS_UID, "Tata"); // (**)
DicomTransferSyntax syntax;
ASSERT_TRUE(f.LookupTransferSyntax(syntax));
// The default transfer syntax depends on the OS endianness
ASSERT_TRUE(syntax == DicomTransferSyntax_LittleEndianExplicit ||
syntax == DicomTransferSyntax_BigEndianExplicit);
ASSERT_THROW(f.Replace(DICOM_TAG_ACCESSION_NUMBER, std::string("Accession"),
false, DicomReplaceMode_ThrowIfAbsent, ""), OrthancException);
f.Replace(DICOM_TAG_ACCESSION_NUMBER, std::string("Accession"), false, DicomReplaceMode_IgnoreIfAbsent, "");
std::string s;
ASSERT_FALSE(f.GetTagValue(s, DICOM_TAG_ACCESSION_NUMBER));
f.Replace(DICOM_TAG_ACCESSION_NUMBER, std::string("Accession"), false, DicomReplaceMode_InsertIfAbsent, "");
ASSERT_TRUE(f.GetTagValue(s, DICOM_TAG_ACCESSION_NUMBER));
ASSERT_EQ(s, "Accession");
f.Replace(DICOM_TAG_ACCESSION_NUMBER, std::string("Accession2"), false, DicomReplaceMode_IgnoreIfAbsent, "");
ASSERT_TRUE(f.GetTagValue(s, DICOM_TAG_ACCESSION_NUMBER));
ASSERT_EQ(s, "Accession2");
f.Replace(DICOM_TAG_ACCESSION_NUMBER, std::string("Accession3"), false, DicomReplaceMode_ThrowIfAbsent, "");
ASSERT_TRUE(f.GetTagValue(s, DICOM_TAG_ACCESSION_NUMBER));
ASSERT_EQ(s, "Accession3");
ASSERT_TRUE(f.GetTagValue(s, DICOM_TAG_PATIENT_NAME));
ASSERT_EQ(s, "World");
ASSERT_TRUE(f.GetTagValue(s, DICOM_TAG_SOP_INSTANCE_UID));
ASSERT_EQ(s, "Toto");
ASSERT_TRUE(f.GetTagValue(s, DICOM_TAG_MEDIA_STORAGE_SOP_INSTANCE_UID)); // Implicitly modified by (*)
ASSERT_EQ(s, "Toto");
ASSERT_TRUE(f.GetTagValue(s, DICOM_TAG_SOP_CLASS_UID));
ASSERT_EQ(s, "Tata");
ASSERT_TRUE(f.GetTagValue(s, DICOM_TAG_MEDIA_STORAGE_SOP_CLASS_UID)); // Implicitly modified by (**)
ASSERT_EQ(s, "Tata");
}
TEST(ParsedDicomFile, InsertReplaceJson)
{
ParsedDicomFile f(true);
Json::Value a;
CreateSampleJson(a);
ASSERT_FALSE(f.HasTag(REFERENCED_STUDY_SEQUENCE));
f.Remove(REFERENCED_STUDY_SEQUENCE); // No effect
f.Insert(REFERENCED_STUDY_SEQUENCE, a, true, "");
ASSERT_TRUE(f.HasTag(REFERENCED_STUDY_SEQUENCE));
ASSERT_THROW(f.Insert(REFERENCED_STUDY_SEQUENCE, a, true, ""), OrthancException);
f.Remove(REFERENCED_STUDY_SEQUENCE);
ASSERT_FALSE(f.HasTag(REFERENCED_STUDY_SEQUENCE));
f.Insert(REFERENCED_STUDY_SEQUENCE, a, true, "");
ASSERT_TRUE(f.HasTag(REFERENCED_STUDY_SEQUENCE));
ASSERT_FALSE(f.HasTag(REFERENCED_PATIENT_SEQUENCE));
ASSERT_THROW(f.Replace(REFERENCED_PATIENT_SEQUENCE, a, false, DicomReplaceMode_ThrowIfAbsent, ""), OrthancException);
ASSERT_FALSE(f.HasTag(REFERENCED_PATIENT_SEQUENCE));
f.Replace(REFERENCED_PATIENT_SEQUENCE, a, false, DicomReplaceMode_IgnoreIfAbsent, "");
ASSERT_FALSE(f.HasTag(REFERENCED_PATIENT_SEQUENCE));
f.Replace(REFERENCED_PATIENT_SEQUENCE, a, false, DicomReplaceMode_InsertIfAbsent, "");
ASSERT_TRUE(f.HasTag(REFERENCED_PATIENT_SEQUENCE));
{
Json::Value b;
f.DatasetToJson(b, DicomToJsonFormat_Full, DicomToJsonFlags_Default, 0);
Json::Value c;
Toolbox::SimplifyDicomAsJson(c, b, DicomToJsonFormat_Human);
ASSERT_EQ(0, c["ReferencedPatientSequence"].compare(a));
ASSERT_NE(0, c["ReferencedStudySequence"].compare(a)); // Because Data URI Scheme decoding was enabled
}
a = "data:application/octet-stream;base64,VGF0YQ=="; // echo -n "Tata" | base64
f.Replace(DICOM_TAG_SOP_INSTANCE_UID, a, false, DicomReplaceMode_InsertIfAbsent, ""); // (*)
f.Replace(DICOM_TAG_SOP_CLASS_UID, a, true, DicomReplaceMode_InsertIfAbsent, ""); // (**)
std::string s;
ASSERT_TRUE(f.GetTagValue(s, DICOM_TAG_SOP_INSTANCE_UID));
ASSERT_EQ(s, a.asString());
ASSERT_TRUE(f.GetTagValue(s, DICOM_TAG_MEDIA_STORAGE_SOP_INSTANCE_UID)); // Implicitly modified by (*)
ASSERT_EQ(s, a.asString());
ASSERT_TRUE(f.GetTagValue(s, DICOM_TAG_SOP_CLASS_UID));
ASSERT_EQ(s, "Tata");
ASSERT_TRUE(f.GetTagValue(s, DICOM_TAG_MEDIA_STORAGE_SOP_CLASS_UID)); // Implicitly modified by (**)
ASSERT_EQ(s, "Tata");
}
TEST(ParsedDicomFile, JsonEncoding)
{
ParsedDicomFile f(true);
for (unsigned int i = 0; i < testEncodingsCount; i++)
{
if (testEncodings[i] != Encoding_Windows1251)
{
//std::cout << EnumerationToString(testEncodings[i]) << std::endl;
f.SetEncoding(testEncodings[i]);
if (testEncodings[i] != Encoding_Ascii)
{
bool hasCodeExtensions;
ASSERT_EQ(testEncodings[i], f.DetectEncoding(hasCodeExtensions));
ASSERT_FALSE(hasCodeExtensions);
}
Json::Value s = Toolbox::ConvertToUtf8(testEncodingsEncoded[i], testEncodings[i], false);
f.Replace(DICOM_TAG_PATIENT_NAME, s, false, DicomReplaceMode_InsertIfAbsent, "");
Json::Value v;
f.DatasetToJson(v, DicomToJsonFormat_Human, DicomToJsonFlags_Default, 0);
ASSERT_EQ(v["PatientName"].asString(), std::string(testEncodingsExpected[i]));
}
}
}
TEST(ParsedDicomFile, ToJsonFlags1)
{
FromDcmtkBridge::RegisterDictionaryTag(DicomTag(0x7053, 0x1000), ValueRepresentation_OtherByte, "MyPrivateTag", 1, 1, "OrthancCreator");
FromDcmtkBridge::RegisterDictionaryTag(DicomTag(0x7050, 0x1000), ValueRepresentation_PersonName, "Declared public tag", 1, 1, "");
ParsedDicomFile f(true);
f.Insert(DicomTag(0x7050, 0x1000), "Some public tag", false, ""); // Even group => public tag
f.Insert(DicomTag(0x7052, 0x1000), "Some unknown tag", false, ""); // Even group => public, unknown tag
f.Insert(DicomTag(0x7053, 0x1000), "Some private tag", false, "OrthancCreator"); // Odd group => private tag
Json::Value v;
f.DatasetToJson(v, DicomToJsonFormat_Short, DicomToJsonFlags_None, 0);
ASSERT_EQ(Json::objectValue, v.type());
ASSERT_EQ(6u, v.getMemberNames().size());
ASSERT_FALSE(v.isMember("7052,1000"));
ASSERT_FALSE(v.isMember("7053,1000"));
ASSERT_TRUE(v.isMember("7050,1000"));
ASSERT_EQ(Json::stringValue, v["7050,1000"].type());
ASSERT_EQ("Some public tag", v["7050,1000"].asString());
f.DatasetToJson(v, DicomToJsonFormat_Short, static_cast<DicomToJsonFlags>(DicomToJsonFlags_IncludePrivateTags | DicomToJsonFlags_IncludeBinary | DicomToJsonFlags_ConvertBinaryToNull), 0);
ASSERT_EQ(Json::objectValue, v.type());
ASSERT_EQ(7u, v.getMemberNames().size());
ASSERT_FALSE(v.isMember("7052,1000"));
ASSERT_TRUE(v.isMember("7050,1000"));
ASSERT_TRUE(v.isMember("7053,1000"));
ASSERT_EQ("Some public tag", v["7050,1000"].asString());
ASSERT_EQ(Json::nullValue, v["7053,1000"].type());
f.DatasetToJson(v, DicomToJsonFormat_Short, static_cast<DicomToJsonFlags>(DicomToJsonFlags_IncludePrivateTags), 0);
ASSERT_EQ(Json::objectValue, v.type());
ASSERT_EQ(6u, v.getMemberNames().size());
ASSERT_FALSE(v.isMember("7052,1000"));
ASSERT_TRUE(v.isMember("7050,1000"));
ASSERT_FALSE(v.isMember("7053,1000"));
f.DatasetToJson(v, DicomToJsonFormat_Short, static_cast<DicomToJsonFlags>(DicomToJsonFlags_IncludePrivateTags | DicomToJsonFlags_IncludeBinary), 0);
ASSERT_EQ(Json::objectValue, v.type());
ASSERT_EQ(7u, v.getMemberNames().size());
ASSERT_FALSE(v.isMember("7052,1000"));
ASSERT_TRUE(v.isMember("7050,1000"));
ASSERT_TRUE(v.isMember("7053,1000"));
ASSERT_EQ("Some public tag", v["7050,1000"].asString());
std::string mime, content;
ASSERT_EQ(Json::stringValue, v["7053,1000"].type());
ASSERT_TRUE(Toolbox::DecodeDataUriScheme(mime, content, v["7053,1000"].asString()));
ASSERT_EQ("application/octet-stream", mime);
ASSERT_EQ("Some private tag", content);
f.DatasetToJson(v, DicomToJsonFormat_Short, static_cast<DicomToJsonFlags>(DicomToJsonFlags_IncludeUnknownTags | DicomToJsonFlags_IncludeBinary | DicomToJsonFlags_ConvertBinaryToNull), 0);
ASSERT_EQ(Json::objectValue, v.type());
ASSERT_EQ(7u, v.getMemberNames().size());
ASSERT_TRUE(v.isMember("7050,1000"));
ASSERT_TRUE(v.isMember("7052,1000"));
ASSERT_FALSE(v.isMember("7053,1000"));
ASSERT_EQ("Some public tag", v["7050,1000"].asString());
ASSERT_EQ(Json::nullValue, v["7052,1000"].type());
f.DatasetToJson(v, DicomToJsonFormat_Short, static_cast<DicomToJsonFlags>(DicomToJsonFlags_IncludeUnknownTags | DicomToJsonFlags_IncludeBinary), 0);
ASSERT_EQ(Json::objectValue, v.type());
ASSERT_EQ(7u, v.getMemberNames().size());
ASSERT_TRUE(v.isMember("7050,1000"));
ASSERT_TRUE(v.isMember("7052,1000"));
ASSERT_FALSE(v.isMember("7053,1000"));
ASSERT_EQ("Some public tag", v["7050,1000"].asString());
ASSERT_EQ(Json::stringValue, v["7052,1000"].type());
ASSERT_TRUE(Toolbox::DecodeDataUriScheme(mime, content, v["7052,1000"].asString()));
ASSERT_EQ("application/octet-stream", mime);
ASSERT_EQ("Some unknown tag", content);
f.DatasetToJson(v, DicomToJsonFormat_Short, static_cast<DicomToJsonFlags>(DicomToJsonFlags_IncludeUnknownTags | DicomToJsonFlags_IncludePrivateTags | DicomToJsonFlags_IncludeBinary | DicomToJsonFlags_ConvertBinaryToNull), 0);
ASSERT_EQ(Json::objectValue, v.type());
ASSERT_EQ(8u, v.getMemberNames().size());
ASSERT_TRUE(v.isMember("7050,1000"));
ASSERT_TRUE(v.isMember("7052,1000"));
ASSERT_TRUE(v.isMember("7053,1000"));
ASSERT_EQ("Some public tag", v["7050,1000"].asString());
ASSERT_EQ(Json::nullValue, v["7052,1000"].type());
ASSERT_EQ(Json::nullValue, v["7053,1000"].type());
}
TEST(ParsedDicomFile, ToJsonFlags2)
{
ParsedDicomFile f(true);
{
// "ParsedDicomFile" uses Little Endian => 'B' (least significant
// byte) will be stored first in the memory buffer and in the
// file, then 'A'. Hence the expected "BA" value below.
Uint16 v[] = { 'A' * 256 + 'B', 0 };
ASSERT_TRUE(f.GetDcmtkObject().getDataset()->putAndInsertUint16Array(DCM_PixelData, v, 2).good());
}
Json::Value v;
f.DatasetToJson(v, DicomToJsonFormat_Short, DicomToJsonFlags_None, 0);
ASSERT_EQ(Json::objectValue, v.type());
ASSERT_EQ(5u, v.getMemberNames().size());
ASSERT_FALSE(v.isMember("7fe0,0010"));
f.DatasetToJson(v, DicomToJsonFormat_Short, static_cast<DicomToJsonFlags>(DicomToJsonFlags_IncludePixelData | DicomToJsonFlags_ConvertBinaryToNull), 0);
ASSERT_EQ(Json::objectValue, v.type());
ASSERT_EQ(6u, v.getMemberNames().size());
ASSERT_TRUE(v.isMember("7fe0,0010"));
ASSERT_EQ(Json::nullValue, v["7fe0,0010"].type());
f.DatasetToJson(v, DicomToJsonFormat_Short, static_cast<DicomToJsonFlags>(DicomToJsonFlags_IncludePixelData | DicomToJsonFlags_ConvertBinaryToAscii), 0);
ASSERT_EQ(Json::objectValue, v.type());
ASSERT_EQ(6u, v.getMemberNames().size());
ASSERT_TRUE(v.isMember("7fe0,0010"));
ASSERT_EQ(Json::stringValue, v["7fe0,0010"].type());
ASSERT_EQ("BA", v["7fe0,0010"].asString().substr(0, 2));
f.DatasetToJson(v, DicomToJsonFormat_Short, DicomToJsonFlags_IncludePixelData, 0);
ASSERT_EQ(Json::objectValue, v.type());
ASSERT_EQ(6u, v.getMemberNames().size());
ASSERT_TRUE(v.isMember("7fe0,0010"));
ASSERT_EQ(Json::stringValue, v["7fe0,0010"].type());
std::string mime, content;
ASSERT_TRUE(Toolbox::DecodeDataUriScheme(mime, content, v["7fe0,0010"].asString()));
ASSERT_EQ("application/octet-stream", mime);
ASSERT_EQ("BA", content.substr(0, 2));
}
TEST(ParsedDicomFile, ToJsonFlags3)
{
ParsedDicomFile f(false);
{
Uint8 v[2] = { 0, 0 };
ASSERT_TRUE(f.GetDcmtkObject().getDataset()->putAndInsertString(DCM_PatientName, "HELLO^").good());
ASSERT_TRUE(f.GetDcmtkObject().getDataset()->putAndInsertUint32(DcmTag(0x4000, 0x0000), 42).good());
ASSERT_TRUE(f.GetDcmtkObject().getDataset()->putAndInsertUint8Array(DCM_PixelData, v, 2).good());
ASSERT_TRUE(f.GetDcmtkObject().getDataset()->putAndInsertString(DcmTag(0x07fe1, 0x0010), "WORLD^").good());
}
std::string s;
Toolbox::EncodeDataUriScheme(s, "application/octet-stream", std::string(2, '\0'));
{
Json::Value v;
f.DatasetToJson(v, DicomToJsonFormat_Short, static_cast<DicomToJsonFlags>(DicomToJsonFlags_IncludePrivateTags | DicomToJsonFlags_IncludePixelData | DicomToJsonFlags_StopAfterPixelData), 0);
ASSERT_EQ(Json::objectValue, v.type());
ASSERT_EQ(3u, v.size());
ASSERT_EQ("HELLO^", v["0010,0010"].asString());
ASSERT_EQ("42", v["4000,0000"].asString());
ASSERT_EQ(s, v["7fe0,0010"].asString());
}
{
Json::Value v;
f.DatasetToJson(v, DicomToJsonFormat_Short, static_cast<DicomToJsonFlags>(DicomToJsonFlags_IncludePrivateTags | DicomToJsonFlags_SkipGroupLengths), 0);
ASSERT_EQ(Json::objectValue, v.type());
ASSERT_EQ(2u, v.size());
ASSERT_EQ("HELLO^", v["0010,0010"].asString());
ASSERT_EQ("WORLD^", v["7fe1,0010"].asString());
}
}
TEST(DicomFindAnswers, Basic)
{
DicomFindAnswers a(false);
{
DicomMap m;
m.SetValue(DICOM_TAG_PATIENT_ID, "hello", false);
a.Add(m);
}
{
ParsedDicomFile d(true);
d.ReplacePlainString(DICOM_TAG_PATIENT_ID, "my");
a.Add(d);
}
{
DicomMap m;
m.SetValue(DICOM_TAG_PATIENT_ID, "world", false);
a.Add(m);
}
Json::Value j;
a.ToJson(j, DicomToJsonFormat_Human);
ASSERT_EQ(3u, j.size());
//std::cout << j;
}
TEST(ParsedDicomFile, FromJson)
{
FromDcmtkBridge::RegisterDictionaryTag(DicomTag(0x7057, 0x1000), ValueRepresentation_OtherByte, "MyPrivateTag2", 1, 1, "ORTHANC");
FromDcmtkBridge::RegisterDictionaryTag(DicomTag(0x7059, 0x1000), ValueRepresentation_OtherByte, "MyPrivateTag3", 1, 1, "");
FromDcmtkBridge::RegisterDictionaryTag(DicomTag(0x7050, 0x1002), ValueRepresentation_PersonName, "Declared public tag2", 1, 1, "");
Json::Value v;
const std::string sopClassUid = "1.2.840.10008.5.1.4.1.1.1"; // CR Image Storage:
// Test the private creator
ASSERT_EQ(DcmTag_ERROR_TagName, FromDcmtkBridge::GetTagName(DicomTag(0x7057, 0x1000), "NOPE"));
ASSERT_EQ("MyPrivateTag2", FromDcmtkBridge::GetTagName(DicomTag(0x7057, 0x1000), "ORTHANC"));
{
v["SOPClassUID"] = sopClassUid;
v["SpecificCharacterSet"] = "ISO_IR 148"; // This is latin-5
v["PatientName"] = "Sébastien";
v["7050-1002"] = "Some public tag"; // Even group => public tag
v["7052-1000"] = "Some unknown tag"; // Even group => public, unknown tag
v["7057-1000"] = "Some private tag"; // Odd group => private tag
v["7059-1000"] = "Some private tag2"; // Odd group => private tag, with an odd length to test padding
std::string s;
Toolbox::EncodeDataUriScheme(s, "application/octet-stream", "Sebastien");
v["StudyDescription"] = s;
v["PixelData"] = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg=="; // A red dot of 5x5 pixels
v["0040,0100"] = Json::arrayValue; // ScheduledProcedureStepSequence
Json::Value vv;
vv["Modality"] = "MR";
v["0040,0100"].append(vv);
vv["Modality"] = "CT";
v["0040,0100"].append(vv);
}
const DicomToJsonFlags toJsonFlags = static_cast<DicomToJsonFlags>(DicomToJsonFlags_IncludeBinary |
DicomToJsonFlags_IncludePixelData |
DicomToJsonFlags_IncludePrivateTags |
DicomToJsonFlags_IncludeUnknownTags |
DicomToJsonFlags_ConvertBinaryToAscii);
{
std::unique_ptr<ParsedDicomFile> dicom
(ParsedDicomFile::CreateFromJson(v, static_cast<DicomFromJsonFlags>(DicomFromJsonFlags_GenerateIdentifiers), ""));
Json::Value vv;
dicom->DatasetToJson(vv, DicomToJsonFormat_Human, toJsonFlags, 0);
ASSERT_EQ(vv["SOPClassUID"].asString(), sopClassUid);
ASSERT_EQ(vv["MediaStorageSOPClassUID"].asString(), sopClassUid);
ASSERT_TRUE(vv.isMember("SOPInstanceUID"));
ASSERT_TRUE(vv.isMember("SeriesInstanceUID"));
ASSERT_TRUE(vv.isMember("StudyInstanceUID"));
ASSERT_TRUE(vv.isMember("PatientID"));
}
{
std::unique_ptr<ParsedDicomFile> dicom
(ParsedDicomFile::CreateFromJson(v, static_cast<DicomFromJsonFlags>(DicomFromJsonFlags_GenerateIdentifiers), ""));
Json::Value vv;
dicom->DatasetToJson(vv, DicomToJsonFormat_Human, static_cast<DicomToJsonFlags>(DicomToJsonFlags_IncludePixelData), 0);
std::string mime, content;
ASSERT_TRUE(Toolbox::DecodeDataUriScheme(mime, content, vv["PixelData"].asString()));
ASSERT_EQ("application/octet-stream", mime);
ASSERT_EQ(5u * 5u * 3u /* the red dot is 5x5 pixels in RGB24 */ + 1 /* for padding */, content.size());
}
{
std::unique_ptr<ParsedDicomFile> dicom
(ParsedDicomFile::CreateFromJson(v, static_cast<DicomFromJsonFlags>(DicomFromJsonFlags_DecodeDataUriScheme), ""));
Json::Value vv;
dicom->DatasetToJson(vv, DicomToJsonFormat_Short, toJsonFlags, 0);
ASSERT_FALSE(vv.isMember("SOPInstanceUID"));
ASSERT_FALSE(vv.isMember("SeriesInstanceUID"));
ASSERT_FALSE(vv.isMember("StudyInstanceUID"));
ASSERT_FALSE(vv.isMember("PatientID"));
ASSERT_EQ(2u, vv["0040,0100"].size());
ASSERT_EQ("MR", vv["0040,0100"][0]["0008,0060"].asString());
ASSERT_EQ("CT", vv["0040,0100"][1]["0008,0060"].asString());
ASSERT_EQ("Some public tag", vv["7050,1002"].asString());
ASSERT_EQ("Some unknown tag", vv["7052,1000"].asString());
ASSERT_EQ("Some private tag", vv["7057,1000"].asString());
ASSERT_EQ("Some private tag2", vv["7059,1000"].asString());
ASSERT_EQ("Sébastien", vv["0010,0010"].asString());
ASSERT_EQ("Sebastien", vv["0008,1030"].asString());
ASSERT_EQ("ISO_IR 148", vv["0008,0005"].asString());
ASSERT_EQ("5", vv[DICOM_TAG_ROWS.Format()].asString());
ASSERT_EQ("5", vv[DICOM_TAG_COLUMNS.Format()].asString());
ASSERT_TRUE(vv[DICOM_TAG_PIXEL_DATA.Format()].asString().empty());
}
}
TEST(TestImages, PatternGrayscale8)
{
Orthanc::Image image(Orthanc::PixelFormat_Grayscale8, 256, 256, false);
for (int y = 0; y < 256; y++)
{
uint8_t *p = reinterpret_cast<uint8_t*>(image.GetRow(y));
for (int x = 0; x < 256; x++, p++)
{
*p = y;
}
}
Orthanc::ImageAccessor r;
image.GetRegion(r, 32, 32, 64, 192);
Orthanc::ImageProcessing::Set(r, 0);
image.GetRegion(r, 160, 32, 64, 192);
Orthanc::ImageProcessing::Set(r, 255);
std::string saved;
{
ParsedDicomFile f(true);
f.ReplacePlainString(DICOM_TAG_SOP_CLASS_UID, "1.2.840.10008.5.1.4.1.1.7");
f.ReplacePlainString(DICOM_TAG_STUDY_INSTANCE_UID, "1.2.276.0.7230010.3.1.2.2831176407.321.1458901422.884998");
f.ReplacePlainString(DICOM_TAG_PATIENT_ID, "ORTHANC");
f.ReplacePlainString(DICOM_TAG_PATIENT_NAME, "Orthanc");
f.ReplacePlainString(DICOM_TAG_STUDY_DESCRIPTION, "Patterns");
f.ReplacePlainString(DICOM_TAG_SERIES_DESCRIPTION, "Grayscale8");
f.EmbedImage(image);
f.SaveToMemoryBuffer(saved);
}
{
Orthanc::ParsedDicomFile f(saved);
std::unique_ptr<Orthanc::ImageAccessor> decoded(f.DecodeFrame(0));
ASSERT_EQ(256u, decoded->GetWidth());
ASSERT_EQ(256u, decoded->GetHeight());
ASSERT_EQ(Orthanc::PixelFormat_Grayscale8, decoded->GetFormat());
for (int y = 0; y < 256; y++)
{
const void* a = image.GetConstRow(y);
const void* b = decoded->GetConstRow(y);
ASSERT_EQ(0, memcmp(a, b, 256));
}
}
}
TEST(TestImages, PatternRGB)
{
Orthanc::Image image(Orthanc::PixelFormat_RGB24, 384, 256, false);
for (int y = 0; y < 256; y++)
{
uint8_t *p = reinterpret_cast<uint8_t*>(image.GetRow(y));
for (int x = 0; x < 128; x++, p += 3)
{
p[0] = y;
p[1] = 0;
p[2] = 0;
}
for (int x = 128; x < 128 * 2; x++, p += 3)
{
p[0] = 0;
p[1] = 255 - y;
p[2] = 0;
}
for (int x = 128 * 2; x < 128 * 3; x++, p += 3)
{
p[0] = 0;
p[1] = 0;
p[2] = y;
}
}
std::string saved;
{
ParsedDicomFile f(true);
f.ReplacePlainString(DICOM_TAG_SOP_CLASS_UID, "1.2.840.10008.5.1.4.1.1.7");
f.ReplacePlainString(DICOM_TAG_STUDY_INSTANCE_UID, "1.2.276.0.7230010.3.1.2.2831176407.321.1458901422.884998");
f.ReplacePlainString(DICOM_TAG_PATIENT_ID, "ORTHANC");
f.ReplacePlainString(DICOM_TAG_PATIENT_NAME, "Orthanc");
f.ReplacePlainString(DICOM_TAG_STUDY_DESCRIPTION, "Patterns");
f.ReplacePlainString(DICOM_TAG_SERIES_DESCRIPTION, "RGB24");
f.EmbedImage(image);
f.SaveToMemoryBuffer(saved);
}
{
Orthanc::ParsedDicomFile f(saved);
std::unique_ptr<Orthanc::ImageAccessor> decoded(f.DecodeFrame(0));
ASSERT_EQ(384u, decoded->GetWidth());
ASSERT_EQ(256u, decoded->GetHeight());
ASSERT_EQ(Orthanc::PixelFormat_RGB24, decoded->GetFormat());
for (int y = 0; y < 256; y++)
{
const void* a = image.GetConstRow(y);
const void* b = decoded->GetConstRow(y);
ASSERT_EQ(0, memcmp(a, b, 3 * 384));
}
}
}
TEST(TestImages, PatternUint16)
{
Orthanc::Image image(Orthanc::PixelFormat_Grayscale16, 256, 256, false);
uint16_t v = 0;
for (int y = 0; y < 256; y++)
{
uint16_t *p = reinterpret_cast<uint16_t*>(image.GetRow(y));
for (int x = 0; x < 256; x++, v++, p++)
{
*p = v;
}
}
Orthanc::ImageAccessor r;
image.GetRegion(r, 32, 32, 64, 192);
Orthanc::ImageProcessing::Set(r, 0);
image.GetRegion(r, 160, 32, 64, 192);
Orthanc::ImageProcessing::Set(r, 65535);
std::string saved;
{
ParsedDicomFile f(true);
f.ReplacePlainString(DICOM_TAG_SOP_CLASS_UID, "1.2.840.10008.5.1.4.1.1.7");
f.ReplacePlainString(DICOM_TAG_STUDY_INSTANCE_UID, "1.2.276.0.7230010.3.1.2.2831176407.321.1458901422.884998");
f.ReplacePlainString(DICOM_TAG_PATIENT_ID, "ORTHANC");
f.ReplacePlainString(DICOM_TAG_PATIENT_NAME, "Orthanc");
f.ReplacePlainString(DICOM_TAG_STUDY_DESCRIPTION, "Patterns");
f.ReplacePlainString(DICOM_TAG_SERIES_DESCRIPTION, "Grayscale16");
f.EmbedImage(image);
f.SaveToMemoryBuffer(saved);
}
{
Orthanc::ParsedDicomFile f(saved);
std::unique_ptr<Orthanc::ImageAccessor> decoded(f.DecodeFrame(0));
ASSERT_EQ(256u, decoded->GetWidth());
ASSERT_EQ(256u, decoded->GetHeight());
ASSERT_EQ(Orthanc::PixelFormat_Grayscale16, decoded->GetFormat());
for (int y = 0; y < 256; y++)
{
const void* a = image.GetConstRow(y);
const void* b = decoded->GetConstRow(y);
ASSERT_EQ(0, memcmp(a, b, 512));
}
}
}
TEST(TestImages, PatternInt16)
{
Orthanc::Image image(Orthanc::PixelFormat_SignedGrayscale16, 256, 256, false);
int16_t v = -32768;
for (int y = 0; y < 256; y++)
{
int16_t *p = reinterpret_cast<int16_t*>(image.GetRow(y));
for (int x = 0; x < 256; x++, v++, p++)
{
*p = v;
}
}
Orthanc::ImageAccessor r;
image.GetRegion(r, 32, 32, 64, 192);
Orthanc::ImageProcessing::Set(r, -32768);
image.GetRegion(r, 160, 32, 64, 192);
Orthanc::ImageProcessing::Set(r, 32767);
std::string saved;
{
ParsedDicomFile f(true);
f.ReplacePlainString(DICOM_TAG_SOP_CLASS_UID, "1.2.840.10008.5.1.4.1.1.7");
f.ReplacePlainString(DICOM_TAG_STUDY_INSTANCE_UID, "1.2.276.0.7230010.3.1.2.2831176407.321.1458901422.884998");
f.ReplacePlainString(DICOM_TAG_PATIENT_ID, "ORTHANC");
f.ReplacePlainString(DICOM_TAG_PATIENT_NAME, "Orthanc");
f.ReplacePlainString(DICOM_TAG_STUDY_DESCRIPTION, "Patterns");
f.ReplacePlainString(DICOM_TAG_SERIES_DESCRIPTION, "SignedGrayscale16");
f.EmbedImage(image);
f.SaveToMemoryBuffer(saved);
}
{
Orthanc::ParsedDicomFile f(saved);
std::unique_ptr<Orthanc::ImageAccessor> decoded(f.DecodeFrame(0));
ASSERT_EQ(256u, decoded->GetWidth());
ASSERT_EQ(256u, decoded->GetHeight());
ASSERT_EQ(Orthanc::PixelFormat_SignedGrayscale16, decoded->GetFormat());
for (int y = 0; y < 256; y++)
{
const void* a = image.GetConstRow(y);
const void* b = decoded->GetConstRow(y);
ASSERT_EQ(0, memcmp(a, b, 512));
}
}
}
static void CheckEncoding(ParsedDicomFile& dicom,
Encoding expected)
{
const char* value = NULL;
ASSERT_TRUE(dicom.GetDcmtkObject().getDataset()->findAndGetString(DCM_SpecificCharacterSet, value).good());
Encoding encoding;
ASSERT_TRUE(GetDicomEncoding(encoding, value));
ASSERT_EQ(expected, encoding);
}
TEST(ParsedDicomFile, DicomMapEncodings1)
{
SetDefaultDicomEncoding(Encoding_Ascii);
ASSERT_EQ(Encoding_Ascii, GetDefaultDicomEncoding());
{
DicomMap m;
ParsedDicomFile dicom(m, GetDefaultDicomEncoding(), false);
ASSERT_EQ(1u, dicom.GetDcmtkObject().getDataset()->card());
CheckEncoding(dicom, Encoding_Ascii);
}
{
DicomMap m;
ParsedDicomFile dicom(m, Encoding_Latin4, false);
ASSERT_EQ(1u, dicom.GetDcmtkObject().getDataset()->card());
CheckEncoding(dicom, Encoding_Latin4);
}
{
DicomMap m;
m.SetValue(DICOM_TAG_SPECIFIC_CHARACTER_SET, "ISO_IR 148", false);
ParsedDicomFile dicom(m, GetDefaultDicomEncoding(), false);
ASSERT_EQ(1u, dicom.GetDcmtkObject().getDataset()->card());
CheckEncoding(dicom, Encoding_Latin5);
}
{
DicomMap m;
m.SetValue(DICOM_TAG_SPECIFIC_CHARACTER_SET, "ISO_IR 148", false);
ParsedDicomFile dicom(m, Encoding_Latin1, false);
ASSERT_EQ(1u, dicom.GetDcmtkObject().getDataset()->card());
CheckEncoding(dicom, Encoding_Latin5);
}
}
TEST(ParsedDicomFile, DicomMapEncodings2)
{
const char* utf8 = NULL;
for (unsigned int i = 0; i < testEncodingsCount; i++)
{
if (testEncodings[i] == Encoding_Utf8)
{
utf8 = testEncodingsEncoded[i];
break;
}
}
ASSERT_TRUE(utf8 != NULL);
for (unsigned int i = 0; i < testEncodingsCount; i++)
{
// 1251 codepage is not supported by the core DICOM standard, ignore it
if (testEncodings[i] != Encoding_Windows1251)
{
{
// Sanity check to test the proper behavior of "EncodingTests.py"
std::string encoded = Toolbox::ConvertFromUtf8(testEncodingsExpected[i], testEncodings[i]);
ASSERT_STREQ(testEncodingsEncoded[i], encoded.c_str());
std::string decoded = Toolbox::ConvertToUtf8(encoded, testEncodings[i], false);
ASSERT_STREQ(testEncodingsExpected[i], decoded.c_str());
if (testEncodings[i] != Encoding_Chinese)
{
// A specific source string is used in "EncodingTests.py" to
// test against Chinese, it is normal that it does not correspond to UTF8
const std::string tmp = Toolbox::ConvertToUtf8(
Toolbox::ConvertFromUtf8(utf8, testEncodings[i]), testEncodings[i], false);
ASSERT_STREQ(testEncodingsExpected[i], tmp.c_str());
}
}
Json::Value v;
{
DicomMap m;
m.SetValue(DICOM_TAG_PATIENT_NAME, testEncodingsExpected[i], false);
ParsedDicomFile dicom(m, testEncodings[i], false);
const char* encoded = NULL;
ASSERT_TRUE(dicom.GetDcmtkObject().getDataset()->findAndGetString(DCM_PatientName, encoded).good());
ASSERT_STREQ(testEncodingsEncoded[i], encoded);
dicom.DatasetToJson(v, DicomToJsonFormat_Human, DicomToJsonFlags_Default, 0);
Encoding encoding;
ASSERT_TRUE(GetDicomEncoding(encoding, v["SpecificCharacterSet"].asCString()));
ASSERT_EQ(encoding, testEncodings[i]);
ASSERT_STREQ(testEncodingsExpected[i], v["PatientName"].asCString());
}
{
DicomMap m;
m.SetValue(DICOM_TAG_SPECIFIC_CHARACTER_SET, GetDicomSpecificCharacterSet(testEncodings[i]), false);
m.SetValue(DICOM_TAG_PATIENT_NAME, testEncodingsExpected[i], false);
ParsedDicomFile dicom(m, testEncodings[i], false);
Json::Value v2;
dicom.DatasetToJson(v2, DicomToJsonFormat_Human, DicomToJsonFlags_Default, 0);
ASSERT_EQ(v2["PatientName"].asString(), v["PatientName"].asString());
ASSERT_EQ(v2["SpecificCharacterSet"].asString(), v["SpecificCharacterSet"].asString());
}
}
}
}
TEST(ParsedDicomFile, ChangeEncoding)
{
for (unsigned int i = 0; i < testEncodingsCount; i++)
{
// 1251 codepage is not supported by the core DICOM standard, ignore it
if (testEncodings[i] != Encoding_Windows1251)
{
DicomMap m;
m.SetValue(DICOM_TAG_PATIENT_NAME, testEncodingsExpected[i], false);
std::string tag;
ParsedDicomFile dicom(m, Encoding_Utf8, false);
bool hasCodeExtensions;
ASSERT_EQ(Encoding_Utf8, dicom.DetectEncoding(hasCodeExtensions));
ASSERT_FALSE(hasCodeExtensions);
ASSERT_TRUE(dicom.GetTagValue(tag, DICOM_TAG_PATIENT_NAME));
ASSERT_EQ(tag, testEncodingsExpected[i]);
{
Json::Value v;
dicom.DatasetToJson(v, DicomToJsonFormat_Human, DicomToJsonFlags_Default, 0);
ASSERT_STREQ(v["SpecificCharacterSet"].asCString(), "ISO_IR 192");
ASSERT_STREQ(v["PatientName"].asCString(), testEncodingsExpected[i]);
}
dicom.ChangeEncoding(testEncodings[i]);
ASSERT_EQ(testEncodings[i], dicom.DetectEncoding(hasCodeExtensions));
ASSERT_FALSE(hasCodeExtensions);
const char* c = NULL;
ASSERT_TRUE(dicom.GetDcmtkObject().getDataset()->findAndGetString(DCM_PatientName, c).good());
EXPECT_STREQ(c, testEncodingsEncoded[i]);
ASSERT_TRUE(dicom.GetTagValue(tag, DICOM_TAG_PATIENT_NAME)); // Decodes to UTF-8
EXPECT_EQ(tag, testEncodingsExpected[i]);
{
Json::Value v;
dicom.DatasetToJson(v, DicomToJsonFormat_Human, DicomToJsonFlags_Default, 0);
ASSERT_STREQ(v["SpecificCharacterSet"].asCString(), GetDicomSpecificCharacterSet(testEncodings[i]));
ASSERT_STREQ(v["PatientName"].asCString(), testEncodingsExpected[i]);
}
}
}
}
TEST(Toolbox, CaseWithAccents)
{
ASSERT_EQ(toUpperResult, Toolbox::ToUpperCaseWithAccents(toUpperSource));
}
TEST(ParsedDicomFile, InvalidCharacterSets)
{
{
// No encoding provided, fallback to default encoding
DicomMap m;
m.SetValue(DICOM_TAG_PATIENT_NAME, "HELLO", false);
ParsedDicomFile d(m, Encoding_Latin3 /* default encoding */, false);
bool hasCodeExtensions;
ASSERT_EQ(Encoding_Latin3, d.DetectEncoding(hasCodeExtensions));
ASSERT_FALSE(hasCodeExtensions);
}
{
// Valid encoding, "ISO_IR 13" is Japanese
DicomMap m;
m.SetValue(DICOM_TAG_SPECIFIC_CHARACTER_SET, "ISO_IR 13", false);
m.SetValue(DICOM_TAG_PATIENT_NAME, "HELLO", false);
ParsedDicomFile d(m, Encoding_Latin3 /* default encoding */, false);
bool hasCodeExtensions;
ASSERT_EQ(Encoding_Japanese, d.DetectEncoding(hasCodeExtensions));
ASSERT_FALSE(hasCodeExtensions);
}
{
// Invalid value for an encoding ("nope" is not in the DICOM standard)
DicomMap m;
m.SetValue(DICOM_TAG_SPECIFIC_CHARACTER_SET, "nope", false);
m.SetValue(DICOM_TAG_PATIENT_NAME, "HELLO", false);
ASSERT_THROW(ParsedDicomFile d(m, Encoding_Latin3, false),
OrthancException);
}
{
// Invalid encoding, as provided as a binary string
DicomMap m;
m.SetValue(DICOM_TAG_SPECIFIC_CHARACTER_SET, "ISO_IR 13", true);
m.SetValue(DICOM_TAG_PATIENT_NAME, "HELLO", false);
ASSERT_THROW(ParsedDicomFile d(m, Encoding_Latin3, false),
OrthancException);
}
{
// Encoding provided as an empty string, fallback to default encoding
// In Orthanc <= 1.3.1, this test was throwing an exception
DicomMap m;
m.SetValue(DICOM_TAG_SPECIFIC_CHARACTER_SET, "", false);
m.SetValue(DICOM_TAG_PATIENT_NAME, "HELLO", false);
ParsedDicomFile d(m, Encoding_Latin3 /* default encoding */, false);
bool hasCodeExtensions;
ASSERT_EQ(Encoding_Latin3, d.DetectEncoding(hasCodeExtensions));
ASSERT_FALSE(hasCodeExtensions);
}
}
TEST(ParsedDicomFile, FloatPrecision)
{
Float32 v;
switch (Toolbox::DetectEndianness())
{
case Endianness_Little:
reinterpret_cast<uint8_t*>(&v)[3] = 0x4E;
reinterpret_cast<uint8_t*>(&v)[2] = 0x9C;
reinterpret_cast<uint8_t*>(&v)[1] = 0xAD;
reinterpret_cast<uint8_t*>(&v)[0] = 0x8F;
break;
case Endianness_Big:
reinterpret_cast<uint8_t*>(&v)[0] = 0x4E;
reinterpret_cast<uint8_t*>(&v)[1] = 0x9C;
reinterpret_cast<uint8_t*>(&v)[2] = 0xAD;
reinterpret_cast<uint8_t*>(&v)[3] = 0x8F;
break;
default:
throw OrthancException(ErrorCode_InternalError);
}
ParsedDicomFile f(false);
ASSERT_TRUE(f.GetDcmtkObject().getDataset()->putAndInsertFloat32(DCM_ExaminedBodyThickness /* VR: FL */, v).good());
{
Float32 u;
ASSERT_TRUE(f.GetDcmtkObject().getDataset()->findAndGetFloat32(DCM_ExaminedBodyThickness, u).good());
ASSERT_FLOAT_EQ(u, v);
ASSERT_TRUE(memcmp(&u, &v, 4) == 0);
}
{
Json::Value json;
f.DatasetToJson(json, DicomToJsonFormat_Short, DicomToJsonFlags_None, 256);
ASSERT_EQ("1314310016", json["0010,9431"].asString());
}
{
DicomMap summary;
f.ExtractDicomSummary(summary, 256);
ASSERT_EQ("1314310016", summary.GetStringValue(DicomTag(0x0010, 0x9431), "nope", false));
}
{
// This flavor uses "Json::Value" serialization
DicomWebJsonVisitor visitor;
f.Apply(visitor);
Float32 u = visitor.GetResult() ["00109431"]["Value"][0].asFloat();
ASSERT_FLOAT_EQ(u, v);
ASSERT_TRUE(memcmp(&u, &v, 4) == 0);
}
}
TEST(Toolbox, RemoveIso2022EscapeSequences)
{
// +----------------------------------+
// | one-byte control messages |
// +----------------------------------+
static const uint8_t iso2022_cstr_oneByteControl[] = {
0x0f, 0x41,
0x0e, 0x42,
0x8e, 0x1b, 0x4e, 0x43,
0x8f, 0x1b, 0x4f, 0x44,
0x8e, 0x1b, 0x4a, 0x45,
0x8f, 0x1b, 0x4a, 0x46,
0x50, 0x51, 0x52, 0x00
};
static const uint8_t iso2022_cstr_oneByteControl_ref[] = {
0x41,
0x42,
0x43,
0x44,
0x8e, 0x1b, 0x4a, 0x45,
0x8f, 0x1b, 0x4a, 0x46,
0x50, 0x51, 0x52, 0x00
};
// +----------------------------------+
// | two-byte control messages |
// +----------------------------------+
static const uint8_t iso2022_cstr_twoByteControl[] = {
0x1b, 0x6e, 0x41,
0x1b, 0x6f, 0x42,
0x1b, 0x4e, 0x43,
0x1b, 0x4f, 0x44,
0x1b, 0x7e, 0x45,
0x1b, 0x7d, 0x46,
0x1b, 0x7c, 0x47, 0x00
};
static const uint8_t iso2022_cstr_twoByteControl_ref[] = {
0x41,
0x42,
0x43,
0x44,
0x45,
0x46,
0x47, 0x00
};
// +----------------------------------+
// | various-length escape sequences |
// +----------------------------------+
static const uint8_t iso2022_cstr_escapeSequence[] = {
0x1b, 0x40, 0x41, // 1b and 40 should not be removed (invalid esc seq)
0x1b, 0x50, 0x42, // ditto
0x1b, 0x7f, 0x43, // ditto
0x1b, 0x21, 0x4a, 0x44, // this will match
0x1b, 0x20, 0x21, 0x2f, 0x40, 0x45, // this will match
0x1b, 0x20, 0x21, 0x2f, 0x2f, 0x40, 0x46, // this will match too
0x1b, 0x20, 0x21, 0x2f, 0x1f, 0x47, 0x48, 0x00 // this will NOT match!
};
static const uint8_t iso2022_cstr_escapeSequence_ref[] = {
0x1b, 0x40, 0x41, // 1b and 40 should not be removed (invalid esc seq)
0x1b, 0x50, 0x42, // ditto
0x1b, 0x7f, 0x43, // ditto
0x44, // this will match
0x45, // this will match
0x46, // this will match too
0x1b, 0x20, 0x21, 0x2f, 0x1f, 0x47, 0x48, 0x00 // this will NOT match!
};
// +----------------------------------+
// | a real-world japanese sample |
// +----------------------------------+
static const uint8_t iso2022_cstr_real_ir13[] = {
0xd4, 0xcf, 0xc0, 0xde, 0x5e, 0xc0, 0xdb, 0xb3,
0x3d, 0x1b, 0x24, 0x42, 0x3b, 0x33, 0x45, 0x44,
0x1b, 0x28, 0x4a, 0x5e, 0x1b, 0x24, 0x42, 0x42,
0x40, 0x4f, 0x3a, 0x1b, 0x28, 0x4a, 0x3d, 0x1b,
0x24, 0x42, 0x24, 0x64, 0x24, 0x5e, 0x24, 0x40,
0x1b, 0x28, 0x4a, 0x5e, 0x1b, 0x24, 0x42, 0x24,
0x3f, 0x24, 0x6d, 0x24, 0x26, 0x1b, 0x28, 0x4a, 0x00
};
static const uint8_t iso2022_cstr_real_ir13_ref[] = {
0xd4, 0xcf, 0xc0, 0xde, 0x5e, 0xc0, 0xdb, 0xb3,
0x3d,
0x3b, 0x33, 0x45, 0x44,
0x5e,
0x42,
0x40, 0x4f, 0x3a,
0x3d,
0x24, 0x64, 0x24, 0x5e, 0x24, 0x40,
0x5e,
0x24,
0x3f, 0x24, 0x6d, 0x24, 0x26, 0x00
};
// +----------------------------------+
// | the actual test |
// +----------------------------------+
std::string iso2022_str_oneByteControl(
reinterpret_cast<const char*>(iso2022_cstr_oneByteControl));
std::string iso2022_str_oneByteControl_ref(
reinterpret_cast<const char*>(iso2022_cstr_oneByteControl_ref));
std::string iso2022_str_twoByteControl(
reinterpret_cast<const char*>(iso2022_cstr_twoByteControl));
std::string iso2022_str_twoByteControl_ref(
reinterpret_cast<const char*>(iso2022_cstr_twoByteControl_ref));
std::string iso2022_str_escapeSequence(
reinterpret_cast<const char*>(iso2022_cstr_escapeSequence));
std::string iso2022_str_escapeSequence_ref(
reinterpret_cast<const char*>(iso2022_cstr_escapeSequence_ref));
std::string iso2022_str_real_ir13(
reinterpret_cast<const char*>(iso2022_cstr_real_ir13));
std::string iso2022_str_real_ir13_ref(
reinterpret_cast<const char*>(iso2022_cstr_real_ir13_ref));
std::string dest;
Toolbox::RemoveIso2022EscapeSequences(dest, iso2022_str_oneByteControl);
ASSERT_EQ(dest, iso2022_str_oneByteControl_ref);
Toolbox::RemoveIso2022EscapeSequences(dest, iso2022_str_twoByteControl);
ASSERT_EQ(dest, iso2022_str_twoByteControl_ref);
Toolbox::RemoveIso2022EscapeSequences(dest, iso2022_str_escapeSequence);
ASSERT_EQ(dest, iso2022_str_escapeSequence_ref);
Toolbox::RemoveIso2022EscapeSequences(dest, iso2022_str_real_ir13);
ASSERT_EQ(dest, iso2022_str_real_ir13_ref);
}
static std::string DecodeFromSpecification(const std::string& s)
{
std::vector<std::string> tokens;
Toolbox::TokenizeString(tokens, s, ' ');
std::string result;
result.resize(tokens.size());
for (size_t i = 0; i < tokens.size(); i++)
{
std::vector<std::string> components;
Toolbox::TokenizeString(components, tokens[i], '/');
if (components.size() != 2)
{
throw;
}
int a = boost::lexical_cast<int>(components[0]);
int b = boost::lexical_cast<int>(components[1]);
if (a < 0 || a > 15 ||
b < 0 || b > 15 ||
(a == 0 && b == 0))
{
throw;
}
result[i] = static_cast<uint8_t>(a * 16 + b);
}
return result;
}
// Compatibility wrapper
static pugi::xpath_node SelectNode(const pugi::xml_document& doc,
const char* xpath)
{
#if PUGIXML_VERSION <= 140
return doc.select_single_node(xpath); // Deprecated in pugixml 1.5
#else
return doc.select_node(xpath);
#endif
}
TEST(Toolbox, EncodingsKorean)
{
// http://dicom.nema.org/MEDICAL/dicom/current/output/chtml/part05/sect_I.2.html
std::string korean = DecodeFromSpecification(
"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 "
"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 "
"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 "
"04/03 11/01 14/06 11/05 11/15");
// This array can be re-generated using command-line:
// echo -n "Hong^Gildong=..." | hexdump -v -e '14/1 "0x%02x, "' -e '"\n"'
static const uint8_t utf8raw[] = {
0x48, 0x6f, 0x6e, 0x67, 0x5e, 0x47, 0x69, 0x6c, 0x64, 0x6f, 0x6e, 0x67, 0x3d, 0xe6,
0xb4, 0xaa, 0x5e, 0xe5, 0x90, 0x89, 0xe6, 0xb4, 0x9e, 0x3d, 0xed, 0x99, 0x8d, 0x5e,
0xea, 0xb8, 0xb8, 0xeb, 0x8f, 0x99
};
std::string utf8(reinterpret_cast<const char*>(utf8raw), sizeof(utf8raw));
ParsedDicomFile dicom(false);
dicom.ReplacePlainString(DICOM_TAG_SPECIFIC_CHARACTER_SET, "\\ISO 2022 IR 149");
ASSERT_TRUE(dicom.GetDcmtkObject().getDataset()->putAndInsertString
(DCM_PatientName, korean.c_str(), OFBool(true)).good());
bool hasCodeExtensions;
Encoding encoding = dicom.DetectEncoding(hasCodeExtensions);
ASSERT_EQ(Encoding_Korean, encoding);
ASSERT_TRUE(hasCodeExtensions);
std::string value;
ASSERT_TRUE(dicom.GetTagValue(value, DICOM_TAG_PATIENT_NAME));
ASSERT_EQ(utf8, value);
DicomWebJsonVisitor visitor;
dicom.Apply(visitor);
ASSERT_EQ(utf8.substr(0, 12), visitor.GetResult()["00100010"]["Value"][0]["Alphabetic"].asString());
ASSERT_EQ(utf8.substr(13, 10), visitor.GetResult()["00100010"]["Value"][0]["Ideographic"].asString());
ASSERT_EQ(utf8.substr(24), visitor.GetResult()["00100010"]["Value"][0]["Phonetic"].asString());
#if ORTHANC_ENABLE_PUGIXML == 1
// http://dicom.nema.org/medical/dicom/current/output/chtml/part18/sect_F.3.html#table_F.3.1-1
std::string xml;
visitor.FormatXml(xml);
pugi::xml_document doc;
doc.load_buffer(xml.c_str(), xml.size());
pugi::xpath_node node = SelectNode(doc, "//NativeDicomModel/DicomAttribute[@tag=\"00080005\"]/Value");
ASSERT_STREQ("ISO 2022 IR 149", node.node().text().as_string());
node = SelectNode(doc, "//NativeDicomModel/DicomAttribute[@tag=\"00080005\"]");
ASSERT_STREQ("CS", node.node().attribute("vr").value());
node = SelectNode(doc, "//NativeDicomModel/DicomAttribute[@tag=\"00100010\"]");
ASSERT_STREQ("PN", node.node().attribute("vr").value());
node = SelectNode(doc, "//NativeDicomModel/DicomAttribute[@tag=\"00100010\"]/PersonName/Alphabetic/FamilyName");
ASSERT_STREQ("Hong", node.node().text().as_string());
node = SelectNode(doc, "//NativeDicomModel/DicomAttribute[@tag=\"00100010\"]/PersonName/Alphabetic/GivenName");
ASSERT_STREQ("Gildong", node.node().text().as_string());
node = SelectNode(doc, "//NativeDicomModel/DicomAttribute[@tag=\"00100010\"]/PersonName/Ideographic/FamilyName");
ASSERT_EQ(utf8.substr(13, 3), node.node().text().as_string());
node = SelectNode(doc, "//NativeDicomModel/DicomAttribute[@tag=\"00100010\"]/PersonName/Ideographic/GivenName");
ASSERT_EQ(utf8.substr(17, 6), node.node().text().as_string());
node = SelectNode(doc, "//NativeDicomModel/DicomAttribute[@tag=\"00100010\"]/PersonName/Phonetic/FamilyName");
ASSERT_EQ(utf8.substr(24, 3), node.node().text().as_string());
node = SelectNode(doc, "//NativeDicomModel/DicomAttribute[@tag=\"00100010\"]/PersonName/Phonetic/GivenName");
ASSERT_EQ(utf8.substr(28), node.node().text().as_string());
#endif
{
DicomMap m;
m.FromDicomWeb(visitor.GetResult());
ASSERT_EQ(2u, m.GetSize());
std::string s;
ASSERT_TRUE(m.LookupStringValue(s, DICOM_TAG_SPECIFIC_CHARACTER_SET, false));
ASSERT_EQ("ISO 2022 IR 149", s);
ASSERT_TRUE(m.LookupStringValue(s, DICOM_TAG_PATIENT_NAME, false));
std::vector<std::string> v;
Toolbox::TokenizeString(v, s, '=');
ASSERT_EQ(3u, v.size());
ASSERT_EQ("Hong^Gildong", v[0]);
ASSERT_EQ(utf8, s);
}
}
TEST(Toolbox, EncodingsJapaneseKanji)
{
// http://dicom.nema.org/MEDICAL/dicom/current/output/chtml/part05/sect_H.3.html
std::string japanese = DecodeFromSpecification(
"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 "
"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 "
"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 "
"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 "
"03/15 02/04 06/13 02/04 02/06 01/11 02/08 04/02");
// This array can be re-generated using command-line:
// echo -n "Yamada^Tarou=..." | hexdump -v -e '14/1 "0x%02x, "' -e '"\n"'
static const uint8_t utf8raw[] = {
0x59, 0x61, 0x6d, 0x61, 0x64, 0x61, 0x5e, 0x54, 0x61, 0x72, 0x6f, 0x75, 0x3d, 0xe5,
0xb1, 0xb1, 0xe7, 0x94, 0xb0, 0x5e, 0xe5, 0xa4, 0xaa, 0xe9, 0x83, 0x8e, 0x3d, 0xe3,
0x82, 0x84, 0xe3, 0x81, 0xbe, 0xe3, 0x81, 0xa0, 0x5e, 0xe3, 0x81, 0x9f, 0xe3, 0x82,
0x8d, 0xe3, 0x81, 0x86
};
std::string utf8(reinterpret_cast<const char*>(utf8raw), sizeof(utf8raw));
ParsedDicomFile dicom(false);
dicom.ReplacePlainString(DICOM_TAG_SPECIFIC_CHARACTER_SET, "\\ISO 2022 IR 87");
ASSERT_TRUE(dicom.GetDcmtkObject().getDataset()->putAndInsertString
(DCM_PatientName, japanese.c_str(), OFBool(true)).good());
bool hasCodeExtensions;
Encoding encoding = dicom.DetectEncoding(hasCodeExtensions);
ASSERT_EQ(Encoding_JapaneseKanji, encoding);
ASSERT_TRUE(hasCodeExtensions);
std::string value;
ASSERT_TRUE(dicom.GetTagValue(value, DICOM_TAG_PATIENT_NAME));
ASSERT_EQ(utf8, value);
DicomWebJsonVisitor visitor;
dicom.Apply(visitor);
ASSERT_EQ(utf8.substr(0, 12), visitor.GetResult()["00100010"]["Value"][0]["Alphabetic"].asString());
ASSERT_EQ(utf8.substr(13, 13), visitor.GetResult()["00100010"]["Value"][0]["Ideographic"].asString());
ASSERT_EQ(utf8.substr(27), visitor.GetResult()["00100010"]["Value"][0]["Phonetic"].asString());
#if ORTHANC_ENABLE_PUGIXML == 1
// http://dicom.nema.org/medical/dicom/current/output/chtml/part18/sect_F.3.html#table_F.3.1-1
std::string xml;
visitor.FormatXml(xml);
pugi::xml_document doc;
doc.load_buffer(xml.c_str(), xml.size());
pugi::xpath_node node = SelectNode(doc, "//NativeDicomModel/DicomAttribute[@tag=\"00080005\"]/Value");
ASSERT_STREQ("ISO 2022 IR 87", node.node().text().as_string());
node = SelectNode(doc, "//NativeDicomModel/DicomAttribute[@tag=\"00080005\"]");
ASSERT_STREQ("CS", node.node().attribute("vr").value());
node = SelectNode(doc, "//NativeDicomModel/DicomAttribute[@tag=\"00100010\"]");
ASSERT_STREQ("PN", node.node().attribute("vr").value());
node = SelectNode(doc, "//NativeDicomModel/DicomAttribute[@tag=\"00100010\"]/PersonName/Alphabetic/FamilyName");
ASSERT_STREQ("Yamada", node.node().text().as_string());
node = SelectNode(doc, "//NativeDicomModel/DicomAttribute[@tag=\"00100010\"]/PersonName/Alphabetic/GivenName");
ASSERT_STREQ("Tarou", node.node().text().as_string());
node = SelectNode(doc, "//NativeDicomModel/DicomAttribute[@tag=\"00100010\"]/PersonName/Ideographic/FamilyName");
ASSERT_EQ(utf8.substr(13, 6), node.node().text().as_string());
node = SelectNode(doc, "//NativeDicomModel/DicomAttribute[@tag=\"00100010\"]/PersonName/Ideographic/GivenName");
ASSERT_EQ(utf8.substr(20, 6), node.node().text().as_string());
node = SelectNode(doc, "//NativeDicomModel/DicomAttribute[@tag=\"00100010\"]/PersonName/Phonetic/FamilyName");
ASSERT_EQ(utf8.substr(27, 9), node.node().text().as_string());
node = SelectNode(doc, "//NativeDicomModel/DicomAttribute[@tag=\"00100010\"]/PersonName/Phonetic/GivenName");
ASSERT_EQ(utf8.substr(37), node.node().text().as_string());
#endif
{
DicomMap m;
m.FromDicomWeb(visitor.GetResult());
ASSERT_EQ(2u, m.GetSize());
std::string s;
ASSERT_TRUE(m.LookupStringValue(s, DICOM_TAG_SPECIFIC_CHARACTER_SET, false));
ASSERT_EQ("ISO 2022 IR 87", s);
ASSERT_TRUE(m.LookupStringValue(s, DICOM_TAG_PATIENT_NAME, false));
std::vector<std::string> v;
Toolbox::TokenizeString(v, s, '=');
ASSERT_EQ(3u, v.size());
ASSERT_EQ("Yamada^Tarou", v[0]);
ASSERT_EQ(utf8, s);
}
}
TEST(Toolbox, EncodingsChinese3)
{
// http://dicom.nema.org/MEDICAL/dicom/current/output/chtml/part05/sect_J.3.html
static const uint8_t chinese[] = {
0x57, 0x61, 0x6e, 0x67, 0x5e, 0x58, 0x69, 0x61, 0x6f, 0x44, 0x6f,
0x6e, 0x67, 0x3d, 0xcd, 0xf5, 0x5e, 0xd0, 0xa1, 0xb6, 0xab, 0x3d, 0x00
};
ParsedDicomFile dicom(false);
dicom.ReplacePlainString(DICOM_TAG_SPECIFIC_CHARACTER_SET, "GB18030");
ASSERT_TRUE(dicom.GetDcmtkObject().getDataset()->putAndInsertString
(DCM_PatientName, reinterpret_cast<const char*>(chinese), OFBool(true)).good());
bool hasCodeExtensions;
Encoding encoding = dicom.DetectEncoding(hasCodeExtensions);
ASSERT_EQ(Encoding_Chinese, encoding);
ASSERT_FALSE(hasCodeExtensions);
std::string value;
ASSERT_TRUE(dicom.GetTagValue(value, DICOM_TAG_PATIENT_NAME));
std::vector<std::string> tokens;
Orthanc::Toolbox::TokenizeString(tokens, value, '=');
ASSERT_EQ(3u, tokens.size());
ASSERT_EQ("Wang^XiaoDong", tokens[0]);
ASSERT_TRUE(tokens[2].empty());
std::vector<std::string> middle;
Orthanc::Toolbox::TokenizeString(middle, tokens[1], '^');
ASSERT_EQ(2u, middle.size());
ASSERT_EQ(3u, middle[0].size());
ASSERT_EQ(6u, middle[1].size());
// CDF5 in GB18030
ASSERT_EQ(static_cast<char>(0xe7), middle[0][0]);
ASSERT_EQ(static_cast<char>(0x8e), middle[0][1]);
ASSERT_EQ(static_cast<char>(0x8b), middle[0][2]);
// D0A1 in GB18030
ASSERT_EQ(static_cast<char>(0xe5), middle[1][0]);
ASSERT_EQ(static_cast<char>(0xb0), middle[1][1]);
ASSERT_EQ(static_cast<char>(0x8f), middle[1][2]);
// B6AB in GB18030
ASSERT_EQ(static_cast<char>(0xe4), middle[1][3]);
ASSERT_EQ(static_cast<char>(0xb8), middle[1][4]);
ASSERT_EQ(static_cast<char>(0x9c), middle[1][5]);
}
TEST(Toolbox, EncodingsChinese4)
{
// http://dicom.nema.org/MEDICAL/dicom/current/output/chtml/part05/sect_J.4.html
static const uint8_t chinese[] = {
0x54, 0x68, 0x65, 0x20, 0x66, 0x69, 0x72, 0x73, 0x74, 0x20, 0x6c, 0x69, 0x6e,
0x65, 0x20, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x73, 0xd6, 0xd0, 0xce,
0xc4, 0x2e, 0x0d, 0x0a, 0x54, 0x68, 0x65, 0x20, 0x73, 0x65, 0x63, 0x6f, 0x6e,
0x64, 0x20, 0x6c, 0x69, 0x6e, 0x65, 0x20, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64,
0x65, 0x73, 0xd6, 0xd0, 0xce, 0xc4, 0x2c, 0x20, 0x74, 0x6f, 0x6f, 0x2e, 0x0d,
0x0a, 0x54, 0x68, 0x65, 0x20, 0x74, 0x68, 0x69, 0x72, 0x64, 0x20, 0x6c, 0x69,
0x6e, 0x65, 0x2e, 0x0d, 0x0a, 0x00
};
static const uint8_t patternRaw[] = {
0xe4, 0xb8, 0xad, 0xe6, 0x96, 0x87
};
const std::string pattern(reinterpret_cast<const char*>(patternRaw), sizeof(patternRaw));
ParsedDicomFile dicom(false);
dicom.ReplacePlainString(DICOM_TAG_SPECIFIC_CHARACTER_SET, "GB18030");
ASSERT_TRUE(dicom.GetDcmtkObject().getDataset()->putAndInsertString
(DCM_PatientComments, reinterpret_cast<const char*>(chinese), OFBool(true)).good());
bool hasCodeExtensions;
Encoding encoding = dicom.DetectEncoding(hasCodeExtensions);
ASSERT_EQ(Encoding_Chinese, encoding);
ASSERT_FALSE(hasCodeExtensions);
std::string value;
ASSERT_TRUE(dicom.GetTagValue(value, DICOM_TAG_PATIENT_COMMENTS));
std::vector<std::string> lines;
Orthanc::Toolbox::TokenizeString(lines, value, '\n');
ASSERT_EQ(4u, lines.size());
ASSERT_TRUE(boost::starts_with(lines[0], "The first line includes"));
ASSERT_TRUE(boost::ends_with(lines[0], ".\r"));
ASSERT_TRUE(lines[0].find(pattern) != std::string::npos);
ASSERT_TRUE(boost::starts_with(lines[1], "The second line includes"));
ASSERT_TRUE(boost::ends_with(lines[1], ", too.\r"));
ASSERT_TRUE(lines[1].find(pattern) != std::string::npos);
ASSERT_EQ("The third line.\r", lines[2]);
ASSERT_FALSE(lines[1].find(pattern) == std::string::npos);
ASSERT_TRUE(lines[3].empty());
}
TEST(Toolbox, EncodingsSimplifiedChinese2)
{
// http://dicom.nema.org/MEDICAL/dicom/current/output/chtml/part05/sect_K.2.html
static const uint8_t chinese[] = {
0x5a, 0x68, 0x61, 0x6e, 0x67, 0x5e, 0x58, 0x69, 0x61, 0x6f, 0x44, 0x6f,
0x6e, 0x67, 0x3d, 0x1b, 0x24, 0x29, 0x41, 0xd5, 0xc5, 0x5e, 0x1b, 0x24,
0x29, 0x41, 0xd0, 0xa1, 0xb6, 0xab, 0x3d, 0x20, 0x00
};
// echo -n "Zhang^XiaoDong=..." | hexdump -v -e '14/1 "0x%02x, "' -e '"\n"'
static const uint8_t utf8[] = {
0x5a, 0x68, 0x61, 0x6e, 0x67, 0x5e, 0x58, 0x69, 0x61, 0x6f, 0x44, 0x6f, 0x6e, 0x67,
0x3d, 0xe5, 0xbc, 0xa0, 0x5e, 0xe5, 0xb0, 0x8f, 0xe4, 0xb8, 0x9c, 0x3d
};
ParsedDicomFile dicom(false);
dicom.ReplacePlainString(DICOM_TAG_SPECIFIC_CHARACTER_SET, "\\ISO 2022 IR 58");
ASSERT_TRUE(dicom.GetDcmtkObject().getDataset()->putAndInsertString
(DCM_PatientName, reinterpret_cast<const char*>(chinese), OFBool(true)).good());
bool hasCodeExtensions;
Encoding encoding = dicom.DetectEncoding(hasCodeExtensions);
ASSERT_EQ(Encoding_SimplifiedChinese, encoding);
ASSERT_TRUE(hasCodeExtensions);
std::string value;
ASSERT_TRUE(dicom.GetTagValue(value, DICOM_TAG_PATIENT_NAME));
ASSERT_EQ(value, std::string(reinterpret_cast<const char*>(utf8), sizeof(utf8)));
}
TEST(Toolbox, EncodingsSimplifiedChinese3)
{
// http://dicom.nema.org/MEDICAL/dicom/current/output/chtml/part05/sect_K.2.html
static const uint8_t chinese[] = {
0x31, 0x2e, 0x1b, 0x24, 0x29, 0x41, 0xb5, 0xda, 0xd2, 0xbb, 0xd0, 0xd0, 0xce, 0xc4, 0xd7, 0xd6, 0xa1, 0xa3, 0x0d, 0x0a,
0x32, 0x2e, 0x1b, 0x24, 0x29, 0x41, 0xb5, 0xda, 0xb6, 0xfe, 0xd0, 0xd0, 0xce, 0xc4, 0xd7, 0xd6, 0xa1, 0xa3, 0x0d, 0x0a,
0x33, 0x2e, 0x1b, 0x24, 0x29, 0x41, 0xb5, 0xda, 0xc8, 0xfd, 0xd0, 0xd0, 0xce, 0xc4, 0xd7, 0xd6, 0xa1, 0xa3, 0x0d, 0x0a, 0x00
};
static const uint8_t line1[] = {
0x31, 0x2e, 0xe7, 0xac, 0xac, 0xe4, 0xb8, 0x80, 0xe8, 0xa1, 0x8c, 0xe6, 0x96, 0x87,
0xe5, 0xad, 0x97, 0xe3, 0x80, 0x82, '\r'
};
static const uint8_t line2[] = {
0x32, 0x2e, 0xe7, 0xac, 0xac, 0xe4, 0xba, 0x8c, 0xe8, 0xa1, 0x8c, 0xe6, 0x96, 0x87,
0xe5, 0xad, 0x97, 0xe3, 0x80, 0x82, '\r'
};
static const uint8_t line3[] = {
0x33, 0x2e, 0xe7, 0xac, 0xac, 0xe4, 0xb8, 0x89, 0xe8, 0xa1, 0x8c, 0xe6, 0x96, 0x87,
0xe5, 0xad, 0x97, 0xe3, 0x80, 0x82, '\r'
};
ParsedDicomFile dicom(false);
dicom.ReplacePlainString(DICOM_TAG_SPECIFIC_CHARACTER_SET, "\\ISO 2022 IR 58");
ASSERT_TRUE(dicom.GetDcmtkObject().getDataset()->putAndInsertString
(DCM_PatientName, reinterpret_cast<const char*>(chinese), OFBool(true)).good());
bool hasCodeExtensions;
Encoding encoding = dicom.DetectEncoding(hasCodeExtensions);
ASSERT_EQ(Encoding_SimplifiedChinese, encoding);
ASSERT_TRUE(hasCodeExtensions);
std::string value;
ASSERT_TRUE(dicom.GetTagValue(value, DICOM_TAG_PATIENT_NAME));
std::vector<std::string> lines;
Toolbox::TokenizeString(lines, value, '\n');
ASSERT_EQ(4u, lines.size());
ASSERT_EQ(std::string(reinterpret_cast<const char*>(line1), sizeof(line1)), lines[0]);
ASSERT_EQ(std::string(reinterpret_cast<const char*>(line2), sizeof(line2)), lines[1]);
ASSERT_EQ(std::string(reinterpret_cast<const char*>(line3), sizeof(line3)), lines[2]);
ASSERT_TRUE(lines[3].empty());
}
static void SetTagKey(ParsedDicomFile& dicom,
const DicomTag& tag,
const DicomTag& value)
{
// This function emulates a call to function
// "dicom.GetDcmtkObject().getDataset()->putAndInsertTagKey(tag,
// value)" that was not available in DCMTK 3.6.0
std::unique_ptr<DcmAttributeTag> element(new DcmAttributeTag(ToDcmtkBridge::Convert(tag)));
DcmTagKey v = ToDcmtkBridge::Convert(value);
if (!element->putTagVal(v).good())
{
throw OrthancException(ErrorCode_InternalError);
}
dicom.GetDcmtkObject().getDataset()->insert(element.release());
}
TEST(DicomWebJson, ValueRepresentation)
{
// http://dicom.nema.org/medical/dicom/current/output/chtml/part18/sect_F.2.3.html
ParsedDicomFile dicom(false);
dicom.ReplacePlainString(DicomTag(0x0040, 0x0241), "AE");
dicom.ReplacePlainString(DicomTag(0x0010, 0x1010), "AS");
SetTagKey(dicom, DicomTag(0x0020, 0x9165), DicomTag(0x0010, 0x0020));
dicom.ReplacePlainString(DicomTag(0x0008, 0x0052), "CS");
dicom.ReplacePlainString(DicomTag(0x0008, 0x0012), "DA");
dicom.ReplacePlainString(DicomTag(0x0010, 0x1020), "42"); // DS
dicom.ReplacePlainString(DicomTag(0x0008, 0x002a), "DT");
dicom.ReplacePlainString(DicomTag(0x0010, 0x9431), "43"); // FL
dicom.ReplacePlainString(DicomTag(0x0008, 0x1163), "44"); // FD
dicom.ReplacePlainString(DicomTag(0x0008, 0x1160), "45"); // IS
dicom.ReplacePlainString(DicomTag(0x0008, 0x0070), "LO");
dicom.ReplacePlainString(DicomTag(0x0010, 0x4000), "LT");
dicom.ReplacePlainString(DicomTag(0x0028, 0x2000), "OB");
dicom.ReplacePlainString(DicomTag(0x7fe0, 0x0009), "3.14159"); // OD (other double)
dicom.ReplacePlainString(DicomTag(0x0064, 0x0009), "2.71828"); // OF (other float)
dicom.ReplacePlainString(DicomTag(0x0066, 0x0040), "46"); // OL (other long)
ASSERT_THROW(dicom.ReplacePlainString(DicomTag(0x0028, 0x1201), "O"), OrthancException);
dicom.ReplacePlainString(DicomTag(0x0028, 0x1201), "OWOW");
dicom.ReplacePlainString(DicomTag(0x0010, 0x0010), "PN");
dicom.ReplacePlainString(DicomTag(0x0008, 0x0050), "SH");
dicom.ReplacePlainString(DicomTag(0x0018, 0x6020), "-15"); // SL
dicom.ReplacePlainString(DicomTag(0x0018, 0x9219), "-16"); // SS
dicom.ReplacePlainString(DicomTag(0x0008, 0x0081), "ST");
dicom.ReplacePlainString(DicomTag(0x0008, 0x0013), "TM");
dicom.ReplacePlainString(DicomTag(0x0008, 0x0119), "UC");
dicom.ReplacePlainString(DicomTag(0x0008, 0x0016), "UI");
dicom.ReplacePlainString(DicomTag(0x0008, 0x1161), "128"); // UL
dicom.ReplacePlainString(DicomTag(0x4342, 0x1234), "UN"); // Inexistent tag
dicom.ReplacePlainString(DicomTag(0x0008, 0x0120), "UR");
dicom.ReplacePlainString(DicomTag(0x0008, 0x0301), "17"); // US
dicom.ReplacePlainString(DicomTag(0x0040, 0x0031), "UT");
DicomWebJsonVisitor visitor;
dicom.Apply(visitor);
std::string s;
// The tag (0002,0002) is "Media Storage SOP Class UID" and is
// automatically copied by DCMTK from tag (0008,0016)
ASSERT_EQ("UI", visitor.GetResult() ["00020002"]["vr"].asString());
ASSERT_EQ("UI", visitor.GetResult() ["00020002"]["Value"][0].asString());
ASSERT_EQ("AE", visitor.GetResult() ["00400241"]["vr"].asString());
ASSERT_EQ("AE", visitor.GetResult() ["00400241"]["Value"][0].asString());
ASSERT_EQ("AS", visitor.GetResult() ["00101010"]["vr"].asString());
ASSERT_EQ("AS", visitor.GetResult() ["00101010"]["Value"][0].asString());
ASSERT_EQ("AT", visitor.GetResult() ["00209165"]["vr"].asString());
ASSERT_EQ("00100020", visitor.GetResult() ["00209165"]["Value"][0].asString());
ASSERT_EQ("CS", visitor.GetResult() ["00080052"]["vr"].asString());
ASSERT_EQ("CS", visitor.GetResult() ["00080052"]["Value"][0].asString());
ASSERT_EQ("DA", visitor.GetResult() ["00080012"]["vr"].asString());
ASSERT_EQ("DA", visitor.GetResult() ["00080012"]["Value"][0].asString());
ASSERT_EQ("DS", visitor.GetResult() ["00101020"]["vr"].asString());
ASSERT_EQ("42", visitor.GetResult() ["00101020"]["Value"][0].asString());
ASSERT_EQ("DT", visitor.GetResult() ["0008002A"]["vr"].asString());
ASSERT_EQ("DT", visitor.GetResult() ["0008002A"]["Value"][0].asString());
ASSERT_EQ("FL", visitor.GetResult() ["00109431"]["vr"].asString());
ASSERT_FLOAT_EQ(43.0f, visitor.GetResult() ["00109431"]["Value"][0].asFloat());
ASSERT_EQ("FD", visitor.GetResult() ["00081163"]["vr"].asString());
ASSERT_FLOAT_EQ(44.0f, visitor.GetResult() ["00081163"]["Value"][0].asFloat());
ASSERT_EQ("IS", visitor.GetResult() ["00081160"]["vr"].asString());
ASSERT_FLOAT_EQ(45.0f, visitor.GetResult() ["00081160"]["Value"][0].asFloat());
ASSERT_EQ("LO", visitor.GetResult() ["00080070"]["vr"].asString());
ASSERT_EQ("LO", visitor.GetResult() ["00080070"]["Value"][0].asString());
ASSERT_EQ("LT", visitor.GetResult() ["00104000"]["vr"].asString());
ASSERT_EQ("LT", visitor.GetResult() ["00104000"]["Value"][0].asString());
ASSERT_EQ("OB", visitor.GetResult() ["00282000"]["vr"].asString());
Toolbox::DecodeBase64(s, visitor.GetResult() ["00282000"]["InlineBinary"].asString());
ASSERT_EQ("OB", s);
#if DCMTK_VERSION_NUMBER >= 361
ASSERT_EQ("OD", visitor.GetResult() ["7FE00009"]["vr"].asString());
ASSERT_FLOAT_EQ(3.14159f, boost::lexical_cast<float>(visitor.GetResult() ["7FE00009"]["Value"][0].asString()));
#else
ASSERT_EQ("UN", visitor.GetResult() ["7FE00009"]["vr"].asString());
Toolbox::DecodeBase64(s, visitor.GetResult() ["7FE00009"]["InlineBinary"].asString());
ASSERT_EQ(8u, s.size()); // Because of padding
ASSERT_EQ(0, s[7]);
ASSERT_EQ("3.14159", s.substr(0, 7));
#endif
ASSERT_EQ("OF", visitor.GetResult() ["00640009"]["vr"].asString());
ASSERT_FLOAT_EQ(2.71828f, boost::lexical_cast<float>(visitor.GetResult() ["00640009"]["Value"][0].asString()));
#if DCMTK_VERSION_NUMBER < 361
ASSERT_EQ("UN", visitor.GetResult() ["00660040"]["vr"].asString());
Toolbox::DecodeBase64(s, visitor.GetResult() ["00660040"]["InlineBinary"].asString());
ASSERT_EQ("46", s);
#elif DCMTK_VERSION_NUMBER == 361
ASSERT_EQ("UL", visitor.GetResult() ["00660040"]["vr"].asString());
ASSERT_EQ(46, visitor.GetResult() ["00660040"]["Value"][0].asInt());
#else
ASSERT_EQ("OL", visitor.GetResult() ["00660040"]["vr"].asString());
ASSERT_EQ(46, visitor.GetResult() ["00660040"]["Value"][0].asInt());
#endif
ASSERT_EQ("OW", visitor.GetResult() ["00281201"]["vr"].asString());
Toolbox::DecodeBase64(s, visitor.GetResult() ["00281201"]["InlineBinary"].asString());
ASSERT_EQ("OWOW", s);
ASSERT_EQ("PN", visitor.GetResult() ["00100010"]["vr"].asString());
ASSERT_EQ("PN", visitor.GetResult() ["00100010"]["Value"][0]["Alphabetic"].asString());
ASSERT_EQ("SH", visitor.GetResult() ["00080050"]["vr"].asString());
ASSERT_EQ("SH", visitor.GetResult() ["00080050"]["Value"][0].asString());
ASSERT_EQ("SL", visitor.GetResult() ["00186020"]["vr"].asString());
ASSERT_EQ(-15, visitor.GetResult() ["00186020"]["Value"][0].asInt());
ASSERT_EQ("SS", visitor.GetResult() ["00189219"]["vr"].asString());
ASSERT_EQ(-16, visitor.GetResult() ["00189219"]["Value"][0].asInt());
ASSERT_EQ("ST", visitor.GetResult() ["00080081"]["vr"].asString());
ASSERT_EQ("ST", visitor.GetResult() ["00080081"]["Value"][0].asString());
ASSERT_EQ("TM", visitor.GetResult() ["00080013"]["vr"].asString());
ASSERT_EQ("TM", visitor.GetResult() ["00080013"]["Value"][0].asString());
#if DCMTK_VERSION_NUMBER >= 361
ASSERT_EQ("UC", visitor.GetResult() ["00080119"]["vr"].asString());
ASSERT_EQ("UC", visitor.GetResult() ["00080119"]["Value"][0].asString());
#else
ASSERT_EQ("UN", visitor.GetResult() ["00080119"]["vr"].asString());
Toolbox::DecodeBase64(s, visitor.GetResult() ["00080119"]["InlineBinary"].asString());
ASSERT_EQ("UC", s);
#endif
ASSERT_EQ("UI", visitor.GetResult() ["00080016"]["vr"].asString());
ASSERT_EQ("UI", visitor.GetResult() ["00080016"]["Value"][0].asString());
ASSERT_EQ("UL", visitor.GetResult() ["00081161"]["vr"].asString());
ASSERT_EQ(128u, visitor.GetResult() ["00081161"]["Value"][0].asUInt());
ASSERT_EQ("UN", visitor.GetResult() ["43421234"]["vr"].asString());
Toolbox::DecodeBase64(s, visitor.GetResult() ["43421234"]["InlineBinary"].asString());
ASSERT_EQ("UN", s);
#if DCMTK_VERSION_NUMBER >= 361
ASSERT_EQ("UR", visitor.GetResult() ["00080120"]["vr"].asString());
ASSERT_EQ("UR", visitor.GetResult() ["00080120"]["Value"][0].asString());
#else
ASSERT_EQ("UN", visitor.GetResult() ["00080120"]["vr"].asString());
Toolbox::DecodeBase64(s, visitor.GetResult() ["00080120"]["InlineBinary"].asString());
ASSERT_EQ("UR", s);
#endif
#if DCMTK_VERSION_NUMBER >= 361
ASSERT_EQ("US", visitor.GetResult() ["00080301"]["vr"].asString());
ASSERT_EQ(17u, visitor.GetResult() ["00080301"]["Value"][0].asUInt());
#else
ASSERT_EQ("UN", visitor.GetResult() ["00080301"]["vr"].asString());
Toolbox::DecodeBase64(s, visitor.GetResult() ["00080301"]["InlineBinary"].asString());
ASSERT_EQ("17", s);
#endif
ASSERT_EQ("UT", visitor.GetResult() ["00400031"]["vr"].asString());
ASSERT_EQ("UT", visitor.GetResult() ["00400031"]["Value"][0].asString());
std::string xml;
visitor.FormatXml(xml);
{
DicomMap m;
m.FromDicomWeb(visitor.GetResult());
ASSERT_EQ(31u, m.GetSize());
ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0002, 0x0002), false)); ASSERT_EQ("UI", s);
ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0040, 0x0241), false)); ASSERT_EQ("AE", s);
ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0010, 0x1010), false)); ASSERT_EQ("AS", s);
ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0020, 0x9165), false)); ASSERT_EQ("00100020", s);
ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0008, 0x0052), false)); ASSERT_EQ("CS", s);
ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0008, 0x0012), false)); ASSERT_EQ("DA", s);
ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0010, 0x1020), false)); ASSERT_EQ("42", s);
ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0008, 0x002a), false)); ASSERT_EQ("DT", s);
ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0010, 0x9431), false)); ASSERT_EQ("43", s);
ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0008, 0x1163), false)); ASSERT_EQ("44", s);
ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0008, 0x1160), false)); ASSERT_EQ("45", s);
ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0008, 0x0070), false)); ASSERT_EQ("LO", s);
ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0010, 0x4000), false)); ASSERT_EQ("LT", s);
ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0028, 0x2000), true)); ASSERT_EQ("OB", s);
ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x7fe0, 0x0009), true));
#if DCMTK_VERSION_NUMBER >= 361
ASSERT_FLOAT_EQ(3.14159f, boost::lexical_cast<float>(s));
#else
ASSERT_EQ(8u, s.size()); // Because of padding
ASSERT_EQ(0, s[7]);
ASSERT_EQ("3.14159", s.substr(0, 7));
#endif
ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0064, 0x0009), true));
ASSERT_FLOAT_EQ(2.71828f, boost::lexical_cast<float>(s));
ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0028, 0x1201), true)); ASSERT_EQ("OWOW", s);
ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0010, 0x0010), false)); ASSERT_EQ("PN", s);
ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0008, 0x0050), false)); ASSERT_EQ("SH", s);
ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0018, 0x6020), false)); ASSERT_EQ("-15", s);
ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0018, 0x9219), false)); ASSERT_EQ("-16", s);
ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0008, 0x0081), false)); ASSERT_EQ("ST", s);
ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0008, 0x0013), false)); ASSERT_EQ("TM", s);
ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0008, 0x0016), false)); ASSERT_EQ("UI", s);
ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0008, 0x1161), false)); ASSERT_EQ("128", s);
ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x4342, 0x1234), true)); ASSERT_EQ("UN", s);
ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0040, 0x0031), false)); ASSERT_EQ("UT", s);
#if DCMTK_VERSION_NUMBER >= 361
ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0066, 0x0040), false)); ASSERT_EQ("46", s);
ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0008, 0x0119), false)); ASSERT_EQ("UC", s);
ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0008, 0x0120), false)); ASSERT_EQ("UR", s);
ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0008, 0x0301), false)); ASSERT_EQ("17", s);
#else
ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0066, 0x0040), true)); ASSERT_EQ("46", s); // OL
ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0008, 0x0119), true)); ASSERT_EQ("UC", s);
ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0008, 0x0120), true)); ASSERT_EQ("UR", s);
ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0008, 0x0301), true)); ASSERT_EQ("17", s); // US (but tag unknown to DCMTK 3.6.0)
#endif
}
}
TEST(DicomWebJson, Sequence)
{
ParsedDicomFile dicom(false);
{
std::unique_ptr<DcmSequenceOfItems> sequence(new DcmSequenceOfItems(DCM_ReferencedSeriesSequence));
for (unsigned int i = 0; i < 3; i++)
{
std::unique_ptr<DcmItem> item(new DcmItem);
std::string s = "item" + boost::lexical_cast<std::string>(i);
item->putAndInsertString(DCM_ReferencedSOPInstanceUID, s.c_str(), OFFalse);
ASSERT_TRUE(sequence->insert(item.release(), false, false).good());
}
ASSERT_TRUE(dicom.GetDcmtkObject().getDataset()->insert(sequence.release(), false, false).good());
}
DicomWebJsonVisitor visitor;
dicom.Apply(visitor);
ASSERT_EQ("SQ", visitor.GetResult() ["00081115"]["vr"].asString());
ASSERT_EQ(3u, visitor.GetResult() ["00081115"]["Value"].size());
std::set<std::string> items;
for (Json::Value::ArrayIndex i = 0; i < 3; i++)
{
ASSERT_EQ(1u, visitor.GetResult() ["00081115"]["Value"][i].size());
ASSERT_EQ(1u, visitor.GetResult() ["00081115"]["Value"][i]["00081155"]["Value"].size());
ASSERT_EQ("UI", visitor.GetResult() ["00081115"]["Value"][i]["00081155"]["vr"].asString());
items.insert(visitor.GetResult() ["00081115"]["Value"][i]["00081155"]["Value"][0].asString());
}
ASSERT_EQ(3u, items.size());
ASSERT_TRUE(items.find("item0") != items.end());
ASSERT_TRUE(items.find("item1") != items.end());
ASSERT_TRUE(items.find("item2") != items.end());
std::string xml;
visitor.FormatXml(xml);
{
DicomMap m;
m.FromDicomWeb(visitor.GetResult());
ASSERT_EQ(0u, m.GetSize()); // Sequences are not handled by DicomMap
}
}
TEST(ParsedDicomCache, Basic)
{
ParsedDicomCache cache(10);
ASSERT_EQ(0u, cache.GetCurrentSize());
ASSERT_EQ(0u, cache.GetNumberOfItems());
DicomMap tags;
tags.SetValue(DICOM_TAG_PATIENT_ID, "patient1", false);
cache.Acquire("a", new ParsedDicomFile(tags, Encoding_Latin1, true), 20);
ASSERT_EQ(20u, cache.GetCurrentSize());
ASSERT_EQ(1u, cache.GetNumberOfItems());
{
ParsedDicomCache::Accessor accessor(cache, "b");
ASSERT_FALSE(accessor.IsValid());
ASSERT_THROW(accessor.GetDicom(), OrthancException);
ASSERT_THROW(accessor.GetFileSize(), OrthancException);
}
{
ParsedDicomCache::Accessor accessor(cache, "a");
ASSERT_TRUE(accessor.IsValid());
std::string s;
ASSERT_TRUE(accessor.GetDicom().GetTagValue(s, DICOM_TAG_PATIENT_ID));
ASSERT_EQ("patient1", s);
ASSERT_EQ(20u, accessor.GetFileSize());
}
tags.SetValue(DICOM_TAG_PATIENT_ID, "patient2", false);
cache.Acquire("b", new ParsedDicomFile(tags, Encoding_Latin1, true), 5);
ASSERT_EQ(5u, cache.GetCurrentSize());
ASSERT_EQ(1u, cache.GetNumberOfItems());
cache.Acquire("c", new ParsedDicomFile(true), 5);
ASSERT_EQ(10u, cache.GetCurrentSize());
ASSERT_EQ(2u, cache.GetNumberOfItems());
{
ParsedDicomCache::Accessor accessor(cache, "b");
ASSERT_TRUE(accessor.IsValid());
std::string s;
ASSERT_TRUE(accessor.GetDicom().GetTagValue(s, DICOM_TAG_PATIENT_ID));
ASSERT_EQ("patient2", s);
ASSERT_EQ(5u, accessor.GetFileSize());
}
cache.Acquire("d", new ParsedDicomFile(true), 5);
ASSERT_EQ(10u, cache.GetCurrentSize());
ASSERT_EQ(2u, cache.GetNumberOfItems());
ASSERT_TRUE(ParsedDicomCache::Accessor(cache, "b").IsValid());
ASSERT_FALSE(ParsedDicomCache::Accessor(cache, "c").IsValid()); // recycled by LRU
ASSERT_TRUE(ParsedDicomCache::Accessor(cache, "d").IsValid());
cache.Invalidate("d");
ASSERT_EQ(5u, cache.GetCurrentSize());
ASSERT_EQ(1u, cache.GetNumberOfItems());
ASSERT_TRUE(ParsedDicomCache::Accessor(cache, "b").IsValid());
ASSERT_FALSE(ParsedDicomCache::Accessor(cache, "d").IsValid());
cache.Acquire("e", new ParsedDicomFile(true), 15);
ASSERT_EQ(15u, cache.GetCurrentSize());
ASSERT_EQ(1u, cache.GetNumberOfItems());
ASSERT_FALSE(ParsedDicomCache::Accessor(cache, "c").IsValid());
ASSERT_FALSE(ParsedDicomCache::Accessor(cache, "d").IsValid());
ASSERT_TRUE(ParsedDicomCache::Accessor(cache, "e").IsValid());
cache.Invalidate("e");
ASSERT_EQ(0u, cache.GetCurrentSize());
ASSERT_EQ(0u, cache.GetNumberOfItems());
ASSERT_FALSE(ParsedDicomCache::Accessor(cache, "e").IsValid());
}
static bool MyIsMatch(const DicomPath& a,
const DicomPath& b)
{
bool expected = DicomPath::IsMatch(a, b);
std::vector<DicomTag> prefixTags;
std::vector<size_t> prefixIndexes;
for (size_t i = 0; i < b.GetPrefixLength(); i++)
{
prefixTags.push_back(b.GetPrefixTag(i));
prefixIndexes.push_back(b.GetPrefixIndex(i));
}
if (expected == DicomPath::IsMatch(a, prefixTags, prefixIndexes, b.GetFinalTag()))
{
return expected;
}
else
{
throw OrthancException(ErrorCode_InternalError);
}
}
TEST(DicomModification, DicomPath)
{
// Those are samples inspired by those from "man dcmodify"
static const DicomTag DICOM_TAG_ACQUISITION_MATRIX(0x0018, 0x1310);
static const DicomTag DICOM_TAG_REFERENCED_PERFORMED_PROCEDURE_STEP_SEQUENCE(0x0008, 0x1111);
DicomPath path = DicomPath::Parse("(0010,0010)");
ASSERT_FALSE(path.HasUniversal());
ASSERT_EQ(0u, path.GetPrefixLength());
ASSERT_EQ(DICOM_TAG_PATIENT_NAME, path.GetFinalTag());
ASSERT_THROW(path.GetPrefixTag(0), OrthancException);
path = DicomPath::Parse("0018,1310");
ASSERT_FALSE(path.HasUniversal());
ASSERT_EQ(0u, path.GetPrefixLength());
ASSERT_EQ(DICOM_TAG_ACQUISITION_MATRIX, path.GetFinalTag());
ASSERT_EQ("(0018,1310)", path.Format());
// The following sample won't work without DCMTK
path = DicomPath::Parse("PatientID");
ASSERT_FALSE(path.HasUniversal());
ASSERT_EQ(0u, path.GetPrefixLength());
ASSERT_EQ(DICOM_TAG_PATIENT_ID, path.GetFinalTag());
ASSERT_EQ("(0010,0020)", path.Format());
path = DicomPath::Parse("(0018,1310)");
ASSERT_FALSE(path.HasUniversal());
ASSERT_EQ(0u, path.GetPrefixLength());
ASSERT_EQ(DICOM_TAG_ACQUISITION_MATRIX, path.GetFinalTag());
ASSERT_EQ("(0018,1310)", path.Format());
path = DicomPath::Parse("(0008,1111)[0].PatientName");
ASSERT_FALSE(path.HasUniversal());
ASSERT_EQ(1u, path.GetPrefixLength());
ASSERT_EQ(DICOM_TAG_REFERENCED_PERFORMED_PROCEDURE_STEP_SEQUENCE, path.GetPrefixTag(0));
ASSERT_FALSE(path.IsPrefixUniversal(0));
ASSERT_EQ(0u, path.GetPrefixIndex(0));
ASSERT_THROW(path.GetPrefixTag(1), OrthancException);
ASSERT_EQ(DICOM_TAG_PATIENT_NAME, path.GetFinalTag());
path = DicomPath::Parse("(0008,1111)[1].(0008,1111)[2].(0010,0010)");
ASSERT_FALSE(path.HasUniversal());
ASSERT_EQ(2u, path.GetPrefixLength());
ASSERT_EQ(DICOM_TAG_REFERENCED_PERFORMED_PROCEDURE_STEP_SEQUENCE, path.GetPrefixTag(0));
ASSERT_FALSE(path.IsPrefixUniversal(0));
ASSERT_EQ(1u, path.GetPrefixIndex(0));
ASSERT_EQ(DICOM_TAG_REFERENCED_PERFORMED_PROCEDURE_STEP_SEQUENCE, path.GetPrefixTag(1));
ASSERT_FALSE(path.IsPrefixUniversal(1));
ASSERT_EQ(2u, path.GetPrefixIndex(1));
ASSERT_THROW(path.GetPrefixTag(2), OrthancException);
ASSERT_EQ(DICOM_TAG_PATIENT_NAME, path.GetFinalTag());
path = DicomPath::Parse("(0008,1111)[*].PatientName");
ASSERT_TRUE(path.HasUniversal());
ASSERT_EQ(1u, path.GetPrefixLength());
ASSERT_EQ(DICOM_TAG_REFERENCED_PERFORMED_PROCEDURE_STEP_SEQUENCE, path.GetPrefixTag(0));
ASSERT_TRUE(path.IsPrefixUniversal(0));
ASSERT_THROW(path.GetPrefixIndex(0), OrthancException);
ASSERT_THROW(path.GetPrefixTag(1), OrthancException);
ASSERT_EQ(DICOM_TAG_PATIENT_NAME, path.GetFinalTag());
ASSERT_EQ("(0008,1111)[*].(0010,0010)", path.Format());
path = DicomPath::Parse("(0008,1111)[1].(0008,1111)[*].(0010,0010)");
ASSERT_TRUE(path.HasUniversal());
ASSERT_EQ(2u, path.GetPrefixLength());
ASSERT_EQ(DICOM_TAG_REFERENCED_PERFORMED_PROCEDURE_STEP_SEQUENCE, path.GetPrefixTag(0));
ASSERT_FALSE(path.IsPrefixUniversal(0));
ASSERT_EQ(1u, path.GetPrefixIndex(0));
ASSERT_EQ(DICOM_TAG_REFERENCED_PERFORMED_PROCEDURE_STEP_SEQUENCE, path.GetPrefixTag(0));
ASSERT_TRUE(path.IsPrefixUniversal(1));
ASSERT_THROW(path.GetPrefixIndex(1), OrthancException);
ASSERT_THROW(path.GetPrefixTag(2), OrthancException);
ASSERT_EQ(DICOM_TAG_PATIENT_NAME, path.GetFinalTag());
path = DicomPath::Parse("PatientID[1].PatientName");
ASSERT_FALSE(path.HasUniversal());
ASSERT_EQ(1u, path.GetPrefixLength());
ASSERT_EQ(DICOM_TAG_PATIENT_ID, path.GetPrefixTag(0));
ASSERT_FALSE(path.IsPrefixUniversal(0));
ASSERT_EQ(1u, path.GetPrefixIndex(0));
ASSERT_THROW(path.GetPrefixTag(1), OrthancException);
ASSERT_EQ(DICOM_TAG_PATIENT_NAME, path.GetFinalTag());
path = DicomPath::Parse(" PatientID [ 42 ] . PatientName ");
ASSERT_FALSE(path.HasUniversal());
ASSERT_EQ(1u, path.GetPrefixLength());
ASSERT_EQ(DICOM_TAG_PATIENT_ID, path.GetPrefixTag(0));
ASSERT_FALSE(path.IsPrefixUniversal(0));
ASSERT_EQ(42u, path.GetPrefixIndex(0));
ASSERT_THROW(path.GetPrefixTag(1), OrthancException);
ASSERT_EQ(DICOM_TAG_PATIENT_NAME, path.GetFinalTag());
ASSERT_EQ("(0010,0020)[42].(0010,0010)", path.Format());
ASSERT_THROW(path.SetPrefixIndex(1, 44), OrthancException);
path.SetPrefixIndex(0, 44);
ASSERT_EQ("(0010,0020)[44].(0010,0010)", path.Format());
ASSERT_THROW(DicomPath::Parse("nope"), OrthancException);
ASSERT_THROW(DicomPath::Parse("(0010,0010)[.PatientID"), OrthancException);
ASSERT_THROW(DicomPath::Parse("(0010,0010)[].PatientID"), OrthancException);
ASSERT_THROW(DicomPath::Parse("(0010,0010[].PatientID"), OrthancException);
ASSERT_THROW(DicomPath::Parse("(0010,0010)0].PatientID"), OrthancException);
ASSERT_THROW(DicomPath::Parse("(0010,0010)[-1].PatientID"), OrthancException);
ASSERT_TRUE(MyIsMatch(DicomPath::Parse("(0010,0010)"),
DicomPath::Parse("(0010,0010)")));
ASSERT_FALSE(MyIsMatch(DicomPath::Parse("(0010,0010)"),
DicomPath::Parse("(0010,0020)")));
ASSERT_TRUE(MyIsMatch(DicomPath::Parse("(0010,0010)"),
DicomPath::Parse("(0010,0010)[1].(0010,0020)")));
ASSERT_FALSE(MyIsMatch(DicomPath::Parse("(0010,0010)[1].(0010,0020)"),
DicomPath::Parse("(0010,0010)")));
ASSERT_TRUE(MyIsMatch(DicomPath::Parse("(0010,0010)[1].(0010,0020)"),
DicomPath::Parse("(0010,0010)[1].(0010,0020)")));
ASSERT_TRUE(MyIsMatch(DicomPath::Parse("(0010,0010)[*].(0010,0020)"),
DicomPath::Parse("(0010,0010)[1].(0010,0020)")));
ASSERT_FALSE(MyIsMatch(DicomPath::Parse("(0010,0010)[2].(0010,0020)"),
DicomPath::Parse("(0010,0010)[1].(0010,0020)")));
ASSERT_THROW(MyIsMatch(DicomPath::Parse("(0010,0010)[1].(0010,0020)"),
DicomPath::Parse("(0010,0010)[*].(0010,0020)")), OrthancException);
ASSERT_TRUE(MyIsMatch(DicomPath::Parse("(0010,0010)[*].(0010,0020)[*].(0010,0030)"),
DicomPath::Parse("(0010,0010)[1].(0010,0020)[2].(0010,0030)[3].(0010,0040)")));
ASSERT_TRUE(MyIsMatch(DicomPath::Parse("(0010,0010)[1].(0010,0020)[2].(0010,0030)"),
DicomPath::Parse("(0010,0010)[1].(0010,0020)[2].(0010,0030)[3].(0010,0040)")));
ASSERT_FALSE(MyIsMatch(DicomPath::Parse("(0010,0010)[1].(0010,0020)[3].(0010,0030)"),
DicomPath::Parse("(0010,0010)[1].(0010,0020)[2].(0010,0030)[3].(0010,0040)")));
ASSERT_FALSE(MyIsMatch(DicomPath::Parse("(0010,0010)[2].(0010,0020)[2].(0010,0030)"),
DicomPath::Parse("(0010,0010)[1].(0010,0020)[2].(0010,0030)[3].(0010,0040)")));
}
TEST(ParsedDicomFile, DicomPath)
{
Json::Value v = Json::objectValue;
v["PatientName"] = "Hello";
v["ReferencedSOPClassUID"] = "1.2.840.10008.5.1.4.1.1.4";
{
Json::Value a = Json::arrayValue;
{
Json::Value item = Json::objectValue;
item["ReferencedSOPClassUID"] = "1.2.840.10008.5.1.4.1.1.4";
item["ReferencedSOPInstanceUID"] = "1.2.840.113619.2.176.2025.1499492.7040.1171286241.719";
a.append(item);
}
{
Json::Value item = Json::objectValue;
item["ReferencedSOPClassUID"] = "1.2.840.10008.5.1.4.1.1.4"; // ReferencedSOPClassUID
item["ReferencedSOPInstanceUID"] = "1.2.840.113619.2.176.2025.1499492.7040.1171286241.726";
a.append(item);
}
v["ReferencedImageSequence"] = a;
}
{
Json::Value a = Json::arrayValue;
{
Json::Value item = Json::objectValue;
item["StudyInstanceUID"] = "1.2.840.113704.1.111.7016.1342451220.40";
{
Json::Value b = Json::arrayValue;
{
Json::Value c = Json::objectValue;
c["CodeValue"] = "122403";
c["0008,103e"] = "WORLD"; // Series description
b.append(c);
}
item["PurposeOfReferenceCodeSequence"] = b;
}
a.append(item);
}
v["RelatedSeriesSequence"] = a;
}
static const char* CODE_VALUE = "0008,0100";
static const char* PATIENT_ID = "0010,0020";
static const char* PATIENT_NAME = "0010,0010";
static const char* PURPOSE_CODE_SEQ = "0040,a170";
static const char* REF_IM_SEQ = "0008,1140";
static const char* REF_SOP_CLASS = "0008,1150";
static const char* REF_SOP_INSTANCE = "0008,1155";
static const char* REL_SERIES_SEQ = "0008,1250";
static const char* STUDY_INSTANCE_UID = "0020,000d";
{
std::unique_ptr<ParsedDicomFile> dicom(ParsedDicomFile::CreateFromJson(v, DicomFromJsonFlags_None, ""));
Json::Value vv;
dicom->DatasetToJson(vv, DicomToJsonFormat_Short, DicomToJsonFlags_None, 0);
ASSERT_EQ(5u, vv.size());
ASSERT_TRUE(vv.isMember(PATIENT_NAME));
ASSERT_EQ(2u, vv[REF_IM_SEQ].size());
ASSERT_EQ(1u, vv[REL_SERIES_SEQ].size());
ASSERT_EQ(2u, vv[REF_IM_SEQ][0].size());
ASSERT_EQ(2u, vv[REL_SERIES_SEQ][0].size());
ASSERT_EQ(1u, vv[REL_SERIES_SEQ][0][PURPOSE_CODE_SEQ].size());
ASSERT_TRUE(vv[REF_IM_SEQ][0].isMember(REF_SOP_CLASS));
ASSERT_TRUE(vv[REF_IM_SEQ][1].isMember(REF_SOP_CLASS));
ASSERT_TRUE(vv[REL_SERIES_SEQ][0][PURPOSE_CODE_SEQ][0].isMember(CODE_VALUE));
}
{
std::unique_ptr<ParsedDicomFile> dicom(ParsedDicomFile::CreateFromJson(v, DicomFromJsonFlags_None, ""));
dicom->RemovePath(DicomPath::Parse("ReferencedImageSequence[*].ReferencedSOPClassUID"));
Json::Value vv;
dicom->DatasetToJson(vv, DicomToJsonFormat_Short, DicomToJsonFlags_None, 0);
ASSERT_EQ(2u, vv[REF_IM_SEQ].size());
ASSERT_EQ(1u, vv[REF_IM_SEQ][0].size());
ASSERT_EQ(1u, vv[REF_IM_SEQ][1].size());
ASSERT_FALSE(vv[REF_IM_SEQ][0].isMember(REF_SOP_CLASS));
ASSERT_FALSE(vv[REF_IM_SEQ][1].isMember(REF_SOP_CLASS));
}
{
std::unique_ptr<ParsedDicomFile> dicom(ParsedDicomFile::CreateFromJson(v, DicomFromJsonFlags_None, ""));
dicom->RemovePath(DicomPath::Parse("ReferencedImageSequence[0].ReferencedSOPClassUID"));
Json::Value vv;
dicom->DatasetToJson(vv, DicomToJsonFormat_Short, DicomToJsonFlags_None, 0);
ASSERT_EQ(2u, vv[REF_IM_SEQ].size());
ASSERT_EQ(1u, vv[REF_IM_SEQ][0].size());
ASSERT_EQ(2u, vv[REF_IM_SEQ][1].size());
ASSERT_FALSE(vv[REF_IM_SEQ][0].isMember(REF_SOP_CLASS));
ASSERT_TRUE(vv[REF_IM_SEQ][1].isMember(REF_SOP_CLASS));
}
{
std::unique_ptr<ParsedDicomFile> dicom(ParsedDicomFile::CreateFromJson(v, DicomFromJsonFlags_None, ""));
dicom->RemovePath(DicomPath::Parse("ReferencedImageSequence[1].ReferencedSOPClassUID"));
Json::Value vv;
dicom->DatasetToJson(vv, DicomToJsonFormat_Short, DicomToJsonFlags_None, 0);
ASSERT_EQ(2u, vv[REF_IM_SEQ].size());
ASSERT_EQ(2u, vv[REF_IM_SEQ][0].size());
ASSERT_EQ(1u, vv[REF_IM_SEQ][1].size());
ASSERT_TRUE(vv[REF_IM_SEQ][0].isMember(REF_SOP_CLASS));
ASSERT_FALSE(vv[REF_IM_SEQ][1].isMember(REF_SOP_CLASS));
}
{
std::unique_ptr<ParsedDicomFile> dicom(ParsedDicomFile::CreateFromJson(v, DicomFromJsonFlags_None, ""));
dicom->RemovePath(DicomPath::Parse("RelatedSeriesSequence[0].PurposeOfReferenceCodeSequence[0].CodeValue"));
Json::Value vv;
dicom->DatasetToJson(vv, DicomToJsonFormat_Short, DicomToJsonFlags_None, 0);
static const char* SERIES_DESCRIPTION = "0008,103e";
ASSERT_EQ("WORLD", vv[REL_SERIES_SEQ][0][PURPOSE_CODE_SEQ][0][SERIES_DESCRIPTION].asString());
ASSERT_FALSE(vv[REL_SERIES_SEQ][0][PURPOSE_CODE_SEQ][0].isMember(CODE_VALUE));
}
{
std::unique_ptr<ParsedDicomFile> dicom(ParsedDicomFile::CreateFromJson(v, DicomFromJsonFlags_None, ""));
dicom->RemovePath(DicomPath::Parse("RelatedSeriesSequence[0].PurposeOfReferenceCodeSequence"));
Json::Value vv;
dicom->DatasetToJson(vv, DicomToJsonFormat_Short, DicomToJsonFlags_None, 0);
ASSERT_EQ(1u, vv[REL_SERIES_SEQ][0].size());
ASSERT_FALSE(vv[REL_SERIES_SEQ][0].isMember(PURPOSE_CODE_SEQ));
}
{
std::unique_ptr<ParsedDicomFile> dicom(ParsedDicomFile::CreateFromJson(v, DicomFromJsonFlags_None, ""));
dicom->RemovePath(DicomPath::Parse("RelatedSeriesSequence"));
Json::Value vv;
dicom->DatasetToJson(vv, DicomToJsonFormat_Short, DicomToJsonFlags_None, 0);
ASSERT_FALSE(vv.isMember(REL_SERIES_SEQ));
}
{
std::unique_ptr<ParsedDicomFile> dicom(ParsedDicomFile::CreateFromJson(v, DicomFromJsonFlags_None, ""));
dicom->RemovePath(DicomPath(DICOM_TAG_PATIENT_NAME));
dicom->ReplacePath(DicomPath::Parse("ReferencedImageSequence[*].ReferencedSOPClassUID"),
"Hello1", false, DicomReplaceMode_ThrowIfAbsent, "");
ASSERT_THROW(dicom->ReplacePath(DicomPath::Parse("ReferencedImageSequence[*].PatientID"),
"Hello2", false, DicomReplaceMode_ThrowIfAbsent, ""), OrthancException);
dicom->ReplacePath(DicomPath::Parse("ReferencedImageSequence[*].PatientID"),
"Hello3", false, DicomReplaceMode_InsertIfAbsent, "");
dicom->ReplacePath(DicomPath::Parse("ReferencedImageSequence[*].PatientName"),
"Hello4", false, DicomReplaceMode_IgnoreIfAbsent, "");
dicom->ReplacePath(DicomPath::Parse("RelatedSeriesSequence[*].PurposeOfReferenceCodeSequence[*].CodeValue"),
"Hello5", false, DicomReplaceMode_ThrowIfAbsent, "");
Json::Value vv;
dicom->DatasetToJson(vv, DicomToJsonFormat_Short, DicomToJsonFlags_None, 0);
ASSERT_EQ(4u, vv.size());
ASSERT_FALSE(vv.isMember(PATIENT_NAME));
ASSERT_EQ("Hello1", vv[REF_IM_SEQ][0][REF_SOP_CLASS].asString());
ASSERT_EQ("Hello3", vv[REF_IM_SEQ][0][PATIENT_ID].asString());
ASSERT_EQ("Hello1", vv[REF_IM_SEQ][1][REF_SOP_CLASS].asString());
ASSERT_EQ("Hello3", vv[REF_IM_SEQ][1][PATIENT_ID].asString());
ASSERT_EQ("Hello5", vv[REL_SERIES_SEQ][0][PURPOSE_CODE_SEQ][0][CODE_VALUE].asString());
}
{
std::unique_ptr<ParsedDicomFile> dicom(ParsedDicomFile::CreateFromJson(v, DicomFromJsonFlags_None, ""));
dicom->ReplacePath(DicomPath::Parse("ReferencedImageSequence[1].ReferencedSOPClassUID"),
"Hello1", false, DicomReplaceMode_ThrowIfAbsent, "");
dicom->ReplacePath(DicomPath::Parse("RelatedSeriesSequence[0].PurposeOfReferenceCodeSequence[0].CodeValue"),
"Hello2", false, DicomReplaceMode_ThrowIfAbsent, "");
Json::Value vv;
dicom->DatasetToJson(vv, DicomToJsonFormat_Short, DicomToJsonFlags_None, 0);
ASSERT_EQ("1.2.840.10008.5.1.4.1.1.4", vv[REF_IM_SEQ][0][REF_SOP_CLASS].asString());
ASSERT_EQ("Hello1", vv[REF_IM_SEQ][1][REF_SOP_CLASS].asString());
ASSERT_EQ("Hello2", vv[REL_SERIES_SEQ][0][PURPOSE_CODE_SEQ][0][CODE_VALUE].asString());
}
{
std::unique_ptr<ParsedDicomFile> dicom(ParsedDicomFile::CreateFromJson(v, DicomFromJsonFlags_None, ""));
dicom->ClearPath(DicomPath::Parse("ReferencedImageSequence[1].ReferencedSOPClassUID"), true);
dicom->ClearPath(DicomPath::Parse("RelatedSeriesSequence[0].PurposeOfReferenceCodeSequence[0].CodeValue"), true);
dicom->ClearPath(DicomPath::Parse("ReferencedImageSequence[0].PatientID"), false);
dicom->ClearPath(DicomPath::Parse("ReferencedImageSequence[0].PatientName"), true);
Json::Value vv;
dicom->DatasetToJson(vv, DicomToJsonFormat_Short, DicomToJsonFlags_None, 0);
ASSERT_EQ(3u, vv[REF_IM_SEQ][0].size());
ASSERT_EQ(2u, vv[REF_IM_SEQ][1].size());
ASSERT_EQ("1.2.840.10008.5.1.4.1.1.4", vv[REF_IM_SEQ][0][REF_SOP_CLASS].asString());
ASSERT_EQ("1.2.840.113619.2.176.2025.1499492.7040.1171286241.719", vv[REF_IM_SEQ][0][REF_SOP_INSTANCE].asString());
ASSERT_EQ("", vv[REF_IM_SEQ][0][PATIENT_ID].asString());
ASSERT_EQ("", vv[REF_IM_SEQ][1][REF_SOP_CLASS].asString());
ASSERT_EQ("1.2.840.113619.2.176.2025.1499492.7040.1171286241.726", vv[REF_IM_SEQ][1][REF_SOP_INSTANCE].asString());
ASSERT_EQ("", vv[REL_SERIES_SEQ][0][PURPOSE_CODE_SEQ][0][CODE_VALUE].asString());
}
{
std::unique_ptr<ParsedDicomFile> dicom(ParsedDicomFile::CreateFromJson(v, DicomFromJsonFlags_None, ""));
{
DicomModification modif;
modif.Replace(DicomPath(DICOM_TAG_PATIENT_NAME), "Hello1", false);
modif.Replace(DicomPath::Parse("ReferencedImageSequence[1].ReferencedSOPClassUID"), "Hello2", false);
modif.Replace(DicomPath::Parse("RelatedSeriesSequence[0].PurposeOfReferenceCodeSequence[0].CodeValue"), "Hello3", false);
modif.Apply(*dicom);
}
Json::Value vv;
dicom->DatasetToJson(vv, DicomToJsonFormat_Short, DicomToJsonFlags_None, 0);
ASSERT_TRUE(vv.isMember(PATIENT_NAME));
ASSERT_EQ("Hello1", vv[PATIENT_NAME].asString());
ASSERT_EQ("1.2.840.10008.5.1.4.1.1.4", vv[REF_IM_SEQ][0][REF_SOP_CLASS].asString());
ASSERT_EQ("Hello2", vv[REF_IM_SEQ][1][REF_SOP_CLASS].asString());
ASSERT_EQ("Hello3", vv[REL_SERIES_SEQ][0][PURPOSE_CODE_SEQ][0][CODE_VALUE].asString());
ASSERT_EQ(2u, vv[REL_SERIES_SEQ][0].size());
}
{
std::unique_ptr<ParsedDicomFile> dicom(ParsedDicomFile::CreateFromJson(v, DicomFromJsonFlags_None, ""));
{
DicomModification modif;
modif.Remove(DicomPath(DICOM_TAG_PATIENT_NAME));
modif.Remove(DicomPath::Parse("ReferencedImageSequence[1].ReferencedSOPClassUID"));
modif.Remove(DicomPath::Parse("RelatedSeriesSequence[0].PurposeOfReferenceCodeSequence"));
modif.Apply(*dicom);
}
Json::Value vv;
dicom->DatasetToJson(vv, DicomToJsonFormat_Short, DicomToJsonFlags_None, 0);
ASSERT_FALSE(vv.isMember(PATIENT_NAME));
ASSERT_EQ(2u, vv[REF_IM_SEQ][0].size());
ASSERT_TRUE(vv[REF_IM_SEQ][0].isMember(REF_SOP_CLASS));
ASSERT_EQ(1u, vv[REF_IM_SEQ][1].size());
ASSERT_FALSE(vv[REF_IM_SEQ][1].isMember(REF_SOP_CLASS));
ASSERT_EQ(1u, vv[REL_SERIES_SEQ][0].size());
}
{
std::unique_ptr<ParsedDicomFile> dicom1(ParsedDicomFile::CreateFromJson(v, DicomFromJsonFlags_None, ""));
std::unique_ptr<ParsedDicomFile> dicom2(dicom1->Clone(true));
{
DicomModification modif;
modif.SetupAnonymization(DicomVersion_2023b);
modif.Apply(*dicom1);
modif.Apply(*dicom2);
}
// Same anonymization context and same input DICOM => hence, same output DICOM
Json::Value vv1, vv2;
dicom1->DatasetToJson(vv1, DicomToJsonFormat_Short, DicomToJsonFlags_None, 0);
dicom2->DatasetToJson(vv2, DicomToJsonFormat_Short, DicomToJsonFlags_None, 0);
ASSERT_EQ(vv1.toStyledString(), vv2.toStyledString());
ASSERT_TRUE(Toolbox::IsUuid(vv1[PATIENT_NAME].asString()));
ASSERT_EQ("1.2.840.10008.5.1.4.1.1.4", vv1[REF_IM_SEQ][0][REF_SOP_CLASS].asString());
ASSERT_NE("1.2.840.113619.2.176.2025.1499492.7040.1171286241.719", vv1[REF_IM_SEQ][0][REF_SOP_INSTANCE].asString());
ASSERT_NE("1.2.840.113619.2.176.2025.1499492.7040.1171286241.726", vv1[REF_IM_SEQ][1][REF_SOP_INSTANCE].asString());
ASSERT_NE("1.2.840.113704.1.111.7016.1342451220.40", vv1[REL_SERIES_SEQ][0][STUDY_INSTANCE_UID].asString());
// Contrarily to Orthanc 1.9.4, the "SERIES_DESCRIPTION" is also removed from nested sequences
ASSERT_EQ(1u, vv1[REL_SERIES_SEQ][0][PURPOSE_CODE_SEQ][0].size());
ASSERT_EQ("122403", vv1[REL_SERIES_SEQ][0][PURPOSE_CODE_SEQ][0]["0008,0100"].asString());
}
{
std::unique_ptr<ParsedDicomFile> dicom(ParsedDicomFile::CreateFromJson(v, DicomFromJsonFlags_None, ""));
{
DicomModification modif;
modif.SetupAnonymization(DicomVersion_2023b);
modif.Keep(DicomPath::Parse("ReferencedImageSequence[1].ReferencedSOPInstanceUID"));
modif.Keep(DicomPath::Parse("RelatedSeriesSequence"));
modif.Apply(*dicom);
}
Json::Value vv;
dicom->DatasetToJson(vv, DicomToJsonFormat_Short, DicomToJsonFlags_None, 0);
ASSERT_NE("1.2.840.113619.2.176.2025.1499492.7040.1171286241.719", vv[REF_IM_SEQ][0][REF_SOP_INSTANCE].asString());
ASSERT_EQ("1.2.840.113619.2.176.2025.1499492.7040.1171286241.726", vv[REF_IM_SEQ][1][REF_SOP_INSTANCE].asString()); // kept
ASSERT_EQ("1.2.840.113704.1.111.7016.1342451220.40", vv[REL_SERIES_SEQ][0][STUDY_INSTANCE_UID].asString()); // kept
}
{
std::unique_ptr<ParsedDicomFile> dicom(ParsedDicomFile::CreateFromJson(v, DicomFromJsonFlags_None, ""));
DicomMap m;
ASSERT_TRUE(dicom->LookupSequenceItem(m, DicomPath(DICOM_TAG_REFERENCED_IMAGE_SEQUENCE), 0));
ASSERT_EQ(2u, m.GetSize());
ASSERT_EQ("1.2.840.113619.2.176.2025.1499492.7040.1171286241.719",
m.GetStringValue(DICOM_TAG_REFERENCED_SOP_INSTANCE_UID, "", false));
ASSERT_TRUE(dicom->LookupSequenceItem(m, DicomPath(DICOM_TAG_REFERENCED_IMAGE_SEQUENCE), 1));
ASSERT_EQ(2u, m.GetSize());
ASSERT_EQ("1.2.840.113619.2.176.2025.1499492.7040.1171286241.726",
m.GetStringValue(DICOM_TAG_REFERENCED_SOP_INSTANCE_UID, "", false));
ASSERT_FALSE(dicom->LookupSequenceItem(m, DicomPath(DICOM_TAG_REFERENCED_IMAGE_SEQUENCE), 2));
ASSERT_TRUE(dicom->LookupSequenceItem(m, DicomPath(DicomTag(0x0008, 0x1250), 0, DicomTag(0x0040, 0xa170)), 0));
ASSERT_EQ(2u, m.GetSize());
ASSERT_EQ("122403", m.GetStringValue(DicomTag(0x0008, 0x0100), "", false));
ASSERT_EQ("WORLD", m.GetStringValue(DICOM_TAG_SERIES_DESCRIPTION, "", false));
ASSERT_FALSE(dicom->LookupSequenceItem(m, DicomPath(DicomTag(0x0008, 0x1250), 0, DicomTag(0x0040, 0xa170)), 1));
}
}
TEST(FromDcmtkBridge, VisitorRemoveTag)
{
class V : public ITagVisitor
{
private:
uint32_t seen_;
public:
V() : seen_(0)
{
}
unsigned int GetSeen() const
{
return seen_;
}
virtual Action VisitNotSupported(const std::vector<DicomTag>& parentTags,
const std::vector<size_t>& parentIndexes,
const DicomTag& tag,
ValueRepresentation vr) ORTHANC_OVERRIDE
{
seen_ |= (1 << 0);
if (parentTags.size() == 0u &&
parentIndexes.size() == 0u &&
DcmTagKey(tag.GetGroup(), tag.GetElement()) == DCM_PixelData)
{
return Action_Remove;
}
else
{
throw OrthancException(ErrorCode_InternalError);
}
}
virtual Action VisitSequence(const std::vector<DicomTag>& parentTags,
const std::vector<size_t>& parentIndexes,
const DicomTag& tag,
size_t countItems) ORTHANC_OVERRIDE
{
seen_ |= (1 << 1);
if (parentTags.size() == 0u &&
parentIndexes.size() == 0u &&
tag == DICOM_TAG_REFERENCED_IMAGE_SEQUENCE &&
countItems == 1)
{
return Action_None;
}
else if (parentTags.size() == 1u &&
parentIndexes.size() == 1u &&
parentTags[0] == DICOM_TAG_REFERENCED_IMAGE_SEQUENCE &&
parentIndexes[0] == 0u &&
countItems == 0 &&
DcmTagKey(tag.GetGroup(), tag.GetElement()) == DCM_ReferencedPatientSequence)
{
return Action_Remove;
}
else if (parentTags.size() == 1u &&
parentIndexes.size() == 1u &&
parentTags[0] == DICOM_TAG_REFERENCED_IMAGE_SEQUENCE &&
parentIndexes[0] == 0u &&
countItems == 1 &&
DcmTagKey(tag.GetGroup(), tag.GetElement()) == DCM_ReferencedStudySequence)
{
return Action_Remove;
}
else
{
throw OrthancException(ErrorCode_InternalError);
}
}
virtual Action VisitIntegers(const std::vector<DicomTag>& parentTags,
const std::vector<size_t>& parentIndexes,
const DicomTag& tag,
ValueRepresentation vr,
const std::vector<int64_t>& values) ORTHANC_OVERRIDE
{
seen_ |= (1 << 2);
if (parentTags.size() == 0u &&
parentIndexes.size() == 0u &&
DcmTagKey(tag.GetGroup(), tag.GetElement()) == DCM_TagAngleSecondAxis &&
values.size() == 2 &&
values[0] == 12 &&
values[1] == 13)
{
return Action_Remove;
}
else
{
throw OrthancException(ErrorCode_InternalError);
}
}
virtual Action VisitDoubles(const std::vector<DicomTag>& parentTags,
const std::vector<size_t>& parentIndexes,
const DicomTag& tag,
ValueRepresentation vr,
const std::vector<double>& values) ORTHANC_OVERRIDE
{
seen_ |= (1 << 3);
if (parentTags.size() == 1u &&
parentIndexes.size() == 1u &&
parentTags[0] == DICOM_TAG_REFERENCED_IMAGE_SEQUENCE &&
parentIndexes[0] == 0u &&
DcmTagKey(tag.GetGroup(), tag.GetElement()) == DCM_ExaminedBodyThickness &&
values.size() == 3 &&
std::abs(values[0] - 42.0f) <= 0.001f &&
std::abs(values[1] - 43.0f) <= 0.001f &&
std::abs(values[2] - 47.0f) <= 0.001f)
{
return Action_Remove;
}
else
{
throw OrthancException(ErrorCode_InternalError);
}
}
virtual Action VisitAttributes(const std::vector<DicomTag>& parentTags,
const std::vector<size_t>& parentIndexes,
const DicomTag& tag,
const std::vector<DicomTag>& values) ORTHANC_OVERRIDE
{
seen_ |= (1 << 4);
if (parentTags.size() == 1u &&
parentIndexes.size() == 1u &&
parentTags[0] == DICOM_TAG_REFERENCED_IMAGE_SEQUENCE &&
parentIndexes[0] == 0u &&
DcmTagKey(tag.GetGroup(), tag.GetElement()) == DCM_DimensionIndexPointer &&
values.size() == 2 &&
values[0] == DICOM_TAG_STUDY_DATE &&
values[1] == DICOM_TAG_STUDY_TIME)
{
return Action_Remove;
}
else
{
throw OrthancException(ErrorCode_InternalError);
}
}
virtual Action VisitBinary(const std::vector<DicomTag>& parentTags,
const std::vector<size_t>& parentIndexes,
const DicomTag& tag,
ValueRepresentation vr,
const void* data,
size_t size) ORTHANC_OVERRIDE
{
seen_ |= (1 << 5);
if (parentTags.size() == 1u &&
parentIndexes.size() == 1u &&
parentTags[0] == DICOM_TAG_REFERENCED_IMAGE_SEQUENCE &&
parentIndexes[0] == 0u &&
tag.GetGroup() == 0x0011 &&
tag.GetElement() == 0x1311 &&
size == 4u &&
memcmp(data, "abcd", 4) == 0)
{
return Action_Remove;
}
else
{
throw OrthancException(ErrorCode_InternalError);
}
}
virtual Action VisitString(std::string& newValue,
const std::vector<DicomTag>& parentTags,
const std::vector<size_t>& parentIndexes,
const DicomTag& tag,
ValueRepresentation vr,
const std::string& value) ORTHANC_OVERRIDE
{
seen_ |= (1 << 6);
return Action_Remove;
}
};
std::unique_ptr<ParsedDicomFile> dicom;
{
Json::Value v = Json::objectValue;
v["PatientName"] = "Hello";
v["ReferencedSOPClassUID"] = "1.2.840.10008.5.1.4.1.1.4";
v["ReferencedImageSequence"][0]["ReferencedSOPClassUID"] = "1.2.840.10008.5.1.4.1.1.4";
v["ReferencedImageSequence"][0]["ReferencedSOPInstanceUID"] = "1.2.840.113619.2.176.2025.1499492.7040.1171286241.719";
v["ReferencedImageSequence"][0]["ReferencedPatientSequence"] = Json::arrayValue; // Empty nested sequence
v["ReferencedImageSequence"][0]["ReferencedStudySequence"][0]["PatientID"] = "Hello"; // Non-empty nested sequence
v["ReferencedImageSequence"][0]["0011,1311"] = "abcd"; // Binary
dicom.reset(ParsedDicomFile::CreateFromJson(v, DicomFromJsonFlags_None, "PrivateCreator"));
{
// Test value multiplicity (cannot be done using "ParsedDicomFile::CreateFromJson()")
const int16_t a[] = { 12, 13 };
std::unique_ptr<DcmSignedShort> s(new DcmSignedShort(DCM_TagAngleSecondAxis)); // VisitIntegers()
ASSERT_TRUE(s->putSint16Array(a, 2).good());
dicom->GetDcmtkObject().getDataset()->insert(s.release());
}
DcmItem *parent = NULL;
ASSERT_TRUE(dicom->GetDcmtkObject().getDataset()->findAndGetSequenceItem(DCM_ReferencedImageSequence, parent, 0).good());
{
const float a[] = { 42, 43, 47 };
std::unique_ptr<DcmFloatingPointSingle> s(new DcmFloatingPointSingle(DCM_ExaminedBodyThickness)); // VisitDoubles()
ASSERT_TRUE(s->putFloat32Array(a, 3).good());
parent->insert(s.release());
}
{
const uint16_t a[] = { 0x0008, 0x0020, 0x0008, 0x0030 };
std::unique_ptr<DcmAttributeTag> s(new DcmAttributeTag(DCM_DimensionIndexPointer)); // VisitAttributes()
ASSERT_TRUE(s->putUint16Array(a, 2).good());
parent->insert(s.release());
}
ASSERT_TRUE(dicom->GetDcmtkObject().getDataset()->insert(new DcmPixelItem(DCM_PixelData)).good()); // VisitNotSupported()
}
{
V visitor;
dicom->Apply(visitor);
ASSERT_EQ(127u, visitor.GetSeen()); // Make sure all the methods have been applied
}
{
Json::Value b;
dicom->DatasetToJson(b, DicomToJsonFormat_Short, DicomToJsonFlags_Default, 0);
ASSERT_EQ(Json::objectValue, b.type());
Json::Value::Members members = b.getMemberNames();
ASSERT_EQ(1u, members.size());
ASSERT_EQ("0008,1140", members[0]);
// Check that "b["0008,1140"]" is a sequence with one single empty object
ASSERT_EQ(Json::arrayValue, b["0008,1140"].type());
ASSERT_EQ(1u, b["0008,1140"].size());
ASSERT_EQ(Json::objectValue, b["0008,1140"][0].type());
ASSERT_EQ(0u, b["0008,1140"][0].size());
}
}
TEST(ParsedDicomFile, MultipleFloatValue)
{
// from https://discourse.orthanc-server.org/t/qido-includefield-with-sequences/4746/6
Json::Value v = Json::objectValue;
v["4010,1001"][0]["4010,1004"] = "30\\20\\10";
std::unique_ptr<ParsedDicomFile> dicom(ParsedDicomFile::CreateFromJson(v, DicomFromJsonFlags_None, ""));
ASSERT_TRUE(dicom->HasTag(Orthanc::DicomTag(0x4010, 0x1001)));
DicomMap m;
ASSERT_TRUE(dicom->LookupSequenceItem(m, DicomPath(DicomTag(0x4010, 0x1001)), 0));
ASSERT_EQ(1u, m.GetSize());
std::string value = m.GetStringValue(DicomTag(0x4010, 0x1004), "", false);
ASSERT_EQ("30\\20\\10", value);
}
TEST(ParsedDicomFile, ImageInformation)
{
// If modifying this test, make sure to reflect the modification in
// "TEST(DicomImageInformation, FromDcmtkTests)" in file "ImageProcessingTests.cpp"
double wc, ww;
double ri, rs;
PhotometricInterpretation p;
{
ParsedDicomFile dicom(false);
dicom.GetDefaultWindowing(wc, ww, 5);
dicom.GetRescale(ri, rs, 5);
ASSERT_DOUBLE_EQ(128.0, wc);
ASSERT_DOUBLE_EQ(256.0, ww);
ASSERT_FALSE(dicom.LookupPhotometricInterpretation(p));
ASSERT_DOUBLE_EQ(0.0, ri);
ASSERT_DOUBLE_EQ(1.0, rs);
}
{
ParsedDicomFile dicom(false);
ASSERT_TRUE(dicom.GetDcmtkObject().getDataset()->putAndInsertString(DCM_BitsStored, "4").good());
ASSERT_TRUE(dicom.GetDcmtkObject().getDataset()->putAndInsertString(DCM_PhotometricInterpretation, "RGB").good());
dicom.GetDefaultWindowing(wc, ww, 5);
ASSERT_DOUBLE_EQ(8.0, wc);
ASSERT_DOUBLE_EQ(16.0, ww);
ASSERT_TRUE(dicom.LookupPhotometricInterpretation(p));
ASSERT_EQ(PhotometricInterpretation_RGB, p);
}
{
ParsedDicomFile dicom(false);
ASSERT_TRUE(dicom.GetDcmtkObject().getDataset()->putAndInsertString(DCM_WindowCenter, "12").good());
ASSERT_TRUE(dicom.GetDcmtkObject().getDataset()->putAndInsertString(DCM_WindowWidth, "-22").good());
ASSERT_TRUE(dicom.GetDcmtkObject().getDataset()->putAndInsertString(DCM_RescaleIntercept, "-22").good());
ASSERT_TRUE(dicom.GetDcmtkObject().getDataset()->putAndInsertString(DCM_RescaleSlope, "-23").good());
dicom.GetDefaultWindowing(wc, ww, 5);
dicom.GetRescale(ri, rs, 5);
ASSERT_DOUBLE_EQ(12.0, wc);
ASSERT_DOUBLE_EQ(-22.0, ww);
ASSERT_DOUBLE_EQ(-22.0, ri);
ASSERT_DOUBLE_EQ(-23.0, rs);
}
{
ParsedDicomFile dicom(false);
ASSERT_TRUE(dicom.GetDcmtkObject().getDataset()->putAndInsertString(DCM_WindowCenter, "12\\13\\14").good());
ASSERT_TRUE(dicom.GetDcmtkObject().getDataset()->putAndInsertString(DCM_WindowWidth, "-22\\-23\\-24").good());
ASSERT_TRUE(dicom.GetDcmtkObject().getDataset()->putAndInsertString(DCM_RescaleIntercept, "32\\33\\34").good());
ASSERT_TRUE(dicom.GetDcmtkObject().getDataset()->putAndInsertString(DCM_RescaleSlope, "-42\\-43\\-44").good());
dicom.GetDefaultWindowing(wc, ww, 5);
dicom.GetRescale(ri, rs, 5);
ASSERT_DOUBLE_EQ(12.0, wc);
ASSERT_DOUBLE_EQ(-22.0, ww);
ASSERT_DOUBLE_EQ(0.0, ri);
ASSERT_DOUBLE_EQ(1.0, rs);
}
{
// Philips multiframe
Json::Value v = Json::objectValue;
v["PerFrameFunctionalGroupsSequence"][0]["FrameVOILUTSequence"][0]["WindowCenter"] = "614";
v["PerFrameFunctionalGroupsSequence"][0]["FrameVOILUTSequence"][0]["WindowWidth"] = "1067";
v["PerFrameFunctionalGroupsSequence"][0]["PixelValueTransformationSequence"][0]["RescaleIntercept"] = "12";
v["PerFrameFunctionalGroupsSequence"][0]["PixelValueTransformationSequence"][0]["RescaleSlope"] = "2.551648";
v["PerFrameFunctionalGroupsSequence"][1]["FrameVOILUTSequence"][0]["WindowCenter"] = "-61";
v["PerFrameFunctionalGroupsSequence"][1]["FrameVOILUTSequence"][0]["WindowWidth"] = "-63";
v["PerFrameFunctionalGroupsSequence"][1]["PixelValueTransformationSequence"][0]["RescaleIntercept"] = "13";
v["PerFrameFunctionalGroupsSequence"][1]["PixelValueTransformationSequence"][0]["RescaleSlope"] = "-14";
std::unique_ptr<ParsedDicomFile> dicom(ParsedDicomFile::CreateFromJson(v, DicomFromJsonFlags_None, ""));
dicom->GetDefaultWindowing(wc, ww, 0);
dicom->GetRescale(ri, rs, 0);
ASSERT_DOUBLE_EQ(614.0, wc);
ASSERT_DOUBLE_EQ(1067.0, ww);
ASSERT_DOUBLE_EQ(12.0, ri);
ASSERT_DOUBLE_EQ(2.551648, rs);
dicom->GetDefaultWindowing(wc, ww, 1);
dicom->GetRescale(ri, rs, 1);
ASSERT_DOUBLE_EQ(-61.0, wc);
ASSERT_DOUBLE_EQ(-63.0, ww);
ASSERT_DOUBLE_EQ(13.0, ri);
ASSERT_DOUBLE_EQ(-14.0, rs);
dicom->GetDefaultWindowing(wc, ww, 2);
dicom->GetRescale(ri, rs, 2);
ASSERT_DOUBLE_EQ(128.0, wc);
ASSERT_DOUBLE_EQ(256.0, ww);
ASSERT_DOUBLE_EQ(0.0, ri);
ASSERT_DOUBLE_EQ(1.0, rs);
}
{
// RT-DOSE
Json::Value v = Json::objectValue;
v["RescaleIntercept"] = "10";
v["RescaleSlope"] = "20";
v["PerFrameFunctionalGroupsSequence"][0]["PixelValueTransformationSequence"][0]["RescaleIntercept"] = "30";
v["PerFrameFunctionalGroupsSequence"][0]["PixelValueTransformationSequence"][0]["RescaleSlope"] = "40";
std::unique_ptr<ParsedDicomFile> dicom(ParsedDicomFile::CreateFromJson(v, DicomFromJsonFlags_None, ""));
dicom->GetRescale(ri, rs, 0);
ASSERT_DOUBLE_EQ(10.0, ri);
ASSERT_DOUBLE_EQ(20.0, rs);
v["SOPClassUID"] = "1.2.840.10008.5.1.4.1.1.481.2";
dicom.reset(ParsedDicomFile::CreateFromJson(v, DicomFromJsonFlags_None, ""));
dicom->GetRescale(ri, rs, 0);
ASSERT_DOUBLE_EQ(0.0, ri);
ASSERT_DOUBLE_EQ(1.0, rs);
}
}
TEST(DicomMap, DicomWebWithInteger64)
{
/**
* This failed in Orthanc <= 1.9.7 with
* "http://localhost:8042/dicom-web/studies/1.3.6.1.4.1.14519.5.2.1.314316487728501506587013300243937537423/series/1.3.6.1.4.1.1459.5.2.1.62266640231940987006694557463549207147/instances/1.3.6.1.4.1.14519.5.2.1.147718809116229175846174241356499989705/metadata"
* of patient "GLIOMA01-i_03A6" from collection "ICDC-Glioma" of
* TCIA.
**/
Json::Value v = Json::objectValue;
v["00191297"]["Value"][0] = 29362240;
v["00191297"]["Value"][1] = Json::Int64(4294948074l);
v["00191297"]["vr"] = "UL";
DicomMap m;
m.FromDicomWeb(v);
}
TEST(ParsedDicomFile, InjectEmptyPixelData)
{
static const char* PIXEL_DATA = "7FE00010";
{
ParsedDicomFile dicom(true);
DicomWebJsonVisitor visitor;
dicom.Apply(visitor);
ASSERT_FALSE(visitor.GetResult().isMember(PIXEL_DATA));
}
{
ParsedDicomFile dicom(true);
dicom.InjectEmptyPixelData(ValueRepresentation_OtherByte);
dicom.InjectEmptyPixelData(ValueRepresentation_OtherWord); // Must be ignored
DicomWebJsonVisitor visitor;
dicom.Apply(visitor);
ASSERT_TRUE(visitor.GetResult().isMember(PIXEL_DATA));
ASSERT_EQ(2u, visitor.GetResult() [PIXEL_DATA].size());
ASSERT_EQ("", visitor.GetResult() [PIXEL_DATA]["InlineBinary"].asString());
ASSERT_EQ("OB", visitor.GetResult() [PIXEL_DATA]["vr"].asString());
}
{
ParsedDicomFile dicom(true);
dicom.InjectEmptyPixelData(ValueRepresentation_OtherWord);
dicom.InjectEmptyPixelData(ValueRepresentation_OtherByte); // Must be ignored
DicomWebJsonVisitor visitor;
dicom.Apply(visitor);
ASSERT_TRUE(visitor.GetResult().isMember(PIXEL_DATA));
ASSERT_EQ(2u, visitor.GetResult() [PIXEL_DATA].size());
ASSERT_EQ("", visitor.GetResult() [PIXEL_DATA]["InlineBinary"].asString());
ASSERT_EQ("OW", visitor.GetResult() [PIXEL_DATA]["vr"].asString());
}
}
TEST(ParsedDicomFile, RemoveFromPixelData)
{
ParsedDicomFile dicom(true);
ASSERT_TRUE(dicom.GetDcmtkObject().getDataset()->putAndInsertString(DcmTag(0x7fe0, 0x0000), "").good());
ASSERT_TRUE(dicom.GetDcmtkObject().getDataset()->putAndInsertString(DcmTag(0x7fe0, 0x0009), "").good());
ASSERT_TRUE(dicom.GetDcmtkObject().getDataset()->putAndInsertUint8Array(DcmTag(0x7fe0, 0x0010), NULL, 0).good());
ASSERT_TRUE(dicom.GetDcmtkObject().getDataset()->putAndInsertString(DcmTag(0x7fe0, 0x0011), "").good());
ASSERT_TRUE(dicom.GetDcmtkObject().getDataset()->putAndInsertString(DcmTag(0x7fe1, 0x0000), "").good());
{
DicomMap m;
dicom.ExtractDicomSummary(m, 0);
ASSERT_EQ(10u, m.GetSize());
ASSERT_TRUE(m.HasTag(DICOM_TAG_MEDIA_STORAGE_SOP_INSTANCE_UID));
ASSERT_TRUE(m.HasTag(DICOM_TAG_SOP_INSTANCE_UID));
ASSERT_TRUE(m.HasTag(DICOM_TAG_PATIENT_ID));
ASSERT_TRUE(m.HasTag(DICOM_TAG_SERIES_INSTANCE_UID));
ASSERT_TRUE(m.HasTag(DICOM_TAG_STUDY_INSTANCE_UID));
ASSERT_TRUE(m.HasTag(0x7fe0, 0x0000));
ASSERT_TRUE(m.HasTag(0x7fe0, 0x0009));
ASSERT_TRUE(m.HasTag(DICOM_TAG_PIXEL_DATA));
ASSERT_TRUE(m.HasTag(0x7fe0, 0x0011));
ASSERT_TRUE(m.HasTag(0x7fe1, 0x0000));
}
dicom.RemoveFromPixelData();
{
DicomMap m;
dicom.ExtractDicomSummary(m, 0);
ASSERT_EQ(7u, m.GetSize());
ASSERT_TRUE(m.HasTag(DICOM_TAG_MEDIA_STORAGE_SOP_INSTANCE_UID));
ASSERT_TRUE(m.HasTag(DICOM_TAG_SOP_INSTANCE_UID));
ASSERT_TRUE(m.HasTag(DICOM_TAG_PATIENT_ID));
ASSERT_TRUE(m.HasTag(DICOM_TAG_SERIES_INSTANCE_UID));
ASSERT_TRUE(m.HasTag(DICOM_TAG_STUDY_INSTANCE_UID));
ASSERT_TRUE(m.HasTag(0x7fe0, 0x0000));
ASSERT_TRUE(m.HasTag(0x7fe0, 0x0009));
ASSERT_FALSE(m.HasTag(DICOM_TAG_PIXEL_DATA));
ASSERT_FALSE(m.HasTag(0x7fe0, 0x0011));
ASSERT_FALSE(m.HasTag(0x7fe1, 0x0000));
}
}
TEST(ParsedDicomFile, GuessPixelDataValueRepresentation)
{
typedef std::list< std::pair<E_TransferSyntax, DicomTransferSyntax> > Syntaxes;
// Create a list of the main non-retired transfer syntaxes, from:
// https://www.dicomlibrary.com/dicom/transfer-syntax/
Syntaxes compressedSyntaxes;
compressedSyntaxes.push_back(std::make_pair(EXS_DeflatedLittleEndianExplicit, DicomTransferSyntax_DeflatedLittleEndianExplicit));
compressedSyntaxes.push_back(std::make_pair(EXS_JPEGProcess1, DicomTransferSyntax_JPEGProcess1));
compressedSyntaxes.push_back(std::make_pair(EXS_JPEGProcess2_4, DicomTransferSyntax_JPEGProcess2_4));
compressedSyntaxes.push_back(std::make_pair(EXS_JPEGProcess14, DicomTransferSyntax_JPEGProcess14));
compressedSyntaxes.push_back(std::make_pair(EXS_JPEGProcess14SV1, DicomTransferSyntax_JPEGProcess14SV1));
compressedSyntaxes.push_back(std::make_pair(EXS_JPEGLSLossless, DicomTransferSyntax_JPEGLSLossless));
compressedSyntaxes.push_back(std::make_pair(EXS_JPEGLSLossy, DicomTransferSyntax_JPEGLSLossy));
compressedSyntaxes.push_back(std::make_pair(EXS_JPEG2000LosslessOnly, DicomTransferSyntax_JPEG2000LosslessOnly));
compressedSyntaxes.push_back(std::make_pair(EXS_JPEG2000, DicomTransferSyntax_JPEG2000));
compressedSyntaxes.push_back(std::make_pair(EXS_JPEG2000MulticomponentLosslessOnly, DicomTransferSyntax_JPEG2000MulticomponentLosslessOnly));
compressedSyntaxes.push_back(std::make_pair(EXS_JPEG2000Multicomponent, DicomTransferSyntax_JPEG2000Multicomponent));
compressedSyntaxes.push_back(std::make_pair(EXS_JPIPReferenced, DicomTransferSyntax_JPIPReferenced));
compressedSyntaxes.push_back(std::make_pair(EXS_JPIPReferencedDeflate, DicomTransferSyntax_JPIPReferencedDeflate));
compressedSyntaxes.push_back(std::make_pair(EXS_RLELossless, DicomTransferSyntax_RLELossless));
compressedSyntaxes.push_back(std::make_pair(EXS_MPEG2MainProfileAtMainLevel, DicomTransferSyntax_MPEG2MainProfileAtMainLevel));
compressedSyntaxes.push_back(std::make_pair(EXS_MPEG4HighProfileLevel4_1, DicomTransferSyntax_MPEG4HighProfileLevel4_1));
compressedSyntaxes.push_back(std::make_pair(EXS_MPEG4BDcompatibleHighProfileLevel4_1, DicomTransferSyntax_MPEG4BDcompatibleHighProfileLevel4_1));
for (unsigned int i = 0; i < 3; i++)
{
unsigned int bitsAllocated;
switch (i)
{
case 0: bitsAllocated = 1; break;
case 1: bitsAllocated = 8; break;
case 2: bitsAllocated = 16; break;
default:
throw OrthancException(ErrorCode_InternalError);
}
for (Syntaxes::const_iterator it = compressedSyntaxes.begin(); it != compressedSyntaxes.end(); ++it)
{
// All the compressed transfer syntaxes must have "OB" pixel data
ASSERT_EQ(ValueRepresentation_OtherByte, DicomImageInformation::GuessPixelDataValueRepresentation(it->second, bitsAllocated));
{
DicomMap dicom;
dicom.SetValue(DICOM_TAG_BITS_ALLOCATED, boost::lexical_cast<std::string>(bitsAllocated), false);
ASSERT_EQ(ValueRepresentation_OtherByte, dicom.GuessPixelDataValueRepresentation(it->second));
}
{
DicomMap dicom;
ASSERT_EQ(ValueRepresentation_OtherByte, dicom.GuessPixelDataValueRepresentation(it->second));
}
{
ParsedDicomFile dicom(true);
ASSERT_TRUE(dicom.GetDcmtkObject().getDataset()->putAndInsertUint16(DCM_BitsAllocated, bitsAllocated).good());
ASSERT_TRUE(dicom.GetDcmtkObject().chooseRepresentation(it->first, NULL).good());
dicom.GetDcmtkObject().removeAllButCurrentRepresentations();
DicomTransferSyntax ts;
ASSERT_TRUE(dicom.LookupTransferSyntax(ts));
ASSERT_EQ(ts, it->second);
ASSERT_EQ(ValueRepresentation_OtherByte, dicom.GuessPixelDataValueRepresentation());
}
}
{
// Little endian implicit is always OW
ASSERT_EQ(ValueRepresentation_OtherWord, DicomImageInformation::GuessPixelDataValueRepresentation(DicomTransferSyntax_LittleEndianImplicit, bitsAllocated));
{
DicomMap dicom;
dicom.SetValue(DICOM_TAG_BITS_ALLOCATED, boost::lexical_cast<std::string>(bitsAllocated), false);
ASSERT_EQ(ValueRepresentation_OtherWord, dicom.GuessPixelDataValueRepresentation(DicomTransferSyntax_LittleEndianImplicit));
}
{
DicomMap dicom;
ASSERT_EQ(ValueRepresentation_OtherWord, dicom.GuessPixelDataValueRepresentation(DicomTransferSyntax_LittleEndianImplicit));
}
{
ParsedDicomFile dicom(true);
ASSERT_TRUE(dicom.GetDcmtkObject().getDataset()->putAndInsertUint16(DCM_BitsAllocated, bitsAllocated).good());
ASSERT_TRUE(dicom.GetDcmtkObject().chooseRepresentation(EXS_LittleEndianImplicit, NULL).good());
dicom.GetDcmtkObject().removeAllButCurrentRepresentations();
ASSERT_EQ(ValueRepresentation_OtherWord, dicom.GuessPixelDataValueRepresentation());
}
}
}
// Explicit little and big endian with <= 8 bpp is OB
for (unsigned int i = 0; i < 2; i++)
{
unsigned int bitsAllocated;
switch (i)
{
case 0: bitsAllocated = 1; break;
case 1: bitsAllocated = 8; break;
default:
throw OrthancException(ErrorCode_InternalError);
}
ASSERT_EQ(ValueRepresentation_OtherByte, DicomImageInformation::GuessPixelDataValueRepresentation(DicomTransferSyntax_LittleEndianExplicit, bitsAllocated));
ASSERT_EQ(ValueRepresentation_OtherByte, DicomImageInformation::GuessPixelDataValueRepresentation(DicomTransferSyntax_BigEndianExplicit, bitsAllocated));
{
DicomMap dicom;
dicom.SetValue(DICOM_TAG_BITS_ALLOCATED, boost::lexical_cast<std::string>(bitsAllocated), false);
ASSERT_EQ(ValueRepresentation_OtherByte, dicom.GuessPixelDataValueRepresentation(DicomTransferSyntax_LittleEndianExplicit));
ASSERT_EQ(ValueRepresentation_OtherByte, dicom.GuessPixelDataValueRepresentation(DicomTransferSyntax_BigEndianExplicit));
}
{
DicomMap dicom;
ASSERT_EQ(ValueRepresentation_OtherByte, dicom.GuessPixelDataValueRepresentation(DicomTransferSyntax_LittleEndianExplicit));
ASSERT_EQ(ValueRepresentation_OtherByte, dicom.GuessPixelDataValueRepresentation(DicomTransferSyntax_BigEndianExplicit));
}
{
ParsedDicomFile dicom(true);
ASSERT_TRUE(dicom.GetDcmtkObject().getDataset()->putAndInsertUint16(DCM_BitsAllocated, bitsAllocated).good());
ASSERT_TRUE(dicom.GetDcmtkObject().chooseRepresentation(EXS_LittleEndianExplicit, NULL).good());
dicom.GetDcmtkObject().removeAllButCurrentRepresentations();
ASSERT_EQ(ValueRepresentation_OtherByte, dicom.GuessPixelDataValueRepresentation());
}
{
ParsedDicomFile dicom(true);
ASSERT_TRUE(dicom.GetDcmtkObject().getDataset()->putAndInsertUint16(DCM_BitsAllocated, bitsAllocated).good());
ASSERT_TRUE(dicom.GetDcmtkObject().chooseRepresentation(EXS_BigEndianExplicit, NULL).good());
dicom.GetDcmtkObject().removeAllButCurrentRepresentations();
ASSERT_EQ(ValueRepresentation_OtherByte, dicom.GuessPixelDataValueRepresentation());
}
}
// Explicit little and big endian with > 8 bpp is OW
ASSERT_EQ(ValueRepresentation_OtherWord, DicomImageInformation::GuessPixelDataValueRepresentation(DicomTransferSyntax_LittleEndianExplicit, 16));
ASSERT_EQ(ValueRepresentation_OtherWord, DicomImageInformation::GuessPixelDataValueRepresentation(DicomTransferSyntax_BigEndianExplicit, 16));
{
DicomMap dicom;
dicom.SetValue(DICOM_TAG_BITS_ALLOCATED, "16", false);
ASSERT_EQ(ValueRepresentation_OtherWord, dicom.GuessPixelDataValueRepresentation(DicomTransferSyntax_LittleEndianExplicit));
ASSERT_EQ(ValueRepresentation_OtherWord, dicom.GuessPixelDataValueRepresentation(DicomTransferSyntax_BigEndianExplicit));
}
{
ParsedDicomFile dicom(true);
ASSERT_TRUE(dicom.GetDcmtkObject().getDataset()->putAndInsertUint16(DCM_BitsAllocated, 16).good());
ASSERT_TRUE(dicom.GetDcmtkObject().chooseRepresentation(EXS_LittleEndianExplicit, NULL).good());
dicom.GetDcmtkObject().removeAllButCurrentRepresentations();
ASSERT_EQ(ValueRepresentation_OtherWord, dicom.GuessPixelDataValueRepresentation());
}
{
ParsedDicomFile dicom(true);
ASSERT_TRUE(dicom.GetDcmtkObject().getDataset()->putAndInsertUint16(DCM_BitsAllocated, 16).good());
ASSERT_TRUE(dicom.GetDcmtkObject().chooseRepresentation(EXS_BigEndianExplicit, NULL).good());
dicom.GetDcmtkObject().removeAllButCurrentRepresentations();
ASSERT_EQ(ValueRepresentation_OtherWord, dicom.GuessPixelDataValueRepresentation());
}
}
#if ORTHANC_SANDBOXED != 1
TEST(ParsedDicomFile, DISABLED_InjectEmptyPixelData2)
{
static const char* PIXEL_DATA = "7FE00010";
for (int i = 0; i <= DicomTransferSyntax_XML; i++)
{
DicomTransferSyntax a = (DicomTransferSyntax) i;
std::string path = (std::string(getenv("HOME")) +
"/Subversion/orthanc-tests/Database/TransferSyntaxes/" +
std::string(GetTransferSyntaxUid(a)) + ".dcm");
if (Orthanc::SystemToolbox::IsRegularFile(path))
{
printf("\n======= %s\n", GetTransferSyntaxUid(a));
std::string source;
Orthanc::SystemToolbox::ReadFile(source, path);
ParsedDicomFile dicom(source);
std::unique_ptr<DcmElement> removal(dicom.GetDcmtkObject().getDataset()->remove(DCM_PixelData));
{
DicomWebJsonVisitor visitor;
dicom.Apply(visitor);
ASSERT_FALSE(visitor.GetResult().isMember(PIXEL_DATA));
}
{
DicomWebJsonVisitor visitor;
dicom.InjectEmptyPixelData(ValueRepresentation_OtherByte);
dicom.Apply(visitor);
ASSERT_TRUE(visitor.GetResult().isMember(PIXEL_DATA));
ASSERT_EQ("OB", visitor.GetResult() [PIXEL_DATA]["vr"].asString());
}
removal.reset(dicom.GetDcmtkObject().getDataset()->remove(DCM_PixelData));
{
DicomWebJsonVisitor visitor;
dicom.InjectEmptyPixelData(ValueRepresentation_OtherWord);
dicom.Apply(visitor);
ASSERT_TRUE(visitor.GetResult().isMember(PIXEL_DATA));
ASSERT_EQ("OW", visitor.GetResult() [PIXEL_DATA]["vr"].asString());
}
}
}
}
#endif
#if ORTHANC_ENABLE_DCMTK_TRANSCODING == 1
#include "../Sources/DicomNetworking/DicomStoreUserConnection.h"
#include "../Sources/DicomParsing/DcmtkTranscoder.h"
TEST(Toto, DISABLED_Transcode3)
{
DicomAssociationParameters p;
p.SetRemotePort(2000);
DicomStoreUserConnection scu(p);
scu.SetCommonClassesProposed(false);
scu.SetRetiredBigEndianProposed(true);
DcmtkTranscoder transcoder(1);
for (int j = 0; j < 2; j++)
{
for (int i = 0; i <= DicomTransferSyntax_XML; i++)
{
DicomTransferSyntax a = (DicomTransferSyntax) i;
std::string path = (std::string(getenv("HOME")) +
"/Subversion/orthanc-tests/Database/TransferSyntaxes/" +
std::string(GetTransferSyntaxUid(a)) + ".dcm");
if (Orthanc::SystemToolbox::IsRegularFile(path))
{
printf("\n======= %s\n", GetTransferSyntaxUid(a));
std::string source;
Orthanc::SystemToolbox::ReadFile(source, path);
std::string c, k;
try
{
scu.Transcode(c, k, transcoder, source.c_str(), source.size(),
DicomTransferSyntax_LittleEndianExplicit, false, "", 0);
}
catch (OrthancException& e)
{
if (e.GetErrorCode() == ErrorCode_NotImplemented)
{
LOG(ERROR) << "cannot transcode " << GetTransferSyntaxUid(a);
}
else
{
throw;
}
}
}
}
}
}
TEST(Toto, DISABLED_Transcode4)
{
std::unique_ptr<DcmFileFormat> toto;
{
std::string source;
Orthanc::SystemToolbox::ReadFile(source, std::string(getenv("HOME")) +
"/Subversion/orthanc-tests/Database/KarstenHilbertRF.dcm");
toto.reset(FromDcmtkBridge::LoadFromMemoryBuffer(source.c_str(), source.size()));
}
const std::string sourceUid = IDicomTranscoder::GetSopInstanceUid(*toto);
DicomTransferSyntax sourceSyntax;
ASSERT_TRUE(FromDcmtkBridge::LookupOrthancTransferSyntax(sourceSyntax, *toto));
DcmtkTranscoder transcoder(1);
for (int i = 0; i <= DicomTransferSyntax_XML; i++)
{
DicomTransferSyntax a = (DicomTransferSyntax) i;
std::set<DicomTransferSyntax> s;
s.insert(a);
std::string t;
IDicomTranscoder::DicomImage source, target;
source.AcquireParsed(dynamic_cast<DcmFileFormat*>(toto->clone()));
if (!transcoder.Transcode(target, source, s, true))
{
printf("**************** CANNOT: [%s] => [%s]\n",
GetTransferSyntaxUid(sourceSyntax), GetTransferSyntaxUid(a));
}
else
{
DicomTransferSyntax targetSyntax;
ASSERT_TRUE(FromDcmtkBridge::LookupOrthancTransferSyntax(targetSyntax, target.GetParsed()));
ASSERT_EQ(targetSyntax, a);
bool lossy = (a == DicomTransferSyntax_JPEGProcess1 ||
a == DicomTransferSyntax_JPEGProcess2_4 ||
a == DicomTransferSyntax_JPEGLSLossy);
printf("SIZE: %d\n", static_cast<int>(t.size()));
if (sourceUid == IDicomTranscoder::GetSopInstanceUid(target.GetParsed()))
{
ASSERT_FALSE(lossy);
}
else
{
ASSERT_TRUE(lossy);
}
}
}
}
#endif