# HG changeset patch # User Sebastien Jodogne # Date 1355494548 -3600 # Node ID 4d7469f72a0bf99c82907301ac82132a21b06c7d # Parent b3322636b06d8abb064384de7f1c2ccd388bae33 embedding of dicom dictionaries diff -r b3322636b06d -r 4d7469f72a0b CMakeLists.txt --- a/CMakeLists.txt Fri Dec 14 11:24:24 2012 +0100 +++ b/CMakeLists.txt Fri Dec 14 15:15:48 2012 +0100 @@ -43,26 +43,6 @@ SET(STANDALONE_BUILD ON) endif() -if (${STANDALONE_BUILD}) - # We embed all the resources in the binaries for standalone builds - add_definitions(-DORTHANC_STANDALONE=1) - EmbedResources( - PREPARE_DATABASE OrthancServer/PrepareDatabase.sql - ORTHANC_EXPLORER OrthancExplorer - CONFIGURATION_SAMPLE Resources/Configuration.json - ) -else() - add_definitions( - -DORTHANC_STANDALONE=0 - -DORTHANC_PATH=\"${CMAKE_SOURCE_DIR}\" - ) - EmbedResources( - PREPARE_DATABASE OrthancServer/PrepareDatabase.sql - CONFIGURATION_SAMPLE Resources/Configuration.json - ) -endif() - - # Prepare the third-party dependencies SET(THIRD_PARTY_SOURCES ${CMAKE_SOURCE_DIR}/Resources/md5/md5.c @@ -93,6 +73,32 @@ include(${CMAKE_SOURCE_DIR}/Resources/CMake/LibPngConfiguration.cmake) +# Prepare the embedded files +set(EMBEDDED_FILES + PREPARE_DATABASE ${CMAKE_CURRENT_SOURCE_DIR}/OrthancServer/PrepareDatabase.sql + CONFIGURATION_SAMPLE ${CMAKE_CURRENT_SOURCE_DIR}/Resources/Configuration.json + ) + +if (${STANDALONE_BUILD}) + # We embed all the resources in the binaries for standalone builds + add_definitions(-DORTHANC_STANDALONE=1) + EmbedResources( + ${EMBEDDED_FILES} + ORTHANC_EXPLORER ${CMAKE_CURRENT_SOURCE_DIR}/OrthancExplorer + ${DCMTK_DICTIONARIES} + ) +else() + add_definitions( + -DORTHANC_STANDALONE=0 + -DORTHANC_PATH=\"${CMAKE_SOURCE_DIR}\" + ) + EmbedResources( + ${EMBEDDED_FILES} + ) +endif() + + + # The main instructions to build the Orthanc binaries add_library(CoreLibrary STATIC diff -r b3322636b06d -r 4d7469f72a0b Core/Cache/MemoryCache.cpp --- a/Core/Cache/MemoryCache.cpp Fri Dec 14 11:24:24 2012 +0100 +++ b/Core/Cache/MemoryCache.cpp Fri Dec 14 15:15:48 2012 +0100 @@ -32,6 +32,8 @@ #include "MemoryCache.h" +#include // This fixes a problem in glog for recent + // releases of MinGW #include namespace Orthanc diff -r b3322636b06d -r 4d7469f72a0b Core/DicomFormat/DicomTag.cpp --- a/Core/DicomFormat/DicomTag.cpp Fri Dec 14 11:24:24 2012 +0100 +++ b/Core/DicomFormat/DicomTag.cpp Fri Dec 14 15:15:48 2012 +0100 @@ -70,4 +70,49 @@ sprintf(b, "%04x,%04x", group_, element_); return std::string(b); } + + + const char* DicomTag::GetMainTagsName() const + { + if (*this == DICOM_TAG_ACCESSION_NUMBER) + return "AccessionNumber"; + + if (*this == DICOM_TAG_SOP_INSTANCE_UID) + return "SOPInstanceUID"; + + if (*this == DICOM_TAG_PATIENT_ID) + return "PatientID"; + + if (*this == DICOM_TAG_SERIES_INSTANCE_UID) + return "SeriesInstanceUID"; + + if (*this == DICOM_TAG_STUDY_INSTANCE_UID) + return "StudyInstanceUID"; + + if (*this == DICOM_TAG_PIXEL_DATA) + return "PixelData"; + + if (*this == DICOM_TAG_IMAGE_INDEX) + return "ImageIndex"; + + if (*this == DICOM_TAG_INSTANCE_NUMBER) + return "InstanceNumber"; + + if (*this == DICOM_TAG_NUMBER_OF_SLICES) + return "NumberOfSlices"; + + if (*this == DICOM_TAG_NUMBER_OF_FRAMES) + return "NumberOfFrames"; + + if (*this == DICOM_TAG_CARDIAC_NUMBER_OF_IMAGES) + return "CardiacNumberOfImages"; + + if (*this == DICOM_TAG_IMAGES_IN_ACQUISITION) + return "ImagesInAcquisition"; + + if (*this == DICOM_TAG_PATIENT_NAME) + return "PatientName"; + + return ""; + } } diff -r b3322636b06d -r 4d7469f72a0b Core/DicomFormat/DicomTag.h --- a/Core/DicomFormat/DicomTag.h Fri Dec 14 11:24:24 2012 +0100 +++ b/Core/DicomFormat/DicomTag.h Fri Dec 14 15:15:48 2012 +0100 @@ -64,6 +64,8 @@ return element_; } + const char* GetMainTagsName() const; + bool operator< (const DicomTag& other) const; bool operator== (const DicomTag& other) const diff -r b3322636b06d -r 4d7469f72a0b INSTALL --- a/INSTALL Fri Dec 14 11:24:24 2012 +0100 +++ b/INSTALL Fri Dec 14 15:15:48 2012 +0100 @@ -76,7 +76,7 @@ the following command: # cd ~/OrthancBuild -# cmake -DCMAKE_TOOLCHAIN_FILE=~/Orthanc/Resources/MinGWToolchain.cmake -DCMAKE_BUILD_TYPE=DEBUG ~/Orthanc +# cmake -DCMAKE_TOOLCHAIN_FILE=~/Orthanc/Resources/MinGWToolchain.cmake -DSTANDALONE_BUILD=ON -DCMAKE_BUILD_TYPE=DEBUG ~/Orthanc # make @@ -94,7 +94,7 @@ ------------------------------------------------------ # cd [...]\OrthancBuild -# cmake -G "Visual Studio 8 2005" [...]\Orthanc +# cmake -DSTANDALONE_BUILD=ON -G "Visual Studio 8 2005" [...]\Orthanc Then open the "[...]/OrthancBuild/Orthanc.sln" with Visual Studio. diff -r b3322636b06d -r 4d7469f72a0b NEWS --- a/NEWS Fri Dec 14 11:24:24 2012 +0100 +++ b/NEWS Fri Dec 14 15:15:48 2012 +0100 @@ -4,6 +4,7 @@ * Recycling of disk space * Protection of patients against recycling (also in Orthanc Explorer) * Raw access to the value of the DICOM tags in the REST API +* The DICOM dictionaries are embedded in Windows builds Version 0.3.1 (2012/12/05) diff -r b3322636b06d -r 4d7469f72a0b OrthancServer/DicomProtocol/DicomServer.cpp --- a/OrthancServer/DicomProtocol/DicomServer.cpp Fri Dec 14 11:24:24 2012 +0100 +++ b/OrthancServer/DicomProtocol/DicomServer.cpp Fri Dec 14 15:15:48 2012 +0100 @@ -34,7 +34,9 @@ #include "../../Core/OrthancException.h" #include "../../Core/Toolbox.h" +#include "../../Core/Uuid.h" #include "../Internals/CommandDispatcher.h" +#include "EmbeddedResources.h" #include #include @@ -51,15 +53,44 @@ }; + static void LoadEmbeddedDictionary(DcmDataDictionary& dictionary, + EmbeddedResources::FileResourceId resource) + { + Toolbox::TemporaryFile tmp; + + FILE* fp = fopen(tmp.GetPath().c_str(), "wb"); + fwrite(EmbeddedResources::GetFileResourceBuffer(resource), + EmbeddedResources::GetFileResourceSize(resource), 1, fp); + fclose(fp); + + if (!dictionary.loadDictionary(tmp.GetPath().c_str())) + { + throw OrthancException(ErrorCode_InternalError); + } + } + + + void DicomServer::ServerThread(DicomServer* server) { /* Disable "gethostbyaddr" (which results in memory leaks) and use raw IP addresses */ dcmDisableGethostbyaddr.set(OFTrue); +#if ORTHANC_STANDALONE == 1 + LOG(WARNING) << "Loading the embedded dictionaries"; + dcmDataDict.clear(); + DcmDataDictionary& d = dcmDataDict.wrlock(); + LoadEmbeddedDictionary(d, EmbeddedResources::DICTIONARY_DICOM); + LoadEmbeddedDictionary(d, EmbeddedResources::DICTIONARY_PRIVATE); + LoadEmbeddedDictionary(d, EmbeddedResources::DICTIONARY_DICONDE); + dcmDataDict.unlock(); +#endif + /* make sure data dictionary is loaded */ if (!dcmDataDict.isDictionaryLoaded()) { LOG(ERROR) << "no data dictionary loaded, check environment variable: " << DCM_DICT_ENVIRONMENT_VARIABLE; + throw OrthancException(ErrorCode_InternalError); } /* initialize network, i.e. create an instance of T_ASC_Network*. */ diff -r b3322636b06d -r 4d7469f72a0b OrthancServer/DicomProtocol/IStoreRequestHandler.h --- a/OrthancServer/DicomProtocol/IStoreRequestHandler.h Fri Dec 14 11:24:24 2012 +0100 +++ b/OrthancServer/DicomProtocol/IStoreRequestHandler.h Fri Dec 14 15:15:48 2012 +0100 @@ -47,7 +47,7 @@ { } - virtual void Handle(const std::vector& dicomFile, + virtual void Handle(const std::string& dicomFile, const DicomMap& dicomSummary, const Json::Value& dicomJson, const std::string& distantAet) = 0; diff -r b3322636b06d -r 4d7469f72a0b OrthancServer/FromDcmtkBridge.cpp --- a/OrthancServer/FromDcmtkBridge.cpp Fri Dec 14 11:24:24 2012 +0100 +++ b/OrthancServer/FromDcmtkBridge.cpp Fri Dec 14 15:15:48 2012 +0100 @@ -60,6 +60,8 @@ #include #include +#include +#include namespace Orthanc { @@ -73,10 +75,13 @@ is.setEos(); file_.reset(new DcmFileFormat); + file_->transferInit(); if (!file_->read(is).good()) { throw OrthancException(ErrorCode_BadFileFormat); } + file_->loadAllDataIntoMemory(); + file_->transferEnd(); } @@ -144,6 +149,58 @@ return true; } + + static void SendSequence(RestApiOutput& output, + DcmSequenceOfItems& sequence) + { + // This element is a sequence + Json::Value v = Json::arrayValue; + + for (unsigned long i = 0; i < sequence.card(); i++) + { + v.append(boost::lexical_cast(i)); + } + + output.AnswerJson(v); + } + + static void SendField(RestApiOutput& output, + DcmElement& element) + { + // This element is not a sequence + std::string buffer; + buffer.resize(65536); + Uint32 length = element.getLength(); + Uint32 offset = 0; + + output.GetLowLevelOutput().SendOkHeader("application/octet-stream", true, length, NULL); + + while (offset < length) + { + Uint32 nbytes; + if (length - offset < buffer.size()) + { + nbytes = length - offset; + } + else + { + nbytes = buffer.size(); + } + + if (element.getPartialValue(&buffer[0], offset, nbytes).good()) + { + output.GetLowLevelOutput().Send(&buffer[0], nbytes); + offset += nbytes; + } + else + { + return; + } + } + + output.MarkLowLevelOutputDone(); + } + static void SendPathValueForLeaf(RestApiOutput& output, const std::string& tag, DcmItem& dicom) @@ -154,57 +211,22 @@ return; } - DcmElement* element = NULL; - if (dicom.findAndGetElement(k, element).good() && element != NULL) + DcmSequenceOfItems* sequence = NULL; + if (dicom.findAndGetSequence(k, sequence).good() && + sequence != NULL && + sequence->getVR() == EVR_SQ) { - if (element->getVR() == EVR_SQ) - { - // This element is a sequence - Json::Value v = Json::arrayValue; - DcmSequenceOfItems& sequence = dynamic_cast(*element); - - for (unsigned long i = 0; i < sequence.card(); i++) - { - v.append(boost::lexical_cast(i)); - } - - output.AnswerJson(v); - } - else - { - // This element is not a sequence - std::string buffer; - buffer.resize(65536); - Uint32 length = element->getLength(); - Uint32 offset = 0; - - output.GetLowLevelOutput().SendOkHeader("application/octet-stream", true, length, NULL); + SendSequence(output, *sequence); + return; + } - while (offset < length) - { - Uint32 nbytes; - if (length - offset < buffer.size()) - { - nbytes = length - offset; - } - else - { - nbytes = buffer.size(); - } - - if (element->getPartialValue(&buffer[0], offset, nbytes).good()) - { - output.GetLowLevelOutput().Send(&buffer[0], nbytes); - offset += nbytes; - } - else - { - return; - } - } - - output.MarkLowLevelOutputDone(); - } + DcmElement* element = NULL; + if (dicom.findAndGetElement(k, element).good() && + element != NULL && + element->getVR() != EVR_UNKNOWN && + element->getVR() != EVR_SQ) + { + SendField(output, *element); } } @@ -728,15 +750,11 @@ { // Some patches for important tags because of different DICOM // dictionaries between DCMTK versions - if (t == DICOM_TAG_PATIENT_NAME) - return "PatientName"; - - if (t == DicomTag(0x0010, 0x0030)) - return "PatientBirthDate"; - - if (t == DicomTag(0x0010, 0x0040)) - return "PatientSex"; - + std::string n = t.GetMainTagsName(); + if (n.size() != 0) + { + return n; + } // End of patches DcmTagKey tag(t.GetGroup(), t.GetElement()); @@ -824,4 +842,53 @@ } } + bool FromDcmtkBridge::SaveToMemoryBuffer(std::string& buffer, + DcmDataset* dataSet) + { + // Determine the transfer syntax which shall be used to write the + // information to the file. We always switch to the Little Endian + // syntax, with explicit length. + + // http://support.dcmtk.org/docs/dcxfer_8h-source.html + E_TransferSyntax xfer = EXS_LittleEndianExplicit; + E_EncodingType encodingType = /*opt_sequenceType*/ EET_ExplicitLength; + + uint32_t s = dataSet->getLength(xfer, encodingType); + + buffer.resize(s); + DcmOutputBufferStream ob(&buffer[0], s); + + dataSet->transferInit(); + +#if DCMTK_VERSION_NUMBER >= 360 + OFCondition c = dataSet->write(ob, xfer, encodingType, NULL, + /*opt_groupLength*/ EGL_recalcGL, + /*opt_paddingType*/ EPD_withoutPadding); +#else + OFCondition c = dataSet->write(ob, xfer, encodingType, NULL); +#endif + + dataSet->transferEnd(); + if (c.good()) + { + return true; + } + else + { + buffer.clear(); + return false; + } + +#if 0 + OFCondition cond = cbdata->dcmff->saveFile(fileName.c_str(), xfer, + encodingType, + /*opt_groupLength*/ EGL_recalcGL, + /*opt_paddingType*/ EPD_withoutPadding, + OFstatic_cast(Uint32, /*opt_filepad*/ 0), + OFstatic_cast(Uint32, /*opt_itempad*/ 0), + (opt_useMetaheader) ? EWM_fileformat : EWM_dataset); +#endif + } + + } diff -r b3322636b06d -r 4d7469f72a0b OrthancServer/FromDcmtkBridge.h --- a/OrthancServer/FromDcmtkBridge.h Fri Dec 14 11:24:24 2012 +0100 +++ b/OrthancServer/FromDcmtkBridge.h Fri Dec 14 15:15:48 2012 +0100 @@ -136,5 +136,8 @@ const DicomMap& values); static std::string GenerateUniqueIdentifier(DicomRootLevel level); + + static bool SaveToMemoryBuffer(std::string& buffer, + DcmDataset* dataSet); }; } diff -r b3322636b06d -r 4d7469f72a0b OrthancServer/Internals/StoreScp.cpp --- a/OrthancServer/Internals/StoreScp.cpp Fri Dec 14 11:24:24 2012 +0100 +++ b/OrthancServer/Internals/StoreScp.cpp Fri Dec 14 15:15:48 2012 +0100 @@ -58,55 +58,6 @@ }; - static int SaveToMemoryBuffer(DcmDataset* dataSet, - std::vector& buffer) - { - // Determine the transfer syntax which shall be used to write the - // information to the file. We always switch to the Little Endian - // syntax, with explicit length. - - // http://support.dcmtk.org/docs/dcxfer_8h-source.html - E_TransferSyntax xfer = EXS_LittleEndianExplicit; - E_EncodingType encodingType = /*opt_sequenceType*/ EET_ExplicitLength; - - uint32_t s = dataSet->getLength(xfer, encodingType); - - buffer.resize(s); - DcmOutputBufferStream ob(&buffer[0], s); - - dataSet->transferInit(); - -#if DCMTK_VERSION_NUMBER >= 360 - OFCondition c = dataSet->write(ob, xfer, encodingType, NULL, - /*opt_groupLength*/ EGL_recalcGL, - /*opt_paddingType*/ EPD_withoutPadding); -#else - OFCondition c = dataSet->write(ob, xfer, encodingType, NULL); -#endif - - dataSet->transferEnd(); - if (c.good()) - { - return 0; - } - else - { - buffer.clear(); - return -1; - } - -#if 0 - OFCondition cond = cbdata->dcmff->saveFile(fileName.c_str(), xfer, - encodingType, - /*opt_groupLength*/ EGL_recalcGL, - /*opt_paddingType*/ EPD_withoutPadding, - OFstatic_cast(Uint32, /*opt_filepad*/ 0), - OFstatic_cast(Uint32, /*opt_itempad*/ 0), - (opt_useMetaheader) ? EWM_fileformat : EWM_dataset); -#endif - } - - static void storeScpCallback( void *callbackData, @@ -159,14 +110,14 @@ { DicomMap summary; Json::Value dicomJson; - std::vector buffer; + std::string buffer; try { FromDcmtkBridge::Convert(summary, **imageDataSet); FromDcmtkBridge::ToJson(dicomJson, **imageDataSet); - if (SaveToMemoryBuffer(*imageDataSet, buffer) < 0) + if (FromDcmtkBridge::SaveToMemoryBuffer(buffer, *imageDataSet) < 0) { LOG(ERROR) << "cannot write DICOM file to memory"; rsp->DimseStatus = STATUS_STORE_Refused_OutOfResources; diff -r b3322636b06d -r 4d7469f72a0b OrthancServer/OrthancRestApi.cpp --- a/OrthancServer/OrthancRestApi.cpp Fri Dec 14 11:24:24 2012 +0100 +++ b/OrthancServer/OrthancRestApi.cpp Fri Dec 14 15:15:48 2012 +0100 @@ -758,22 +758,29 @@ RETRIEVE_CONTEXT(call); const std::string& postData = call.GetPostBody(); + if (postData.size() == 0) + { + return; + } LOG(INFO) << "Receiving a DICOM file of " << postData.size() << " bytes through HTTP"; - // Prepare an input stream for the memory buffer - DcmInputBufferStream is; - if (postData.size() > 0) + DcmFileFormat dicomFile; + { + // Prepare an input stream for the memory buffer + DcmInputBufferStream is; is.setBuffer(&postData[0], postData.size()); - } - is.setEos(); + is.setEos(); - DcmFileFormat dicomFile; - if (!dicomFile.read(is).good()) - { - call.GetOutput().SignalError(Orthanc_HttpStatus_415_UnsupportedMediaType); - return; + dicomFile.transferInit(); + if (!dicomFile.read(is).good()) + { + call.GetOutput().SignalError(Orthanc_HttpStatus_415_UnsupportedMediaType); + return; + } + dicomFile.loadAllDataIntoMemory(); + dicomFile.transferEnd(); } DicomMap dicomSummary; @@ -788,9 +795,9 @@ if (postData.size() > 0) { status = context.Store - (reinterpret_cast(&postData[0]), - postData.size(), dicomSummary, dicomJson, ""); - } + (reinterpret_cast(&postData[0]), postData.size(), + dicomSummary, dicomJson, ""); + } Json::Value result = Json::objectValue; diff -r b3322636b06d -r 4d7469f72a0b OrthancServer/ServerContext.cpp --- a/OrthancServer/ServerContext.cpp Fri Dec 14 11:24:24 2012 +0100 +++ b/OrthancServer/ServerContext.cpp Fri Dec 14 15:15:48 2012 +0100 @@ -36,6 +36,8 @@ #include +#define ENABLE_DICOM_CACHE 1 + static const size_t DICOM_CACHE_SIZE = 2; @@ -178,6 +180,12 @@ ParsedDicomFile& ServerContext::GetDicomFile(const std::string& instancePublicId) { +#if ENABLE_DICOM_CACHE == 0 + static std::auto_ptr p; + p.reset(provider_.Provide(instancePublicId)); + return dynamic_cast(*p); +#else return dynamic_cast(dicomCache_.Access(instancePublicId)); +#endif } } diff -r b3322636b06d -r 4d7469f72a0b OrthancServer/main.cpp --- a/OrthancServer/main.cpp Fri Dec 14 11:24:24 2012 +0100 +++ b/OrthancServer/main.cpp Fri Dec 14 15:15:48 2012 +0100 @@ -58,15 +58,14 @@ { } - virtual void Handle(const std::vector& dicomFile, + virtual void Handle(const std::string& dicomFile, const DicomMap& dicomSummary, const Json::Value& dicomJson, const std::string& remoteAet) { if (dicomFile.size() > 0) { - context_.Store(reinterpret_cast(&dicomFile[0]), dicomFile.size(), - dicomSummary, dicomJson, remoteAet); + context_.Store(&dicomFile[0], dicomFile.size(), dicomSummary, dicomJson, remoteAet); } } }; @@ -187,7 +186,7 @@ google::InitGoogleLogging("Orthanc"); - + int status = 0; try { bool isInitialized = false; @@ -289,10 +288,15 @@ catch (OrthancException& e) { LOG(ERROR) << "EXCEPTION [" << e.What() << "]"; -return -1; + status = -1; + } + catch (...) + { + LOG(ERROR) << "NATIVE EXCEPTION"; + status = -1; } OrthancFinalize(); - return 0; + return status; } diff -r b3322636b06d -r 4d7469f72a0b Resources/CMake/AutoGeneratedCode.cmake --- a/Resources/CMake/AutoGeneratedCode.cmake Fri Dec 14 11:24:24 2012 +0100 +++ b/Resources/CMake/AutoGeneratedCode.cmake Fri Dec 14 15:15:48 2012 +0100 @@ -11,8 +11,8 @@ set(IS_PATH_NAME false) foreach(arg ${ARGN}) if (${IS_PATH_NAME}) - list(APPEND SCRIPT_ARGUMENTS "${CMAKE_CURRENT_SOURCE_DIR}/${arg}") - list(APPEND DEPENDENCIES "${CMAKE_CURRENT_SOURCE_DIR}/${arg}") + list(APPEND SCRIPT_ARGUMENTS "${arg}") + list(APPEND DEPENDENCIES "${arg}") set(IS_PATH_NAME false) else() list(APPEND SCRIPT_ARGUMENTS "${arg}") diff -r b3322636b06d -r 4d7469f72a0b Resources/CMake/DcmtkConfiguration.cmake --- a/Resources/CMake/DcmtkConfiguration.cmake Fri Dec 14 11:24:24 2012 +0100 +++ b/Resources/CMake/DcmtkConfiguration.cmake Fri Dec 14 15:15:48 2012 +0100 @@ -70,6 +70,14 @@ set(DCMTK_BUNDLES_LOG4CPLUS 1) + if (${STANDALONE_BUILD}) + set(DCMTK_DICTIONARIES + DICTIONARY_DICOM ${DCMTK_SOURCES_DIR}/dcmdata/data/dicom.dic + DICTIONARY_PRIVATE ${DCMTK_SOURCES_DIR}/dcmdata/data/private.dic + DICTIONARY_DICONDE ${DCMTK_SOURCES_DIR}/dcmdata/data/diconde.dic + ) + endif() + else() # The following line allows to manually add libraries at the # command-line, which is necessary for Ubuntu/Debian packages @@ -103,6 +111,7 @@ "\\1\\2\\3" DCMTK_VERSION_NUMBER ${DCMTK_VERSION_NUMBER1}) + endif() add_definitions(-DDCMTK_VERSION_NUMBER=${DCMTK_VERSION_NUMBER})