Mercurial > hg > orthanc
changeset 3248:c2798f7daf8b
Merged in jku11/orthanc (pull request #7)
Support for DICOM storage SOP classes BreastProjectionXRayImageStorageForProcessing and BreastProjectionXRayImageStorageForPresentation.
author | Sébastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Sat, 16 Feb 2019 10:29:26 +0000 |
parents | ae68bf751187 (diff) fcc2b99feaaf (current diff) |
children | bce0e4c9ca45 |
files | |
diffstat | 54 files changed, 2111 insertions(+), 701 deletions(-) [+] |
line wrap: on
line diff
--- a/.hgignore Wed Feb 13 13:01:21 2019 +0000 +++ b/.hgignore Sat Feb 16 10:29:26 2019 +0000 @@ -4,3 +4,4 @@ *.cpp.orig *.h.orig .vs/ +*~
--- a/CMakeLists.txt Wed Feb 13 13:01:21 2019 +0000 +++ b/CMakeLists.txt Sat Feb 16 10:29:26 2019 +0000 @@ -338,6 +338,29 @@ ##################################################################### +## Build a static library to share code between the plugins +##################################################################### + +if (ENABLE_PLUGINS AND + (BUILD_SERVE_FOLDERS OR BUILD_MODALITY_WORKLISTS)) + add_library(ThirdPartyPlugins STATIC + ${BOOST_SOURCES} + ${JSONCPP_SOURCES} + ${LIBICONV_SOURCES} + ${LIBICU_SOURCES} + Plugins/Samples/Common/OrthancPluginCppWrapper.cpp + ) + + # Add the "-fPIC" option as this static library must be embedded + # inside shared libraries (important on UNIX) + set_property( + TARGET ThirdPartyPlugins + PROPERTY POSITION_INDEPENDENT_CODE ON + ) +endif() + + +##################################################################### ## Build the "ServeFolders" plugin ##################################################################### @@ -359,14 +382,12 @@ endif() add_library(ServeFolders SHARED - ${BOOST_SOURCES} - ${JSONCPP_SOURCES} - ${LIBICONV_SOURCES} Plugins/Samples/ServeFolders/Plugin.cpp - Plugins/Samples/Common/OrthancPluginCppWrapper.cpp ${SERVE_FOLDERS_RESOURCES} ) + target_link_libraries(ServeFolders ThirdPartyPlugins) + set_target_properties( ServeFolders PROPERTIES VERSION ${ORTHANC_VERSION} @@ -404,14 +425,12 @@ endif() add_library(ModalityWorklists SHARED - ${BOOST_SOURCES} - ${JSONCPP_SOURCES} - ${LIBICONV_SOURCES} - Plugins/Samples/Common/OrthancPluginCppWrapper.cpp Plugins/Samples/ModalityWorklists/Plugin.cpp ${MODALITY_WORKLISTS_RESOURCES} ) + target_link_libraries(ModalityWorklists ThirdPartyPlugins) + set_target_properties( ModalityWorklists PROPERTIES VERSION ${ORTHANC_VERSION}
--- a/Core/DicomParsing/DicomDirWriter.cpp Wed Feb 13 13:01:21 2019 +0000 +++ b/Core/DicomParsing/DicomDirWriter.cpp Sat Feb 16 10:29:26 2019 +0000 @@ -161,6 +161,7 @@ static bool GetUtf8TagValue(std::string& result, DcmItem& source, Encoding encoding, + bool hasCodeExtensions, const DcmTagKey& key) { DcmElement* element = NULL; @@ -174,7 +175,7 @@ { if (s != NULL) { - result = Toolbox::ConvertToUtf8(s, encoding); + result = Toolbox::ConvertToUtf8(s, encoding, hasCodeExtensions); } return true; @@ -202,6 +203,7 @@ static bool CopyString(DcmDirectoryRecord& target, DcmDataset& source, Encoding encoding, + bool hasCodeExtensions, const DcmTagKey& key, bool optional, bool copyEmpty) @@ -214,7 +216,7 @@ } std::string value; - bool found = GetUtf8TagValue(value, source, encoding, key); + bool found = GetUtf8TagValue(value, source, encoding, hasCodeExtensions, key); if (!found) { @@ -231,33 +233,37 @@ static void CopyStringType1(DcmDirectoryRecord& target, DcmDataset& source, Encoding encoding, + bool hasCodeExtensions, const DcmTagKey& key) { - CopyString(target, source, encoding, key, false, false); + CopyString(target, source, encoding, hasCodeExtensions, key, false, false); } static void CopyStringType1C(DcmDirectoryRecord& target, DcmDataset& source, Encoding encoding, + bool hasCodeExtensions, const DcmTagKey& key) { - CopyString(target, source, encoding, key, true, false); + CopyString(target, source, encoding, hasCodeExtensions, key, true, false); } static void CopyStringType2(DcmDirectoryRecord& target, DcmDataset& source, Encoding encoding, + bool hasCodeExtensions, const DcmTagKey& key) { - CopyString(target, source, encoding, key, false, true); + CopyString(target, source, encoding, hasCodeExtensions, key, false, true); } static void CopyStringType3(DcmDirectoryRecord& target, DcmDataset& source, Encoding encoding, + bool hasCodeExtensions, const DcmTagKey& key) { - CopyString(target, source, encoding, key, true, true); + CopyString(target, source, encoding, hasCodeExtensions, key, true, true); } @@ -298,17 +304,19 @@ void FillPatient(DcmDirectoryRecord& record, DcmDataset& dicom, - Encoding encoding) + Encoding encoding, + bool hasCodeExtensions) { // cf. "DicomDirInterface::buildPatientRecord()" - CopyStringType1C(record, dicom, encoding, DCM_PatientID); - CopyStringType2(record, dicom, encoding, DCM_PatientName); + CopyStringType1C(record, dicom, encoding, hasCodeExtensions, DCM_PatientID); + CopyStringType2(record, dicom, encoding, hasCodeExtensions, DCM_PatientName); } void FillStudy(DcmDirectoryRecord& record, DcmDataset& dicom, - Encoding encoding) + Encoding encoding, + bool hasCodeExtensions) { // cf. "DicomDirInterface::buildStudyRecord()" @@ -316,19 +324,19 @@ SystemToolbox::GetNowDicom(nowDate, nowTime, utc_); std::string studyDate; - if (!GetUtf8TagValue(studyDate, dicom, encoding, DCM_StudyDate) && - !GetUtf8TagValue(studyDate, dicom, encoding, DCM_SeriesDate) && - !GetUtf8TagValue(studyDate, dicom, encoding, DCM_AcquisitionDate) && - !GetUtf8TagValue(studyDate, dicom, encoding, DCM_ContentDate)) + if (!GetUtf8TagValue(studyDate, dicom, encoding, hasCodeExtensions, DCM_StudyDate) && + !GetUtf8TagValue(studyDate, dicom, encoding, hasCodeExtensions, DCM_SeriesDate) && + !GetUtf8TagValue(studyDate, dicom, encoding, hasCodeExtensions, DCM_AcquisitionDate) && + !GetUtf8TagValue(studyDate, dicom, encoding, hasCodeExtensions, DCM_ContentDate)) { studyDate = nowDate; } std::string studyTime; - if (!GetUtf8TagValue(studyTime, dicom, encoding, DCM_StudyTime) && - !GetUtf8TagValue(studyTime, dicom, encoding, DCM_SeriesTime) && - !GetUtf8TagValue(studyTime, dicom, encoding, DCM_AcquisitionTime) && - !GetUtf8TagValue(studyTime, dicom, encoding, DCM_ContentTime)) + if (!GetUtf8TagValue(studyTime, dicom, encoding, hasCodeExtensions, DCM_StudyTime) && + !GetUtf8TagValue(studyTime, dicom, encoding, hasCodeExtensions, DCM_SeriesTime) && + !GetUtf8TagValue(studyTime, dicom, encoding, hasCodeExtensions, DCM_AcquisitionTime) && + !GetUtf8TagValue(studyTime, dicom, encoding, hasCodeExtensions, DCM_ContentTime)) { studyTime = nowTime; } @@ -336,52 +344,54 @@ /* copy attribute values from dataset to study record */ SetTagValue(record, DCM_StudyDate, studyDate); SetTagValue(record, DCM_StudyTime, studyTime); - CopyStringType2(record, dicom, encoding, DCM_StudyDescription); - CopyStringType1(record, dicom, encoding, DCM_StudyInstanceUID); + CopyStringType2(record, dicom, encoding, hasCodeExtensions, DCM_StudyDescription); + CopyStringType1(record, dicom, encoding, hasCodeExtensions, DCM_StudyInstanceUID); /* use type 1C instead of 1 in order to avoid unwanted overwriting */ - CopyStringType1C(record, dicom, encoding, DCM_StudyID); - CopyStringType2(record, dicom, encoding, DCM_AccessionNumber); + CopyStringType1C(record, dicom, encoding, hasCodeExtensions, DCM_StudyID); + CopyStringType2(record, dicom, encoding, hasCodeExtensions, DCM_AccessionNumber); } void FillSeries(DcmDirectoryRecord& record, DcmDataset& dicom, - Encoding encoding) + Encoding encoding, + bool hasCodeExtensions) { // cf. "DicomDirInterface::buildSeriesRecord()" /* copy attribute values from dataset to series record */ - CopyStringType1(record, dicom, encoding, DCM_Modality); - CopyStringType1(record, dicom, encoding, DCM_SeriesInstanceUID); + CopyStringType1(record, dicom, encoding, hasCodeExtensions, DCM_Modality); + CopyStringType1(record, dicom, encoding, hasCodeExtensions, DCM_SeriesInstanceUID); /* use type 1C instead of 1 in order to avoid unwanted overwriting */ - CopyStringType1C(record, dicom, encoding, DCM_SeriesNumber); + CopyStringType1C(record, dicom, encoding, hasCodeExtensions, DCM_SeriesNumber); // Add extended (non-standard) type 3 tags, those are not generated by DCMTK // http://dicom.nema.org/medical/Dicom/2016a/output/chtml/part02/sect_7.3.html // https://groups.google.com/d/msg/orthanc-users/Y7LOvZMDeoc/9cp3kDgxAwAJ if (extendedSopClass_) { - CopyStringType3(record, dicom, encoding, DCM_SeriesDescription); + CopyStringType3(record, dicom, encoding, hasCodeExtensions, DCM_SeriesDescription); } } void FillInstance(DcmDirectoryRecord& record, DcmDataset& dicom, Encoding encoding, + bool hasCodeExtensions, DcmMetaInfo& metaInfo, const char* path) { // cf. "DicomDirInterface::buildImageRecord()" /* copy attribute values from dataset to image record */ - CopyStringType1(record, dicom, encoding, DCM_InstanceNumber); - //CopyElementType1C(record, dicom, encoding, DCM_ImageType); + CopyStringType1(record, dicom, encoding, hasCodeExtensions, DCM_InstanceNumber); + //CopyElementType1C(record, dicom, encoding, hasCodeExtensions, DCM_ImageType); // REMOVED since 0.9.7: copyElementType1C(dicom, DCM_ReferencedImageSequence, record); std::string sopClassUid, sopInstanceUid, transferSyntaxUid; - if (!GetUtf8TagValue(sopClassUid, dicom, encoding, DCM_SOPClassUID) || - !GetUtf8TagValue(sopInstanceUid, dicom, encoding, DCM_SOPInstanceUID) || - !GetUtf8TagValue(transferSyntaxUid, metaInfo, encoding, DCM_TransferSyntaxUID)) + if (!GetUtf8TagValue(sopClassUid, dicom, encoding, hasCodeExtensions, DCM_SOPClassUID) || + !GetUtf8TagValue(sopInstanceUid, dicom, encoding, hasCodeExtensions, DCM_SOPInstanceUID) || + !GetUtf8TagValue(transferSyntaxUid, metaInfo, encoding, hasCodeExtensions, DCM_TransferSyntaxUID)) { throw OrthancException(ErrorCode_BadFileFormat); } @@ -401,7 +411,9 @@ const char* path) { DcmDataset& dataset = *dicom.GetDcmtkObject().getDataset(); - Encoding encoding = dicom.GetEncoding(); + + bool hasCodeExtensions; + Encoding encoding = dicom.DetectEncoding(hasCodeExtensions); bool found; std::string id; @@ -410,7 +422,7 @@ switch (level) { case ResourceType_Patient: - if (!GetUtf8TagValue(id, dataset, encoding, DCM_PatientID)) + if (!GetUtf8TagValue(id, dataset, encoding, hasCodeExtensions, DCM_PatientID)) { // Be tolerant about missing patient ID. Fixes issue #124 // (GET /studies/ID/media fails for certain dicom file). @@ -422,17 +434,17 @@ break; case ResourceType_Study: - found = GetUtf8TagValue(id, dataset, encoding, DCM_StudyInstanceUID); + found = GetUtf8TagValue(id, dataset, encoding, hasCodeExtensions, DCM_StudyInstanceUID); type = ERT_Study; break; case ResourceType_Series: - found = GetUtf8TagValue(id, dataset, encoding, DCM_SeriesInstanceUID); + found = GetUtf8TagValue(id, dataset, encoding, hasCodeExtensions, DCM_SeriesInstanceUID); type = ERT_Series; break; case ResourceType_Instance: - found = GetUtf8TagValue(id, dataset, encoding, DCM_SOPInstanceUID); + found = GetUtf8TagValue(id, dataset, encoding, hasCodeExtensions, DCM_SOPInstanceUID); type = ERT_Image; break; @@ -459,26 +471,26 @@ switch (level) { case ResourceType_Patient: - FillPatient(*record, dataset, encoding); + FillPatient(*record, dataset, encoding, hasCodeExtensions); break; case ResourceType_Study: - FillStudy(*record, dataset, encoding); + FillStudy(*record, dataset, encoding, hasCodeExtensions); break; case ResourceType_Series: - FillSeries(*record, dataset, encoding); + FillSeries(*record, dataset, encoding, hasCodeExtensions); break; case ResourceType_Instance: - FillInstance(*record, dataset, encoding, *dicom.GetDcmtkObject().getMetaInfo(), path); + FillInstance(*record, dataset, encoding, hasCodeExtensions, *dicom.GetDcmtkObject().getMetaInfo(), path); break; default: throw OrthancException(ErrorCode_InternalError); } - CopyStringType1C(*record, dataset, encoding, DCM_SpecificCharacterSet); + CopyStringType1C(*record, dataset, encoding, hasCodeExtensions, DCM_SpecificCharacterSet); target = record.get(); GetRoot().insertSub(record.release());
--- a/Core/DicomParsing/DicomWebJsonVisitor.cpp Wed Feb 13 13:01:21 2019 +0000 +++ b/Core/DicomParsing/DicomWebJsonVisitor.cpp Sat Feb 16 10:29:26 2019 +0000 @@ -43,9 +43,12 @@ static const char* const KEY_ALPHABETIC = "Alphabetic"; +static const char* const KEY_IDEOGRAPHIC = "Ideographic"; +static const char* const KEY_PHONETIC = "Phonetic"; static const char* const KEY_BULK_DATA_URI = "BulkDataURI"; static const char* const KEY_INLINE_BINARY = "InlineBinary"; static const char* const KEY_SQ = "SQ"; +static const char* const KEY_TAG = "tag"; static const char* const KEY_VALUE = "Value"; static const char* const KEY_VR = "vr"; @@ -53,9 +56,42 @@ namespace Orthanc { #if ORTHANC_ENABLE_PUGIXML == 1 + static void DecomposeXmlPersonName(pugi::xml_node& target, + const std::string& source) + { + std::vector<std::string> tokens; + Toolbox::TokenizeString(tokens, source, '^'); + + if (tokens.size() >= 1) + { + target.append_child("FamilyName").text() = tokens[0].c_str(); + } + + if (tokens.size() >= 2) + { + target.append_child("GivenName").text() = tokens[1].c_str(); + } + + if (tokens.size() >= 3) + { + target.append_child("MiddleName").text() = tokens[2].c_str(); + } + + if (tokens.size() >= 4) + { + target.append_child("NamePrefix").text() = tokens[3].c_str(); + } + + if (tokens.size() >= 5) + { + target.append_child("NameSuffix").text() = tokens[4].c_str(); + } + } + static void ExploreXmlDataset(pugi::xml_node& target, const Json::Value& source) { + // http://dicom.nema.org/medical/dicom/current/output/chtml/part18/sect_F.3.html#table_F.3.1-1 assert(source.type() == Json::objectValue); Json::Value::Members members = source.getMemberNames(); @@ -65,15 +101,15 @@ const Json::Value& content = source[members[i]]; assert(content.type() == Json::objectValue && - content.isMember("vr") && - content["vr"].type() == Json::stringValue); - const std::string vr = content["vr"].asString(); + content.isMember(KEY_VR) && + content[KEY_VR].type() == Json::stringValue); + const std::string vr = content[KEY_VR].asString(); const std::string keyword = FromDcmtkBridge::GetTagName(tag, ""); pugi::xml_node node = target.append_child("DicomAttribute"); - node.append_attribute("tag").set_value(members[i].c_str()); - node.append_attribute("vr").set_value(vr.c_str()); + node.append_attribute(KEY_TAG).set_value(members[i].c_str()); + node.append_attribute(KEY_VR).set_value(vr.c_str()); if (keyword != std::string(DcmTag_ERROR_TagName)) { @@ -99,40 +135,38 @@ } if (vr == "PN") { - if (content[KEY_VALUE][j].isMember(KEY_ALPHABETIC) && - content[KEY_VALUE][j][KEY_ALPHABETIC].type() == Json::stringValue) + bool hasAlphabetic = (content[KEY_VALUE][j].isMember(KEY_ALPHABETIC) && + content[KEY_VALUE][j][KEY_ALPHABETIC].type() == Json::stringValue); + + bool hasIdeographic = (content[KEY_VALUE][j].isMember(KEY_IDEOGRAPHIC) && + content[KEY_VALUE][j][KEY_IDEOGRAPHIC].type() == Json::stringValue); + + bool hasPhonetic = (content[KEY_VALUE][j].isMember(KEY_PHONETIC) && + content[KEY_VALUE][j][KEY_PHONETIC].type() == Json::stringValue); + + if (hasAlphabetic || + hasIdeographic || + hasPhonetic) { - std::vector<std::string> tokens; - Toolbox::TokenizeString(tokens, content[KEY_VALUE][j][KEY_ALPHABETIC].asString(), '^'); - pugi::xml_node child = node.append_child("PersonName"); child.append_attribute("number").set_value(number.c_str()); - - pugi::xml_node name = child.append_child(KEY_ALPHABETIC); - - if (tokens.size() >= 1) + + if (hasAlphabetic) { - name.append_child("FamilyName").text() = tokens[0].c_str(); - } - - if (tokens.size() >= 2) - { - name.append_child("GivenName").text() = tokens[1].c_str(); + pugi::xml_node name = child.append_child(KEY_ALPHABETIC); + DecomposeXmlPersonName(name, content[KEY_VALUE][j][KEY_ALPHABETIC].asString()); } - - if (tokens.size() >= 3) + + if (hasIdeographic) { - name.append_child("MiddleName").text() = tokens[2].c_str(); + pugi::xml_node name = child.append_child(KEY_IDEOGRAPHIC); + DecomposeXmlPersonName(name, content[KEY_VALUE][j][KEY_IDEOGRAPHIC].asString()); } - - if (tokens.size() >= 4) + + if (hasPhonetic) { - name.append_child("NamePrefix").text() = tokens[3].c_str(); - } - - if (tokens.size() >= 5) - { - name.append_child("NameSuffix").text() = tokens[4].c_str(); + pugi::xml_node name = child.append_child(KEY_PHONETIC); + DecomposeXmlPersonName(name, content[KEY_VALUE][j][KEY_PHONETIC].asString()); } } } @@ -517,8 +551,25 @@ Json::Value value = Json::objectValue; if (!tokens[i].empty()) { - value[KEY_ALPHABETIC] = tokens[i]; + std::vector<std::string> components; + Toolbox::TokenizeString(components, tokens[i], '='); + + if (components.size() >= 1) + { + value[KEY_ALPHABETIC] = components[0]; + } + + if (components.size() >= 2) + { + value[KEY_IDEOGRAPHIC] = components[1]; + } + + if (components.size() >= 3) + { + value[KEY_PHONETIC] = components[2]; + } } + node[KEY_VALUE].append(value); break; }
--- a/Core/DicomParsing/FromDcmtkBridge.cpp Wed Feb 13 13:01:21 2019 +0000 +++ b/Core/DicomParsing/FromDcmtkBridge.cpp Sat Feb 16 10:29:26 2019 +0000 @@ -414,37 +414,49 @@ } - Encoding FromDcmtkBridge::DetectEncoding(DcmItem& dataset, + Encoding FromDcmtkBridge::DetectEncoding(bool& hasCodeExtensions, + DcmItem& dataset, Encoding defaultEncoding) { - Encoding encoding = defaultEncoding; + // http://dicom.nema.org/medical/dicom/current/output/chtml/part03/sect_C.12.html#sect_C.12.1.1.2 OFString tmp; - if (dataset.findAndGetOFString(DCM_SpecificCharacterSet, tmp).good()) + if (dataset.findAndGetOFStringArray(DCM_SpecificCharacterSet, tmp).good()) { - std::string characterSet = Toolbox::StripSpaces(std::string(tmp.c_str())); - - if (characterSet.empty()) + std::vector<std::string> tokens; + Toolbox::TokenizeString(tokens, std::string(tmp.c_str()), '\\'); + + hasCodeExtensions = (tokens.size() > 1); + + for (size_t i = 0; i < tokens.size(); i++) { - // Empty specific character set tag: Use the default encoding - } - else if (GetDicomEncoding(encoding, characterSet.c_str())) - { - // The specific character set is supported by the Orthanc core - } - else - { - LOG(WARNING) << "Value of Specific Character Set (0008,0005) is not supported: " << characterSet - << ", fallback to ASCII (remove all special characters)"; - encoding = Encoding_Ascii; + std::string characterSet = Toolbox::StripSpaces(tokens[i]); + + if (!characterSet.empty()) + { + Encoding encoding; + + if (GetDicomEncoding(encoding, characterSet.c_str())) + { + // The specific character set is supported by the Orthanc core + return encoding; + } + else + { + LOG(WARNING) << "Value of Specific Character Set (0008,0005) is not supported: " << characterSet + << ", fallback to ASCII (remove all special characters)"; + return Encoding_Ascii; + } + } } } else { - // No specific character set tag: Use the default encoding + hasCodeExtensions = false; } - - return encoding; + + // No specific character set tag: Use the default encoding + return defaultEncoding; } @@ -454,8 +466,9 @@ Encoding defaultEncoding) { std::set<DicomTag> ignoreTagLength; - - Encoding encoding = DetectEncoding(dataset, defaultEncoding); + + bool hasCodeExtensions; + Encoding encoding = DetectEncoding(hasCodeExtensions, dataset, defaultEncoding); target.Clear(); for (unsigned long i = 0; i < dataset.card(); i++) @@ -466,7 +479,7 @@ target.SetValue(element->getTag().getGTag(), element->getTag().getETag(), ConvertLeafElement(*element, DicomToJsonFlags_Default, - maxStringLength, encoding, ignoreTagLength)); + maxStringLength, encoding, hasCodeExtensions, ignoreTagLength)); } } } @@ -488,6 +501,7 @@ DicomToJsonFlags flags, unsigned int maxStringLength, Encoding encoding, + bool hasCodeExtensions, const std::set<DicomTag>& ignoreTagLength) { if (!element.isLeaf()) @@ -507,7 +521,7 @@ else { std::string s(c); - std::string utf8 = Toolbox::ConvertToUtf8(s, encoding); + std::string utf8 = Toolbox::ConvertToUtf8(s, encoding, hasCodeExtensions); if (maxStringLength != 0 && utf8.size() > maxStringLength && @@ -855,6 +869,7 @@ DicomToJsonFlags flags, unsigned int maxStringLength, Encoding encoding, + bool hasCodeExtensions, const std::set<DicomTag>& ignoreTagLength) { if (parent.type() == Json::nullValue) @@ -869,7 +884,7 @@ { // The "0" below lets "LeafValueToJson()" take care of "TooLong" values std::auto_ptr<DicomValue> v(FromDcmtkBridge::ConvertLeafElement - (element, flags, 0, encoding, ignoreTagLength)); + (element, flags, 0, encoding, hasCodeExtensions, ignoreTagLength)); if (ignoreTagLength.find(GetTag(element)) == ignoreTagLength.end()) { @@ -894,7 +909,7 @@ { DcmItem* child = sequence.getItem(i); Json::Value& v = target.append(Json::objectValue); - DatasetToJson(v, *child, format, flags, maxStringLength, encoding, ignoreTagLength); + DatasetToJson(v, *child, format, flags, maxStringLength, encoding, hasCodeExtensions, ignoreTagLength); } } } @@ -906,6 +921,7 @@ DicomToJsonFlags flags, unsigned int maxStringLength, Encoding encoding, + bool hasCodeExtensions, const std::set<DicomTag>& ignoreTagLength) { assert(parent.type() == Json::objectValue); @@ -952,7 +968,7 @@ } FromDcmtkBridge::ElementToJson(parent, *element, format, flags, - maxStringLength, encoding, ignoreTagLength); + maxStringLength, encoding, hasCodeExtensions, ignoreTagLength); } } @@ -965,10 +981,11 @@ Encoding defaultEncoding, const std::set<DicomTag>& ignoreTagLength) { - Encoding encoding = DetectEncoding(dataset, defaultEncoding); + bool hasCodeExtensions; + Encoding encoding = DetectEncoding(hasCodeExtensions, dataset, defaultEncoding); target = Json::objectValue; - DatasetToJson(target, dataset, format, flags, maxStringLength, encoding, ignoreTagLength); + DatasetToJson(target, dataset, format, flags, maxStringLength, encoding, hasCodeExtensions, ignoreTagLength); } @@ -980,7 +997,7 @@ { std::set<DicomTag> ignoreTagLength; target = Json::objectValue; - DatasetToJson(target, dataset, format, flags, maxStringLength, Encoding_Ascii, ignoreTagLength); + DatasetToJson(target, dataset, format, flags, maxStringLength, Encoding_Ascii, false, ignoreTagLength); } @@ -2033,6 +2050,7 @@ void FromDcmtkBridge::ChangeStringEncoding(DcmItem& dataset, Encoding source, + bool hasSourceCodeExtensions, Encoding target) { // Recursive exploration of a dataset to change the encoding of @@ -2055,7 +2073,7 @@ element->getString(c).good() && c != NULL) { - std::string a = Toolbox::ConvertToUtf8(c, source); + std::string a = Toolbox::ConvertToUtf8(c, source, hasSourceCodeExtensions); std::string b = Toolbox::ConvertFromUtf8(a, target); element->putString(b.c_str()); } @@ -2069,7 +2087,7 @@ for (unsigned long j = 0; j < sequence.card(); j++) { - ChangeStringEncoding(*sequence.getItem(j), source, target); + ChangeStringEncoding(*sequence.getItem(j), source, hasSourceCodeExtensions, target); } } } @@ -2192,13 +2210,15 @@ ITagVisitor& visitor, const std::vector<DicomTag>& parentTags, const std::vector<size_t>& parentIndexes, - Encoding encoding); + Encoding encoding, + bool hasCodeExtensions); static void ApplyVisitorToDataset(DcmItem& dataset, ITagVisitor& visitor, const std::vector<DicomTag>& parentTags, const std::vector<size_t>& parentIndexes, - Encoding encoding) + Encoding encoding, + bool hasCodeExtensions) { assert(parentTags.size() == parentIndexes.size()); @@ -2211,7 +2231,7 @@ } else { - ApplyVisitorToElement(*element, visitor, parentTags, parentIndexes, encoding); + ApplyVisitorToElement(*element, visitor, parentTags, parentIndexes, encoding, hasCodeExtensions); } } } @@ -2222,7 +2242,8 @@ const std::vector<DicomTag>& parentTags, const std::vector<size_t>& parentIndexes, const DicomTag& tag, - Encoding encoding) + Encoding encoding, + bool hasCodeExtensions) { // TODO - Merge this function, that is more recent, with ConvertLeafElement() @@ -2299,7 +2320,7 @@ if (c != NULL) // This case corresponds to the empty string { std::string s(c); - utf8 = Toolbox::ConvertToUtf8(s, encoding); + utf8 = Toolbox::ConvertToUtf8(s, encoding, hasCodeExtensions); } std::string newValue; @@ -2380,7 +2401,7 @@ std::string s(reinterpret_cast<const char*>(data), l); ITagVisitor::Action action = visitor.VisitString (ignored, parentTags, parentIndexes, tag, vr, - Toolbox::ConvertToUtf8(s, encoding)); + Toolbox::ConvertToUtf8(s, encoding, hasCodeExtensions)); if (action != ITagVisitor::Action_None) { @@ -2608,7 +2629,8 @@ ITagVisitor& visitor, const std::vector<DicomTag>& parentTags, const std::vector<size_t>& parentIndexes, - Encoding encoding) + Encoding encoding, + bool hasCodeExtensions) { assert(parentTags.size() == parentIndexes.size()); @@ -2616,7 +2638,7 @@ if (element.isLeaf()) { - ApplyVisitorToLeaf(element, visitor, parentTags, parentIndexes, tag, encoding); + ApplyVisitorToLeaf(element, visitor, parentTags, parentIndexes, tag, encoding, hasCodeExtensions); } else { @@ -2640,7 +2662,7 @@ { indexes.back() = static_cast<size_t>(i); DcmItem* child = sequence.getItem(i); - ApplyVisitorToDataset(*child, visitor, tags, indexes, encoding); + ApplyVisitorToDataset(*child, visitor, tags, indexes, encoding, hasCodeExtensions); } } } @@ -2653,7 +2675,8 @@ { std::vector<DicomTag> parentTags; std::vector<size_t> parentIndexes; - Encoding encoding = DetectEncoding(dataset, defaultEncoding); - ApplyVisitorToDataset(dataset, visitor, parentTags, parentIndexes, encoding); + bool hasCodeExtensions; + Encoding encoding = DetectEncoding(hasCodeExtensions, dataset, defaultEncoding); + ApplyVisitorToDataset(dataset, visitor, parentTags, parentIndexes, encoding, hasCodeExtensions); } }
--- a/Core/DicomParsing/FromDcmtkBridge.h Wed Feb 13 13:01:21 2019 +0000 +++ b/Core/DicomParsing/FromDcmtkBridge.h Sat Feb 16 10:29:26 2019 +0000 @@ -92,6 +92,7 @@ DicomToJsonFlags flags, unsigned int maxStringLength, Encoding encoding, + bool hasCodeExtensions, const std::set<DicomTag>& ignoreTagLength); static void ElementToJson(Json::Value& parent, @@ -100,6 +101,7 @@ DicomToJsonFlags flags, unsigned int maxStringLength, Encoding dicomEncoding, + bool hasCodeExtensions, const std::set<DicomTag>& ignoreTagLength); static void ExtractDicomAsJson(Json::Value& target, @@ -112,6 +114,7 @@ static void ChangeStringEncoding(DcmItem& dataset, Encoding source, + bool hasSourceCodeExtensions, Encoding target); public: @@ -124,8 +127,17 @@ unsigned int maxMultiplicity, const std::string& privateCreator); + static Encoding DetectEncoding(bool& hasCodeExtensions, + DcmItem& dataset, + Encoding defaultEncoding); + static Encoding DetectEncoding(DcmItem& dataset, - Encoding defaultEncoding); + Encoding defaultEncoding) + { + // Compatibility wrapper for Orthanc <= 1.5.4 + bool hasCodeExtensions; // ignored + return DetectEncoding(hasCodeExtensions, dataset, defaultEncoding); + } static DicomTag Convert(const DcmTag& tag); @@ -137,6 +149,7 @@ DicomToJsonFlags flags, unsigned int maxStringLength, Encoding encoding, + bool hasCodeExtensions, const std::set<DicomTag>& ignoreTagLength); static void ExtractHeaderAsJson(Json::Value& target,
--- a/Core/DicomParsing/ParsedDicomFile.cpp Wed Feb 13 13:01:21 2019 +0000 +++ b/Core/DicomParsing/ParsedDicomFile.cpp Sat Feb 16 10:29:26 2019 +0000 @@ -645,7 +645,10 @@ } InvalidateCache(); - std::auto_ptr<DcmElement> element(FromDcmtkBridge::FromJson(tag, value, decodeDataUriScheme, GetEncoding())); + + bool hasCodeExtensions; + Encoding encoding = DetectEncoding(hasCodeExtensions); + std::auto_ptr<DcmElement> element(FromDcmtkBridge::FromJson(tag, value, decodeDataUriScheme, encoding)); InsertInternal(*pimpl_->file_->getDataset(), element.release()); } @@ -706,8 +709,9 @@ } else { - Encoding encoding = GetEncoding(); - if (GetEncoding() != Encoding_Utf8) + bool hasCodeExtensions; + Encoding encoding = DetectEncoding(hasCodeExtensions); + if (encoding != Encoding_Utf8) { binary = Toolbox::ConvertFromUtf8(utf8Value, encoding); decoded = &binary; @@ -766,7 +770,10 @@ } std::auto_ptr<DcmElement> element(FromDcmtkBridge::CreateElementForTag(tag)); - FromDcmtkBridge::FillElementWithString(*element, tag, utf8Value, decodeDataUriScheme, GetEncoding()); + + bool hasCodeExtensions; + Encoding encoding = DetectEncoding(hasCodeExtensions); + FromDcmtkBridge::FillElementWithString(*element, tag, utf8Value, decodeDataUriScheme, encoding); InsertInternal(dicom, element.release()); UpdateStorageUid(tag, utf8Value, false); @@ -805,7 +812,9 @@ } } - InsertInternal(dicom, FromDcmtkBridge::FromJson(tag, value, decodeDataUriScheme, GetEncoding())); + bool hasCodeExtensions; + Encoding encoding = DetectEncoding(hasCodeExtensions); + InsertInternal(dicom, FromDcmtkBridge::FromJson(tag, value, decodeDataUriScheme, encoding)); if (tag == DICOM_TAG_SOP_CLASS_UID || tag == DICOM_TAG_SOP_INSTANCE_UID) @@ -875,10 +884,13 @@ return false; } + bool hasCodeExtensions; + Encoding encoding = DetectEncoding(hasCodeExtensions); + std::set<DicomTag> tmp; std::auto_ptr<DicomValue> v(FromDcmtkBridge::ConvertLeafElement (*element, DicomToJsonFlags_Default, - 0, GetEncoding(), tmp)); + 0, encoding, hasCodeExtensions, tmp)); if (v.get() == NULL || v->IsNull()) @@ -1294,9 +1306,10 @@ } - Encoding ParsedDicomFile::GetEncoding() const + Encoding ParsedDicomFile::DetectEncoding(bool& hasCodeExtensions) const { - return FromDcmtkBridge::DetectEncoding(*pimpl_->file_->getDataset(), + return FromDcmtkBridge::DetectEncoding(hasCodeExtensions, + *pimpl_->file_->getDataset(), GetDefaultDicomEncoding()); } @@ -1532,12 +1545,13 @@ void ParsedDicomFile::ChangeEncoding(Encoding target) { - Encoding source = GetEncoding(); + bool hasCodeExtensions; + Encoding source = DetectEncoding(hasCodeExtensions); if (source != target) // Avoid unnecessary conversion { ReplacePlainString(DICOM_TAG_SPECIFIC_CHARACTER_SET, GetDicomSpecificCharacterSet(target)); - FromDcmtkBridge::ChangeStringEncoding(*pimpl_->file_->getDataset(), source, target); + FromDcmtkBridge::ChangeStringEncoding(*pimpl_->file_->getDataset(), source, hasCodeExtensions, target); } }
--- a/Core/DicomParsing/ParsedDicomFile.h Wed Feb 13 13:01:21 2019 +0000 +++ b/Core/DicomParsing/ParsedDicomFile.h Sat Feb 16 10:29:26 2019 +0000 @@ -186,7 +186,7 @@ void EmbedImage(MimeType mime, const std::string& content); - Encoding GetEncoding() const; + Encoding DetectEncoding(bool& hasCodeExtensions) const; // WARNING: This function only sets the encoding, it will not // convert the encoding of the tags. Use "ChangeEncoding()" if need be.
--- a/Core/Enumerations.cpp Wed Feb 13 13:01:21 2019 +0000 +++ b/Core/Enumerations.cpp Sat Feb 16 10:29:26 2019 +0000 @@ -647,6 +647,15 @@ case Encoding_Chinese: return "Chinese"; + case Encoding_Korean: + return "Korean"; + + case Encoding_JapaneseKanji: + return "JapaneseKanji"; + + case Encoding_SimplifiedChinese: + return "SimplifiedChinese"; + default: throw OrthancException(ErrorCode_ParameterOutOfRange); } @@ -1202,6 +1211,21 @@ return Encoding_Chinese; } + if (s == "KOREAN") + { + return Encoding_Korean; + } + + if (s == "JAPANESEKANJI") + { + return Encoding_JapaneseKanji; + } + + if (s == "SIMPLIFIEDCHINESE") + { + return Encoding_SimplifiedChinese; + } + throw OrthancException(ErrorCode_ParameterOutOfRange); } @@ -1836,11 +1860,13 @@ { encoding = Encoding_Hebrew; } - else if (s == "ISO_IR 166" || s == "ISO 2022 IR 166") + else if (s == "ISO_IR 166" || + s == "ISO 2022 IR 166") { encoding = Encoding_Thai; } - else if (s == "ISO_IR 13" || s == "ISO 2022 IR 13") + else if (s == "ISO_IR 13" || + s == "ISO 2022 IR 13") { encoding = Encoding_Japanese; } @@ -1855,18 +1881,22 @@ **/ encoding = Encoding_Chinese; } + else if (s == "ISO 2022 IR 149") + { + encoding = Encoding_Korean; + } + else if (s == "ISO 2022 IR 87") + { + encoding = Encoding_JapaneseKanji; + } + else if (s == "ISO 2022 IR 58") + { + encoding = Encoding_SimplifiedChinese; + } /* - else if (s == "ISO 2022 IR 149") - { - TODO - } else if (s == "ISO 2022 IR 159") { - TODO - } - else if (s == "ISO 2022 IR 87") - { - TODO + TODO - Supplementary Kanji set } */ else @@ -2013,6 +2043,15 @@ case Encoding_Thai: return "ISO_IR 166"; + case Encoding_Korean: + return "ISO 2022 IR 149"; + + case Encoding_JapaneseKanji: + return "ISO 2022 IR 87"; + + case Encoding_SimplifiedChinese: + return "ISO 2022 IR 58"; + default: throw OrthancException(ErrorCode_ParameterOutOfRange); }
--- a/Core/Enumerations.h Wed Feb 13 13:01:21 2019 +0000 +++ b/Core/Enumerations.h Sat Feb 16 10:29:26 2019 +0000 @@ -442,10 +442,11 @@ Encoding_Hebrew, Encoding_Thai, // TIS 620-2533 Encoding_Japanese, // JIS X 0201 (Shift JIS): Katakana - Encoding_Chinese // GB18030 - Chinese simplified - //Encoding_JapaneseKanji, // Multibyte - JIS X 0208: Kanji + Encoding_Chinese, // GB18030 - Chinese simplified + Encoding_JapaneseKanji, // Multibyte - JIS X 0208: Kanji //Encoding_JapaneseSupplementaryKanji, // Multibyte - JIS X 0212: Supplementary Kanji set - //Encoding_Korean, // Multibyte - KS X 1001: Hangul and Hanja + Encoding_Korean, // Multibyte - KS X 1001: Hangul and Hanja + Encoding_SimplifiedChinese // ISO 2022 IR 58 };
--- a/Core/Images/ImageProcessing.cpp Wed Feb 13 13:01:21 2019 +0000 +++ b/Core/Images/ImageProcessing.cpp Sat Feb 16 10:29:26 2019 +0000 @@ -20,7 +20,7 @@ * you do not wish to do so, delete this exception statement from your * version. If you delete this exception statement from all source files * in the program, then also delete it here. - * + * * 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 @@ -147,8 +147,8 @@ { // Y = 0.2126 R + 0.7152 G + 0.0722 B int32_t v = (2126 * static_cast<int32_t>(s[0]) + - 7152 * static_cast<int32_t>(s[1]) + - 0722 * static_cast<int32_t>(s[2])) / 10000; + 7152 * static_cast<int32_t>(s[1]) + + 0722 * static_cast<int32_t>(s[2])) / 10000; if (static_cast<int32_t>(v) < static_cast<int32_t>(minValue)) { @@ -168,7 +168,7 @@ static void MemsetZeroInternal(ImageAccessor& image) - { + { const unsigned int height = image.GetHeight(); const size_t lineSize = image.GetBytesPerPixel() * image.GetWidth(); const size_t pitch = image.GetPitch(); @@ -535,8 +535,8 @@ for (unsigned int x = 0; x < width; x++, q++) { *q = static_cast<uint8_t>((2126 * static_cast<uint32_t>(p[0]) + - 7152 * static_cast<uint32_t>(p[1]) + - 0722 * static_cast<uint32_t>(p[2])) / 10000); + 7152 * static_cast<uint32_t>(p[1]) + + 0722 * static_cast<uint32_t>(p[2])) / 10000); p += 4; } } @@ -554,8 +554,8 @@ for (unsigned int x = 0; x < width; x++, q++) { *q = static_cast<uint8_t>((2126 * static_cast<uint32_t>(p[2]) + - 7152 * static_cast<uint32_t>(p[1]) + - 0722 * static_cast<uint32_t>(p[0])) / 10000); + 7152 * static_cast<uint32_t>(p[1]) + + 0722 * static_cast<uint32_t>(p[0])) / 10000); p += 4; } } @@ -788,33 +788,33 @@ { switch (image.GetFormat()) { - case PixelFormat_Grayscale8: - SetInternal<uint8_t>(image, value); - return; + case PixelFormat_Grayscale8: + SetInternal<uint8_t>(image, value); + return; - case PixelFormat_Grayscale16: - SetInternal<uint16_t>(image, value); - return; + case PixelFormat_Grayscale16: + SetInternal<uint16_t>(image, value); + return; - case PixelFormat_Grayscale32: - SetInternal<uint32_t>(image, value); - return; + case PixelFormat_Grayscale32: + SetInternal<uint32_t>(image, value); + return; - case PixelFormat_Grayscale64: - SetInternal<uint64_t>(image, value); - return; + case PixelFormat_Grayscale64: + SetInternal<uint64_t>(image, value); + return; - case PixelFormat_SignedGrayscale16: - SetInternal<int16_t>(image, value); - return; + case PixelFormat_SignedGrayscale16: + SetInternal<int16_t>(image, value); + return; - case PixelFormat_Float32: - assert(sizeof(float) == 4); - SetInternal<float>(image, value); - return; + case PixelFormat_Float32: + assert(sizeof(float) == 4); + SetInternal<float>(image, value); + return; - default: - throw OrthancException(ErrorCode_NotImplemented); + default: + throw OrthancException(ErrorCode_NotImplemented); } } @@ -830,32 +830,32 @@ switch (image.GetFormat()) { - case PixelFormat_RGBA32: - p[0] = red; - p[1] = green; - p[2] = blue; - p[3] = alpha; - size = 4; - break; + case PixelFormat_RGBA32: + p[0] = red; + p[1] = green; + p[2] = blue; + p[3] = alpha; + size = 4; + break; - case PixelFormat_BGRA32: - p[0] = blue; - p[1] = green; - p[2] = red; - p[3] = alpha; - size = 4; - break; + case PixelFormat_BGRA32: + p[0] = blue; + p[1] = green; + p[2] = red; + p[3] = alpha; + size = 4; + break; - case PixelFormat_RGB24: - p[0] = red; - p[1] = green; - p[2] = blue; - size = 3; - break; + case PixelFormat_RGB24: + p[0] = red; + p[1] = green; + p[2] = blue; + size = 3; + break; - default: - throw OrthancException(ErrorCode_NotImplemented); - } + default: + throw OrthancException(ErrorCode_NotImplemented); + } const unsigned int width = image.GetWidth(); const unsigned int height = image.GetHeight(); @@ -898,44 +898,44 @@ { switch (image.GetFormat()) { - case PixelFormat_Grayscale8: - { - uint8_t a, b; - GetMinMaxValueInternal<uint8_t>(a, b, image); - minValue = a; - maxValue = b; - break; - } + case PixelFormat_Grayscale8: + { + uint8_t a, b; + GetMinMaxValueInternal<uint8_t>(a, b, image); + minValue = a; + maxValue = b; + break; + } - case PixelFormat_Grayscale16: - { - uint16_t a, b; - GetMinMaxValueInternal<uint16_t>(a, b, image); - minValue = a; - maxValue = b; - break; - } + case PixelFormat_Grayscale16: + { + uint16_t a, b; + GetMinMaxValueInternal<uint16_t>(a, b, image); + minValue = a; + maxValue = b; + break; + } - case PixelFormat_Grayscale32: - { - uint32_t a, b; - GetMinMaxValueInternal<uint32_t>(a, b, image); - minValue = a; - maxValue = b; - break; - } + case PixelFormat_Grayscale32: + { + uint32_t a, b; + GetMinMaxValueInternal<uint32_t>(a, b, image); + minValue = a; + maxValue = b; + break; + } - case PixelFormat_SignedGrayscale16: - { - int16_t a, b; - GetMinMaxValueInternal<int16_t>(a, b, image); - minValue = a; - maxValue = b; - break; - } + case PixelFormat_SignedGrayscale16: + { + int16_t a, b; + GetMinMaxValueInternal<int16_t>(a, b, image); + minValue = a; + maxValue = b; + break; + } - default: - throw OrthancException(ErrorCode_NotImplemented); + default: + throw OrthancException(ErrorCode_NotImplemented); } } @@ -946,18 +946,18 @@ { switch (image.GetFormat()) { - case PixelFormat_Float32: - { - assert(sizeof(float) == 4); - float a, b; - GetMinMaxValueInternal<float>(a, b, image); - minValue = a; - maxValue = b; - break; - } + case PixelFormat_Float32: + { + assert(sizeof(float) == 4); + float a, b; + GetMinMaxValueInternal<float>(a, b, image); + minValue = a; + maxValue = b; + break; + } - default: - throw OrthancException(ErrorCode_NotImplemented); + default: + throw OrthancException(ErrorCode_NotImplemented); } } @@ -968,20 +968,20 @@ { switch (image.GetFormat()) { - case PixelFormat_Grayscale8: - AddConstantInternal<uint8_t>(image, value); - return; + case PixelFormat_Grayscale8: + AddConstantInternal<uint8_t>(image, value); + return; - case PixelFormat_Grayscale16: - AddConstantInternal<uint16_t>(image, value); - return; + case PixelFormat_Grayscale16: + AddConstantInternal<uint16_t>(image, value); + return; - case PixelFormat_SignedGrayscale16: - AddConstantInternal<int16_t>(image, value); - return; + case PixelFormat_SignedGrayscale16: + AddConstantInternal<int16_t>(image, value); + return; - default: - throw OrthancException(ErrorCode_NotImplemented); + default: + throw OrthancException(ErrorCode_NotImplemented); } } @@ -992,41 +992,41 @@ { switch (image.GetFormat()) { - case PixelFormat_Grayscale8: - if (useRound) - { - MultiplyConstantInternal<uint8_t, true>(image, factor); - } - else - { - MultiplyConstantInternal<uint8_t, false>(image, factor); - } - return; + case PixelFormat_Grayscale8: + if (useRound) + { + MultiplyConstantInternal<uint8_t, true>(image, factor); + } + else + { + MultiplyConstantInternal<uint8_t, false>(image, factor); + } + return; - case PixelFormat_Grayscale16: - if (useRound) - { - MultiplyConstantInternal<uint16_t, true>(image, factor); - } - else - { - MultiplyConstantInternal<uint16_t, false>(image, factor); - } - return; + case PixelFormat_Grayscale16: + if (useRound) + { + MultiplyConstantInternal<uint16_t, true>(image, factor); + } + else + { + MultiplyConstantInternal<uint16_t, false>(image, factor); + } + return; - case PixelFormat_SignedGrayscale16: - if (useRound) - { - MultiplyConstantInternal<int16_t, true>(image, factor); - } - else - { - MultiplyConstantInternal<int16_t, false>(image, factor); - } - return; + case PixelFormat_SignedGrayscale16: + if (useRound) + { + MultiplyConstantInternal<int16_t, true>(image, factor); + } + else + { + MultiplyConstantInternal<int16_t, false>(image, factor); + } + return; - default: - throw OrthancException(ErrorCode_NotImplemented); + default: + throw OrthancException(ErrorCode_NotImplemented); } } @@ -1038,70 +1038,100 @@ { switch (image.GetFormat()) { - case PixelFormat_Grayscale8: - if (useRound) - { - ShiftScaleInternal<uint8_t, true>(image, offset, scaling); - } - else - { - ShiftScaleInternal<uint8_t, false>(image, offset, scaling); - } - return; + case PixelFormat_Grayscale8: + if (useRound) + { + ShiftScaleInternal<uint8_t, true>(image, offset, scaling); + } + else + { + ShiftScaleInternal<uint8_t, false>(image, offset, scaling); + } + return; - case PixelFormat_Grayscale16: - if (useRound) - { - ShiftScaleInternal<uint16_t, true>(image, offset, scaling); - } - else - { - ShiftScaleInternal<uint16_t, false>(image, offset, scaling); - } - return; + case PixelFormat_Grayscale16: + if (useRound) + { + ShiftScaleInternal<uint16_t, true>(image, offset, scaling); + } + else + { + ShiftScaleInternal<uint16_t, false>(image, offset, scaling); + } + return; - case PixelFormat_SignedGrayscale16: - if (useRound) - { - ShiftScaleInternal<int16_t, true>(image, offset, scaling); - } - else - { - ShiftScaleInternal<int16_t, false>(image, offset, scaling); - } - return; + case PixelFormat_SignedGrayscale16: + if (useRound) + { + ShiftScaleInternal<int16_t, true>(image, offset, scaling); + } + else + { + ShiftScaleInternal<int16_t, false>(image, offset, scaling); + } + return; - default: - throw OrthancException(ErrorCode_NotImplemented); + default: + throw OrthancException(ErrorCode_NotImplemented); } } - void ImageProcessing::Invert(ImageAccessor& image) + void ImageProcessing::Invert(ImageAccessor& image, int64_t maxValue) { const unsigned int width = image.GetWidth(); const unsigned int height = image.GetHeight(); - + switch (image.GetFormat()) { - case PixelFormat_Grayscale8: - { - for (unsigned int y = 0; y < height; y++) - { - uint8_t* p = reinterpret_cast<uint8_t*>(image.GetRow(y)); + case PixelFormat_Grayscale16: + { + uint16_t maxValueUint16 = (uint16_t)(std::min(maxValue, static_cast<int64_t>(std::numeric_limits<uint16_t>::max()))); - for (unsigned int x = 0; x < width; x++, p++) - { - *p = 255 - (*p); - } + for (unsigned int y = 0; y < height; y++) + { + uint16_t* p = reinterpret_cast<uint16_t*>(image.GetRow(y)); + + for (unsigned int x = 0; x < width; x++, p++) + { + *p = maxValueUint16 - (*p); } - - return; } - default: - throw OrthancException(ErrorCode_NotImplemented); - } + return; + } + case PixelFormat_Grayscale8: + { + uint8_t maxValueUint8 = (uint8_t)(std::min(maxValue, static_cast<int64_t>(std::numeric_limits<uint8_t>::max()))); + + for (unsigned int y = 0; y < height; y++) + { + uint8_t* p = reinterpret_cast<uint8_t*>(image.GetRow(y)); + + for (unsigned int x = 0; x < width; x++, p++) + { + *p = maxValueUint8 - (*p); + } + } + + return; + } + + default: + throw OrthancException(ErrorCode_NotImplemented); + } + + } + + void ImageProcessing::Invert(ImageAccessor& image) + { + switch (image.GetFormat()) + { + case PixelFormat_Grayscale8: + return Invert(image, 255); + default: + throw OrthancException(ErrorCode_NotImplemented); // you should use the Invert(image, maxValue) overload + } } @@ -1113,7 +1143,7 @@ { private: typedef typename PixelTraits<Format>::PixelType PixelType; - + Orthanc::ImageAccessor& image_; PixelType value_; @@ -1144,7 +1174,7 @@ y = y + yi; d = d - 2 * dx; } - + d = d + 2*dy; } } @@ -1157,13 +1187,13 @@ int dx = x1 - x0; int dy = y1 - y0; int xi = 1; - + if (dx < 0) { xi = -1; dx = -dx; } - + int d = 2 * dx - dy; int x = x0; @@ -1176,7 +1206,7 @@ x = x + xi; d = d - 2 * dy; } - + d = d + 2 * dx; } } @@ -1216,7 +1246,7 @@ { // This is an implementation of Bresenham's line algorithm // https://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm#All_cases - + if (abs(y1 - y0) < abs(x1 - x0)) { if (x0 > x1) @@ -1252,30 +1282,30 @@ int64_t value) { switch (image.GetFormat()) - { - case Orthanc::PixelFormat_Grayscale8: - { - BresenhamPixelWriter<Orthanc::PixelFormat_Grayscale8> writer(image, value); - writer.DrawSegment(x0, y0, x1, y1); - break; - } + { + case Orthanc::PixelFormat_Grayscale8: + { + BresenhamPixelWriter<Orthanc::PixelFormat_Grayscale8> writer(image, value); + writer.DrawSegment(x0, y0, x1, y1); + break; + } - case Orthanc::PixelFormat_Grayscale16: - { - BresenhamPixelWriter<Orthanc::PixelFormat_Grayscale16> writer(image, value); - writer.DrawSegment(x0, y0, x1, y1); - break; - } + case Orthanc::PixelFormat_Grayscale16: + { + BresenhamPixelWriter<Orthanc::PixelFormat_Grayscale16> writer(image, value); + writer.DrawSegment(x0, y0, x1, y1); + break; + } - case Orthanc::PixelFormat_SignedGrayscale16: - { - BresenhamPixelWriter<Orthanc::PixelFormat_SignedGrayscale16> writer(image, value); - writer.DrawSegment(x0, y0, x1, y1); - break; - } - - default: - throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + case Orthanc::PixelFormat_SignedGrayscale16: + { + BresenhamPixelWriter<Orthanc::PixelFormat_SignedGrayscale16> writer(image, value); + writer.DrawSegment(x0, y0, x1, y1); + break; + } + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); } } @@ -1292,46 +1322,46 @@ { switch (image.GetFormat()) { - case Orthanc::PixelFormat_BGRA32: - { - PixelTraits<Orthanc::PixelFormat_BGRA32>::PixelType pixel; - pixel.red_ = red; - pixel.green_ = green; - pixel.blue_ = blue; - pixel.alpha_ = alpha; + case Orthanc::PixelFormat_BGRA32: + { + PixelTraits<Orthanc::PixelFormat_BGRA32>::PixelType pixel; + pixel.red_ = red; + pixel.green_ = green; + pixel.blue_ = blue; + pixel.alpha_ = alpha; + + BresenhamPixelWriter<Orthanc::PixelFormat_BGRA32> writer(image, pixel); + writer.DrawSegment(x0, y0, x1, y1); + break; + } - BresenhamPixelWriter<Orthanc::PixelFormat_BGRA32> writer(image, pixel); - writer.DrawSegment(x0, y0, x1, y1); - break; - } - - case Orthanc::PixelFormat_RGBA32: - { - PixelTraits<Orthanc::PixelFormat_RGBA32>::PixelType pixel; - pixel.red_ = red; - pixel.green_ = green; - pixel.blue_ = blue; - pixel.alpha_ = alpha; + case Orthanc::PixelFormat_RGBA32: + { + PixelTraits<Orthanc::PixelFormat_RGBA32>::PixelType pixel; + pixel.red_ = red; + pixel.green_ = green; + pixel.blue_ = blue; + pixel.alpha_ = alpha; + + BresenhamPixelWriter<Orthanc::PixelFormat_RGBA32> writer(image, pixel); + writer.DrawSegment(x0, y0, x1, y1); + break; + } - BresenhamPixelWriter<Orthanc::PixelFormat_RGBA32> writer(image, pixel); - writer.DrawSegment(x0, y0, x1, y1); - break; - } - - case Orthanc::PixelFormat_RGB24: - { - PixelTraits<Orthanc::PixelFormat_RGB24>::PixelType pixel; - pixel.red_ = red; - pixel.green_ = green; - pixel.blue_ = blue; + case Orthanc::PixelFormat_RGB24: + { + PixelTraits<Orthanc::PixelFormat_RGB24>::PixelType pixel; + pixel.red_ = red; + pixel.green_ = green; + pixel.blue_ = blue; - BresenhamPixelWriter<Orthanc::PixelFormat_RGB24> writer(image, pixel); - writer.DrawSegment(x0, y0, x1, y1); - break; - } - - default: - throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + BresenhamPixelWriter<Orthanc::PixelFormat_RGB24> writer(image, pixel); + writer.DrawSegment(x0, y0, x1, y1); + break; + } + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); } } }
--- a/Core/Images/ImageProcessing.h Wed Feb 13 13:01:21 2019 +0000 +++ b/Core/Images/ImageProcessing.h Sat Feb 16 10:29:26 2019 +0000 @@ -83,6 +83,8 @@ void Invert(ImageAccessor& image); + void Invert(ImageAccessor& image, int64_t maxValue); + void DrawLineSegment(ImageAccessor& image, int x0, int y0,
--- a/Core/JobsEngine/JobStatus.cpp Wed Feb 13 13:01:21 2019 +0000 +++ b/Core/JobsEngine/JobStatus.cpp Sat Feb 16 10:29:26 2019 +0000 @@ -49,10 +49,12 @@ JobStatus::JobStatus(ErrorCode code, + const std::string& details, IJob& job) : errorCode_(code), progress_(job.GetProgress()), - publicContent_(Json::objectValue) + publicContent_(Json::objectValue), + details_(details) { if (progress_ < 0) {
--- a/Core/JobsEngine/JobStatus.h Wed Feb 13 13:01:21 2019 +0000 +++ b/Core/JobsEngine/JobStatus.h Sat Feb 16 10:29:26 2019 +0000 @@ -46,11 +46,13 @@ Json::Value publicContent_; Json::Value serialized_; bool hasSerialized_; + std::string details_; public: JobStatus(); JobStatus(ErrorCode code, + const std::string& details, IJob& job); ErrorCode GetErrorCode() const @@ -84,5 +86,10 @@ { return hasSerialized_; } + + const std::string& GetDetails() const + { + return details_; + } }; }
--- a/Core/JobsEngine/JobStepResult.cpp Wed Feb 13 13:01:21 2019 +0000 +++ b/Core/JobsEngine/JobStepResult.cpp Sat Feb 16 10:29:26 2019 +0000 @@ -46,14 +46,28 @@ } - JobStepResult JobStepResult::Failure(const ErrorCode& error) + JobStepResult JobStepResult::Failure(const ErrorCode& error, + const char* details) { JobStepResult result(JobStepCode_Failure); result.error_ = error; + + if (details != NULL) + { + result.failureDetails_ = details; + } + return result; } + JobStepResult JobStepResult::Failure(const OrthancException& exception) + { + return Failure(exception.GetErrorCode(), + exception.HasDetails() ? exception.GetDetails() : NULL); + } + + unsigned int JobStepResult::GetRetryTimeout() const { if (code_ == JobStepCode_Retry) @@ -78,4 +92,17 @@ throw OrthancException(ErrorCode_BadSequenceOfCalls); } } + + + const std::string& JobStepResult::GetFailureDetails() const + { + if (code_ == JobStepCode_Failure) + { + return failureDetails_; + } + else + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + } }
--- a/Core/JobsEngine/JobStepResult.h Wed Feb 13 13:01:21 2019 +0000 +++ b/Core/JobsEngine/JobStepResult.h Sat Feb 16 10:29:26 2019 +0000 @@ -37,12 +37,15 @@ namespace Orthanc { + class OrthancException; + class JobStepResult { private: JobStepCode code_; unsigned int timeout_; ErrorCode error_; + std::string failureDetails_; explicit JobStepResult(JobStepCode code) : code_(code), @@ -71,7 +74,10 @@ static JobStepResult Retry(unsigned int timeout); - static JobStepResult Failure(const ErrorCode& error); + static JobStepResult Failure(const ErrorCode& error, + const char* details); + + static JobStepResult Failure(const OrthancException& exception); JobStepCode GetCode() const { @@ -81,5 +87,7 @@ unsigned int GetRetryTimeout() const; ErrorCode GetFailureCode() const; + + const std::string& GetFailureDetails() const; }; }
--- a/Core/JobsEngine/JobsEngine.cpp Wed Feb 13 13:01:21 2019 +0000 +++ b/Core/JobsEngine/JobsEngine.cpp Sat Feb 16 10:29:26 2019 +0000 @@ -75,39 +75,39 @@ } catch (OrthancException& e) { - result = JobStepResult::Failure(e.GetErrorCode()); + result = JobStepResult::Failure(e); } catch (boost::bad_lexical_cast&) { - result = JobStepResult::Failure(ErrorCode_BadFileFormat); + result = JobStepResult::Failure(ErrorCode_BadFileFormat, NULL); } catch (...) { - result = JobStepResult::Failure(ErrorCode_InternalError); + result = JobStepResult::Failure(ErrorCode_InternalError, NULL); } switch (result.GetCode()) { case JobStepCode_Success: running.GetJob().Stop(JobStopReason_Success); - running.UpdateStatus(ErrorCode_Success); + running.UpdateStatus(ErrorCode_Success, ""); running.MarkSuccess(); return false; case JobStepCode_Failure: running.GetJob().Stop(JobStopReason_Failure); - running.UpdateStatus(result.GetFailureCode()); + running.UpdateStatus(result.GetFailureCode(), result.GetFailureDetails()); running.MarkFailure(); return false; case JobStepCode_Retry: running.GetJob().Stop(JobStopReason_Retry); - running.UpdateStatus(ErrorCode_Success); + running.UpdateStatus(ErrorCode_Success, ""); running.MarkRetry(result.GetRetryTimeout()); return false; case JobStepCode_Continue: - running.UpdateStatus(ErrorCode_Success); + running.UpdateStatus(ErrorCode_Success, ""); return true; default:
--- a/Core/JobsEngine/JobsRegistry.cpp Wed Feb 13 13:01:21 2019 +0000 +++ b/Core/JobsEngine/JobsRegistry.cpp Sat Feb 16 10:29:26 2019 +0000 @@ -110,7 +110,7 @@ job->GetJobType(jobType_); job->Start(); - lastStatus_ = JobStatus(ErrorCode_Success, *job_); + lastStatus_ = JobStatus(ErrorCode_Success, "", *job_); } const std::string& GetId() const @@ -318,7 +318,7 @@ job_->GetJobType(jobType_); job_->Start(); - lastStatus_ = JobStatus(ErrorCode_Success, *job_); + lastStatus_ = JobStatus(ErrorCode_Success, "", *job_); } }; @@ -739,7 +739,7 @@ } - bool JobsRegistry::SubmitAndWait(Json::Value& successContent, + void JobsRegistry::SubmitAndWait(Json::Value& successContent, IJob* job, // Takes ownership int priority) { @@ -764,7 +764,25 @@ else if (state == JobState_Failure) { // Failure - break; + JobsIndex::const_iterator it = jobsIndex_.find(id); + if (it != jobsIndex_.end()) // Should always be true, already tested in GetStateInternal() + { + ErrorCode code = it->second->GetLastStatus().GetErrorCode(); + const std::string& details = it->second->GetLastStatus().GetDetails(); + + if (details.empty()) + { + throw OrthancException(code); + } + else + { + throw OrthancException(code, details); + } + } + else + { + throw OrthancException(ErrorCode_InternalError); + } } else if (state == JobState_Success) { @@ -781,7 +799,7 @@ successContent = status.GetPublicContent(); } - break; + return; } else { @@ -790,8 +808,6 @@ } } } - - return (state == JobState_Success); } @@ -1320,7 +1336,8 @@ } - void JobsRegistry::RunningJob::UpdateStatus(ErrorCode code) + void JobsRegistry::RunningJob::UpdateStatus(ErrorCode code, + const std::string& details) { if (!IsValid()) { @@ -1328,7 +1345,7 @@ } else { - JobStatus status(code, *job_); + JobStatus status(code, details, *job_); boost::mutex::scoped_lock lock(registry_.mutex_); registry_.CheckInvariants();
--- a/Core/JobsEngine/JobsRegistry.h Wed Feb 13 13:01:21 2019 +0000 +++ b/Core/JobsEngine/JobsRegistry.h Sat Feb 16 10:29:26 2019 +0000 @@ -175,7 +175,7 @@ void Submit(IJob* job, // Takes ownership int priority); - bool SubmitAndWait(Json::Value& successContent, + void SubmitAndWait(Json::Value& successContent, IJob* job, // Takes ownership int priority); @@ -248,7 +248,8 @@ void MarkRetry(unsigned int timeout); - void UpdateStatus(ErrorCode code); + void UpdateStatus(ErrorCode code, + const std::string& details); }; }; }
--- a/Core/JobsEngine/SetOfCommandsJob.cpp Wed Feb 13 13:01:21 2019 +0000 +++ b/Core/JobsEngine/SetOfCommandsJob.cpp Sat Feb 16 10:29:26 2019 +0000 @@ -174,7 +174,7 @@ // Error if (!permissive_) { - return JobStepResult::Failure(ErrorCode_InternalError); + return JobStepResult::Failure(ErrorCode_InternalError, NULL); } } } @@ -186,7 +186,7 @@ } else { - return JobStepResult::Failure(e.GetErrorCode()); + return JobStepResult::Failure(e); } }
--- a/Core/Toolbox.cpp Wed Feb 13 13:01:21 2019 +0000 +++ b/Core/Toolbox.cpp Sat Feb 16 10:29:26 2019 +0000 @@ -464,55 +464,55 @@ case Encoding_Latin1: return "ISO-8859-1"; - break; case Encoding_Latin2: return "ISO-8859-2"; - break; case Encoding_Latin3: return "ISO-8859-3"; - break; case Encoding_Latin4: return "ISO-8859-4"; - break; case Encoding_Latin5: return "ISO-8859-9"; - break; case Encoding_Cyrillic: return "ISO-8859-5"; - break; case Encoding_Windows1251: return "WINDOWS-1251"; - break; case Encoding_Arabic: return "ISO-8859-6"; - break; case Encoding_Greek: return "ISO-8859-7"; - break; case Encoding_Hebrew: return "ISO-8859-8"; - break; case Encoding_Japanese: return "SHIFT-JIS"; - break; case Encoding_Chinese: return "GB18030"; - break; case Encoding_Thai: +#if BOOST_LOCALE_WITH_ICU == 1 + return "tis620.2533"; +#else return "TIS620.2533-0"; - break; +#endif + + case Encoding_Korean: + return "ISO-IR-149"; + + case Encoding_JapaneseKanji: + return "JIS"; + + case Encoding_SimplifiedChinese: + return "GB2312"; default: throw OrthancException(ErrorCode_NotImplemented); @@ -522,32 +522,52 @@ #if ORTHANC_ENABLE_LOCALE == 1 + // http://dicom.nema.org/medical/dicom/current/output/chtml/part03/sect_C.12.html#sect_C.12.1.1.2 std::string Toolbox::ConvertToUtf8(const std::string& source, - Encoding sourceEncoding) + Encoding sourceEncoding, + bool hasCodeExtensions) { // The "::skip" flag makes boost skip invalid UTF-8 // characters. This can occur in badly-encoded DICOM files. try { - if (sourceEncoding == Encoding_Utf8) - { - // Already in UTF-8: No conversion is required - return boost::locale::conv::utf_to_utf<char>(source, boost::locale::conv::skip); - } - else if (sourceEncoding == Encoding_Ascii) + if (sourceEncoding == Encoding_Ascii) { return ConvertToAscii(source); } - else + else { - const char* encoding = GetBoostLocaleEncoding(sourceEncoding); - return boost::locale::conv::to_utf<char>(source, encoding, boost::locale::conv::skip); + std::string s; + + if (sourceEncoding == Encoding_Utf8) + { + // Already in UTF-8: No conversion is required, but we ensure + // the output is correctly encoded + s = boost::locale::conv::utf_to_utf<char>(source, boost::locale::conv::skip); + } + else + { + const char* encoding = GetBoostLocaleEncoding(sourceEncoding); + s = boost::locale::conv::to_utf<char>(source, encoding, boost::locale::conv::skip); + } + + if (hasCodeExtensions) + { + std::string t; + RemoveIso2022EscapeSequences(t, s); + return t; + } + else + { + return s; + } } } - catch (std::runtime_error&) + catch (std::runtime_error& e) { // Bad input string or bad encoding + LOG(INFO) << e.what(); return ConvertToAscii(source); } } @@ -1593,6 +1613,182 @@ return boost::regex_replace(source, pattern, formatter); } + + + namespace Iso2022 + { + /** + Returns whether the string s contains a single-byte control message + at index i + **/ + static inline bool IsControlMessage1(const std::string& s, size_t i) + { + if (i < s.size()) + { + char c = s[i]; + return + (c == '\x0f') || // Locking shift zero + (c == '\x0e'); // Locking shift one + } + else + { + return false; + } + } + + /** + Returns whether the string s contains a double-byte control message + at index i + **/ + static inline size_t IsControlMessage2(const std::string& s, size_t i) + { + if (i + 1 < s.size()) + { + char c1 = s[i]; + char c2 = s[i + 1]; + return (c1 == 0x1b) && ( + (c2 == '\x6e') || // Locking shift two + (c2 == '\x6f') || // Locking shift three + (c2 == '\x4e') || // Single shift two (alt) + (c2 == '\x4f') || // Single shift three (alt) + (c2 == '\x7c') || // Locking shift three right + (c2 == '\x7d') || // Locking shift two right + (c2 == '\x7e') // Locking shift one right + ); + } + else + { + return false; + } + } + + /** + Returns whether the string s contains a triple-byte control message + at index i + **/ + static inline size_t IsControlMessage3(const std::string& s, size_t i) + { + if (i + 2 < s.size()) + { + char c1 = s[i]; + char c2 = s[i + 1]; + char c3 = s[i + 2]; + return ((c1 == '\x8e' && c2 == 0x1b && c3 == '\x4e') || + (c1 == '\x8f' && c2 == 0x1b && c3 == '\x4f')); + } + else + { + return false; + } + } + + /** + This function returns true if the index i in the supplied string s: + - is valid + - contains the c character + This function returns false otherwise. + **/ + static inline bool TestCharValue( + const std::string& s, size_t i, char c) + { + if (i < s.size()) + return s[i] == c; + else + return false; + } + + /** + This function returns true if the index i in the supplied string s: + - is valid + - has a c character that is >= cMin and <= cMax (included) + This function returns false otherwise. + **/ + static inline bool TestCharRange( + const std::string& s, size_t i, char cMin, char cMax) + { + if (i < s.size()) + return (s[i] >= cMin) && (s[i] <= cMax); + else + return false; + } + + /** + This function returns the total length in bytes of the escape sequence + located in string s at index i, if there is one, or 0 otherwise. + **/ + static inline size_t GetEscapeSequenceLength(const std::string& s, size_t i) + { + if (TestCharValue(s, i, 0x1b)) + { + size_t j = i+1; + + // advance reading cursor while we are in a sequence + while (TestCharRange(s, j, '\x20', '\x2f')) + ++j; + + // check there is a valid termination byte AND we're long enough (there + // must be at least one byte between 0x20 and 0x2f + if (TestCharRange(s, j, '\x30', '\x7f') && (j - i) >= 2) + return j - i + 1; + else + return 0; + } + else + return 0; + } + } + + + + /** + This function will strip all ISO/IEC 2022 control codes and escape + sequences. + Please see https://en.wikipedia.org/wiki/ISO/IEC_2022 (as of 2019-02) + for a list of those. + + Please note that this operation is potentially destructive, because + it removes the character set information from the byte stream. + + However, in the case where the encoding is unique, then suppressing + the escape sequences allows to provide us with a clean string after + conversion to utf-8 with boost. + **/ + void Toolbox::RemoveIso2022EscapeSequences(std::string& dest, const std::string& src) + { + // we need AT MOST the same size as the source string in the output + dest.clear(); + if (dest.capacity() < src.size()) + dest.reserve(src.size()); + + size_t i = 0; + + // uint8_t view to the string + while (i < src.size()) + { + size_t j = i; + + // The i index will only be incremented if a message is detected + // in that case, the message is skipped and the index is set to the + // next position to read + if (Iso2022::IsControlMessage1(src, i)) + i += 1; + else if (Iso2022::IsControlMessage2(src, i)) + i += 2; + else if (Iso2022::IsControlMessage3(src, i)) + i += 3; + else + i += Iso2022::GetEscapeSequenceLength(src, i); + + // if the index was NOT incremented, this means there was no message at + // this location: we then may copy the character at this index and + // increment the index to point to the next read position + if (j == i) + { + dest.push_back(src[i]); + i++; + } + } + } }
--- a/Core/Toolbox.h Wed Feb 13 13:01:21 2019 +0000 +++ b/Core/Toolbox.h Sat Feb 16 10:29:26 2019 +0000 @@ -163,7 +163,8 @@ #if ORTHANC_ENABLE_LOCALE == 1 std::string ConvertToUtf8(const std::string& source, - Encoding sourceEncoding); + Encoding sourceEncoding, + bool hasCodeExtensions); std::string ConvertFromUtf8(const std::string& source, Encoding targetEncoding); @@ -248,6 +249,9 @@ std::string SubstituteVariables(const std::string& source, const std::map<std::string, std::string>& dictionary); + + void RemoveIso2022EscapeSequences(std::string& dest, + const std::string& src); } }
--- a/INSTALL Wed Feb 13 13:01:21 2019 +0000 +++ b/INSTALL Sat Feb 16 10:29:26 2019 +0000 @@ -71,11 +71,12 @@ -Native Windows build with Microsoft Visual Studio -------------------------------------------------- +Native Windows build with Microsoft Visual Studio 2008 +------------------------------------------------------ # cd [...]\OrthancBuild -# cmake -DSTANDALONE_BUILD=ON -DSTATIC_BUILD=ON -DALLOW_DOWNLOADS=ON -DUSE_LEGACY_JSONCPP=ON -G "Visual Studio 9 2008" [...]\Orthanc +# cmake -DSTANDALONE_BUILD=ON -DSTATIC_BUILD=ON -DALLOW_DOWNLOADS=ON \ + -DUSE_LEGACY_JSONCPP=ON -G "Visual Studio 9 2008" [...]\Orthanc Then open the "[...]/OrthancBuild/Orthanc.sln" with Visual Studio. @@ -91,6 +92,16 @@ Visual Studio that do not support C++11 +Orthanc as compiled above will not work properly with some Asian +encodings (unit tests will fail). In international setups, you can +compile Orthanc together with ICU as follows: + +# cmake -DSTANDALONE_BUILD=ON -DSTATIC_BUILD=ON -DALLOW_DOWNLOADS=ON \ + -DBOOST_LOCALE_BACKEND=icu -DUSE_LEGACY_JSONCPP=ON -DUSE_LEGACY_LIBICU=ON \ + -G "Visual Studio 9 2008" [...]\Orthanc + + + Native Windows build with Microsoft Visual Studio 2015, Ninja and QtCreator --------------------------------------------------------------------------- @@ -105,6 +116,11 @@ * Import build from [...]\OrthancBuild +Instructions to include support for Asian encodings: + +# cmake -G Ninja -DSTATIC_BUILD=ON -DBOOST_LOCALE_BACKEND=icu [...]\Orthanc + + Cross-Compilation for Windows under GNU/Linux ---------------------------------------------
--- a/NEWS Wed Feb 13 13:01:21 2019 +0000 +++ b/NEWS Sat Feb 16 10:29:26 2019 +0000 @@ -1,6 +1,14 @@ Pending changes in the mainline =============================== +* Separation of ideographic and phonetic characters in DICOMweb JSON and XML +* Support of the following multi-byte specific character sets: + - Japanese Kanji (ISO 2022 IR 87) + - Korean (ISO 2022 IR 149) + - Simplified Chinese (ISO 2022 IR 58) +* Basic support for character sets with code extensions (ISO 2022 escape sequences) +* Fix issue #134 (/patient/modify gives 500, should really be 400) + Version 1.5.4 (2019-02-08) ==========================
--- a/OrthancServer/OrthancRestApi/OrthancRestAnonymizeModify.cpp Wed Feb 13 13:01:21 2019 +0000 +++ b/OrthancServer/OrthancRestApi/OrthancRestAnonymizeModify.cpp Sat Feb 16 10:29:26 2019 +0000 @@ -519,7 +519,10 @@ else if (tag["Type"] == "String") { std::string value = tag["Value"].asString(); - dicom.ReplacePlainString(*it, Toolbox::ConvertFromUtf8(value, dicom.GetEncoding())); + + bool hasCodeExtensions; + Encoding encoding = dicom.DetectEncoding(hasCodeExtensions); + dicom.ReplacePlainString(*it, Toolbox::ConvertFromUtf8(value, encoding)); } } }
--- a/OrthancServer/OrthancRestApi/OrthancRestApi.cpp Wed Feb 13 13:01:21 2019 +0000 +++ b/OrthancServer/OrthancRestApi/OrthancRestApi.cpp Sat Feb 16 10:29:26 2019 +0000 @@ -250,17 +250,11 @@ if (synchronous) { Json::Value successContent; - if (context.GetJobsEngine().GetRegistry().SubmitAndWait - (successContent, raii.release(), priority)) - { - // Success in synchronous execution - output.AnswerJson(successContent); - } - else - { - // Error during synchronous execution - output.SignalError(HttpStatus_500_InternalServerError); - } + context.GetJobsEngine().GetRegistry().SubmitAndWait + (successContent, raii.release(), priority); + + // Success in synchronous execution + output.AnswerJson(successContent); } else {
--- a/OrthancServer/OrthancRestApi/OrthancRestArchive.cpp Wed Feb 13 13:01:21 2019 +0000 +++ b/OrthancServer/OrthancRestApi/OrthancRestArchive.cpp Sat Feb 16 10:29:26 2019 +0000 @@ -147,8 +147,9 @@ job->SetSynchronousTarget(tmp); Json::Value publicContent; - if (context.GetJobsEngine().GetRegistry().SubmitAndWait - (publicContent, job.release(), priority)) + context.GetJobsEngine().GetRegistry().SubmitAndWait + (publicContent, job.release(), priority); + { // The archive is now created: Prepare the sending of the ZIP file FilesystemHttpSender sender(tmp->GetPath(), MimeType_Zip); @@ -157,10 +158,6 @@ // Send the ZIP output.AnswerStream(sender); } - else - { - output.SignalError(HttpStatus_500_InternalServerError); - } } else {
--- a/OrthancServer/Search/DatabaseLookup.cpp Wed Feb 13 13:01:21 2019 +0000 +++ b/OrthancServer/Search/DatabaseLookup.cpp Sat Feb 16 10:29:26 2019 +0000 @@ -95,7 +95,8 @@ bool DatabaseLookup::IsMatch(DcmItem& item, - Encoding encoding) const + Encoding encoding, + bool hasCodeExtensions) const { for (size_t i = 0; i < constraints_.size(); i++) { @@ -118,7 +119,7 @@ std::set<DicomTag> ignoreTagLength; std::auto_ptr<DicomValue> value(FromDcmtkBridge::ConvertLeafElement (*element, DicomToJsonFlags_None, - 0, encoding, ignoreTagLength)); + 0, encoding, hasCodeExtensions, ignoreTagLength)); // WARNING: Also modify "HierarchicalMatcher::Setup()" if modifying this code if (value.get() == NULL ||
--- a/OrthancServer/Search/DatabaseLookup.h Wed Feb 13 13:01:21 2019 +0000 +++ b/OrthancServer/Search/DatabaseLookup.h Sat Feb 16 10:29:26 2019 +0000 @@ -74,7 +74,8 @@ bool IsMatch(const DicomMap& value) const; bool IsMatch(DcmItem& item, - Encoding encoding) const; + Encoding encoding, + bool hasCodeExtensions) const; void AddDicomConstraint(const DicomTag& tag, const std::string& dicomQuery,
--- a/OrthancServer/Search/HierarchicalMatcher.cpp Wed Feb 13 13:01:21 2019 +0000 +++ b/OrthancServer/Search/HierarchicalMatcher.cpp Sat Feb 16 10:29:26 2019 +0000 @@ -53,7 +53,9 @@ caseSensitivePN = lock.GetConfiguration().GetBooleanParameter("CaseSensitivePN", false); } - Setup(*query.GetDcmtkObject().getDataset(), caseSensitivePN, query.GetEncoding()); + bool hasCodeExtensions; + Encoding encoding = query.DetectEncoding(hasCodeExtensions); + Setup(*query.GetDcmtkObject().getDataset(), caseSensitivePN, encoding, hasCodeExtensions); } @@ -72,7 +74,8 @@ void HierarchicalMatcher::Setup(DcmItem& dataset, bool caseSensitivePN, - Encoding encoding) + Encoding encoding, + bool hasCodeExtensions) { for (unsigned long i = 0; i < dataset.card(); i++) { @@ -108,7 +111,7 @@ } else if (sequence.card() == 1) { - sequences_[tag] = new HierarchicalMatcher(*sequence.getItem(0), caseSensitivePN, encoding); + sequences_[tag] = new HierarchicalMatcher(*sequence.getItem(0), caseSensitivePN, encoding, hasCodeExtensions); } else { @@ -122,7 +125,7 @@ std::set<DicomTag> ignoreTagLength; std::auto_ptr<DicomValue> value(FromDcmtkBridge::ConvertLeafElement (*element, DicomToJsonFlags_None, - 0, encoding, ignoreTagLength)); + 0, encoding, hasCodeExtensions, ignoreTagLength)); // WARNING: Also modify "DatabaseLookup::IsMatch()" if modifying this code if (value.get() == NULL || @@ -197,15 +200,19 @@ bool HierarchicalMatcher::Match(ParsedDicomFile& dicom) const { + bool hasCodeExtensions; + Encoding encoding = dicom.DetectEncoding(hasCodeExtensions); + return MatchInternal(*dicom.GetDcmtkObject().getDataset(), - dicom.GetEncoding()); + encoding, hasCodeExtensions); } bool HierarchicalMatcher::MatchInternal(DcmItem& item, - Encoding encoding) const + Encoding encoding, + bool hasCodeExtensions) const { - if (!flatConstraints_.IsMatch(item, encoding)) + if (!flatConstraints_.IsMatch(item, encoding, hasCodeExtensions)) { return false; } @@ -228,7 +235,7 @@ for (unsigned long i = 0; i < sequence->card(); i++) { - if (it->second->MatchInternal(*sequence->getItem(i), encoding)) + if (it->second->MatchInternal(*sequence->getItem(i), encoding, hasCodeExtensions)) { match = true; break; @@ -247,7 +254,8 @@ DcmDataset* HierarchicalMatcher::ExtractInternal(DcmItem& source, - Encoding encoding) const + Encoding encoding, + bool hasCodeExtensions) const { std::auto_ptr<DcmDataset> target(new DcmDataset); @@ -283,13 +291,13 @@ { cloned->append(new DcmItem(*sequence->getItem(i))); } - else if (it->second->MatchInternal(*sequence->getItem(i), encoding)) // TODO Might be optimized + else if (it->second->MatchInternal(*sequence->getItem(i), encoding, hasCodeExtensions)) // TODO Might be optimized { // It is necessary to encapsulate the child dataset into a // "DcmItem" object before it can be included in a // sequence. Otherwise, "dciodvfy" reports an error "Bad // tag in sequence - Expecting Item or Sequence Delimiter." - std::auto_ptr<DcmDataset> child(it->second->ExtractInternal(*sequence->getItem(i), encoding)); + std::auto_ptr<DcmDataset> child(it->second->ExtractInternal(*sequence->getItem(i), encoding, hasCodeExtensions)); cloned->append(new DcmItem(*child)); } } @@ -304,11 +312,14 @@ ParsedDicomFile* HierarchicalMatcher::Extract(ParsedDicomFile& dicom) const { + bool hasCodeExtensions; + Encoding encoding = dicom.DetectEncoding(hasCodeExtensions); + std::auto_ptr<DcmDataset> dataset(ExtractInternal(*dicom.GetDcmtkObject().getDataset(), - dicom.GetEncoding())); + encoding, hasCodeExtensions)); std::auto_ptr<ParsedDicomFile> result(new ParsedDicomFile(*dataset)); - result->SetEncoding(dicom.GetEncoding()); + result->SetEncoding(encoding); return result.release(); }
--- a/OrthancServer/Search/HierarchicalMatcher.h Wed Feb 13 13:01:21 2019 +0000 +++ b/OrthancServer/Search/HierarchicalMatcher.h Sat Feb 16 10:29:26 2019 +0000 @@ -51,20 +51,24 @@ void Setup(DcmItem& query, bool caseSensitivePN, - Encoding encoding); + Encoding encoding, + bool hasCodeExtensions); HierarchicalMatcher(DcmItem& query, bool caseSensitivePN, - Encoding encoding) + Encoding encoding, + bool hasCodeExtensions) { - Setup(query, caseSensitivePN, encoding); + Setup(query, caseSensitivePN, encoding, hasCodeExtensions); } bool MatchInternal(DcmItem& dicom, - Encoding encoding) const; + Encoding encoding, + bool hasCodeExtensions) const; DcmDataset* ExtractInternal(DcmItem& dicom, - Encoding encoding) const; + Encoding encoding, + bool hasCodeExtensions) const; public: HierarchicalMatcher(ParsedDicomFile& query);
--- a/OrthancServer/ServerJobs/ArchiveJob.cpp Wed Feb 13 13:01:21 2019 +0000 +++ b/OrthancServer/ServerJobs/ArchiveJob.cpp Sat Feb 16 10:29:26 2019 +0000 @@ -943,7 +943,8 @@ synchronousTarget_.unique()) { LOG(WARNING) << "A client has disconnected while creating an archive"; - return JobStepResult::Failure(ErrorCode_NetworkProtocol); + return JobStepResult::Failure(ErrorCode_NetworkProtocol, + "A client has disconnected while creating an archive"); } if (writer_->GetStepsCount() == 0)
--- a/Plugins/Engine/PluginsJob.cpp Wed Feb 13 13:01:21 2019 +0000 +++ b/Plugins/Engine/PluginsJob.cpp Sat Feb 16 10:29:26 2019 +0000 @@ -88,7 +88,7 @@ return JobStepResult::Success(); case OrthancPluginJobStepStatus_Failure: - return JobStepResult::Failure(ErrorCode_Plugin); + return JobStepResult::Failure(ErrorCode_Plugin, NULL); case OrthancPluginJobStepStatus_Continue: return JobStepResult::Continue();
--- a/Plugins/Include/orthanc/OrthancCPlugin.h Wed Feb 13 13:01:21 2019 +0000 +++ b/Plugins/Include/orthanc/OrthancCPlugin.h Sat Feb 16 10:29:26 2019 +0000 @@ -5868,7 +5868,7 @@ * @param context The Orthanc plugin context, as received by OrthancPluginInitialize(). * @param callback The main callback. * @param getMoveSize Callback to read the number of C-Move suboperations. - * @param applyMove Callback to apply one C-Move suboperations. + * @param applyMove Callback to apply one C-Move suboperation. * @param freeMove Callback to free the C-Move driver. * @return 0 if success, other value if error. * @ingroup DicomCallbacks
--- a/Plugins/Samples/Common/OrthancPluginCppWrapper.cpp Wed Feb 13 13:01:21 2019 +0000 +++ b/Plugins/Samples/Common/OrthancPluginCppWrapper.cpp Sat Feb 16 10:29:26 2019 +0000 @@ -891,7 +891,7 @@ } - void OrthancImage::CheckImageAvailable() + void OrthancImage::CheckImageAvailable() const { if (image_ == NULL) { @@ -985,42 +985,42 @@ } - OrthancPluginPixelFormat OrthancImage::GetPixelFormat() + OrthancPluginPixelFormat OrthancImage::GetPixelFormat() const { CheckImageAvailable(); return OrthancPluginGetImagePixelFormat(GetGlobalContext(), image_); } - unsigned int OrthancImage::GetWidth() + unsigned int OrthancImage::GetWidth() const { CheckImageAvailable(); return OrthancPluginGetImageWidth(GetGlobalContext(), image_); } - unsigned int OrthancImage::GetHeight() + unsigned int OrthancImage::GetHeight() const { CheckImageAvailable(); return OrthancPluginGetImageHeight(GetGlobalContext(), image_); } - unsigned int OrthancImage::GetPitch() + unsigned int OrthancImage::GetPitch() const { CheckImageAvailable(); return OrthancPluginGetImagePitch(GetGlobalContext(), image_); } - const void* OrthancImage::GetBuffer() + const void* OrthancImage::GetBuffer() const { CheckImageAvailable(); return OrthancPluginGetImageBuffer(GetGlobalContext(), image_); } - void OrthancImage::CompressPngImage(MemoryBuffer& target) + void OrthancImage::CompressPngImage(MemoryBuffer& target) const { CheckImageAvailable(); @@ -1033,7 +1033,7 @@ void OrthancImage::CompressJpegImage(MemoryBuffer& target, - uint8_t quality) + uint8_t quality) const { CheckImageAvailable(); @@ -1045,7 +1045,7 @@ } - void OrthancImage::AnswerPngImage(OrthancPluginRestOutput* output) + void OrthancImage::AnswerPngImage(OrthancPluginRestOutput* output) const { CheckImageAvailable(); OrthancPluginCompressAndAnswerPngImage(GetGlobalContext(), output, GetPixelFormat(), @@ -1054,7 +1054,7 @@ void OrthancImage::AnswerJpegImage(OrthancPluginRestOutput* output, - uint8_t quality) + uint8_t quality) const { CheckImageAvailable(); OrthancPluginCompressAndAnswerJpegImage(GetGlobalContext(), output, GetPixelFormat(),
--- a/Plugins/Samples/Common/OrthancPluginCppWrapper.h Wed Feb 13 13:01:21 2019 +0000 +++ b/Plugins/Samples/Common/OrthancPluginCppWrapper.h Sat Feb 16 10:29:26 2019 +0000 @@ -335,7 +335,7 @@ void Clear(); - void CheckImageAvailable(); + void CheckImageAvailable() const; public: OrthancImage(); @@ -368,30 +368,30 @@ size_t size, unsigned int frame); - OrthancPluginPixelFormat GetPixelFormat(); + OrthancPluginPixelFormat GetPixelFormat() const; - unsigned int GetWidth(); + unsigned int GetWidth() const; - unsigned int GetHeight(); + unsigned int GetHeight() const; - unsigned int GetPitch(); + unsigned int GetPitch() const; - const void* GetBuffer(); + const void* GetBuffer() const; const OrthancPluginImage* GetObject() const { return image_; } - void CompressPngImage(MemoryBuffer& target); + void CompressPngImage(MemoryBuffer& target) const; void CompressJpegImage(MemoryBuffer& target, - uint8_t quality); + uint8_t quality) const; - void AnswerPngImage(OrthancPluginRestOutput* output); + void AnswerPngImage(OrthancPluginRestOutput* output) const; void AnswerJpegImage(OrthancPluginRestOutput* output, - uint8_t quality); + uint8_t quality) const; };
--- a/Resources/CMake/BoostConfiguration.cmake Wed Feb 13 13:01:21 2019 +0000 +++ b/Resources/CMake/BoostConfiguration.cmake Sat Feb 16 10:29:26 2019 +0000 @@ -228,6 +228,18 @@ if (NOT ENABLE_LOCALE) message("boost::locale is disabled") else() + set(BOOST_ICU_SOURCES + ${BOOST_SOURCES_DIR}/libs/locale/src/icu/boundary.cpp + ${BOOST_SOURCES_DIR}/libs/locale/src/icu/codecvt.cpp + ${BOOST_SOURCES_DIR}/libs/locale/src/icu/collator.cpp + ${BOOST_SOURCES_DIR}/libs/locale/src/icu/conversion.cpp + ${BOOST_SOURCES_DIR}/libs/locale/src/icu/date_time.cpp + ${BOOST_SOURCES_DIR}/libs/locale/src/icu/formatter.cpp + ${BOOST_SOURCES_DIR}/libs/locale/src/icu/icu_backend.cpp + ${BOOST_SOURCES_DIR}/libs/locale/src/icu/numeric.cpp + ${BOOST_SOURCES_DIR}/libs/locale/src/icu/time_zone.cpp + ) + list(APPEND BOOST_SOURCES ${BOOST_SOURCES_DIR}/libs/locale/src/encoding/codepage.cpp ${BOOST_SOURCES_DIR}/libs/locale/src/shared/generator.cpp @@ -246,6 +258,11 @@ if (CMAKE_SYSTEM_NAME STREQUAL "OpenBSD" OR CMAKE_SYSTEM_VERSION STREQUAL "LinuxStandardBase") + add_definitions( + -DBOOST_LOCALE_NO_WINAPI_BACKEND=1 + -DBOOST_LOCALE_NO_POSIX_BACKEND=1 + ) + list(APPEND BOOST_SOURCES ${BOOST_SOURCES_DIR}/libs/locale/src/std/codecvt.cpp ${BOOST_SOURCES_DIR}/libs/locale/src/std/collate.cpp @@ -254,12 +271,16 @@ ${BOOST_SOURCES_DIR}/libs/locale/src/std/std_backend.cpp ) - add_definitions( - -DBOOST_LOCALE_WITH_ICONV=1 - -DBOOST_LOCALE_NO_WINAPI_BACKEND=1 - -DBOOST_LOCALE_NO_POSIX_BACKEND=1 - ) - + if (BOOST_LOCALE_BACKEND STREQUAL "gcc" OR + BOOST_LOCALE_BACKEND STREQUAL "libiconv") + add_definitions(-DBOOST_LOCALE_WITH_ICONV=1) + elseif (BOOST_LOCALE_BACKEND STREQUAL "icu") + add_definitions(-DBOOST_LOCALE_WITH_ICU=1) + list(APPEND BOOST_SOURCES ${BOOST_ICU_SOURCES}) + else() + message(FATAL_ERROR "Unsupported value for BOOST_LOCALE_BACKEND: ${BOOST_LOCALE_BACKEND}") + endif() + elseif (CMAKE_SYSTEM_NAME STREQUAL "Linux" OR CMAKE_SYSTEM_NAME STREQUAL "Darwin" OR CMAKE_SYSTEM_NAME STREQUAL "FreeBSD" OR @@ -268,6 +289,11 @@ CMAKE_SYSTEM_NAME STREQUAL "NaCl32" OR CMAKE_SYSTEM_NAME STREQUAL "NaCl64" OR CMAKE_SYSTEM_NAME STREQUAL "Emscripten") # For WebAssembly or asm.js + add_definitions( + -DBOOST_LOCALE_NO_WINAPI_BACKEND=1 + -DBOOST_LOCALE_NO_STD_BACKEND=1 + ) + list(APPEND BOOST_SOURCES ${BOOST_SOURCES_DIR}/libs/locale/src/posix/codecvt.cpp ${BOOST_SOURCES_DIR}/libs/locale/src/posix/collate.cpp @@ -276,13 +302,25 @@ ${BOOST_SOURCES_DIR}/libs/locale/src/posix/posix_backend.cpp ) + if (CMAKE_SYSTEM_NAME STREQUAL "Emscripten" OR + BOOST_LOCALE_BACKEND STREQUAL "gcc" OR + BOOST_LOCALE_BACKEND STREQUAL "libiconv") + # In WebAssembly or asm.js, we rely on the version of iconv + # that is shipped with the stdlib + add_definitions(-DBOOST_LOCALE_WITH_ICONV=1) + elseif (BOOST_LOCALE_BACKEND STREQUAL "icu") + add_definitions(-DBOOST_LOCALE_WITH_ICU=1) + list(APPEND BOOST_SOURCES ${BOOST_ICU_SOURCES}) + else() + message(FATAL_ERROR "Unsupported value for BOOST_LOCALE_BACKEND: ${BOOST_LOCALE_BACKEND}") + endif() + + elseif (CMAKE_SYSTEM_NAME STREQUAL "Windows") add_definitions( - -DBOOST_LOCALE_WITH_ICONV=1 - -DBOOST_LOCALE_NO_WINAPI_BACKEND=1 + -DBOOST_LOCALE_NO_POSIX_BACKEND=1 -DBOOST_LOCALE_NO_STD_BACKEND=1 ) - - elseif (CMAKE_SYSTEM_NAME STREQUAL "Windows") + list(APPEND BOOST_SOURCES ${BOOST_SOURCES_DIR}/libs/locale/src/win32/collate.cpp ${BOOST_SOURCES_DIR}/libs/locale/src/win32/converter.cpp @@ -291,21 +329,22 @@ ${BOOST_SOURCES_DIR}/libs/locale/src/win32/win_backend.cpp ) - add_definitions( - -DBOOST_LOCALE_NO_POSIX_BACKEND=1 - -DBOOST_LOCALE_NO_STD_BACKEND=1 - ) + # Starting with release 0.8.2, Orthanc statically links against + # libiconv on Windows. Indeed, the "WCONV" library of Windows XP + # seems not to support properly several codepages (notably + # "Latin3", "Hebrew", and "Arabic"). Set "BOOST_LOCALE_BACKEND" + # to "wconv" to use WCONV anyway. - # Starting with release 0.8.2, Orthanc statically links against - # libiconv, even on Windows. Indeed, the "WCONV" library of - # Windows XP seems not to support properly several codepages - # (notably "Latin3", "Hebrew", and "Arabic"). Set - # "USE_BOOST_ICONV" to "OFF" to use WCONV anyway. - - if (USE_BOOST_ICONV) + if (BOOST_LOCALE_BACKEND STREQUAL "libiconv") add_definitions(-DBOOST_LOCALE_WITH_ICONV=1) + elseif (BOOST_LOCALE_BACKEND STREQUAL "icu") + add_definitions(-DBOOST_LOCALE_WITH_ICU=1) + list(APPEND BOOST_SOURCES ${BOOST_ICU_SOURCES}) + elseif (BOOST_LOCALE_BACKEND STREQUAL "wconv") + message("Using Window's wconv") + add_definitions(-DBOOST_LOCALE_WITH_WCONV=1) else() - add_definitions(-DBOOST_LOCALE_WITH_WCONV=1) + message(FATAL_ERROR "Unsupported value for BOOST_LOCALE_BACKEND on Windows: ${BOOST_LOCALE_BACKEND}") endif() else()
--- a/Resources/CMake/Compiler.cmake Wed Feb 13 13:01:21 2019 +0000 +++ b/Resources/CMake/Compiler.cmake Sat Feb 16 10:29:26 2019 +0000 @@ -36,8 +36,14 @@ string(REGEX REPLACE "/MDd" "/MTd" ${flag_var} "${${flag_var}}") endforeach(flag_var) - # Add /Zm256 compiler option to Visual Studio to fix PCH errors - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /Zm256") + if (BOOST_LOCALE_BACKEND STREQUAL "icu") + # If compiling icu, the heap space must be further increased: + # "icudt58l_dat.c(1638339): fatal error C1060: compiler is out of heap space" + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /Zm512") + else() + # Add /Zm256 compiler option to Visual Studio to fix PCH errors + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /Zm256") + endif() add_definitions( -D_CRT_SECURE_NO_WARNINGS=1
--- a/Resources/CMake/DownloadPackage.cmake Wed Feb 13 13:01:21 2019 +0000 +++ b/Resources/CMake/DownloadPackage.cmake Sat Feb 16 10:29:26 2019 +0000 @@ -57,45 +57,64 @@ if (${TAR_EXECUTABLE} MATCHES "TAR_EXECUTABLE-NOTFOUND") message(FATAL_ERROR "Please install the 'tar' package") endif() + + find_program(GUNZIP_EXECUTABLE gunzip) + if (${GUNZIP_EXECUTABLE} MATCHES "GUNZIP_EXECUTABLE-NOTFOUND") + message(FATAL_ERROR "Please install the 'gzip' package") + endif() endif() +macro(DownloadFile MD5 Url) + GetUrlFilename(TMP_FILENAME "${Url}") + + set(TMP_PATH "${CMAKE_SOURCE_DIR}/ThirdPartyDownloads/${TMP_FILENAME}") + if (NOT EXISTS "${TMP_PATH}") + message("Downloading ${Url}") + + # This fixes issue 6: "I think cmake shouldn't download the + # packages which are not in the system, it should stop and let + # user know." + # https://code.google.com/p/orthanc/issues/detail?id=6 + if (NOT STATIC_BUILD AND NOT ALLOW_DOWNLOADS) + message(FATAL_ERROR "CMake is not allowed to download from Internet. Please set the ALLOW_DOWNLOADS option to ON") + endif() + + if ("${MD5}" STREQUAL "no-check") + message(WARNING "Not checking the MD5 of: ${Url}") + file(DOWNLOAD "${Url}" "${TMP_PATH}" + SHOW_PROGRESS TIMEOUT 300 INACTIVITY_TIMEOUT 60 + STATUS Failure) + else() + file(DOWNLOAD "${Url}" "${TMP_PATH}" + SHOW_PROGRESS TIMEOUT 300 INACTIVITY_TIMEOUT 60 + EXPECTED_MD5 "${MD5}" STATUS Failure) + endif() + + list(GET Failure 0 Status) + if (NOT Status EQUAL 0) + message(FATAL_ERROR "Cannot download file: ${Url}") + endif() + + else() + message("Using local copy of ${Url}") + + if ("${MD5}" STREQUAL "no-check") + message(WARNING "Not checking the MD5 of: ${Url}") + else() + file(MD5 ${TMP_PATH} ActualMD5) + if (NOT "${ActualMD5}" STREQUAL "${MD5}") + message(FATAL_ERROR "The MD5 hash of a previously download file is invalid: ${TMP_PATH}") + endif() + endif() + endif() +endmacro() + + macro(DownloadPackage MD5 Url TargetDirectory) if (NOT IS_DIRECTORY "${TargetDirectory}") - GetUrlFilename(TMP_FILENAME "${Url}") - - set(TMP_PATH "${CMAKE_SOURCE_DIR}/ThirdPartyDownloads/${TMP_FILENAME}") - if (NOT EXISTS "${TMP_PATH}") - message("Downloading ${Url}") - - # This fixes issue 6: "I think cmake shouldn't download the - # packages which are not in the system, it should stop and let - # user know." - # https://code.google.com/p/orthanc/issues/detail?id=6 - if (NOT STATIC_BUILD AND NOT ALLOW_DOWNLOADS) - message(FATAL_ERROR "CMake is not allowed to download from Internet. Please set the ALLOW_DOWNLOADS option to ON") - endif() - - if ("${MD5}" STREQUAL "no-check") - message(WARNING "Not checking the MD5 of: ${Url}") - file(DOWNLOAD "${Url}" "${TMP_PATH}" SHOW_PROGRESS TIMEOUT 300 INACTIVITY_TIMEOUT 60) - else() - file(DOWNLOAD "${Url}" "${TMP_PATH}" SHOW_PROGRESS TIMEOUT 300 INACTIVITY_TIMEOUT 60 EXPECTED_MD5 "${MD5}") - endif() - - else() - message("Using local copy of ${Url}") - - if ("${MD5}" STREQUAL "no-check") - message(WARNING "Not checking the MD5 of: ${Url}") - else() - file(MD5 ${TMP_PATH} ActualMD5) - if (NOT "${ActualMD5}" STREQUAL "${MD5}") - message(FATAL_ERROR "The MD5 hash of a previously download file is invalid: ${TMP_PATH}") - endif() - endif() - endif() - + DownloadFile("${MD5}" "${Url}") + GetUrlExtension(TMP_EXTENSION "${Url}") #message(${TMP_EXTENSION}) message("Uncompressing ${TMP_FILENAME}") @@ -140,7 +159,7 @@ OUTPUT_QUIET ) else() - message(FATAL_ERROR "Support your platform here") + message(FATAL_ERROR "Unsupported package extension: ${TMP_EXTENSION}") endif() else() @@ -170,7 +189,7 @@ RESULT_VARIABLE Failure ) else() - message(FATAL_ERROR "Unknown package format.") + message(FATAL_ERROR "Unsupported package extension: ${TMP_EXTENSION}") endif() endif() @@ -183,3 +202,58 @@ endif() endif() endmacro() + + + +macro(DownloadCompressedFile MD5 Url TargetFile) + message(${MD5}) + message(${Url}) + message(${TargetFile}) + if (NOT EXISTS "${TargetFile}") + DownloadFile("${MD5}" "${Url}") + + GetUrlExtension(TMP_EXTENSION "${Url}") + #message(${TMP_EXTENSION}) + message("Uncompressing ${TMP_FILENAME}") + + if ("${CMAKE_HOST_SYSTEM_NAME}" STREQUAL "Windows") + # How to silently extract files using 7-zip + # http://superuser.com/questions/331148/7zip-command-line-extract-silently-quietly + + if ("${TMP_EXTENSION}" STREQUAL "gz") + execute_process( + COMMAND ${ZIP_EXECUTABLE} e -y ${TMP_PATH} + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + RESULT_VARIABLE Failure + OUTPUT_QUIET + ) + + if (Failure) + message(FATAL_ERROR "Error while running the uncompression tool") + endif() + + else() + message(FATAL_ERROR "Unsupported file extension: ${TMP_EXTENSION}") + endif() + + else() + if ("${TMP_EXTENSION}" STREQUAL "gz") + execute_process( + COMMAND sh -c "${GUNZIP_EXECUTABLE} -c ${TMP_PATH}" + OUTPUT_FILE "${TargetFile}" + RESULT_VARIABLE Failure + ) + else() + message(FATAL_ERROR "Unsupported file extension: ${TMP_EXTENSION}") + endif() + endif() + + if (Failure) + message(FATAL_ERROR "Error while running the uncompression tool") + endif() + + if (NOT EXISTS "${TargetFile}") + message(FATAL_ERROR "The file was not uncompressed at the proper location. Check the CMake instructions.") + endif() + endif() +endmacro()
--- a/Resources/CMake/LibIconvConfiguration.cmake Wed Feb 13 13:01:21 2019 +0000 +++ b/Resources/CMake/LibIconvConfiguration.cmake Sat Feb 16 10:29:26 2019 +0000 @@ -1,98 +1,90 @@ -if (NOT ENABLE_LOCALE) - message("Support for locales is disabled") - -elseif (NOT USE_BOOST_ICONV) - message("Not using libiconv") +message("Using libiconv") -else() - message("Using libiconv") +if (STATIC_BUILD OR NOT USE_SYSTEM_LIBICONV) + set(LIBICONV_SOURCES_DIR ${CMAKE_BINARY_DIR}/libiconv-1.15) + set(LIBICONV_URL "http://orthanc.osimis.io/ThirdPartyDownloads/libiconv-1.15.tar.gz") + set(LIBICONV_MD5 "ace8b5f2db42f7b3b3057585e80d9808") - if (STATIC_BUILD OR NOT USE_SYSTEM_LIBICONV) - set(LIBICONV_SOURCES_DIR ${CMAKE_BINARY_DIR}/libiconv-1.15) - set(LIBICONV_URL "http://orthanc.osimis.io/ThirdPartyDownloads/libiconv-1.15.tar.gz") - set(LIBICONV_MD5 "ace8b5f2db42f7b3b3057585e80d9808") - - DownloadPackage(${LIBICONV_MD5} ${LIBICONV_URL} "${LIBICONV_SOURCES_DIR}") + DownloadPackage(${LIBICONV_MD5} ${LIBICONV_URL} "${LIBICONV_SOURCES_DIR}") - # Disable the support of libiconv that is shipped by default with - # the C standard library on Linux. Setting this macro redirects - # calls from "iconv*()" to "libiconv*()" by defining macros in the - # C headers of "libiconv-1.15". - add_definitions(-DLIBICONV_PLUG=1) + # Disable the support of libiconv that is shipped by default with + # the C standard library on Linux. Setting this macro redirects + # calls from "iconv*()" to "libiconv*()" by defining macros in the + # C headers of "libiconv-1.15". + add_definitions(-DLIBICONV_PLUG=1) - # https://groups.google.com/d/msg/android-ndk/AS1nkxnk6m4/EQm09hD1tigJ - add_definitions( - -DBUILDING_LIBICONV=1 - -DIN_LIBRARY=1 - -DLIBDIR="" - -DICONV_CONST= - ) + # https://groups.google.com/d/msg/android-ndk/AS1nkxnk6m4/EQm09hD1tigJ + add_definitions( + -DBUILDING_LIBICONV=1 + -DIN_LIBRARY=1 + -DLIBDIR="" + -DICONV_CONST= + #-DENABLE_EXTRA=1 + ) - configure_file( - ${LIBICONV_SOURCES_DIR}/srclib/localcharset.h - ${LIBICONV_SOURCES_DIR}/include - COPYONLY) + configure_file( + ${LIBICONV_SOURCES_DIR}/srclib/localcharset.h + ${LIBICONV_SOURCES_DIR}/include + COPYONLY) - set(HAVE_VISIBILITY 0) - set(ICONV_CONST ${ICONV_CONST}) - set(USE_MBSTATE_T 1) - set(BROKEN_WCHAR_H 0) - set(EILSEQ) - set(HAVE_WCHAR_T 1) - configure_file( - ${LIBICONV_SOURCES_DIR}/include/iconv.h.build.in - ${LIBICONV_SOURCES_DIR}/include/iconv.h - ) - unset(HAVE_VISIBILITY) - unset(ICONV_CONST) - unset(USE_MBSTATE_T) - unset(BROKEN_WCHAR_H) - unset(EILSEQ) - unset(HAVE_WCHAR_T) + set(HAVE_VISIBILITY 0) + set(ICONV_CONST ${ICONV_CONST}) + set(USE_MBSTATE_T 1) + set(BROKEN_WCHAR_H 0) + set(EILSEQ) + set(HAVE_WCHAR_T 1) + configure_file( + ${LIBICONV_SOURCES_DIR}/include/iconv.h.build.in + ${LIBICONV_SOURCES_DIR}/include/iconv.h + ) + unset(HAVE_VISIBILITY) + unset(ICONV_CONST) + unset(USE_MBSTATE_T) + unset(BROKEN_WCHAR_H) + unset(EILSEQ) + unset(HAVE_WCHAR_T) - if (NOT EXISTS ${LIBICONV_SOURCES_DIR}/include/config.h) - # Create an empty "config.h" for libiconv - file(WRITE ${LIBICONV_SOURCES_DIR}/include/config.h "") - endif() + if (NOT EXISTS ${LIBICONV_SOURCES_DIR}/include/config.h) + # Create an empty "config.h" for libiconv + file(WRITE ${LIBICONV_SOURCES_DIR}/include/config.h "") + endif() - include_directories( - ${LIBICONV_SOURCES_DIR}/include - ) + include_directories( + ${LIBICONV_SOURCES_DIR}/include + ) - set(LIBICONV_SOURCES - ${LIBICONV_SOURCES_DIR}/lib/iconv.c - ${LIBICONV_SOURCES_DIR}/lib/relocatable.c - ${LIBICONV_SOURCES_DIR}/libcharset/lib/localcharset.c - ${LIBICONV_SOURCES_DIR}/libcharset/lib/relocatable.c - ) + set(LIBICONV_SOURCES + ${LIBICONV_SOURCES_DIR}/lib/iconv.c + ${LIBICONV_SOURCES_DIR}/lib/relocatable.c + ${LIBICONV_SOURCES_DIR}/libcharset/lib/localcharset.c + ${LIBICONV_SOURCES_DIR}/libcharset/lib/relocatable.c + ) - source_group(ThirdParty\\libiconv REGULAR_EXPRESSION ${LIBICONV_SOURCES_DIR}/.*) + source_group(ThirdParty\\libiconv REGULAR_EXPRESSION ${LIBICONV_SOURCES_DIR}/.*) - if (CMAKE_SYSTEM_NAME STREQUAL "Windows") - add_definitions(-DHAVE_WORKING_O_NOFOLLOW=0) - else() - add_definitions(-DHAVE_WORKING_O_NOFOLLOW=1) - endif() + if (CMAKE_SYSTEM_NAME STREQUAL "Windows") + add_definitions(-DHAVE_WORKING_O_NOFOLLOW=0) + else() + add_definitions(-DHAVE_WORKING_O_NOFOLLOW=1) + endif() - else() - CHECK_INCLUDE_FILE_CXX(iconv.h HAVE_ICONV_H) - if (NOT HAVE_ICONV_H) - message(FATAL_ERROR "Please install the libiconv-dev package") - endif() +else() + CHECK_INCLUDE_FILE_CXX(iconv.h HAVE_ICONV_H) + if (NOT HAVE_ICONV_H) + message(FATAL_ERROR "Please install the libiconv-dev package") + endif() - # Check whether the support for libiconv is bundled within the - # standard C library - CHECK_FUNCTION_EXISTS(iconv_open HAVE_ICONV_LIB) - if (NOT HAVE_ICONV_LIB) - # No builtin support for libiconv, try and find an external library. - # Open question: Does this make sense on any platform? - CHECK_LIBRARY_EXISTS(iconv iconv_open "" HAVE_ICONV_LIB_2) - if (NOT HAVE_ICONV_LIB_2) - message(FATAL_ERROR "Please install the libiconv-dev package") - else() - link_libraries(iconv) - endif() + # Check whether the support for libiconv is bundled within the + # standard C library + CHECK_FUNCTION_EXISTS(iconv_open HAVE_ICONV_LIB) + if (NOT HAVE_ICONV_LIB) + # No builtin support for libiconv, try and find an external library. + # Open question: Does this make sense on any platform? + CHECK_LIBRARY_EXISTS(iconv iconv_open "" HAVE_ICONV_LIB_2) + if (NOT HAVE_ICONV_LIB_2) + message(FATAL_ERROR "Please install the libiconv-dev package") + else() + link_libraries(iconv) endif() - endif() endif()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/CMake/LibIcuConfiguration.cmake Sat Feb 16 10:29:26 2019 +0000 @@ -0,0 +1,71 @@ + +# Check out: ../ThirdParty/icu/README.txt + +# http://userguide.icu-project.org/packaging +# http://userguide.icu-project.org/howtouseicu + +message("Using libicu") + +if (STATIC_BUILD OR NOT USE_SYSTEM_LIBICU) + include(${CMAKE_CURRENT_LIST_DIR}/../ThirdParty/icu/Version.cmake) + DownloadPackage(${LIBICU_MD5} ${LIBICU_URL} "${LIBICU_SOURCES_DIR}") + DownloadCompressedFile(${LIBICU_DATA_MD5} ${LIBICU_DATA_URL} ${LIBICU_DATA}) + + include_directories(BEFORE + ${LIBICU_SOURCES_DIR}/source/common + ${LIBICU_SOURCES_DIR}/source/i18n + ) + + set(LIBICU_SOURCES + ${CMAKE_BINARY_DIR}/${LIBICU_DATA} + ) + + aux_source_directory(${LIBICU_SOURCES_DIR}/source/common LIBICU_SOURCES) + aux_source_directory(${LIBICU_SOURCES_DIR}/source/i18n LIBICU_SOURCES) + + add_definitions( + #-DU_COMBINED_IMPLEMENTATION + #-DU_DEF_ICUDATA_ENTRY_POINT=icudt63l_dat + #-DU_LIB_SUFFIX_C_NAME=l + + #-DUCONFIG_NO_SERVICE=1 + -DU_COMMON_IMPLEMENTATION + -DU_ENABLE_DYLOAD=0 + -DU_HAVE_STD_STRING=1 + -DU_I18N_IMPLEMENTATION + -DU_IO_IMPLEMENTATION + -DU_STATIC_IMPLEMENTATION=1 + #-DU_CHARSET_IS_UTF8 + -DUNISTR_FROM_STRING_EXPLICIT= + ) + + set_source_files_properties( + ${CMAKE_BINARY_DIR}/${LIBICU_DATA} + PROPERTIES COMPILE_DEFINITIONS "char16_t=uint16_t" + ) + + if(CMAKE_SYSTEM_NAME STREQUAL "Windows") + set_source_files_properties( + ${LIBICU_SOURCES_DIR}/source/common/locmap.c + PROPERTIES COMPILE_DEFINITIONS "LOCALE_SNAME=0x0000005c" + ) + endif() + + source_group(ThirdParty\\libicu REGULAR_EXPRESSION ${LIBICU_SOURCES_DIR}/.*) + +else() + CHECK_INCLUDE_FILE_CXX(unicode/uvernum.h HAVE_ICU_H) + if (NOT HAVE_ICU_H) + message(FATAL_ERROR "Please install the libicu-dev package") + endif() + + find_library(LIBICU_PATH_1 NAMES icuuc) + find_library(LIBICU_PATH_2 NAMES icui18n) + + if (NOT LIBICU_PATH_1 OR + NOT LIBICU_PATH_2) + message(FATAL_ERROR "Please install the libicu-dev package") + else() + link_libraries(icuuc icui18n) + endif() +endif()
--- a/Resources/CMake/OrthancFrameworkConfiguration.cmake Wed Feb 13 13:01:21 2019 +0000 +++ b/Resources/CMake/OrthancFrameworkConfiguration.cmake Sat Feb 16 10:29:26 2019 +0000 @@ -94,7 +94,7 @@ endif() if (NOT ENABLE_LOCALE) - unset(USE_SYSTEM_LIBICONV CACHE) + unset(BOOST_LOCALE_BACKEND CACHE) add_definitions(-DORTHANC_ENABLE_LOCALE=0) endif() @@ -378,16 +378,25 @@ ## -## Locale support: libiconv +## Locale support ## if (ENABLE_LOCALE) if (CMAKE_SYSTEM_NAME STREQUAL "Emscripten") # In WebAssembly or asm.js, we rely on the version of iconv that # is shipped with the stdlib - unset(USE_BOOST_ICONV CACHE) + unset(BOOST_LOCALE_BACKEND CACHE) else() - include(${CMAKE_CURRENT_LIST_DIR}/LibIconvConfiguration.cmake) + if (BOOST_LOCALE_BACKEND STREQUAL "gcc") + elseif (BOOST_LOCALE_BACKEND STREQUAL "libiconv") + include(${CMAKE_CURRENT_LIST_DIR}/LibIconvConfiguration.cmake) + elseif (BOOST_LOCALE_BACKEND STREQUAL "icu") + include(${CMAKE_CURRENT_LIST_DIR}/LibIcuConfiguration.cmake) + elseif (BOOST_LOCALE_BACKEND STREQUAL "wconv") + message("Using Microsoft Window's wconv") + else() + message(FATAL_ERROR "Invalid value for BOOST_LOCALE_BACKEND: ${BOOST_LOCALE_BACKEND}") + endif() endif() add_definitions(-DORTHANC_ENABLE_LOCALE=1) @@ -610,6 +619,7 @@ ${CURL_SOURCES} ${JSONCPP_SOURCES} ${LIBICONV_SOURCES} + ${LIBICU_SOURCES} ${LIBJPEG_SOURCES} ${LIBP11_SOURCES} ${LIBPNG_SOURCES}
--- a/Resources/CMake/OrthancFrameworkParameters.cmake Wed Feb 13 13:01:21 2019 +0000 +++ b/Resources/CMake/OrthancFrameworkParameters.cmake Sat Feb 16 10:29:26 2019 +0000 @@ -43,6 +43,7 @@ set(USE_SYSTEM_GOOGLE_TEST ON CACHE BOOL "Use the system version of Google Test") set(USE_SYSTEM_JSONCPP ON CACHE BOOL "Use the system version of JsonCpp") set(USE_SYSTEM_LIBICONV ON CACHE BOOL "Use the system version of libiconv") +set(USE_SYSTEM_LIBICU ON CACHE BOOL "Use the system version of libicu") set(USE_SYSTEM_LIBJPEG ON CACHE BOOL "Use the system version of libjpeg") set(USE_SYSTEM_LIBP11 OFF CACHE BOOL "Use the system version of libp11 (PKCS#11 wrapper library)") set(USE_SYSTEM_LIBPNG ON CACHE BOOL "Use the system version of libpng") @@ -66,15 +67,15 @@ # Advanced and distribution-specific parameters set(USE_GOOGLE_TEST_DEBIAN_PACKAGE OFF CACHE BOOL "Use the sources of Google Test shipped with libgtest-dev (Debian only)") set(SYSTEM_MONGOOSE_USE_CALLBACKS ON CACHE BOOL "The system version of Mongoose uses callbacks (version >= 3.7)") -set(USE_BOOST_ICONV ON CACHE BOOL "Use iconv instead of wconv (Windows only)") +set(BOOST_LOCALE_BACKEND "libiconv" CACHE STRING "Back-end for locales that is used by Boost (can be \"gcc\", \"libiconv\", \"icu\", or \"wconv\" on Windows)") set(USE_PUGIXML ON CACHE BOOL "Use the Pugixml parser (turn off only for debug)") -set(USE_LEGACY_JSONCPP OFF CACHE BOOL "Use the old branch 0.x.y of JsonCpp, that does not require a C++11 compiler (for old versions of Visual Studio)") +set(USE_LEGACY_JSONCPP OFF CACHE BOOL "Use the old branch 0.x.y of JsonCpp, that does not require a C++11 compiler (for LSB and old versions of Visual Studio)") +set(USE_LEGACY_LIBICU OFF CACHE BOOL "Use icu icu4c-58_2, latest version not requiring a C++11 compiler (for LSB and old versions of Visual Studio)") mark_as_advanced(USE_GOOGLE_TEST_DEBIAN_PACKAGE) mark_as_advanced(SYSTEM_MONGOOSE_USE_CALLBACKS) -mark_as_advanced(USE_BOOST_ICONV) +mark_as_advanced(BOOST_LOCALE_BACKEND) mark_as_advanced(USE_PUGIXML) -mark_as_advanced(USE_LEGACY_JSONCPP) #####################################################################
--- a/Resources/Configuration.json Wed Feb 13 13:01:21 2019 +0000 +++ b/Resources/Configuration.json Sat Feb 16 10:29:26 2019 +0000 @@ -111,7 +111,8 @@ // C-Find requests (including worklists). The allowed values are // "Ascii", "Utf8", "Latin1", "Latin2", "Latin3", "Latin4", // "Latin5", "Cyrillic", "Windows1251", "Arabic", "Greek", "Hebrew", - // "Thai", "Japanese", and "Chinese". + // "Thai", "Japanese", "Chinese", "JapaneseKanji", "Korean", and + // "SimplifiedChinese". "DefaultEncoding" : "Latin1", // The transfer syntaxes that are accepted by Orthanc C-Store SCP
--- a/Resources/LinuxStandardBaseToolchain.cmake Wed Feb 13 13:01:21 2019 +0000 +++ b/Resources/LinuxStandardBaseToolchain.cmake Sat Feb 16 10:29:26 2019 +0000 @@ -1,4 +1,4 @@ -# LSB_CC=gcc-4.8 LSB_CXX=g++-4.8 cmake .. -DCMAKE_BUILD_TYPE=Debug -DCMAKE_TOOLCHAIN_FILE=../Resources/LinuxStandardBaseToolchain.cmake -DUSE_LEGACY_JSONCPP=ON +# LSB_CC=gcc-4.8 LSB_CXX=g++-4.8 cmake .. -DCMAKE_BUILD_TYPE=Debug -DCMAKE_TOOLCHAIN_FILE=../Resources/LinuxStandardBaseToolchain.cmake -DUSE_LEGACY_JSONCPP=ON -DUSE_LEGACY_LIBICU=ON -DBOOST_LOCALE_BACKEND=icu INCLUDE(CMakeForceCompiler)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/ThirdParty/icu/CMakeLists.txt Sat Feb 16 10:29:26 2019 +0000 @@ -0,0 +1,88 @@ +cmake_minimum_required(VERSION 2.8) +project(IcuCodeGeneration) + +set(USE_LEGACY_LIBICU OFF CACHE BOOL "Use icu icu4c-58_2, latest version not requiring a C++11 compiler (for LSB and old versions of Visual Studio)") + +if (NOT USE_LEGACY_LIBICU) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") +endif() + +include(${CMAKE_SOURCE_DIR}/../../CMake/Compiler.cmake) +include(${CMAKE_SOURCE_DIR}/../../CMake/DownloadPackage.cmake) +include(Version.cmake) + +set(SOURCE_DATA + "${LIBICU_SOURCES_DIR}/source/data/in/${LIBICU_DATA_VERSION}${LIBICU_SUFFIX}.dat") + +set(ALLOW_DOWNLOADS ON) +DownloadPackage(${LIBICU_MD5} ${LIBICU_URL} "${LIBICU_SOURCES_DIR}") + +include_directories( + ${LIBICU_SOURCES_DIR}/source/common + ${LIBICU_SOURCES_DIR}/source/i18n + ${LIBICU_SOURCES_DIR}/source/tools/toolutil/ + ) + +aux_source_directory(${LIBICU_SOURCES_DIR}/source/common LIBICU_SOURCES) +aux_source_directory(${LIBICU_SOURCES_DIR}/source/i18n LIBICU_SOURCES) +aux_source_directory(${LIBICU_SOURCES_DIR}/source/tools/toolutil LIBICU_SOURCES) + +if (USE_LEGACY_LIBICU) + list(APPEND LIBICU_SOURCES + ${LIBICU_SOURCES_DIR}/source/stubdata/stubdata.c + ) +else() + list(APPEND LIBICU_SOURCES + ${LIBICU_SOURCES_DIR}/source/stubdata/stubdata.cpp + ) + set_source_files_properties( + ${LIBICU_SOURCES_DIR}/source/tools/genccode/genccode.c + PROPERTIES COMPILE_DEFINITIONS "char16_t=uint16_t" + ) +endif() + + + +add_executable(IcuCodeGeneration + ${LIBICU_SOURCES_DIR}/source/tools/genccode/genccode.c + ${LIBICU_SOURCES} + ) + +configure_file(${SOURCE_DATA} + ${CMAKE_BINARY_DIR}/${LIBICU_DATA_VERSION}.dat + COPYONLY) + +add_custom_command( + OUTPUT ${CMAKE_BINARY_DIR}/${LIBICU_DATA} + COMMAND IcuCodeGeneration ${CMAKE_BINARY_DIR}/${LIBICU_DATA_VERSION}.dat + DEPENDS IcuCodeGeneration + ) + +message(${LIBICU_DATA}.gz) + + +# "--no-name" is necessary for 7-zip on Windows to behave similarly to gunzip +add_custom_command( + OUTPUT ${CMAKE_BINARY_DIR}/${LIBICU_DATA}.gz + COMMAND gzip ${CMAKE_BINARY_DIR}/${LIBICU_DATA_VERSION}_dat.c --no-name -c > ${CMAKE_BINARY_DIR}/${LIBICU_DATA}.gz + DEPENDS ${LIBICU_DATA} + ) + +add_custom_target(Final ALL DEPENDS ${CMAKE_BINARY_DIR}/${LIBICU_DATA}.gz) + +install( + FILES ${CMAKE_BINARY_DIR}/${LIBICU_DATA}.gz + DESTINATION ${CMAKE_SOURCE_DIR}/../../../ThirdPartyDownloads + ) + +add_definitions( + #-DU_COMBINED_IMPLEMENTATION + -DUCONFIG_NO_SERVICE=1 + -DU_COMMON_IMPLEMENTATION + -DU_ENABLE_DYLOAD=0 + -DU_HAVE_STD_STRING=1 + -DU_I18N_IMPLEMENTATION + -DU_IO_IMPLEMENTATION + -DU_STATIC_IMPLEMENTATION=1 + -DU_TOOLUTIL_IMPLEMENTATION + )
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/ThirdParty/icu/README.txt Sat Feb 16 10:29:26 2019 +0000 @@ -0,0 +1,36 @@ +Generating ICU data file +======================== + +This folder generates the "icudtXXX_dat.c" file that contains the +resources internal to ICU. + +IMPORTANT: Since ICU 59, C++11 is mandatory, making it incompatible +with Linux Standard Base (LSB) SDK. The option +"-DUSE_LEGACY_LIBICU=ON" will use the latest version of ICU that does +not use C++11 (58-2). + + +Usage +----- + +Newest release of icu: + +$ cmake .. -G Ninja && ninja install + +Legacy version for LSB: + +$ cmake .. -G Ninja -DUSE_LEGACY_LIBICU=ON && ninja install + +Legacy version using LSB: + +$ LSB_CC=gcc-4.8 LSB_CXX=g++-4.8 cmake .. -G Ninja \ + -DCMAKE_TOOLCHAIN_FILE=../../../LinuxStandardBaseToolchain.cmake \ + -DUSE_LEGACY_LIBICU=ON +$ ninja install + + +Result +------ + +The resulting files are placed in the "ThirdPartyDownloads" folder at +the root of the Orthanc repository (next to the main "CMakeLists.txt").
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/ThirdParty/icu/Version.cmake Sat Feb 16 10:29:26 2019 +0000 @@ -0,0 +1,32 @@ +# NB: Orthanc assume that the platform is of ASCII-family, not of +# EBCDIC-family. A "e" suffix would be needed on EBCDIC. Look for +# macro "U_ICUDATA_TYPE_LETTER" in the source code of icu for more +# information. + +include(TestBigEndian) +TEST_BIG_ENDIAN(IS_BIG_ENDIAN) +if(IS_BIG_ENDIAN) + set(LIBICU_SUFFIX "b") +else() + set(LIBICU_SUFFIX "l") +endif() + +set(LIBICU_BASE_URL "http://orthanc.osimis.io/ThirdPartyDownloads") + +if (USE_LEGACY_LIBICU) + # This is the last version of icu that compiles with C++11 + # support. It can be used for Linux Standard Base and Visual Studio 2008. + set(LIBICU_URL "${LIBICU_BASE_URL}/icu4c-58_2-src.tgz") + set(LIBICU_MD5 "fac212b32b7ec7ab007a12dff1f3aea1") + set(LIBICU_DATA_VERSION "icudt58") + set(LIBICU_DATA_MD5 "ce2c7791ab637898553c121633155fb6") +else() + set(LIBICU_URL "${LIBICU_BASE_URL}/icu4c-63_1-src.tgz") + set(LIBICU_MD5 "9e40f6055294284df958200e308bce50") + set(LIBICU_DATA_VERSION "icudt63") + set(LIBICU_DATA_MD5 "92b5c73a1accd8ecf8c20c89bc6925a9") +endif() + +set(LIBICU_SOURCES_DIR ${CMAKE_BINARY_DIR}/icu) +set(LIBICU_DATA "${LIBICU_DATA_VERSION}${LIBICU_SUFFIX}_dat.c") +set(LIBICU_DATA_URL "${LIBICU_BASE_URL}/${LIBICU_DATA}.gz")
--- a/TODO Wed Feb 13 13:01:21 2019 +0000 +++ b/TODO Sat Feb 16 10:29:26 2019 +0000 @@ -2,9 +2,6 @@ === Orthanc Roadmap === ======================= -The wishlist from Orthanc users is available on Trello: -https://trello.com/b/gcn33tDM/orthanc-wishlist - ======= General @@ -82,6 +79,15 @@ * Support extended association: https://groups.google.com/d/msg/orthanc-users/xD4d3mpc6ms/srF7E2goAAAJ +--------- +Encodings +--------- + +* Support multiple specific character sets (cf. "SCSH32" in orthanc-tests) + - http://dicom.nema.org/medical/dicom/current/output/chtml/part03/sect_C.12.html#sect_C.12.1.1.2 + - Japanese test: http://dicom.nema.org/MEDICAL/dicom/2017c/output/chtml/part05/sect_H.3.2.html +* Support Supplementary Kanji set (ISO 2022 IR 159) + ======= Plugins
--- a/UnitTestsSources/DicomMapTests.cpp Wed Feb 13 13:01:21 2019 +0000 +++ b/UnitTestsSources/DicomMapTests.cpp Sat Feb 16 10:29:26 2019 +0000 @@ -425,7 +425,7 @@ const unsigned char raw[] = { 0x63, 0x72, 0xe2, 0x6e, 0x65 }; std::string latin1((char*) &raw[0], sizeof(raw) / sizeof(char)); - std::string utf8 = Toolbox::ConvertToUtf8(latin1, Encoding_Latin1); + std::string utf8 = Toolbox::ConvertToUtf8(latin1, Encoding_Latin1, false); ParsedDicomFile dicom(false); dicom.SetEncoding(Encoding_Latin1);
--- a/UnitTestsSources/FromDcmtkTests.cpp Wed Feb 13 13:01:21 2019 +0000 +++ b/UnitTestsSources/FromDcmtkTests.cpp Sat Feb 16 10:29:26 2019 +0000 @@ -36,6 +36,7 @@ #include "../Core/DicomNetworking/DicomFindAnswers.h" #include "../Core/DicomParsing/DicomModification.h" +#include "../Core/DicomParsing/DicomWebJsonVisitor.h" #include "../Core/DicomParsing/FromDcmtkBridge.h" #include "../Core/DicomParsing/Internals/DicomImageDecoder.h" #include "../Core/DicomParsing/ToDcmtkBridge.h" @@ -53,6 +54,11 @@ #include <dcmtk/dcmdata/dcelem.h> #include <dcmtk/dcmdata/dcdeftag.h> +#include <boost/algorithm/string/predicate.hpp> + +#if ORTHANC_ENABLE_PUGIXML == 1 +# include <pugixml.hpp> +#endif using namespace Orthanc; @@ -217,7 +223,7 @@ { std::string source(testEncodingsEncoded[i]); std::string expected(testEncodingsExpected[i]); - std::string s = Toolbox::ConvertToUtf8(source, testEncodings[i]); + std::string s = Toolbox::ConvertToUtf8(source, testEncodings[i], false); //std::cout << EnumerationToString(testEncodings[i]) << std::endl; EXPECT_EQ(expected, s); } @@ -260,9 +266,10 @@ 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_FALSE(GetDicomEncoding(e, "ISO 2022 IR 87")); //ASSERT_EQ(Encoding_JapaneseKanji, e); + 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_FALSE(GetDicomEncoding(e, "ISO 2022 IR 149")); //ASSERT_EQ(Encoding_Korean, 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); @@ -282,7 +289,7 @@ ParsedDicomFile f(true); f.SetEncoding(testEncodings[i]); - std::string s = Toolbox::ConvertToUtf8(testEncodingsEncoded[i], testEncodings[i]); + std::string s = Toolbox::ConvertToUtf8(testEncodingsEncoded[i], testEncodings[i], false); f.Insert(DICOM_TAG_PATIENT_NAME, s, false); f.SaveToMemoryBuffer(dicom); } @@ -293,7 +300,9 @@ if (testEncodings[i] != Encoding_Ascii) { - ASSERT_EQ(testEncodings[i], g.GetEncoding()); + bool hasCodeExtensions; + ASSERT_EQ(testEncodings[i], g.DetectEncoding(hasCodeExtensions)); + ASSERT_FALSE(hasCodeExtensions); } std::string tag; @@ -405,16 +414,16 @@ ignoreTagLength.insert(DICOM_TAG_PATIENT_ID); FromDcmtkBridge::ElementToJson(b, *element, DicomToJsonFormat_Short, - DicomToJsonFlags_Default, 0, Encoding_Ascii, ignoreTagLength); + DicomToJsonFlags_Default, 0, Encoding_Ascii, false, ignoreTagLength); ASSERT_TRUE(b.isMember("0010,0010")); ASSERT_EQ("Hello", b["0010,0010"].asString()); FromDcmtkBridge::ElementToJson(b, *element, DicomToJsonFormat_Short, - DicomToJsonFlags_Default, 3, Encoding_Ascii, ignoreTagLength); + DicomToJsonFlags_Default, 3, Encoding_Ascii, false, ignoreTagLength); ASSERT_TRUE(b["0010,0010"].isNull()); // "Hello" has more than 3 characters FromDcmtkBridge::ElementToJson(b, *element, DicomToJsonFormat_Full, - DicomToJsonFlags_Default, 3, Encoding_Ascii, ignoreTagLength); + DicomToJsonFlags_Default, 3, Encoding_Ascii, false, ignoreTagLength); ASSERT_TRUE(b["0010,0010"].isObject()); ASSERT_EQ("PatientName", b["0010,0010"]["Name"].asString()); ASSERT_EQ("TooLong", b["0010,0010"]["Type"].asString()); @@ -422,7 +431,7 @@ ignoreTagLength.insert(DICOM_TAG_PATIENT_NAME); FromDcmtkBridge::ElementToJson(b, *element, DicomToJsonFormat_Short, - DicomToJsonFlags_Default, 3, Encoding_Ascii, ignoreTagLength); + DicomToJsonFlags_Default, 3, Encoding_Ascii, false, ignoreTagLength); ASSERT_EQ("Hello", b["0010,0010"].asString()); } @@ -448,7 +457,7 @@ Json::Value b; std::set<DicomTag> ignoreTagLength; FromDcmtkBridge::ElementToJson(b, *element, DicomToJsonFormat_Short, - DicomToJsonFlags_Default, 0, Encoding_Ascii, ignoreTagLength); + DicomToJsonFlags_Default, 0, Encoding_Ascii, false, ignoreTagLength); ASSERT_EQ("Hello", b["0010,0010"].asString()); } @@ -461,7 +470,7 @@ Json::Value b; std::set<DicomTag> ignoreTagLength; FromDcmtkBridge::ElementToJson(b, *element, DicomToJsonFormat_Short, - DicomToJsonFlags_Default, 0, Encoding_Ascii, ignoreTagLength); + DicomToJsonFlags_Default, 0, Encoding_Ascii, false, ignoreTagLength); ASSERT_EQ(Json::arrayValue, b["0008,1110"].type()); ASSERT_EQ(2u, b["0008,1110"].size()); @@ -480,7 +489,7 @@ Json::Value b; std::set<DicomTag> ignoreTagLength; FromDcmtkBridge::ElementToJson(b, *element, DicomToJsonFormat_Full, - DicomToJsonFlags_Default, 0, Encoding_Ascii, ignoreTagLength); + DicomToJsonFlags_Default, 0, Encoding_Ascii, false, ignoreTagLength); Json::Value c; ServerToolbox::SimplifyTags(c, b, DicomToJsonFormat_Human); @@ -599,10 +608,12 @@ if (testEncodings[i] != Encoding_Ascii) { - ASSERT_EQ(testEncodings[i], f.GetEncoding()); + bool hasCodeExtensions; + ASSERT_EQ(testEncodings[i], f.DetectEncoding(hasCodeExtensions)); + ASSERT_FALSE(hasCodeExtensions); } - Json::Value s = Toolbox::ConvertToUtf8(testEncodingsEncoded[i], testEncodings[i]); + Json::Value s = Toolbox::ConvertToUtf8(testEncodingsEncoded[i], testEncodings[i], false); f.Replace(DICOM_TAG_PATIENT_NAME, s, false, DicomReplaceMode_InsertIfAbsent); Json::Value v; @@ -1161,7 +1172,7 @@ // 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]); + std::string decoded = Toolbox::ConvertToUtf8(encoded, testEncodings[i], false); ASSERT_STREQ(testEncodingsExpected[i], decoded.c_str()); if (testEncodings[i] != Encoding_Chinese) @@ -1169,7 +1180,7 @@ // A specific source string is used in "EncodingTests.py" to // test against Chinese, it is normal that it does not correspond to UTF8 - std::string encoded = Toolbox::ConvertToUtf8(Toolbox::ConvertFromUtf8(utf8, testEncodings[i]), testEncodings[i]); + std::string encoded = Toolbox::ConvertToUtf8(Toolbox::ConvertFromUtf8(utf8, testEncodings[i]), testEncodings[i], false); ASSERT_STREQ(testEncodingsExpected[i], encoded.c_str()); } } @@ -1227,7 +1238,9 @@ std::string tag; ParsedDicomFile dicom(m, Encoding_Utf8); - ASSERT_EQ(Encoding_Utf8, dicom.GetEncoding()); + 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]); @@ -1240,7 +1253,8 @@ dicom.ChangeEncoding(testEncodings[i]); - ASSERT_EQ(testEncodings[i], dicom.GetEncoding()); + ASSERT_EQ(testEncodings[i], dicom.DetectEncoding(hasCodeExtensions)); + ASSERT_FALSE(hasCodeExtensions); const char* c = NULL; ASSERT_TRUE(dicom.GetDcmtkObject().getDataset()->findAndGetString(DCM_PatientName, c).good()); @@ -1275,7 +1289,10 @@ m.SetValue(DICOM_TAG_PATIENT_NAME, "HELLO", false); ParsedDicomFile d(m, Encoding_Latin3 /* default encoding */); - ASSERT_EQ(Encoding_Latin3, d.GetEncoding()); + + bool hasCodeExtensions; + ASSERT_EQ(Encoding_Latin3, d.DetectEncoding(hasCodeExtensions)); + ASSERT_FALSE(hasCodeExtensions); } { @@ -1285,7 +1302,10 @@ m.SetValue(DICOM_TAG_PATIENT_NAME, "HELLO", false); ParsedDicomFile d(m, Encoding_Latin3 /* default encoding */); - ASSERT_EQ(Encoding_Japanese, d.GetEncoding()); + + bool hasCodeExtensions; + ASSERT_EQ(Encoding_Japanese, d.DetectEncoding(hasCodeExtensions)); + ASSERT_FALSE(hasCodeExtensions); } { @@ -1314,6 +1334,528 @@ m.SetValue(DICOM_TAG_PATIENT_NAME, "HELLO", false); ParsedDicomFile d(m, Encoding_Latin3 /* default encoding */); - ASSERT_EQ(Encoding_Latin3, d.GetEncoding()); + + bool hasCodeExtensions; + ASSERT_EQ(Encoding_Latin3, d.DetectEncoding(hasCodeExtensions)); + ASSERT_FALSE(hasCodeExtensions); } } + + + +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; +} + + + +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 = doc.select_single_node("//NativeDicomModel/DicomAttribute[@tag=\"00080005\"]/Value"); + ASSERT_STREQ("ISO_IR 192", node.node().text().as_string()); + + node = doc.select_single_node("//NativeDicomModel/DicomAttribute[@tag=\"00080005\"]"); + ASSERT_STREQ("CS", node.node().attribute("vr").value()); + + node = doc.select_single_node("//NativeDicomModel/DicomAttribute[@tag=\"00100010\"]"); + ASSERT_STREQ("PN", node.node().attribute("vr").value()); + + node = doc.select_single_node("//NativeDicomModel/DicomAttribute[@tag=\"00100010\"]/PersonName/Alphabetic/FamilyName"); + ASSERT_STREQ("Hong", node.node().text().as_string()); + + node = doc.select_single_node("//NativeDicomModel/DicomAttribute[@tag=\"00100010\"]/PersonName/Alphabetic/GivenName"); + ASSERT_STREQ("Gildong", node.node().text().as_string()); + + node = doc.select_single_node("//NativeDicomModel/DicomAttribute[@tag=\"00100010\"]/PersonName/Ideographic/FamilyName"); + ASSERT_EQ(utf8.substr(13, 3), node.node().text().as_string()); + + node = doc.select_single_node("//NativeDicomModel/DicomAttribute[@tag=\"00100010\"]/PersonName/Ideographic/GivenName"); + ASSERT_EQ(utf8.substr(17, 6), node.node().text().as_string()); + + node = doc.select_single_node("//NativeDicomModel/DicomAttribute[@tag=\"00100010\"]/PersonName/Phonetic/FamilyName"); + ASSERT_EQ(utf8.substr(24, 3), node.node().text().as_string()); + + node = doc.select_single_node("//NativeDicomModel/DicomAttribute[@tag=\"00100010\"]/PersonName/Phonetic/GivenName"); + ASSERT_EQ(utf8.substr(28), node.node().text().as_string()); +#endif +} + + + +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 = doc.select_single_node("//NativeDicomModel/DicomAttribute[@tag=\"00080005\"]/Value"); + ASSERT_STREQ("ISO_IR 192", node.node().text().as_string()); + + node = doc.select_single_node("//NativeDicomModel/DicomAttribute[@tag=\"00080005\"]"); + ASSERT_STREQ("CS", node.node().attribute("vr").value()); + + node = doc.select_single_node("//NativeDicomModel/DicomAttribute[@tag=\"00100010\"]"); + ASSERT_STREQ("PN", node.node().attribute("vr").value()); + + node = doc.select_single_node("//NativeDicomModel/DicomAttribute[@tag=\"00100010\"]/PersonName/Alphabetic/FamilyName"); + ASSERT_STREQ("Yamada", node.node().text().as_string()); + + node = doc.select_single_node("//NativeDicomModel/DicomAttribute[@tag=\"00100010\"]/PersonName/Alphabetic/GivenName"); + ASSERT_STREQ("Tarou", node.node().text().as_string()); + + node = doc.select_single_node("//NativeDicomModel/DicomAttribute[@tag=\"00100010\"]/PersonName/Ideographic/FamilyName"); + ASSERT_EQ(utf8.substr(13, 6), node.node().text().as_string()); + + node = doc.select_single_node("//NativeDicomModel/DicomAttribute[@tag=\"00100010\"]/PersonName/Ideographic/GivenName"); + ASSERT_EQ(utf8.substr(20, 6), node.node().text().as_string()); + + node = doc.select_single_node("//NativeDicomModel/DicomAttribute[@tag=\"00100010\"]/PersonName/Phonetic/FamilyName"); + ASSERT_EQ(utf8.substr(27, 9), node.node().text().as_string()); + + node = doc.select_single_node("//NativeDicomModel/DicomAttribute[@tag=\"00100010\"]/PersonName/Phonetic/GivenName"); + ASSERT_EQ(utf8.substr(37), node.node().text().as_string()); +#endif +} + + + +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()); +} +
--- a/UnitTestsSources/MultiThreadingTests.cpp Wed Feb 13 13:01:21 2019 +0000 +++ b/UnitTestsSources/MultiThreadingTests.cpp Sat Feb 16 10:29:26 2019 +0000 @@ -105,7 +105,7 @@ { if (fails_) { - return JobStepResult::Failure(ErrorCode_ParameterOutOfRange); + return JobStepResult::Failure(ErrorCode_ParameterOutOfRange, NULL); } else if (count_ == steps_ - 1) { @@ -724,12 +724,12 @@ engine.Start(); Json::Value content = Json::nullValue; - ASSERT_TRUE(engine.GetRegistry().SubmitAndWait(content, new DummyJob(), rand() % 10)); + engine.GetRegistry().SubmitAndWait(content, new DummyJob(), rand() % 10); ASSERT_EQ(Json::objectValue, content.type()); ASSERT_EQ("world", content["hello"].asString()); content = Json::nullValue; - ASSERT_FALSE(engine.GetRegistry().SubmitAndWait(content, new DummyJob(true), rand() % 10)); + ASSERT_THROW(engine.GetRegistry().SubmitAndWait(content, new DummyJob(true), rand() % 10), OrthancException); ASSERT_EQ(Json::nullValue, content.type()); engine.Stop();
--- a/UnitTestsSources/UnitTestsMain.cpp Wed Feb 13 13:01:21 2019 +0000 +++ b/UnitTestsSources/UnitTestsMain.cpp Sat Feb 16 10:29:26 2019 +0000 @@ -450,7 +450,7 @@ ASSERT_EQ("&abc", Toolbox::ConvertToAscii(s)); // Open in Emacs, then save with UTF-8 encoding, then "hexdump -C" - std::string utf8 = Toolbox::ConvertToUtf8(s, Encoding_Latin1); + std::string utf8 = Toolbox::ConvertToUtf8(s, Encoding_Latin1, false); ASSERT_EQ(15u, utf8.size()); ASSERT_EQ(0xc3, static_cast<unsigned char>(utf8[0])); ASSERT_EQ(0xa0, static_cast<unsigned char>(utf8[1])); @@ -477,8 +477,8 @@ std::string s((char*) &latin1[0], sizeof(latin1) / sizeof(char)); - ASSERT_EQ(s, Toolbox::ConvertFromUtf8(Toolbox::ConvertToUtf8(s, Encoding_Latin1), Encoding_Latin1)); - ASSERT_EQ("cre", Toolbox::ConvertToUtf8(s, Encoding_Utf8)); + ASSERT_EQ(s, Toolbox::ConvertFromUtf8(Toolbox::ConvertToUtf8(s, Encoding_Latin1, false), Encoding_Latin1)); + ASSERT_EQ("cre", Toolbox::ConvertToUtf8(s, Encoding_Utf8, false)); } @@ -690,6 +690,9 @@ ASSERT_EQ(Encoding_Japanese, StringToEncoding(EnumerationToString(Encoding_Japanese))); ASSERT_EQ(Encoding_Chinese, StringToEncoding(EnumerationToString(Encoding_Chinese))); ASSERT_EQ(Encoding_Thai, StringToEncoding(EnumerationToString(Encoding_Thai))); + ASSERT_EQ(Encoding_Korean, StringToEncoding(EnumerationToString(Encoding_Korean))); + ASSERT_EQ(Encoding_JapaneseKanji, StringToEncoding(EnumerationToString(Encoding_JapaneseKanji))); + ASSERT_EQ(Encoding_SimplifiedChinese, StringToEncoding(EnumerationToString(Encoding_SimplifiedChinese))); ASSERT_EQ(ResourceType_Patient, StringToResourceType(EnumerationToString(ResourceType_Patient))); ASSERT_EQ(ResourceType_Study, StringToResourceType(EnumerationToString(ResourceType_Study)));
--- a/UnitTestsSources/VersionsTests.cpp Wed Feb 13 13:01:21 2019 +0000 +++ b/UnitTestsSources/VersionsTests.cpp Sat Feb 16 10:29:26 2019 +0000 @@ -44,7 +44,10 @@ #include <sqlite3.h> #include <lua.h> #include <jpeglib.h> -#include <iconv.h> + +#if BUILDING_LIBICONV == 1 +# include <iconv.h> +#endif #if ORTHANC_ENABLE_SSL == 1 # include <openssl/opensslv.h> @@ -148,12 +151,15 @@ ASSERT_STREQ("Lua 5.3.5", LUA_RELEASE); } + +#if BUILDING_LIBICONV == 1 TEST(Version, LibIconvStatic) { static const int major = 1; static const int minor = 15; ASSERT_EQ((major << 8) + minor, _LIBICONV_VERSION); } +#endif #if ORTHANC_ENABLE_SSL == 1