Skip to content
Commits on Source (5)
repo: 3959d33612ccaadc0d4d707227fbed09ac35e5fe
node: 30418468410719d5301689330d1d282ff61e9751
branch: Orthanc-1.5.4
node: 010d5e6edabed784325c43ce81ec140a6a9ccf84
branch: Orthanc-1.5.5
latesttag: dcmtk-3.6.1
latesttagdistance: 717
changessincelatesttag: 829
latesttagdistance: 806
changessincelatesttag: 923
......@@ -4,3 +4,4 @@ CMakeLists.txt.user
*.cpp.orig
*.h.orig
.vs/
*~
......@@ -45,16 +45,20 @@ before_script:
-DCMAKE_BUILD_TYPE=Debug "-DDCMTK_LIBRARIES=CharLS;dcmjpls;wrap;oflog"
-DALLOW_DOWNLOADS=ON -DUSE_SYSTEM_BOOST=OFF -DUSE_SYSTEM_CIVETWEB=OFF -DUSE_SYSTEM_JSONCPP=OFF
-DUSE_SYSTEM_GOOGLE_LOG=OFF -DUSE_SYSTEM_PUGIXML=OFF -DUSE_GOOGLE_TEST_DEBIAN_PACKAGE=ON
-DBOOST_LOCALE_BACKEND=icu
..; fi
- if [ $TRAVIS_OS_NAME == linux -a $TRAVIS_MINGW == ON ]; then cmake
-DCMAKE_BUILD_TYPE=Debug -DSTATIC_BUILD=ON -DSTANDALONE_BUILD=ON -DALLOW_DOWNLOADS=ON
-DCMAKE_TOOLCHAIN_FILE=Resources/MinGWToolchain.cmake -DUSE_DCMTK_360=ON -DUSE_LEGACY_JSONCPP=ON
-DCMAKE_TOOLCHAIN_FILE=Resources/MinGWToolchain.cmake -DDCMTK_STATIC_VERSION=3.6.0
-DUSE_LEGACY_JSONCPP=ON -DBOOST_LOCALE_BACKEND=libiconv
..; fi
- if [ $TRAVIS_OS_NAME == osx ]; then cmake
-DCMAKE_BUILD_TYPE=Debug -DSTATIC_BUILD=ON -DSTANDALONE_BUILD=ON -DALLOW_DOWNLOADS=ON
-DBOOST_LOCALE_BACKEND=icu
..; fi
script: make && if [ $TRAVIS_MINGW == OFF ]; then ./UnitTests; fi
# Old releases of MinGW are not compatible with GoogleTest 1.8.1
script: make Orthanc ServeFolders ModalityWorklists && if [ $TRAVIS_MINGW == OFF ]; then make UnitTests && ./UnitTests; fi
#script: cp ../README Orthanc
#deploy:
......
......@@ -17,6 +17,7 @@ set(ENABLE_GOOGLE_TEST ON)
set(ENABLE_JPEG ON)
set(ENABLE_LOCALE ON)
set(ENABLE_LUA ON)
set(ENABLE_OPENSSL_ENGINES ON)
set(ENABLE_PNG ON)
set(ENABLE_PUGIXML ON)
set(ENABLE_SQLITE ON)
......@@ -148,7 +149,7 @@ endif()
if (CMAKE_COMPILER_IS_GNUCXX
AND NOT CMAKE_CROSSCOMPILING
AND USE_DCMTK_360)
AND DCMTK_STATIC_VERSION STREQUAL "3.6.0")
# Add the "-pedantic" flag only on the Orthanc sources, and only if
# cross-compiling DCMTK 3.6.0
set(ORTHANC_ALL_SOURCES
......@@ -292,6 +293,10 @@ add_library(CoreLibrary
${AUTOGENERATED_SOURCES}
)
if (LIBICU_LIBRARIES)
target_link_libraries(CoreLibrary ${LIBICU_LIBRARIES})
endif()
#####################################################################
## Build the Orthanc server
......@@ -337,6 +342,33 @@ target_link_libraries(UnitTests
)
#####################################################################
## 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
)
if (LIBICU_LIBRARIES)
target_link_libraries(ThirdPartyPlugins ${LIBICU_LIBRARIES})
endif()
# 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 +391,12 @@ if (ENABLE_PLUGINS AND BUILD_SERVE_FOLDERS)
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 +434,12 @@ if (ENABLE_PLUGINS AND BUILD_MODALITY_WORKLISTS)
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}
......
......@@ -119,7 +119,10 @@ namespace Orthanc
void DicomFindAnswers::Add(const DicomMap& map)
{
AddAnswerInternal(new ParsedDicomFile(map, encoding_));
// We use the permissive mode to be tolerant wrt. invalid DICOM
// files that contain some tags with out-of-range values (such
// tags are removed from the answers)
AddAnswerInternal(new ParsedDicomFile(map, encoding_, true /* permissive */));
}
......
......@@ -165,7 +165,8 @@ namespace Orthanc
static void Check(const OFCondition& cond,
const std::string& aet)
const std::string& aet,
const std::string& command)
{
if (cond.bad())
{
......@@ -207,7 +208,8 @@ namespace Orthanc
}
throw OrthancException(ErrorCode_NetworkProtocol,
"DicomUserConnection to AET \"" + aet + "\": " + info);
"DicomUserConnection - " + command +
" to AET \"" + aet + "\": " + info);
}
}
......@@ -235,13 +237,15 @@ namespace Orthanc
const std::string& aet)
{
Check(ASC_addPresentationContext(params, presentationContextId,
sopClass.c_str(), asPreferred, 1), aet);
sopClass.c_str(), asPreferred, 1),
aet, "initializing");
presentationContextId += 2;
if (asFallback.size() > 0)
{
Check(ASC_addPresentationContext(params, presentationContextId,
sopClass.c_str(), &asFallback[0], asFallback.size()), aet);
sopClass.c_str(), &asFallback[0], asFallback.size()),
aet, "initializing");
presentationContextId += 2;
}
}
......@@ -308,7 +312,8 @@ namespace Orthanc
uint16_t moveOriginatorID)
{
DcmFileFormat dcmff;
Check(dcmff.read(is, EXS_Unknown, EGL_noChange, DCM_MaxReadLength), connection.remoteAet_);
Check(dcmff.read(is, EXS_Unknown, EGL_noChange, DCM_MaxReadLength),
connection.remoteAet_, "C-STORE");
// Determine the storage SOP class UID for this instance
static const DcmTagKey DCM_SOP_CLASS_UID(0x0008, 0x0016);
......@@ -380,7 +385,9 @@ namespace Orthanc
if (!DU_findSOPClassAndInstanceInDataSet(dcmff.getDataset(), sopClass, sopInstance))
#endif
{
throw OrthancException(ErrorCode_NoSopClassOrInstance);
throw OrthancException(ErrorCode_NoSopClassOrInstance,
"Unable to determine the SOP class/instance for C-STORE with AET " +
connection.remoteAet_);
}
// Figure out which of the accepted presentation contexts should be used
......@@ -388,9 +395,11 @@ namespace Orthanc
if (presID == 0)
{
const char *modalityName = dcmSOPClassUIDToModality(sopClass);
if (!modalityName) modalityName = dcmFindNameOfUID(sopClass);
if (!modalityName) modalityName = "unknown SOP class";
throw OrthancException(ErrorCode_NoPresentationContext);
if (modalityName == NULL) modalityName = dcmFindNameOfUID(sopClass);
if (modalityName == NULL) modalityName = "unknown SOP class";
throw OrthancException(ErrorCode_NoPresentationContext,
"Unable to determine the accepted presentation contexts for C-STORE with AET " +
connection.remoteAet_ + " (" + std::string(modalityName) + ")");
}
// Prepare the transmission of data
......@@ -418,7 +427,8 @@ namespace Orthanc
Check(DIMSE_storeUser(assoc_, presID, &request,
NULL, dcmff.getDataset(), /*progressCallback*/ NULL, NULL,
/*opt_blockMode*/ DIMSE_BLOCKING, /*opt_dimse_timeout*/ dimseTimeout_,
&rsp, &statusDetail, NULL), connection.remoteAet_);
&rsp, &statusDetail, NULL),
connection.remoteAet_, "C-STORE");
if (statusDetail != NULL)
{
......@@ -609,7 +619,8 @@ namespace Orthanc
int presID = ASC_findAcceptedPresentationContextID(association, sopClass);
if (presID == 0)
{
throw OrthancException(ErrorCode_DicomFindUnavailable);
throw OrthancException(ErrorCode_DicomFindUnavailable,
"Remote AET is " + remoteAet);
}
T_DIMSE_C_FindRQ request;
......@@ -640,7 +651,7 @@ namespace Orthanc
delete statusDetail;
}
Check(cond, remoteAet);
Check(cond, remoteAet, "C-FIND");
}
......@@ -813,7 +824,8 @@ namespace Orthanc
int presID = ASC_findAcceptedPresentationContextID(pimpl_->assoc_, sopClass);
if (presID == 0)
{
throw OrthancException(ErrorCode_DicomMoveUnavailable);
throw OrthancException(ErrorCode_DicomMoveUnavailable,
"Remote AET is " + remoteAet_);
}
T_DIMSE_C_MoveRQ request;
......@@ -844,7 +856,7 @@ namespace Orthanc
delete responseIdentifiers;
}
Check(cond, remoteAet_);
Check(cond, remoteAet_, "C-MOVE");
}
......@@ -983,7 +995,8 @@ namespace Orthanc
{
if (host.size() > HOST_NAME_MAX - 10)
{
throw OrthancException(ErrorCode_ParameterOutOfRange);
throw OrthancException(ErrorCode_ParameterOutOfRange,
"Invalid host name (too long): " + host);
}
Close();
......@@ -1013,11 +1026,12 @@ namespace Orthanc
<< GetRemoteHost() << ":" << GetRemotePort()
<< " (manufacturer: " << EnumerationToString(GetRemoteManufacturer()) << ")";
Check(ASC_initializeNetwork(NET_REQUESTOR, 0, /*opt_acse_timeout*/ pimpl_->acseTimeout_, &pimpl_->net_), remoteAet_);
Check(ASC_createAssociationParameters(&pimpl_->params_, /*opt_maxReceivePDULength*/ ASC_DEFAULTMAXPDU), remoteAet_);
Check(ASC_initializeNetwork(NET_REQUESTOR, 0, /*opt_acse_timeout*/ pimpl_->acseTimeout_, &pimpl_->net_), remoteAet_, "connecting");
Check(ASC_createAssociationParameters(&pimpl_->params_, /*opt_maxReceivePDULength*/ ASC_DEFAULTMAXPDU), remoteAet_, "connecting");
// Set this application's title and the called application's title in the params
Check(ASC_setAPTitles(pimpl_->params_, localAet_.c_str(), remoteAet_.c_str(), NULL), remoteAet_);
Check(ASC_setAPTitles(pimpl_->params_, localAet_.c_str(), remoteAet_.c_str(), NULL),
remoteAet_, "connecting");
// Set the network addresses of the local and remote entities
char localHost[HOST_NAME_MAX];
......@@ -1032,19 +1046,24 @@ namespace Orthanc
#endif
(remoteHostAndPort, HOST_NAME_MAX - 1, "%s:%d", remoteHost_.c_str(), remotePort_);
Check(ASC_setPresentationAddresses(pimpl_->params_, localHost, remoteHostAndPort), remoteAet_);
Check(ASC_setPresentationAddresses(pimpl_->params_, localHost, remoteHostAndPort),
remoteAet_, "connecting");
// Set various options
Check(ASC_setTransportLayerType(pimpl_->params_, /*opt_secureConnection*/ false), remoteAet_);
Check(ASC_setTransportLayerType(pimpl_->params_, /*opt_secureConnection*/ false),
remoteAet_, "connecting");
SetupPresentationContexts(preferredTransferSyntax_);
// Do the association
Check(ASC_requestAssociation(pimpl_->net_, pimpl_->params_, &pimpl_->assoc_), remoteAet_);
Check(ASC_requestAssociation(pimpl_->net_, pimpl_->params_, &pimpl_->assoc_),
remoteAet_, "connecting");
if (ASC_countAcceptedPresentationContexts(pimpl_->params_) == 0)
{
throw OrthancException(ErrorCode_NoPresentationContext);
throw OrthancException(ErrorCode_NoPresentationContext,
"Unable to negotiate a presentation context with AET " +
remoteAet_);
}
}
......@@ -1118,7 +1137,7 @@ namespace Orthanc
Check(DIMSE_echoUser(pimpl_->assoc_, pimpl_->assoc_->nextMsgID++,
/*opt_blockMode*/ DIMSE_BLOCKING,
/*opt_dimse_timeout*/ pimpl_->dimseTimeout_,
&status, NULL), remoteAet_);
&status, NULL), remoteAet_, "C-ECHO");
return status == STATUS_Success;
}
......
......@@ -263,6 +263,10 @@ namespace Orthanc
UID_BasicTextSRStorage,
UID_BasicVoiceAudioWaveformStorage,
UID_BlendingSoftcopyPresentationStateStorage,
#if DCMTK_VERSION_NUMBER >= 361
UID_BreastProjectionXRayImageStorageForProcessing,
UID_BreastProjectionXRayImageStorageForPresentation,
#endif
UID_BreastTomosynthesisImageStorage,
UID_CardiacElectrophysiologyWaveformStorage,
UID_ChestCADSRStorage,
......
......@@ -161,6 +161,7 @@ namespace Orthanc
static bool GetUtf8TagValue(std::string& result,
DcmItem& source,
Encoding encoding,
bool hasCodeExtensions,
const DcmTagKey& key)
{
DcmElement* element = NULL;
......@@ -174,7 +175,7 @@ namespace Orthanc
{
if (s != NULL)
{
result = Toolbox::ConvertToUtf8(s, encoding);
result = Toolbox::ConvertToUtf8(s, encoding, hasCodeExtensions);
}
return true;
......@@ -202,6 +203,7 @@ namespace Orthanc
static bool CopyString(DcmDirectoryRecord& target,
DcmDataset& source,
Encoding encoding,
bool hasCodeExtensions,
const DcmTagKey& key,
bool optional,
bool copyEmpty)
......@@ -214,7 +216,7 @@ namespace Orthanc
}
std::string value;
bool found = GetUtf8TagValue(value, source, encoding, key);
bool found = GetUtf8TagValue(value, source, encoding, hasCodeExtensions, key);
if (!found)
{
......@@ -231,33 +233,37 @@ namespace Orthanc
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 @@ namespace Orthanc
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 @@ namespace Orthanc
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 @@ namespace Orthanc
/* 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 @@ namespace Orthanc
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 @@ namespace Orthanc
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 @@ namespace Orthanc
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 @@ namespace Orthanc
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());
......
......@@ -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 @@ static const char* const KEY_VR = "vr";
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 @@ namespace Orthanc
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 @@ namespace Orthanc
}
if (vr == "PN")
{
if (content[KEY_VALUE][j].isMember(KEY_ALPHABETIC) &&
content[KEY_VALUE][j][KEY_ALPHABETIC].type() == Json::stringValue)
{
std::vector<std::string> tokens;
Toolbox::TokenizeString(tokens, content[KEY_VALUE][j][KEY_ALPHABETIC].asString(), '^');
bool hasAlphabetic = (content[KEY_VALUE][j].isMember(KEY_ALPHABETIC) &&
content[KEY_VALUE][j][KEY_ALPHABETIC].type() == Json::stringValue);
pugi::xml_node child = node.append_child("PersonName");
child.append_attribute("number").set_value(number.c_str());
bool hasIdeographic = (content[KEY_VALUE][j].isMember(KEY_IDEOGRAPHIC) &&
content[KEY_VALUE][j][KEY_IDEOGRAPHIC].type() == Json::stringValue);
pugi::xml_node name = child.append_child(KEY_ALPHABETIC);
bool hasPhonetic = (content[KEY_VALUE][j].isMember(KEY_PHONETIC) &&
content[KEY_VALUE][j][KEY_PHONETIC].type() == Json::stringValue);
if (tokens.size() >= 1)
if (hasAlphabetic ||
hasIdeographic ||
hasPhonetic)
{
name.append_child("FamilyName").text() = tokens[0].c_str();
}
pugi::xml_node child = node.append_child("PersonName");
child.append_attribute("number").set_value(number.c_str());
if (tokens.size() >= 2)
if (hasAlphabetic)
{
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 @@ namespace Orthanc
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;
}
......
......@@ -141,22 +141,6 @@ namespace Orthanc
}
#endif
}
#else
static void LoadExternalDictionary(DcmDataDictionary& dictionary,
const std::string& directory,
const std::string& filename)
{
boost::filesystem::path p = directory;
p = p / filename;
LOG(WARNING) << "Loading the external DICOM dictionary " << p;
if (!dictionary.loadDictionary(p.string().c_str()))
{
throw OrthancException(ErrorCode_InternalError);
}
}
#endif
......@@ -274,28 +258,37 @@ DCMTK_TO_CTYPE_CONVERTER(DcmtkToFloat64Converter, Float64, DcmFloatingPointDoubl
LOG(INFO) << "The dictionary of private tags has not been loaded";
}
#elif defined(__linux__) || defined(__FreeBSD_kernel__) || defined(__FreeBSD__) || defined(__OpenBSD__)
std::string path = DCMTK_DICTIONARY_DIR;
#else
std::vector<std::string> dictionaries;
const char* env = std::getenv(DCM_DICT_ENVIRONMENT_VARIABLE);
if (env != NULL)
{
path = std::string(env);
// This mimics the behavior of DCMTK:
// https://support.dcmtk.org/docs/file_envvars.html
#if defined(_WIN32)
Toolbox::TokenizeString(dictionaries, std::string(env), ';');
#else
Toolbox::TokenizeString(dictionaries, std::string(env), ':');
#endif
}
else
{
boost::filesystem::path base = DCMTK_DICTIONARY_DIR;
dictionaries.push_back((base / "dicom.dic").string());
dictionaries.push_back((base / "private.dic").string());
}
LoadExternalDictionary(*locker, path, "dicom.dic");
for (size_t i = 0; i < dictionaries.size(); i++)
{
LOG(WARNING) << "Loading external DICOM dictionary: \"" << dictionaries[i] << "\"";
if (loadPrivateDictionary)
if (!locker->loadDictionary(dictionaries[i].c_str()))
{
LoadExternalDictionary(*locker, path, "private.dic");
throw OrthancException(ErrorCode_InexistentFile);
}
else
{
LOG(INFO) << "The dictionary of private tags has not been loaded";
}
#else
#error Support your platform here
#endif
}
......@@ -414,37 +407,49 @@ DCMTK_TO_CTYPE_CONVERTER(DcmtkToFloat64Converter, Float64, DcmFloatingPointDoubl
}
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()));
std::vector<std::string> tokens;
Toolbox::TokenizeString(tokens, std::string(tmp.c_str()), '\\');
if (characterSet.empty())
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()))
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)";
encoding = Encoding_Ascii;
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;
}
......@@ -455,7 +460,8 @@ DCMTK_TO_CTYPE_CONVERTER(DcmtkToFloat64Converter, Float64, DcmFloatingPointDoubl
{
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 +472,7 @@ DCMTK_TO_CTYPE_CONVERTER(DcmtkToFloat64Converter, Float64, DcmFloatingPointDoubl
target.SetValue(element->getTag().getGTag(),
element->getTag().getETag(),
ConvertLeafElement(*element, DicomToJsonFlags_Default,
maxStringLength, encoding, ignoreTagLength));
maxStringLength, encoding, hasCodeExtensions, ignoreTagLength));
}
}
}
......@@ -488,6 +494,7 @@ DCMTK_TO_CTYPE_CONVERTER(DcmtkToFloat64Converter, Float64, DcmFloatingPointDoubl
DicomToJsonFlags flags,
unsigned int maxStringLength,
Encoding encoding,
bool hasCodeExtensions,
const std::set<DicomTag>& ignoreTagLength)
{
if (!element.isLeaf())
......@@ -507,7 +514,7 @@ DCMTK_TO_CTYPE_CONVERTER(DcmtkToFloat64Converter, Float64, DcmFloatingPointDoubl
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 +862,7 @@ DCMTK_TO_CTYPE_CONVERTER(DcmtkToFloat64Converter, Float64, DcmFloatingPointDoubl
DicomToJsonFlags flags,
unsigned int maxStringLength,
Encoding encoding,
bool hasCodeExtensions,
const std::set<DicomTag>& ignoreTagLength)
{
if (parent.type() == Json::nullValue)
......@@ -869,7 +877,7 @@ DCMTK_TO_CTYPE_CONVERTER(DcmtkToFloat64Converter, Float64, DcmFloatingPointDoubl
{
// 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 +902,7 @@ DCMTK_TO_CTYPE_CONVERTER(DcmtkToFloat64Converter, Float64, DcmFloatingPointDoubl
{
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 +914,7 @@ DCMTK_TO_CTYPE_CONVERTER(DcmtkToFloat64Converter, Float64, DcmFloatingPointDoubl
DicomToJsonFlags flags,
unsigned int maxStringLength,
Encoding encoding,
bool hasCodeExtensions,
const std::set<DicomTag>& ignoreTagLength)
{
assert(parent.type() == Json::objectValue);
......@@ -952,7 +961,7 @@ DCMTK_TO_CTYPE_CONVERTER(DcmtkToFloat64Converter, Float64, DcmFloatingPointDoubl
}
FromDcmtkBridge::ElementToJson(parent, *element, format, flags,
maxStringLength, encoding, ignoreTagLength);
maxStringLength, encoding, hasCodeExtensions, ignoreTagLength);
}
}
......@@ -965,10 +974,11 @@ DCMTK_TO_CTYPE_CONVERTER(DcmtkToFloat64Converter, Float64, DcmFloatingPointDoubl
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 +990,7 @@ DCMTK_TO_CTYPE_CONVERTER(DcmtkToFloat64Converter, Float64, DcmFloatingPointDoubl
{
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 +2043,7 @@ DCMTK_TO_CTYPE_CONVERTER(DcmtkToFloat64Converter, Float64, DcmFloatingPointDoubl
void FromDcmtkBridge::ChangeStringEncoding(DcmItem& dataset,
Encoding source,
bool hasSourceCodeExtensions,
Encoding target)
{
// Recursive exploration of a dataset to change the encoding of
......@@ -2055,7 +2066,7 @@ DCMTK_TO_CTYPE_CONVERTER(DcmtkToFloat64Converter, Float64, DcmFloatingPointDoubl
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 +2080,7 @@ DCMTK_TO_CTYPE_CONVERTER(DcmtkToFloat64Converter, Float64, DcmFloatingPointDoubl
for (unsigned long j = 0; j < sequence.card(); j++)
{
ChangeStringEncoding(*sequence.getItem(j), source, target);
ChangeStringEncoding(*sequence.getItem(j), source, hasSourceCodeExtensions, target);
}
}
}
......@@ -2192,13 +2203,15 @@ DCMTK_TO_CTYPE_CONVERTER(DcmtkToFloat64Converter, Float64, DcmFloatingPointDoubl
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 +2224,7 @@ DCMTK_TO_CTYPE_CONVERTER(DcmtkToFloat64Converter, Float64, DcmFloatingPointDoubl
}
else
{
ApplyVisitorToElement(*element, visitor, parentTags, parentIndexes, encoding);
ApplyVisitorToElement(*element, visitor, parentTags, parentIndexes, encoding, hasCodeExtensions);
}
}
}
......@@ -2222,7 +2235,8 @@ DCMTK_TO_CTYPE_CONVERTER(DcmtkToFloat64Converter, Float64, DcmFloatingPointDoubl
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 +2313,7 @@ DCMTK_TO_CTYPE_CONVERTER(DcmtkToFloat64Converter, Float64, DcmFloatingPointDoubl
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 +2394,7 @@ DCMTK_TO_CTYPE_CONVERTER(DcmtkToFloat64Converter, Float64, DcmFloatingPointDoubl
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 +2622,8 @@ DCMTK_TO_CTYPE_CONVERTER(DcmtkToFloat64Converter, Float64, DcmFloatingPointDoubl
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 +2631,7 @@ DCMTK_TO_CTYPE_CONVERTER(DcmtkToFloat64Converter, Float64, DcmFloatingPointDoubl
if (element.isLeaf())
{
ApplyVisitorToLeaf(element, visitor, parentTags, parentIndexes, tag, encoding);
ApplyVisitorToLeaf(element, visitor, parentTags, parentIndexes, tag, encoding, hasCodeExtensions);
}
else
{
......@@ -2640,7 +2655,7 @@ DCMTK_TO_CTYPE_CONVERTER(DcmtkToFloat64Converter, Float64, DcmFloatingPointDoubl
{
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 +2668,8 @@ DCMTK_TO_CTYPE_CONVERTER(DcmtkToFloat64Converter, Float64, DcmFloatingPointDoubl
{
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);
}
}
......@@ -92,6 +92,7 @@ namespace Orthanc
DicomToJsonFlags flags,
unsigned int maxStringLength,
Encoding encoding,
bool hasCodeExtensions,
const std::set<DicomTag>& ignoreTagLength);
static void ElementToJson(Json::Value& parent,
......@@ -100,6 +101,7 @@ namespace Orthanc
DicomToJsonFlags flags,
unsigned int maxStringLength,
Encoding dicomEncoding,
bool hasCodeExtensions,
const std::set<DicomTag>& ignoreTagLength);
static void ExtractDicomAsJson(Json::Value& target,
......@@ -112,6 +114,7 @@ namespace Orthanc
static void ChangeStringEncoding(DcmItem& dataset,
Encoding source,
bool hasSourceCodeExtensions,
Encoding target);
public:
......@@ -124,9 +127,18 @@ namespace Orthanc
unsigned int maxMultiplicity,
const std::string& privateCreator);
static Encoding DetectEncoding(DcmItem& dataset,
static Encoding DetectEncoding(bool& hasCodeExtensions,
DcmItem& dataset,
Encoding defaultEncoding);
static Encoding DetectEncoding(DcmItem& dataset,
Encoding defaultEncoding)
{
// Compatibility wrapper for Orthanc <= 1.5.4
bool hasCodeExtensions; // ignored
return DetectEncoding(hasCodeExtensions, dataset, defaultEncoding);
}
static DicomTag Convert(const DcmTag& tag);
static DicomTag GetTag(const DcmElement& element);
......@@ -137,6 +149,7 @@ namespace Orthanc
DicomToJsonFlags flags,
unsigned int maxStringLength,
Encoding encoding,
bool hasCodeExtensions,
const std::set<DicomTag>& ignoreTagLength);
static void ExtractHeaderAsJson(Json::Value& target,
......
......@@ -645,7 +645,10 @@ namespace Orthanc
}
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 @@ namespace Orthanc
}
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 @@ namespace Orthanc
}
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 @@ namespace Orthanc
}
}
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 @@ namespace Orthanc
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())
......@@ -944,7 +956,8 @@ namespace Orthanc
void ParsedDicomFile::CreateFromDicomMap(const DicomMap& source,
Encoding defaultEncoding)
Encoding defaultEncoding,
bool permissive)
{
pimpl_->file_.reset(new DcmFileFormat);
......@@ -985,25 +998,36 @@ namespace Orthanc
{
if (it->first != DICOM_TAG_SPECIFIC_CHARACTER_SET &&
!it->second->IsNull())
{
try
{
ReplacePlainString(it->first, it->second->GetContent());
}
catch (OrthancException&)
{
if (!permissive)
{
throw;
}
}
}
}
}
ParsedDicomFile::ParsedDicomFile(const DicomMap& map,
Encoding defaultEncoding) :
Encoding defaultEncoding,
bool permissive) :
pimpl_(new PImpl)
{
CreateFromDicomMap(map, defaultEncoding);
CreateFromDicomMap(map, defaultEncoding, permissive);
}
ParsedDicomFile::ParsedDicomFile(const DicomMap& map) :
pimpl_(new PImpl)
{
CreateFromDicomMap(map, GetDefaultDicomEncoding());
CreateFromDicomMap(map, GetDefaultDicomEncoding(), false /* be strict by default */);
}
......@@ -1294,9 +1318,10 @@ namespace Orthanc
}
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 +1557,13 @@ namespace Orthanc
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);
}
}
......
......@@ -81,7 +81,8 @@ namespace Orthanc
bool keepSopInstanceUid);
void CreateFromDicomMap(const DicomMap& source,
Encoding defaultEncoding);
Encoding defaultEncoding,
bool permissive);
void RemovePrivateTagsInternal(const std::set<DicomTag>* toKeep);
......@@ -97,7 +98,8 @@ namespace Orthanc
ParsedDicomFile(bool createIdentifiers); // Create a minimal DICOM instance
ParsedDicomFile(const DicomMap& map,
Encoding defaultEncoding);
Encoding defaultEncoding,
bool permissive);
ParsedDicomFile(const DicomMap& map);
......@@ -186,7 +188,7 @@ namespace Orthanc
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.
......
......@@ -647,6 +647,15 @@ namespace Orthanc
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 @@ namespace Orthanc
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 @@ namespace Orthanc
{
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 @@ namespace Orthanc
**/
encoding = Encoding_Chinese;
}
/*
else if (s == "ISO 2022 IR 149")
{
TODO
encoding = Encoding_Korean;
}
else if (s == "ISO 2022 IR 159")
else if (s == "ISO 2022 IR 87")
{
TODO
encoding = Encoding_JapaneseKanji;
}
else if (s == "ISO 2022 IR 87")
else if (s == "ISO 2022 IR 58")
{
TODO
encoding = Encoding_SimplifiedChinese;
}
/*
else if (s == "ISO 2022 IR 159")
{
TODO - Supplementary Kanji set
}
*/
else
......@@ -2013,6 +2043,15 @@ namespace Orthanc
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);
}
......
......@@ -442,10 +442,11 @@ namespace Orthanc
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
};
......
......@@ -43,9 +43,17 @@
#include <string.h>
#include <limits>
#include <stdint.h>
#include <algorithm>
namespace Orthanc
{
double ImageProcessing::ImagePoint::GetDistanceTo(const ImagePoint& other) const
{
double dx = (double)(other.GetX() - GetX());
double dy = (double)(other.GetY() - GetY());
return sqrt(dx * dx + dy * dy);
}
template <typename TargetType, typename SourceType>
static void ConvertInternal(ImageAccessor& target,
const ImageAccessor& source)
......@@ -1077,22 +1085,40 @@ namespace Orthanc
}
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_Grayscale16:
{
uint16_t maxValueUint16 = (uint16_t)(std::min(maxValue, static_cast<int64_t>(std::numeric_limits<uint16_t>::max())));
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;
}
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 = 255 - (*p);
*p = maxValueUint8 - (*p);
}
}
......@@ -1102,6 +1128,18 @@ namespace Orthanc
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
}
}
......@@ -1334,4 +1372,144 @@ namespace Orthanc
throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
}
}
void ComputePolygonExtent(int32_t& left, int32_t& right, int32_t& top, int32_t& bottom, const std::vector<ImageProcessing::ImagePoint>& points)
{
left = std::numeric_limits<int32_t>::max();
right = std::numeric_limits<int32_t>::min();
top = std::numeric_limits<int32_t>::max();
bottom = std::numeric_limits<int32_t>::min();
for (size_t i = 0; i < points.size(); i++)
{
const ImageProcessing::ImagePoint& p = points[i];
left = std::min(p.GetX(), left);
right = std::max(p.GetX(), right);
bottom = std::max(p.GetY(), bottom);
top = std::min(p.GetY(), top);
}
}
template <PixelFormat TargetFormat>
void FillPolygon_(ImageAccessor& image,
const std::vector<ImageProcessing::ImagePoint>& points,
int64_t value_)
{
typedef typename PixelTraits<TargetFormat>::PixelType TargetType;
TargetType value = PixelTraits<TargetFormat>::IntegerToPixel(value_);
int32_t left;
int32_t right;
int32_t top;
int32_t bottom;
ComputePolygonExtent(left, right, top, bottom, points);
// from http://alienryderflex.com/polygon_fill/
// convert all "corner" points to double only once
std::vector<double> cpx;
std::vector<double> cpy;
size_t cpSize = points.size();
for (size_t i = 0; i < points.size(); i++)
{
cpx.push_back((double)points[i].GetX());
cpy.push_back((double)points[i].GetY());
}
std::vector<int32_t> nodeX;
nodeX.resize(cpSize);
int nodes, pixelX, pixelY, i, j, swap ;
// Loop through the rows of the image.
for (pixelY = top; pixelY < bottom; pixelY++)
{
double y = (double)pixelY;
// Build a list of nodes.
nodes = 0;
j = static_cast<int>(cpSize) - 1;
for (i = 0; i < static_cast<int>(cpSize); i++)
{
if ((cpy[i] < y && cpy[j] >= y) || (cpy[j] < y && cpy[i] >= y))
{
nodeX[nodes++] = (int32_t)(cpx[i] + (y - cpy[i])/(cpy[j] - cpy[i]) * (cpx[j] - cpx[i]));
}
j=i;
}
// Sort the nodes, via a simple “Bubble” sort.
i=0;
while (i < nodes-1)
{
if (nodeX[i] > nodeX[i+1])
{
swap = nodeX[i];
nodeX[i] = nodeX[i+1];
nodeX[i+1] = swap;
if (i > 0)
{
i--;
}
}
else
{
i++;
}
}
TargetType* row = reinterpret_cast<TargetType*>(image.GetRow(pixelY));
// Fill the pixels between node pairs.
for (i=0; i<nodes; i+=2)
{
if (nodeX[i] >= right)
break;
if (nodeX[i+1] >= left)
{
if (nodeX[i]< left)
{
nodeX[i] = left;
}
if (nodeX[i+1] > right)
{
nodeX[i+1] = right;
}
for (pixelX = nodeX[i]; pixelX <= nodeX[i+1]; pixelX++)
{
*(row + pixelX) = value;
}
}
}
}
}
void ImageProcessing::FillPolygon(ImageAccessor& image,
const std::vector<ImagePoint>& points,
int64_t value)
{
switch (image.GetFormat())
{
case Orthanc::PixelFormat_Grayscale8:
{
FillPolygon_<Orthanc::PixelFormat_Grayscale8>(image, points, value);
break;
}
case Orthanc::PixelFormat_Grayscale16:
{
FillPolygon_<Orthanc::PixelFormat_Grayscale16>(image, points, value);
break;
}
case Orthanc::PixelFormat_SignedGrayscale16:
{
FillPolygon_<Orthanc::PixelFormat_SignedGrayscale16>(image, points, value);
break;
}
default:
throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
}
}
}
......@@ -34,6 +34,7 @@
#pragma once
#include "ImageAccessor.h"
#include <vector>
#include <stdint.h>
......@@ -41,6 +42,31 @@ namespace Orthanc
{
namespace ImageProcessing
{
class ImagePoint
{
int32_t x_;
int32_t y_;
public:
ImagePoint(int32_t x, int32_t y)
: x_(x),
y_(y)
{
}
int32_t GetX() const {return x_;}
int32_t GetY() const {return y_;}
void Set(int32_t x, int32_t y)
{
x_ = x;
y_ = y;
}
double GetDistanceTo(const ImagePoint& other) const;
};
void Copy(ImageAccessor& target,
const ImageAccessor& source);
......@@ -83,6 +109,8 @@ namespace Orthanc
void Invert(ImageAccessor& image);
void Invert(ImageAccessor& image, int64_t maxValue);
void DrawLineSegment(ImageAccessor& image,
int x0,
int y0,
......@@ -99,5 +127,9 @@ namespace Orthanc
uint8_t green,
uint8_t blue,
uint8_t alpha);
void FillPolygon(ImageAccessor& image,
const std::vector<ImagePoint>& points,
int64_t value);
}
}
......@@ -103,6 +103,7 @@ namespace Orthanc
static void FloatToPixel(PixelType& target,
float value)
{
value += 0.5f;
if (value < static_cast<float>(std::numeric_limits<PixelType>::min()))
{
target = std::numeric_limits<PixelType>::min();
......
......@@ -49,10 +49,12 @@ namespace Orthanc
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)
{
......
......@@ -46,11 +46,13 @@ namespace Orthanc
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 @@ namespace Orthanc
{
return hasSerialized_;
}
const std::string& GetDetails() const
{
return details_;
}
};
}