Mercurial > hg > orthanc
changeset 4515:8734caa12448
Improved precision of floating-point numbers in DICOM-as-JSON and DICOM summary
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Tue, 16 Feb 2021 14:42:04 +0100 |
parents | 5b929e6b3c36 |
children | 671ee7c1fd46 |
files | NEWS OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.cpp OrthancFramework/UnitTestsSources/FromDcmtkTests.cpp |
diffstat | 3 files changed, 111 insertions(+), 10 deletions(-) [+] |
line wrap: on
line diff
--- 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
--- 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<std::string>(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<std::string>(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<dcmtkType&>(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<std::string>) + DCMTK_TO_CTYPE_CONVERTER(DcmtkToSint16Converter, Sint16, DcmSignedShort, getSint16, boost::lexical_cast<std::string>) + DCMTK_TO_CTYPE_CONVERTER(DcmtkToUint32Converter, Uint32, DcmUnsignedLong, getUint32, boost::lexical_cast<std::string>) + DCMTK_TO_CTYPE_CONVERTER(DcmtkToUint16Converter, Uint16, DcmUnsignedShort, getUint16, boost::lexical_cast<std::string>) + DCMTK_TO_CTYPE_CONVERTER(DcmtkToFloat32Converter, Float32, DcmFloatingPointSingle, getFloat32, FloatToString) + DCMTK_TO_CTYPE_CONVERTER(DcmtkToFloat64Converter, Float64, DcmFloatingPointDouble, getFloat64, DoubleToString) template <typename F> @@ -226,19 +268,18 @@ std::vector<std::string> strings; for (size_t i = 0; i < count; i++) { if (f.Apply(value, element, i)) { - strings.push_back(boost::lexical_cast<std::string>(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<std::string>(value), false); + return new DicomValue(F::ToString(value), false); } else { return new DicomValue; } } - }
--- 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<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); + 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) { // +----------------------------------+