# HG changeset patch # User Sebastien Jodogne # Date 1613482924 -3600 # Node ID 8734caa12448aed4537a840385bd4f44bcec1210 # Parent 5b929e6b3c36733e9ba17b0437b4e764569b1aec Improved precision of floating-point numbers in DICOM-as-JSON and DICOM summary diff -r 5b929e6b3c36 -r 8734caa12448 NEWS --- a/NEWS Tue Feb 16 12:18:41 2021 +0100 +++ b/NEWS Tue Feb 16 14:42:04 2021 +0100 @@ -23,6 +23,7 @@ Maintenance ----------- +* Improved precision of floating-point numbers in DICOM-as-JSON and DICOM summary * Optimization in C-STORE SCP by avoiding an unnecessary DICOM parsing * Fix build on big-endian architectures * Possibility to generate a static library containing the Orthanc Framework diff -r 5b929e6b3c36 -r 8734caa12448 OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.cpp --- a/OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.cpp Tue Feb 16 12:18:41 2021 +0100 +++ b/OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.cpp Tue Feb 16 14:42:04 2021 +0100 @@ -191,26 +191,68 @@ }; -#define DCMTK_TO_CTYPE_CONVERTER(converter, cType, dcmtkType, getter) \ + ORTHANC_FORCE_INLINE + static std::string FloatToString(float v) + { + /** + * From "boost::lexical_cast" documentation: "For more involved + * conversions, such as where precision or formatting need tighter + * control than is offered by the default behavior of + * lexical_cast, the conventional stringstream approach is + * recommended." + * https://www.boost.org/doc/libs/1_65_0/doc/html/boost_lexical_cast.html + * http://www.gotw.ca/publications/mill19.htm + * + * The precision of 17 corresponds to "defaultRealPrecision" in JsonCpp: + * https://github.com/open-source-parsers/jsoncpp/blob/master/include/json/value.h + **/ + + //return boost::lexical_cast(v); // This was used in Orthanc <= 1.9.0 + + std::ostringstream ss; + ss << std::setprecision(17) << v; + return ss.str(); + } + + + ORTHANC_FORCE_INLINE + static std::string DoubleToString(double v) + { + //return boost::lexical_cast(v); // This was used in Orthanc <= 1.9.0 + + std::ostringstream ss; + ss << std::setprecision(17) << v; + return ss.str(); + } + + +#define DCMTK_TO_CTYPE_CONVERTER(converter, cType, dcmtkType, getter, toStringFunction) \ \ struct converter \ { \ typedef cType CType; \ \ + ORTHANC_FORCE_INLINE \ static bool Apply(CType& result, \ DcmElement& element, \ size_t i) \ { \ return dynamic_cast(element).getter(result, i).good(); \ } \ + \ + ORTHANC_FORCE_INLINE \ + static std::string ToString(CType value) \ + { \ + return toStringFunction(value); \ + } \ }; -DCMTK_TO_CTYPE_CONVERTER(DcmtkToSint32Converter, Sint32, DcmSignedLong, getSint32) -DCMTK_TO_CTYPE_CONVERTER(DcmtkToSint16Converter, Sint16, DcmSignedShort, getSint16) -DCMTK_TO_CTYPE_CONVERTER(DcmtkToUint32Converter, Uint32, DcmUnsignedLong, getUint32) -DCMTK_TO_CTYPE_CONVERTER(DcmtkToUint16Converter, Uint16, DcmUnsignedShort, getUint16) -DCMTK_TO_CTYPE_CONVERTER(DcmtkToFloat32Converter, Float32, DcmFloatingPointSingle, getFloat32) -DCMTK_TO_CTYPE_CONVERTER(DcmtkToFloat64Converter, Float64, DcmFloatingPointDouble, getFloat64) + DCMTK_TO_CTYPE_CONVERTER(DcmtkToSint32Converter, Sint32, DcmSignedLong, getSint32, boost::lexical_cast) + DCMTK_TO_CTYPE_CONVERTER(DcmtkToSint16Converter, Sint16, DcmSignedShort, getSint16, boost::lexical_cast) + DCMTK_TO_CTYPE_CONVERTER(DcmtkToUint32Converter, Uint32, DcmUnsignedLong, getUint32, boost::lexical_cast) + DCMTK_TO_CTYPE_CONVERTER(DcmtkToUint16Converter, Uint16, DcmUnsignedShort, getUint16, boost::lexical_cast) + DCMTK_TO_CTYPE_CONVERTER(DcmtkToFloat32Converter, Float32, DcmFloatingPointSingle, getFloat32, FloatToString) + DCMTK_TO_CTYPE_CONVERTER(DcmtkToFloat64Converter, Float64, DcmFloatingPointDouble, getFloat64, DoubleToString) template @@ -226,19 +268,18 @@ std::vector strings; for (size_t i = 0; i < count; i++) { if (f.Apply(value, element, i)) { - strings.push_back(boost::lexical_cast(value)); + strings.push_back(F::ToString(value)); } } return new DicomValue(boost::algorithm::join(strings, "\\"), false); } else if (f.Apply(value, element, 0)) { - return new DicomValue(boost::lexical_cast(value), false); + return new DicomValue(F::ToString(value), false); } else { return new DicomValue; } } - } diff -r 5b929e6b3c36 -r 8734caa12448 OrthancFramework/UnitTestsSources/FromDcmtkTests.cpp --- a/OrthancFramework/UnitTestsSources/FromDcmtkTests.cpp Tue Feb 16 12:18:41 2021 +0100 +++ b/OrthancFramework/UnitTestsSources/FromDcmtkTests.cpp Tue Feb 16 14:42:04 2021 +0100 @@ -1265,6 +1265,65 @@ +TEST(ParsedDicomFile, FloatPrecision) +{ + Float32 v; + + switch (Toolbox::DetectEndianness()) + { + case Endianness_Little: + reinterpret_cast(&v)[3] = 0x4E; + reinterpret_cast(&v)[2] = 0x9C; + reinterpret_cast(&v)[1] = 0xAD; + reinterpret_cast(&v)[0] = 0x8F; + break; + + case Endianness_Big: + reinterpret_cast(&v)[0] = 0x4E; + reinterpret_cast(&v)[1] = 0x9C; + reinterpret_cast(&v)[2] = 0xAD; + reinterpret_cast(&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); + std::string s; + 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) { // +----------------------------------+