# HG changeset patch # User Sebastien Jodogne # Date 1586528297 -7200 # Node ID 638906dcfe32f2832c9580f5706d956a1420340a # Parent 6762506ef4fb68d650aa5abee9db3e490eb2c107# Parent e82bd07c384ec1125176e4f55064feb51ec92d3f integration mainline->transcoding diff -r e82bd07c384e -r 638906dcfe32 CMakeLists.txt --- a/CMakeLists.txt Fri Apr 10 16:12:10 2020 +0200 +++ b/CMakeLists.txt Fri Apr 10 16:18:17 2020 +0200 @@ -26,7 +26,7 @@ set(ENABLE_ZLIB ON) # To test transcoding -#set(ENABLE_DCMTK_TRANSCODING ON) +set(ENABLE_DCMTK_TRANSCODING ON) set(HAS_EMBEDDED_RESOURCES ON) diff -r e82bd07c384e -r 638906dcfe32 Core/DicomNetworking/DicomStoreUserConnection.h --- a/Core/DicomNetworking/DicomStoreUserConnection.h Fri Apr 10 16:12:10 2020 +0200 +++ b/Core/DicomNetworking/DicomStoreUserConnection.h Fri Apr 10 16:18:17 2020 +0200 @@ -79,10 +79,6 @@ bool ProposeStorageClass(const std::string& sopClassUid, const std::set& syntaxes); - bool LookupPresentationContext(uint8_t& presentationContextId, - const std::string& sopClassUid, - DicomTransferSyntax transferSyntax); - public: DicomStoreUserConnection(const DicomAssociationParameters& params); @@ -124,6 +120,10 @@ void PrepareStorageClass(const std::string& sopClassUid, DicomTransferSyntax syntax); + bool LookupPresentationContext(uint8_t& presentationContextId, + const std::string& sopClassUid, + DicomTransferSyntax transferSyntax); + bool NegotiatePresentationContext(uint8_t& presentationContextId, const std::string& sopClassUid, DicomTransferSyntax transferSyntax); diff -r e82bd07c384e -r 638906dcfe32 Core/DicomNetworking/DicomUserConnection.cpp --- a/Core/DicomNetworking/DicomUserConnection.cpp Fri Apr 10 16:12:10 2020 +0200 +++ b/Core/DicomNetworking/DicomUserConnection.cpp Fri Apr 10 16:18:17 2020 +0200 @@ -240,6 +240,10 @@ std::vector& asFallback, const std::string& aet) { + // Presentation context IDs must be odd numbers, hence the + // increments by 2: + // http://dicom.nema.org/medical/dicom/2019e/output/chtml/part08/sect_9.3.2.2.html + Check(ASC_addPresentationContext(params, presentationContextId, sopClass.c_str(), asPreferred, 1), aet, "initializing"); @@ -1177,6 +1181,28 @@ "Unable to negotiate a presentation context with AET " + remoteAet_); } + +#if 0 + // Manual loop over the accepted transfer syntaxes + LST_HEAD **l = &pimpl_->params_->DULparams.acceptedPresentationContext; + if (*l != NULL) + { + DUL_PRESENTATIONCONTEXT* pc = (DUL_PRESENTATIONCONTEXT*) LST_Head(l); + LST_Position(l, (LST_NODE*)pc); + while (pc) + { + if (pc->result == ASC_P_ACCEPTANCE) + { + printf("Accepted: %d [%s] [%s]\n", pc->presentationContextID, pc->abstractSyntax, pc->acceptedTransferSyntax); + } + else + { + printf("Rejected: %d [%s]\n", pc->presentationContextID, pc->abstractSyntax); + } + pc = (DUL_PRESENTATIONCONTEXT*) LST_Next(l); + } + } +#endif } void DicomUserConnection::Close() diff -r e82bd07c384e -r 638906dcfe32 Core/DicomParsing/FromDcmtkBridge.cpp --- a/Core/DicomParsing/FromDcmtkBridge.cpp Fri Apr 10 16:12:10 2020 +0200 +++ b/Core/DicomParsing/FromDcmtkBridge.cpp Fri Apr 10 16:18:17 2020 +0200 @@ -121,6 +121,13 @@ #endif +#include +#if ORTHANC_ENABLE_DCMTK_TRANSCODING == 1 +# include +# include // include to support color images +#endif + + namespace Orthanc { static bool IsBinaryTag(const DcmTag& key) @@ -1198,51 +1205,30 @@ } } - bool FromDcmtkBridge::SaveToMemoryBuffer(std::string& buffer, - DcmDataset& dataSet) + + + static bool SaveToMemoryBufferInternal(std::string& buffer, + DcmFileFormat& dicom, + E_TransferSyntax xfer) { - // 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 - - - /** - * Note that up to Orthanc 0.7.1 (inclusive), the - * "EXS_LittleEndianExplicit" was always used to save the DICOM - * dataset into memory. We now keep the original transfer syntax - * (if available). - **/ - E_TransferSyntax xfer = dataSet.getOriginalXfer(); - if (xfer == EXS_Unknown) - { - // No information about the original transfer syntax: This is - // most probably a DICOM dataset that was read from memory. - xfer = EXS_LittleEndianExplicit; - } - E_EncodingType encodingType = /*opt_sequenceType*/ EET_ExplicitLength; - // Create the meta-header information - DcmFileFormat ff(&dataSet); - ff.validateMetaInfo(xfer); - ff.removeInvalidGroups(); - // Create a memory buffer with the proper size { - const uint32_t estimatedSize = ff.calcElementLength(xfer, encodingType); // (*) + const uint32_t estimatedSize = dicom.calcElementLength(xfer, encodingType); // (*) buffer.resize(estimatedSize); } DcmOutputBufferStream ob(&buffer[0], buffer.size()); // Fill the memory buffer with the meta-header and the dataset - ff.transferInit(); - OFCondition c = ff.write(ob, xfer, encodingType, NULL, - /*opt_groupLength*/ EGL_recalcGL, - /*opt_paddingType*/ EPD_withoutPadding); - ff.transferEnd(); + dicom.transferInit(); + OFCondition c = dicom.write(ob, xfer, encodingType, NULL, + /*opt_groupLength*/ EGL_recalcGL, + /*opt_paddingType*/ EPD_noChange, + /*padlen*/ 0, /*subPadlen*/ 0, /*instanceLength*/ 0, + EWM_updateMeta /* creates new SOP instance UID on lossy */); + dicom.transferEnd(); if (c.good()) { @@ -1265,6 +1251,86 @@ return false; } } + + + bool FromDcmtkBridge::SaveToMemoryBuffer(std::string& buffer, + DcmDataset& dataSet) + { + // Determine the transfer syntax which shall be used to write the + // information to the file. If not possible, switch to the Little + // Endian syntax, with explicit length. + + // http://support.dcmtk.org/docs/dcxfer_8h-source.html + + + /** + * Note that up to Orthanc 0.7.1 (inclusive), the + * "EXS_LittleEndianExplicit" was always used to save the DICOM + * dataset into memory. We now keep the original transfer syntax + * (if available). + **/ + E_TransferSyntax xfer = dataSet.getOriginalXfer(); + if (xfer == EXS_Unknown) + { + // No information about the original transfer syntax: This is + // most probably a DICOM dataset that was read from memory. + xfer = EXS_LittleEndianExplicit; + } + + // Create the meta-header information + DcmFileFormat ff(&dataSet); + ff.validateMetaInfo(xfer); + ff.removeInvalidGroups(); + + return SaveToMemoryBufferInternal(buffer, ff, xfer); + } + + + bool FromDcmtkBridge::SaveToMemoryBuffer(std::string& buffer, + DcmFileFormat& dicom) + { + E_TransferSyntax xfer = dicom.getDataset()->getOriginalXfer(); + if (xfer == EXS_Unknown) + { + throw OrthancException(ErrorCode_InternalError, + "Cannot write a DICOM instance with unknown transfer syntax"); + } + else if (!dicom.validateMetaInfo(xfer).good()) + { + throw OrthancException(ErrorCode_InternalError, + "Cannot setup the transfer syntax to write a DICOM instance"); + } + else + { + return SaveToMemoryBufferInternal(buffer, dicom, xfer); + } + } + + + bool FromDcmtkBridge::Transcode(std::string& buffer, + DcmFileFormat& dicom, + DicomTransferSyntax syntax, + const DcmRepresentationParameter* representation) + { + E_TransferSyntax xfer; + if (!LookupDcmtkTransferSyntax(xfer, syntax)) + { + throw OrthancException(ErrorCode_InternalError); + } + else + { + if (!dicom.getDataset()->chooseRepresentation(xfer, representation).good() || + !dicom.getDataset()->canWriteXfer(xfer) || + !dicom.validateMetaInfo(xfer, EWM_updateMeta).good()) + { + return false; + } + + dicom.removeInvalidGroups(); + + return SaveToMemoryBufferInternal(buffer, dicom, xfer); + } + } ValueRepresentation FromDcmtkBridge::LookupValueRepresentation(const DicomTag& tag) @@ -2073,6 +2139,12 @@ DJEncoderRegistration::registerCodecs(); # endif #endif + + LOG(INFO) << "Registering RLE codecs in DCMTK"; + DcmRLEDecoderRegistration::registerCodecs(); +#if ORTHANC_ENABLE_DCMTK_TRANSCODING == 1 + DcmRLEEncoderRegistration::registerCodecs(); +#endif } @@ -2093,6 +2165,11 @@ DJEncoderRegistration::cleanup(); # endif #endif + + DcmRLEDecoderRegistration::cleanup(); +#if ORTHANC_ENABLE_DCMTK_TRANSCODING == 1 + DcmRLEEncoderRegistration::cleanup(); +#endif } diff -r e82bd07c384e -r 638906dcfe32 Core/DicomParsing/FromDcmtkBridge.h --- a/Core/DicomParsing/FromDcmtkBridge.h Fri Apr 10 16:12:10 2020 +0200 +++ b/Core/DicomParsing/FromDcmtkBridge.h Fri Apr 10 16:18:17 2020 +0200 @@ -205,6 +205,14 @@ static bool SaveToMemoryBuffer(std::string& buffer, DcmDataset& dataSet); + static bool SaveToMemoryBuffer(std::string& buffer, + DcmFileFormat& dicom); + + static bool Transcode(std::string& buffer, + DcmFileFormat& dicom, + DicomTransferSyntax syntax, + const DcmRepresentationParameter* representation); + static ValueRepresentation Convert(DcmEVR vr); static ValueRepresentation LookupValueRepresentation(const DicomTag& tag); diff -r e82bd07c384e -r 638906dcfe32 Core/Enumerations.h --- a/Core/Enumerations.h Fri Apr 10 16:12:10 2020 +0200 +++ b/Core/Enumerations.h Fri Apr 10 16:18:17 2020 +0200 @@ -753,7 +753,7 @@ DicomAssociationRole_Scu, DicomAssociationRole_Scp }; - + /** * WARNING: Do not change the explicit values in the enumerations diff -r e82bd07c384e -r 638906dcfe32 OrthancServer/OrthancRestApi/OrthancRestAnonymizeModify.cpp --- a/OrthancServer/OrthancRestApi/OrthancRestAnonymizeModify.cpp Fri Apr 10 16:12:10 2020 +0200 +++ b/OrthancServer/OrthancRestApi/OrthancRestAnonymizeModify.cpp Fri Apr 10 16:18:17 2020 +0200 @@ -227,7 +227,7 @@ toStore.SetParsedDicomFile(dicom); ServerContext& context = OrthancRestApi::GetContext(call); - StoreStatus status = context.Store(id, toStore); + StoreStatus status = context.Store(id, toStore, StoreInstanceMode_Default); if (status == StoreStatus_Failure) { diff -r e82bd07c384e -r 638906dcfe32 OrthancServer/OrthancRestApi/OrthancRestApi.cpp --- a/OrthancServer/OrthancRestApi/OrthancRestApi.cpp Fri Apr 10 16:12:10 2020 +0200 +++ b/OrthancServer/OrthancRestApi/OrthancRestApi.cpp Fri Apr 10 16:18:17 2020 +0200 @@ -139,7 +139,7 @@ toStore.SetBuffer(dicom); std::string publicId; - StoreStatus status = context.Store(publicId, toStore); + StoreStatus status = context.Store(publicId, toStore, StoreInstanceMode_Default); OrthancRestApi::GetApi(call).AnswerStoredInstance(call, toStore, status); } diff -r e82bd07c384e -r 638906dcfe32 OrthancServer/ServerContext.cpp --- a/OrthancServer/ServerContext.cpp Fri Apr 10 16:12:10 2020 +0200 +++ b/OrthancServer/ServerContext.cpp Fri Apr 10 16:18:17 2020 +0200 @@ -242,7 +242,8 @@ isJobsEngineUnserialized_(false), metricsRegistry_(new MetricsRegistry), isHttpServerSecure_(true), - isExecuteLuaEnabled_(false) + isExecuteLuaEnabled_(false), + overwriteInstances_(false) { { OrthancConfiguration::ReaderLock lock; @@ -339,8 +340,28 @@ StoreStatus ServerContext::Store(std::string& resultPublicId, - DicomInstanceToStore& dicom) + DicomInstanceToStore& dicom, + StoreInstanceMode mode) { + bool overwrite; + switch (mode) + { + case StoreInstanceMode_Default: + overwrite = overwriteInstances_; + break; + + case StoreInstanceMode_OverwriteDuplicate: + overwrite = true; + break; + + case StoreInstanceMode_IgnoreDuplicate: + overwrite = false; + break; + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + try { MetricsRegistry::Timer timer(GetMetricsRegistry(), "orthanc_store_dicom_duration_ms"); @@ -404,7 +425,8 @@ typedef std::map InstanceMetadata; InstanceMetadata instanceMetadata; - StoreStatus status = index_.Store(instanceMetadata, dicom, attachments); + StoreStatus status = index_.Store( + instanceMetadata, dicom, attachments, overwrite); // Only keep the metadata for the "instance" level dicom.GetMetadata().clear(); diff -r e82bd07c384e -r 638906dcfe32 OrthancServer/ServerContext.h --- a/OrthancServer/ServerContext.h Fri Apr 10 16:12:10 2020 +0200 +++ b/OrthancServer/ServerContext.h Fri Apr 10 16:18:17 2020 +0200 @@ -221,6 +221,7 @@ std::unique_ptr metricsRegistry_; bool isHttpServerSecure_; bool isExecuteLuaEnabled_; + bool overwriteInstances_; std::unique_ptr storageCommitmentReports_; @@ -275,7 +276,8 @@ size_t size); StoreStatus Store(std::string& resultPublicId, - DicomInstanceToStore& dicom); + DicomInstanceToStore& dicom, + StoreInstanceMode mode); void AnswerAttachment(RestApiOutput& output, const std::string& resourceId, @@ -426,6 +428,16 @@ return isExecuteLuaEnabled_; } + void SetOverwriteInstances(bool overwrite) + { + overwriteInstances_ = overwrite; + } + + bool IsOverwriteInstances() const + { + return overwriteInstances_; + } + virtual IStorageCommitmentFactory::ILookupHandler* CreateStorageCommitment(const std::string& jobId, const std::string& transactionUid, diff -r e82bd07c384e -r 638906dcfe32 OrthancServer/ServerEnumerations.h --- a/OrthancServer/ServerEnumerations.h Fri Apr 10 16:12:10 2020 +0200 +++ b/OrthancServer/ServerEnumerations.h Fri Apr 10 16:18:17 2020 +0200 @@ -90,6 +90,13 @@ FindStorageAccessMode_DiskOnLookupAndAnswer }; + enum StoreInstanceMode + { + StoreInstanceMode_Default, + StoreInstanceMode_OverwriteDuplicate, + StoreInstanceMode_IgnoreDuplicate + }; + /** * WARNING: Do not change the explicit values in the enumerations diff -r e82bd07c384e -r 638906dcfe32 OrthancServer/ServerIndex.cpp --- a/OrthancServer/ServerIndex.cpp Fri Apr 10 16:12:10 2020 +0200 +++ b/OrthancServer/ServerIndex.cpp Fri Apr 10 16:18:17 2020 +0200 @@ -675,7 +675,6 @@ db_(db), maximumStorageSize_(0), maximumPatients_(0), - overwrite_(false), mainDicomTagsRegistry_(new MainDicomTagsRegistry) { listener_.reset(new Listener(context)); @@ -753,7 +752,8 @@ StoreStatus ServerIndex::Store(std::map& instanceMetadata, DicomInstanceToStore& instanceToStore, - const Attachments& attachments) + const Attachments& attachments, + bool overwrite) { boost::mutex::scoped_lock lock(mutex_); @@ -784,7 +784,7 @@ { // The instance already exists - if (overwrite_) + if (overwrite) { // Overwrite the old instance LOG(INFO) << "Overwriting instance: " << hashInstance; @@ -1660,12 +1660,6 @@ StandaloneRecycling(); } - void ServerIndex::SetOverwriteInstances(bool overwrite) - { - boost::mutex::scoped_lock lock(mutex_); - overwrite_ = overwrite; - } - void ServerIndex::StandaloneRecycling() { diff -r e82bd07c384e -r 638906dcfe32 OrthancServer/ServerIndex.h --- a/OrthancServer/ServerIndex.h Fri Apr 10 16:12:10 2020 +0200 +++ b/OrthancServer/ServerIndex.h Fri Apr 10 16:18:17 2020 +0200 @@ -71,7 +71,6 @@ uint64_t maximumStorageSize_; unsigned int maximumPatients_; - bool overwrite_; std::unique_ptr mainDicomTagsRegistry_; static void FlushThread(ServerIndex* that, @@ -139,11 +138,10 @@ // "count == 0" means no limit on the number of patients void SetMaximumPatientCount(unsigned int count); - void SetOverwriteInstances(bool overwrite); - StoreStatus Store(std::map& instanceMetadata, DicomInstanceToStore& instance, - const Attachments& attachments); + const Attachments& attachments, + bool overwrite); void GetGlobalStatistics(/* out */ uint64_t& diskSize, /* out */ uint64_t& uncompressedSize, diff -r e82bd07c384e -r 638906dcfe32 OrthancServer/ServerJobs/MergeStudyJob.cpp --- a/OrthancServer/ServerJobs/MergeStudyJob.cpp Fri Apr 10 16:12:10 2020 +0200 +++ b/OrthancServer/ServerJobs/MergeStudyJob.cpp Fri Apr 10 16:18:17 2020 +0200 @@ -145,7 +145,8 @@ toStore.SetParsedDicomFile(*modified); std::string modifiedInstance; - if (context_.Store(modifiedInstance, toStore) != StoreStatus_Success) + if (context_.Store(modifiedInstance, toStore, + StoreInstanceMode_Default) != StoreStatus_Success) { LOG(ERROR) << "Error while storing a modified instance " << instance; return false; diff -r e82bd07c384e -r 638906dcfe32 OrthancServer/ServerJobs/Operations/ModifyInstanceOperation.cpp --- a/OrthancServer/ServerJobs/Operations/ModifyInstanceOperation.cpp Fri Apr 10 16:12:10 2020 +0200 +++ b/OrthancServer/ServerJobs/Operations/ModifyInstanceOperation.cpp Fri Apr 10 16:18:17 2020 +0200 @@ -114,7 +114,7 @@ toStore.AddMetadata(ResourceType_Instance, MetadataType_ModifiedFrom, instance.GetId()); std::string modifiedId; - context_.Store(modifiedId, toStore); + context_.Store(modifiedId, toStore, StoreInstanceMode_Default); // Only chain with other commands if this command succeeds outputs.Append(new DicomInstanceOperationValue(instance.GetServerContext(), modifiedId)); diff -r e82bd07c384e -r 638906dcfe32 OrthancServer/ServerJobs/ResourceModificationJob.cpp --- a/OrthancServer/ServerJobs/ResourceModificationJob.cpp Fri Apr 10 16:12:10 2020 +0200 +++ b/OrthancServer/ServerJobs/ResourceModificationJob.cpp Fri Apr 10 16:18:17 2020 +0200 @@ -211,7 +211,8 @@ **/ std::string modifiedInstance; - if (context_.Store(modifiedInstance, toStore) != StoreStatus_Success) + if (context_.Store(modifiedInstance, toStore, + StoreInstanceMode_Default) != StoreStatus_Success) { throw OrthancException(ErrorCode_CannotStoreInstance, "Error while storing a modified instance " + instance); diff -r e82bd07c384e -r 638906dcfe32 OrthancServer/ServerJobs/SplitStudyJob.cpp --- a/OrthancServer/ServerJobs/SplitStudyJob.cpp Fri Apr 10 16:12:10 2020 +0200 +++ b/OrthancServer/ServerJobs/SplitStudyJob.cpp Fri Apr 10 16:18:17 2020 +0200 @@ -138,7 +138,8 @@ toStore.SetParsedDicomFile(*modified); std::string modifiedInstance; - if (context_.Store(modifiedInstance, toStore) != StoreStatus_Success) + if (context_.Store(modifiedInstance, toStore, + StoreInstanceMode_Default) != StoreStatus_Success) { LOG(ERROR) << "Error while storing a modified instance " << instance; return false; diff -r e82bd07c384e -r 638906dcfe32 OrthancServer/main.cpp --- a/OrthancServer/main.cpp Fri Apr 10 16:12:10 2020 +0200 +++ b/OrthancServer/main.cpp Fri Apr 10 16:18:17 2020 +0200 @@ -87,7 +87,7 @@ toStore.SetJson(dicomJson); std::string id; - context_.Store(id, toStore); + context_.Store(id, toStore, StoreInstanceMode_Default); } } }; @@ -1307,7 +1307,7 @@ context.SetStoreMD5ForAttachments(lock.GetConfiguration().GetBooleanParameter("StoreMD5ForAttachments", true)); // New option in Orthanc 1.4.2 - context.GetIndex().SetOverwriteInstances(lock.GetConfiguration().GetBooleanParameter("OverwriteInstances", false)); + context.SetOverwriteInstances(lock.GetConfiguration().GetBooleanParameter("OverwriteInstances", false)); try { diff -r e82bd07c384e -r 638906dcfe32 Resources/CMake/DcmtkConfiguration.cmake --- a/Resources/CMake/DcmtkConfiguration.cmake Fri Apr 10 16:12:10 2020 +0200 +++ b/Resources/CMake/DcmtkConfiguration.cmake Fri Apr 10 16:18:17 2020 +0200 @@ -36,6 +36,14 @@ ) endif() + if (ENABLE_DCMTK_TRANSCODING) + AUX_SOURCE_DIRECTORY(${DCMTK_SOURCES_DIR}/dcmimgle/libsrc DCMTK_SOURCES) + AUX_SOURCE_DIRECTORY(${DCMTK_SOURCES_DIR}/dcmimage/libsrc DCMTK_SOURCES) + include_directories( + ${DCMTK_SOURCES_DIR}/dcmimage/include + ) + endif() + if (ENABLE_DCMTK_JPEG) AUX_SOURCE_DIRECTORY(${DCMTK_SOURCES_DIR}/dcmjpeg/libsrc DCMTK_SOURCES) AUX_SOURCE_DIRECTORY(${DCMTK_SOURCES_DIR}/dcmjpeg/libijg8 DCMTK_SOURCES) @@ -56,17 +64,21 @@ ${DCMTK_SOURCES_DIR}/dcmjpeg/libijg8/jaricom.c ${DCMTK_SOURCES_DIR}/dcmjpeg/libijg12/jaricom.c ${DCMTK_SOURCES_DIR}/dcmjpeg/libijg24/jaricom.c + ) - # Disable support for encoding JPEG (modification in Orthanc 1.0.1) - ${DCMTK_SOURCES_DIR}/dcmjpeg/libsrc/djcodece.cc - ${DCMTK_SOURCES_DIR}/dcmjpeg/libsrc/djencsv1.cc - ${DCMTK_SOURCES_DIR}/dcmjpeg/libsrc/djencbas.cc - ${DCMTK_SOURCES_DIR}/dcmjpeg/libsrc/djencpro.cc - ${DCMTK_SOURCES_DIR}/dcmjpeg/libsrc/djenclol.cc - ${DCMTK_SOURCES_DIR}/dcmjpeg/libsrc/djencode.cc - ${DCMTK_SOURCES_DIR}/dcmjpeg/libsrc/djencext.cc - ${DCMTK_SOURCES_DIR}/dcmjpeg/libsrc/djencsps.cc - ) + if (NOT ENABLE_DCMTK_TRANSCODING) + list(REMOVE_ITEM DCMTK_SOURCES + # Disable support for encoding JPEG (modification in Orthanc 1.0.1) + ${DCMTK_SOURCES_DIR}/dcmjpeg/libsrc/djcodece.cc + ${DCMTK_SOURCES_DIR}/dcmjpeg/libsrc/djencsv1.cc + ${DCMTK_SOURCES_DIR}/dcmjpeg/libsrc/djencbas.cc + ${DCMTK_SOURCES_DIR}/dcmjpeg/libsrc/djencpro.cc + ${DCMTK_SOURCES_DIR}/dcmjpeg/libsrc/djenclol.cc + ${DCMTK_SOURCES_DIR}/dcmjpeg/libsrc/djencode.cc + ${DCMTK_SOURCES_DIR}/dcmjpeg/libsrc/djencext.cc + ${DCMTK_SOURCES_DIR}/dcmjpeg/libsrc/djencsps.cc + ) + endif() endif() @@ -78,15 +90,18 @@ ${DCMTK_SOURCES_DIR}/dcmjpls/include ${DCMTK_SOURCES_DIR}/dcmjpls/libcharls ) - list(REMOVE_ITEM DCMTK_SOURCES - ${DCMTK_SOURCES_DIR}/dcmjpls/libsrc/djcodece.cc - - # Disable support for encoding JPEG-LS (modification in Orthanc 1.0.1) - ${DCMTK_SOURCES_DIR}/dcmjpls/libsrc/djencode.cc - ) list(APPEND DCMTK_SOURCES ${DCMTK_SOURCES_DIR}/dcmjpeg/libsrc/djrplol.cc ) + + if (NOT ENABLE_DCMTK_TRANSCODING) + list(REMOVE_ITEM DCMTK_SOURCES + ${DCMTK_SOURCES_DIR}/dcmjpls/libsrc/djcodece.cc + + # Disable support for encoding JPEG-LS (modification in Orthanc 1.0.1) + ${DCMTK_SOURCES_DIR}/dcmjpls/libsrc/djencode.cc + ) + endif() endif() diff -r e82bd07c384e -r 638906dcfe32 UnitTestsSources/FromDcmtkTests.cpp --- a/UnitTestsSources/FromDcmtkTests.cpp Fri Apr 10 16:12:10 2020 +0200 +++ b/UnitTestsSources/FromDcmtkTests.cpp Fri Apr 10 16:18:17 2020 +0200 @@ -1922,6 +1922,8 @@ #include #include #include +#include // for DJ_RPLossy +#include // for DJ_RPLossless namespace Orthanc @@ -1933,6 +1935,8 @@ { } + virtual DcmFileFormat& GetDicom() = 0; + virtual DicomTransferSyntax GetTransferSyntax() = 0; virtual std::string GetSopClassUid() = 0; @@ -1946,8 +1950,13 @@ virtual void GetCompressedFrame(std::string& target, unsigned int frame) = 0; - virtual IDicomTranscoder* Transcode(std::set syntaxes, - bool allowNewSopInstanceUid) = 0; + // NB: Transcoding can change the value of "GetSopInstanceUid()" + // and "GetTransferSyntax()" if lossy compression is applied + virtual bool Transcode(std::string& target, + std::set syntaxes, + bool allowNewSopInstanceUid) = 0; + + virtual void WriteToMemoryBuffer(std::string& target) = 0; }; @@ -1959,9 +1968,30 @@ DicomTransferSyntax transferSyntax_; std::string sopClassUid_; std::string sopInstanceUid_; + uint16_t bitsStored_; + unsigned int lossyQuality_; + + static std::string GetStringTag(DcmDataset& dataset, + const DcmTagKey& tag) + { + const char* value = NULL; + + if (!dataset.findAndGetString(tag, value).good() || + value == NULL) + { + throw OrthancException(ErrorCode_BadFileFormat, + "Missing SOP class/instance UID in DICOM instance"); + } + else + { + return std::string(value); + } + } void Setup(DcmFileFormat* dicom) { + lossyQuality_ = 90; + dicom_.reset(dicom); if (dicom == NULL || @@ -1992,20 +2022,14 @@ "Unsupported transfer syntax: " + boost::lexical_cast(xfer)); } - const char* a = NULL; - const char* b = NULL; - - if (!dataset.findAndGetString(DCM_SOPClassUID, a).good() || - !dataset.findAndGetString(DCM_SOPInstanceUID, b).good() || - a == NULL || - b == NULL) + if (!dataset.findAndGetUint16(DCM_BitsStored, bitsStored_).good()) { throw OrthancException(ErrorCode_BadFileFormat, - "Missing SOP class/instance UID in DICOM instance"); - } - - sopClassUid_.assign(a); - sopInstanceUid_.assign(b); + "Missing \"Bits Stored\" tag in DICOM instance"); + } + + sopClassUid_ = GetStringTag(dataset, DCM_SOPClassUID); + sopInstanceUid_ = GetStringTag(dataset, DCM_SOPInstanceUID); } public: @@ -2020,6 +2044,35 @@ Setup(FromDcmtkBridge::LoadFromMemoryBuffer(dicom, size)); } + void SetLossyQuality(unsigned int quality) + { + if (quality <= 0 || + quality > 100) + { + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + else + { + lossyQuality_ = quality; + } + } + + unsigned int GetLossyQuality() const + { + return lossyQuality_; + } + + unsigned int GetBitsStored() const + { + return bitsStored_; + } + + virtual DcmFileFormat& GetDicom() + { + assert(dicom_ != NULL); + return *dicom_; + } + virtual DicomTransferSyntax GetTransferSyntax() ORTHANC_OVERRIDE { return transferSyntax_; @@ -2040,6 +2093,15 @@ return index_->GetFramesCount(); } + virtual void WriteToMemoryBuffer(std::string& target) ORTHANC_OVERRIDE + { + if (!FromDcmtkBridge::SaveToMemoryBuffer(target, *dicom_)) + { + throw OrthancException(ErrorCode_InternalError, + "Cannot write the DICOM instance to a memory buffer"); + } + } + virtual ImageAccessor* DecodeFrame(unsigned int frame) ORTHANC_OVERRIDE { assert(dicom_->getDataset() != NULL); @@ -2049,48 +2111,69 @@ virtual void GetCompressedFrame(std::string& target, unsigned int frame) ORTHANC_OVERRIDE { -#if 1 index_->GetRawFrame(target, frame); - printf("%d: %d\n", frame, target.size()); -#endif - -#if 1 - assert(dicom_->getDataset() != NULL); - DcmDataset& dataset = *dicom_->getDataset(); + } + + virtual bool Transcode(std::string& target, + std::set syntaxes, + bool allowNewSopInstanceUid) ORTHANC_OVERRIDE + { + assert(dicom_ != NULL && + dicom_->getDataset() != NULL); - DcmPixelSequence* pixelSequence = FromDcmtkBridge::GetPixelSequence(dataset); - - if (pixelSequence != NULL && - frame == 0 && - pixelSequence->card() != GetFramesCount() + 1) + if (syntaxes.find(GetTransferSyntax()) != syntaxes.end()) { - printf("COMPRESSED\n"); - - // Check out "djcodecd.cc" - - printf("%d fragments\n", pixelSequence->card()); + printf("NO TRANSCODING\n"); - // Skip the first fragment, that is the offset table - for (unsigned long i = 1; ;i++) - { - DcmPixelItem *fragment = NULL; - if (pixelSequence->getItem(fragment, i).good()) - { - printf("fragment %d %d\n", i, fragment->getLength()); - } - else - { - break; - } - } + // No change in the transfer syntax => simply serialize the current dataset + WriteToMemoryBuffer(target); + return true; + } + + printf(">> %d\n", bitsStored_); + + DJ_RPLossy rpLossy(lossyQuality_); + + if (syntaxes.find(DicomTransferSyntax_LittleEndianImplicit) != syntaxes.end() && + FromDcmtkBridge::Transcode(target, *dicom_, DicomTransferSyntax_LittleEndianImplicit, NULL)) + { + transferSyntax_ = DicomTransferSyntax_LittleEndianImplicit; + return true; + } + else if (syntaxes.find(DicomTransferSyntax_LittleEndianExplicit) != syntaxes.end() && + FromDcmtkBridge::Transcode(target, *dicom_, DicomTransferSyntax_LittleEndianExplicit, NULL)) + { + transferSyntax_ = DicomTransferSyntax_LittleEndianExplicit; + return true; } -#endif - } - - virtual IDicomTranscoder* Transcode(std::set syntaxes, - bool allowNewSopInstanceUid) ORTHANC_OVERRIDE - { - throw OrthancException(ErrorCode_NotImplemented); + else if (syntaxes.find(DicomTransferSyntax_BigEndianExplicit) != syntaxes.end() && + FromDcmtkBridge::Transcode(target, *dicom_, DicomTransferSyntax_BigEndianExplicit, NULL)) + { + transferSyntax_ = DicomTransferSyntax_BigEndianExplicit; + return true; + } + else if (syntaxes.find(DicomTransferSyntax_JPEGProcess1) != syntaxes.end() && + allowNewSopInstanceUid && + GetBitsStored() == 8 && + FromDcmtkBridge::Transcode(target, *dicom_, DicomTransferSyntax_JPEGProcess1, &rpLossy)) + { + transferSyntax_ = DicomTransferSyntax_JPEGProcess1; + sopInstanceUid_ = GetStringTag(*dicom_->getDataset(), DCM_SOPInstanceUID); + return true; + } + else if (syntaxes.find(DicomTransferSyntax_JPEGProcess2_4) != syntaxes.end() && + allowNewSopInstanceUid && + GetBitsStored() <= 12 && + FromDcmtkBridge::Transcode(target, *dicom_, DicomTransferSyntax_JPEGProcess2_4, &rpLossy)) + { + transferSyntax_ = DicomTransferSyntax_JPEGProcess2_4; + sopInstanceUid_ = GetStringTag(*dicom_->getDataset(), DCM_SOPInstanceUID); + return true; + } + else + { + return false; + } } }; } @@ -2167,14 +2250,16 @@ } } -#include "dcmtk/dcmjpeg/djrploss.h" /* for DJ_RPLossy */ -#include "dcmtk/dcmjpeg/djrplol.h" /* for DJ_RPLossless */ #include static void TestFile(const std::string& path) { + static unsigned int count = 0; + count++; + + printf("** %s\n", path.c_str()); std::string s; @@ -2182,9 +2267,19 @@ Orthanc::DcmtkTranscoder transcoder(s.c_str(), s.size()); - printf("[%s] [%s] [%s] %d\n", GetTransferSyntaxUid(transcoder.GetTransferSyntax()), + /*if (transcoder.GetBitsStored() != 8) // TODO + return; */ + + { + char buf[1024]; + sprintf(buf, "/tmp/source-%06d.dcm", count); + printf(">> %s\n", buf); + Orthanc::SystemToolbox::WriteFile(s, buf); + } + + printf("[%s] [%s] [%s] %d %d\n", GetTransferSyntaxUid(transcoder.GetTransferSyntax()), transcoder.GetSopClassUid().c_str(), transcoder.GetSopInstanceUid().c_str(), - transcoder.GetFramesCount()); + transcoder.GetFramesCount(), transcoder.GetTransferSyntax()); for (size_t i = 0; i < transcoder.GetFramesCount(); i++) { @@ -2193,27 +2288,65 @@ if (i == 0) { - static unsigned int i = 0; char buf[1024]; - sprintf(buf, "/tmp/frame-%06d.dcm", i++); + sprintf(buf, "/tmp/frame-%06d.raw", count); printf(">> %s\n", buf); Orthanc::SystemToolbox::WriteFile(f, buf); } } + { + std::string t; + transcoder.WriteToMemoryBuffer(t); + + Orthanc::DcmtkTranscoder transcoder2(t.c_str(), t.size()); + printf(">> %d %d ; %lu bytes\n", transcoder.GetTransferSyntax(), transcoder2.GetTransferSyntax(), t.size()); + } + + { + std::string a = transcoder.GetSopInstanceUid(); + DicomTransferSyntax b = transcoder.GetTransferSyntax(); + + std::set syntaxes; + syntaxes.insert(DicomTransferSyntax_JPEGProcess2_4); + //syntaxes.insert(DicomTransferSyntax_LittleEndianExplicit); + + std::string t; + bool ok = transcoder.Transcode(t, syntaxes, true); + printf("Transcoding: %d\n", ok); + + if (ok) + { + printf("[%s] => [%s]\n", a.c_str(), transcoder.GetSopInstanceUid().c_str()); + printf("[%s] => [%s]\n", GetTransferSyntaxUid(b), + GetTransferSyntaxUid(transcoder.GetTransferSyntax())); + + { + char buf[1024]; + sprintf(buf, "/tmp/transcoded-%06d.dcm", count); + printf(">> %s\n", buf); + Orthanc::SystemToolbox::WriteFile(t, buf); + } + + Orthanc::DcmtkTranscoder transcoder2(t.c_str(), t.size()); + printf(" => transcoded transfer syntax %d ; %lu bytes\n", transcoder2.GetTransferSyntax(), t.size()); + } + } + printf("\n"); } -TEST(Toto, Transcode) +TEST(Toto, DISABLED_Transcode) { + //OFLog::configure(OFLogger::DEBUG_LOG_LEVEL); + if (0) { - OFLog::configure(OFLogger::DEBUG_LOG_LEVEL); - std::string s; //SystemToolbox::ReadFile(s, "/home/jodogne/Subversion/orthanc-tests/Database/TransferSyntaxes/1.2.840.10008.1.2.4.50.dcm"); //SystemToolbox::ReadFile(s, "/home/jodogne/DICOM/Alain.dcm"); - SystemToolbox::ReadFile(s, "/home/jodogne/Subversion/orthanc-tests/Database/Brainix/Epi/IM-0001-0002.dcm"); + //SystemToolbox::ReadFile(s, "/home/jodogne/Subversion/orthanc-tests/Database/Brainix/Epi/IM-0001-0002.dcm"); + SystemToolbox::ReadFile(s, "/home/jodogne/Subversion/orthanc-tests/Database/TransferSyntaxes/1.2.840.10008.1.2.1.dcm"); std::unique_ptr dicom(FromDcmtkBridge::LoadFromMemoryBuffer(s.c_str(), s.size())); @@ -2225,7 +2358,7 @@ #if 0 E_TransferSyntax target = EXS_LittleEndianExplicit; p = NULL; -#elif 1 +#elif 0 E_TransferSyntax target = EXS_JPEGProcess14SV1; DJ_RPLossless rp_lossless(6, 0); p = &rp_lossless; @@ -2257,10 +2390,137 @@ TestFile(it->path().string()); } } - + } + + if (0) + { TestFile("/home/jodogne/Subversion/orthanc-tests/Database/Multiframe.dcm"); TestFile("/home/jodogne/Subversion/orthanc-tests/Database/Issue44/Monochrome1-Jpeg.dcm"); } + + if (0) + { + TestFile("/home/jodogne/Subversion/orthanc-tests/Database/TransferSyntaxes/1.2.840.10008.1.2.1.dcm"); + } +} + + + +#include "../Core/DicomNetworking/DicomAssociation.h" +#include "../Core/DicomNetworking/DicomControlUserConnection.h" +#include "../Core/DicomNetworking/DicomStoreUserConnection.h" + +TEST(Toto, DISABLED_DicomAssociation) +{ + DicomAssociationParameters params; + params.SetLocalApplicationEntityTitle("ORTHANC"); + params.SetRemoteApplicationEntityTitle("PACS"); + params.SetRemotePort(2001); + +#if 0 + DicomAssociation assoc; + assoc.ProposeGenericPresentationContext(UID_StorageCommitmentPushModelSOPClass); + assoc.ProposeGenericPresentationContext(UID_VerificationSOPClass); + assoc.ProposePresentationContext(UID_ComputedRadiographyImageStorage, + DicomTransferSyntax_JPEGProcess1); + assoc.ProposePresentationContext(UID_ComputedRadiographyImageStorage, + DicomTransferSyntax_JPEGProcess2_4); + assoc.ProposePresentationContext(UID_ComputedRadiographyImageStorage, + DicomTransferSyntax_JPEG2000); + + assoc.Open(params); + + int presID = ASC_findAcceptedPresentationContextID(&assoc.GetDcmtkAssociation(), UID_ComputedRadiographyImageStorage); + printf(">> %d\n", presID); + + std::map pc; + printf(">> %d\n", assoc.LookupAcceptedPresentationContext(pc, UID_ComputedRadiographyImageStorage)); + + for (std::map::const_iterator + it = pc.begin(); it != pc.end(); ++it) + { + printf("[%s] => %d\n", GetTransferSyntaxUid(it->first), it->second); + } +#else + { + DicomControlUserConnection assoc(params); + + try + { + printf(">> %d\n", assoc.Echo()); + } + catch (OrthancException&) + { + } + } + + params.SetRemoteApplicationEntityTitle("PACS"); + params.SetRemotePort(2000); + + { + DicomControlUserConnection assoc(params); + printf(">> %d\n", assoc.Echo()); + } + +#endif +} + +static void TestTranscode(DicomStoreUserConnection& scu, + const std::string& sopClassUid, + DicomTransferSyntax transferSyntax) +{ + uint8_t id; + + if (scu.NegotiatePresentationContext(id, sopClassUid, transferSyntax)) + { + printf("**** OK, without transcoding !! %d\n", id); + } + else + { + // Transcoding - only in Orthanc >= 1.7.0 + + const DicomTransferSyntax uncompressed[] = { + DicomTransferSyntax_LittleEndianImplicit, // Default transfer syntax + DicomTransferSyntax_LittleEndianExplicit, + DicomTransferSyntax_BigEndianExplicit + }; + + bool found = false; + for (size_t i = 0; i < 3; i++) + { + if (scu.LookupPresentationContext(id, sopClassUid, uncompressed[i])) + { + printf("**** TRANSCODING to %s => %d\n", + GetTransferSyntaxUid(uncompressed[i]), id); + found = true; + break; + } + } + + if (!found) + { + printf("**** KO KO KO\n"); + } + } +} + + +TEST(Toto, DISABLED_Store) +{ + DicomAssociationParameters params; + params.SetLocalApplicationEntityTitle("ORTHANC"); + params.SetRemoteApplicationEntityTitle("PACS"); + params.SetRemotePort(2000); + + DicomStoreUserConnection assoc(params); + assoc.PrepareStorageClass(UID_MRImageStorage, DicomTransferSyntax_JPEGProcess1); + assoc.PrepareStorageClass(UID_MRImageStorage, DicomTransferSyntax_JPEGProcess2_4); + //assoc.PrepareStorageClass(UID_MRImageStorage, DicomTransferSyntax_LittleEndianExplicit); + + //assoc.SetUncompressedSyntaxesProposed(false); + //assoc.SetCommonClassesProposed(false); + TestTranscode(assoc, UID_MRImageStorage, DicomTransferSyntax_JPEG2000); + //TestTranscode(assoc, UID_MRImageStorage, DicomTransferSyntax_LittleEndianExplicit); } #endif diff -r e82bd07c384e -r 638906dcfe32 UnitTestsSources/MultiThreadingTests.cpp --- a/UnitTestsSources/MultiThreadingTests.cpp Fri Apr 10 16:12:10 2020 +0200 +++ b/UnitTestsSources/MultiThreadingTests.cpp Fri Apr 10 16:18:17 2020 +0200 @@ -1316,7 +1316,7 @@ DicomInstanceToStore toStore; toStore.SetParsedDicomFile(dicom); - return (context_->Store(id, toStore) == StoreStatus_Success); + return (context_->Store(id, toStore, StoreInstanceMode_Default) == StoreStatus_Success); } }; } diff -r e82bd07c384e -r 638906dcfe32 UnitTestsSources/ServerIndexTests.cpp --- a/UnitTestsSources/ServerIndexTests.cpp Fri Apr 10 16:12:10 2020 +0200 +++ b/UnitTestsSources/ServerIndexTests.cpp Fri Apr 10 16:18:17 2020 +0200 @@ -726,7 +726,8 @@ std::map instanceMetadata; DicomInstanceToStore toStore; toStore.SetSummary(instance); - ASSERT_EQ(StoreStatus_Success, index.Store(instanceMetadata, toStore, attachments)); + ASSERT_EQ(StoreStatus_Success, index.Store(instanceMetadata, toStore, attachments, + false /* don't overwrite */)); ASSERT_EQ(5u, instanceMetadata.size()); ASSERT_TRUE(instanceMetadata.find(MetadataType_Instance_RemoteAet) != instanceMetadata.end()); ASSERT_TRUE(instanceMetadata.find(MetadataType_Instance_ReceptionDate) != instanceMetadata.end()); @@ -803,7 +804,7 @@ DicomInstanceHasher hasher(instance); std::string id = hasher.HashInstance(); - context.GetIndex().SetOverwriteInstances(overwrite); + context.SetOverwriteInstances(overwrite); uint64_t diskSize, uncompressedSize, countPatients, countStudies, countSeries, countInstances; context.GetIndex().GetGlobalStatistics(diskSize, uncompressedSize, countPatients, @@ -819,7 +820,7 @@ ASSERT_EQ(id, toStore.GetHasher().HashInstance()); std::string id2; - ASSERT_EQ(StoreStatus_Success, context.Store(id2, toStore)); + ASSERT_EQ(StoreStatus_Success, context.Store(id2, toStore, StoreInstanceMode_Default)); ASSERT_EQ(id, id2); } @@ -854,7 +855,8 @@ toStore.SetOrigin(DicomInstanceOrigin::FromPlugins()); std::string id2; - ASSERT_EQ(overwrite ? StoreStatus_Success : StoreStatus_AlreadyStored, context.Store(id2, toStore)); + ASSERT_EQ(overwrite ? StoreStatus_Success : StoreStatus_AlreadyStored, + context.Store(id2, toStore, StoreInstanceMode_Default)); ASSERT_EQ(id, id2); }