# HG changeset patch # User Sebastien Jodogne # Date 1586522360 -7200 # Node ID 897ca31032534a47310a3b01efa9241bf2e9f155 # Parent 0d5f3a438e14c22a202c24c0b560cdf60f13d90c new class: DicomStoreUserConnection diff -r 0d5f3a438e14 -r 897ca3103253 UnitTestsSources/FromDcmtkTests.cpp --- a/UnitTestsSources/FromDcmtkTests.cpp Fri Apr 10 11:39:40 2020 +0200 +++ b/UnitTestsSources/FromDcmtkTests.cpp Fri Apr 10 14:39:20 2020 +0200 @@ -2680,7 +2680,7 @@ struct ProposedPresentationContext { - std::string sopClassUid_; + std::string abstractSyntax_; std::set transferSyntaxes_; }; @@ -2748,17 +2748,17 @@ isOpen_ = false; } - void AddAccepted(const std::string& sopClassUid, + void AddAccepted(const std::string& abstractSyntax, DicomTransferSyntax syntax, uint8_t presentationContextId) { - AcceptedPresentationContexts::iterator found = accepted_.find(sopClassUid); + AcceptedPresentationContexts::iterator found = accepted_.find(abstractSyntax); if (found == accepted_.end()) { std::map syntaxes; syntaxes[syntax] = presentationContextId; - accepted_[sopClassUid] = syntaxes; + accepted_[abstractSyntax] = syntaxes; } else { @@ -2766,8 +2766,8 @@ { LOG(WARNING) << "The same transfer syntax (" << GetTransferSyntaxUid(syntax) - << ") was accepted twice for the same SOP class UID (" - << sopClassUid << ")"; + << ") was accepted twice for the same abstract syntax UID (" + << abstractSyntax << ")"; } else { @@ -2907,7 +2907,7 @@ for (size_t i = 0; i < proposed_.size(); i++) { assert(presentationContextId <= 255); - const char* sopClassUid = proposed_[i].sopClassUid_.c_str(); + const char* abstractSyntax = proposed_[i].abstractSyntax_.c_str(); const std::set& source = proposed_[i].transferSyntaxes_; @@ -2922,7 +2922,7 @@ assert(!transferSyntaxes.empty()); CheckConnecting(parameters, ASC_addPresentationContext( - params_, presentationContextId, sopClassUid, + params_, presentationContextId, abstractSyntax, &transferSyntaxes[0], transferSyntaxes.size(), dcmtkRole)); presentationContextId += 2; @@ -2976,14 +2976,14 @@ } bool LookupAcceptedPresentationContext(std::map& target, - const std::string& sopClassUid) const + const std::string& abstractSyntax) const { if (!IsOpen()) { throw OrthancException(ErrorCode_BadSequenceOfCalls, "Connection not opened"); } - AcceptedPresentationContexts::const_iterator found = accepted_.find(sopClassUid); + AcceptedPresentationContexts::const_iterator found = accepted_.find(abstractSyntax); if (found == accepted_.end()) { @@ -2996,21 +2996,21 @@ } } - void ProposeGenericPresentationContext(const std::string& sopClassUid) + void ProposeGenericPresentationContext(const std::string& abstractSyntax) { std::set ts; ts.insert(DicomTransferSyntax_LittleEndianImplicit); ts.insert(DicomTransferSyntax_LittleEndianExplicit); - ts.insert(DicomTransferSyntax_BigEndianExplicit); - ProposePresentationContext(sopClassUid, ts); + ts.insert(DicomTransferSyntax_BigEndianExplicit); // Retired + ProposePresentationContext(abstractSyntax, ts); } - void ProposePresentationContext(const std::string& sopClassUid, + void ProposePresentationContext(const std::string& abstractSyntax, DicomTransferSyntax transferSyntax) { std::set ts; ts.insert(transferSyntax); - ProposePresentationContext(sopClassUid, ts); + ProposePresentationContext(abstractSyntax, ts); } size_t GetRemainingPropositions() const @@ -3019,7 +3019,7 @@ return MAX_PROPOSED_PRESENTATIONS - proposed_.size(); } - void ProposePresentationContext(const std::string& sopClassUid, + void ProposePresentationContext(const std::string& abstractSyntax, const std::set& transferSyntaxes) { if (transferSyntaxes.empty()) @@ -3040,7 +3040,7 @@ } ProposedPresentationContext context; - context.sopClassUid_ = sopClassUid; + context.abstractSyntax_ = abstractSyntax; context.transferSyntaxes_ = transferSyntaxes; proposed_.push_back(context); @@ -3791,26 +3791,12 @@ } public: - DicomControlUserConnection() - { - SetupPresentationContexts(); - } - DicomControlUserConnection(const DicomAssociationParameters& params) : parameters_(params) { SetupPresentationContexts(); } - void SetParameters(const DicomAssociationParameters& params) - { - if (!parameters_.IsEqual(params)) - { - association_.Close(); - parameters_ = params; - } - } - const DicomAssociationParameters& GetParameters() const { return parameters_; @@ -4043,12 +4029,302 @@ }; - class DicomStorageUserConnection : public boost::noncopyable + class DicomStoreUserConnection : public boost::noncopyable { private: - std::unique_ptr association_; - + typedef std::map > StorageClasses; + + DicomAssociationParameters parameters_; + DicomAssociation association_; + StorageClasses storageClasses_; + bool proposeCommonClasses_; + bool proposeUncompressedSyntaxes_; + bool proposeRetiredBigEndian_; + + + /** + + Orthanc < 1.7.0: + + Input | Output + -------------+--------------------------------------------- + Compressed | Same transfer syntax + Uncompressed | Same transfer syntax, or other uncompressed + + Orthanc >= 1.7.0: + + Input | Output + -------------+--------------------------------------------- + Compressed | Same transfer syntax, or uncompressed + Uncompressed | Same transfer syntax, or other uncompressed + + **/ + + + // Return "false" if there is not enough room remaining in the association + bool ProposeStorageClass(const std::string& sopClassUid, + const std::set& syntaxes) + { + size_t requiredCount = syntaxes.size(); + if (proposeUncompressedSyntaxes_) + { + requiredCount += 1; + } + + if (association_.GetRemainingPropositions() <= requiredCount) + { + return false; // Not enough room + } + + for (std::set::const_iterator + it = syntaxes.begin(); it != syntaxes.end(); ++it) + { + association_.ProposePresentationContext(sopClassUid, *it); + } + + if (proposeUncompressedSyntaxes_) + { + std::set uncompressed; + + if (syntaxes.find(DicomTransferSyntax_LittleEndianImplicit) == syntaxes.end()) + { + uncompressed.insert(DicomTransferSyntax_LittleEndianImplicit); + } + + if (syntaxes.find(DicomTransferSyntax_LittleEndianExplicit) == syntaxes.end()) + { + uncompressed.insert(DicomTransferSyntax_LittleEndianExplicit); + } + + if (proposeRetiredBigEndian_ && + syntaxes.find(DicomTransferSyntax_BigEndianExplicit) == syntaxes.end()) + { + uncompressed.insert(DicomTransferSyntax_BigEndianExplicit); + } + + if (!uncompressed.empty()) + { + association_.ProposePresentationContext(sopClassUid, uncompressed); + } + } + + return true; + } + + + bool LookupPresentationContext(uint8_t& presentationContextId, + const std::string& sopClassUid, + DicomTransferSyntax transferSyntax) + { + typedef std::map PresentationContexts; + + PresentationContexts pc; + if (association_.IsOpen() && + association_.LookupAcceptedPresentationContext(pc, sopClassUid)) + { + PresentationContexts::const_iterator found = pc.find(transferSyntax); + if (found != pc.end()) + { + presentationContextId = found->second; + return true; + } + } + + return false; + } + + + bool NegotiatePresentationContext(uint8_t& presentationContextId, + const std::string& sopClassUid, + DicomTransferSyntax transferSyntax) + { + /** + * Step 1: Check whether this presentation context is already + * available in the previously negociated assocation. + **/ + + if (LookupPresentationContext(presentationContextId, sopClassUid, transferSyntax)) + { + return true; + } + + // The association must be re-negotiated + association_.ClearPresentationContexts(); + PrepareStorageClass(sopClassUid, transferSyntax); + + + /** + * Step 2: Propose at least the mandatory SOP class. + **/ + + { + StorageClasses::const_iterator mandatory = storageClasses_.find(sopClassUid); + + if (mandatory == storageClasses_.end() || + mandatory->second.find(transferSyntax) == mandatory->second.end()) + { + throw OrthancException(ErrorCode_InternalError); + } + + if (!ProposeStorageClass(sopClassUid, mandatory->second)) + { + // Should never happen in real life: There are no more than + // 128 transfer syntaxes in DICOM! + throw OrthancException(ErrorCode_InternalError, + "Too many transfer syntaxes for SOP class UID: " + sopClassUid); + } + } + + + /** + * Step 3: Propose all the previously spotted SOP classes, as + * registered through the "PrepareStorageClass()" method. + **/ + + for (StorageClasses::const_iterator it = storageClasses_.begin(); + it != storageClasses_.end(); ++it) + { + if (it->first != sopClassUid) + { + ProposeStorageClass(it->first, it->second); + } + } + + + /** + * Step 4: As long as there is room left in the proposed + * presentation contexts, propose the uncompressed transfer syntaxes + * for the most common SOP classes, as can be found in the + * "dcmShortSCUStorageSOPClassUIDs" array from DCMTK. The + * preferred transfer syntax is "LittleEndianImplicit". + **/ + + if (proposeCommonClasses_) + { + std::set ts; + ts.insert(DicomTransferSyntax_LittleEndianImplicit); + + for (int i = 0; i < numberOfDcmShortSCUStorageSOPClassUIDs; i++) + { + std::string c(dcmShortSCUStorageSOPClassUIDs[i]); + + if (c != sopClassUid && + storageClasses_.find(c) == storageClasses_.end()) + { + ProposeStorageClass(c, ts); + } + } + } + + + /** + * Step 5: Open the association, and check whether the pair (SOP + * class UID, transfer syntax) was accepted by the remote host. + **/ + + association_.Open(parameters_); + return LookupPresentationContext(presentationContextId, sopClassUid, transferSyntax); + } + public: + DicomStoreUserConnection(const DicomAssociationParameters& params) : + parameters_(params), + proposeCommonClasses_(true), + proposeUncompressedSyntaxes_(true), + proposeRetiredBigEndian_(false) + { + } + + const DicomAssociationParameters& GetParameters() const + { + return parameters_; + } + + void SetCommonClassesProposed(bool proposed) + { + proposeCommonClasses_ = proposed; + } + + bool IsCommonClassesProposed() const + { + return proposeCommonClasses_; + } + + void SetUncompressedSyntaxesProposed(bool proposed) + { + proposeUncompressedSyntaxes_ = proposed; + } + + bool IsUncompressedSyntaxesProposed() const + { + return proposeUncompressedSyntaxes_; + } + + void SetRetiredBigEndianProposed(bool propose) + { + proposeRetiredBigEndian_ = propose; + } + + bool IsRetiredBigEndianProposed() const + { + return proposeRetiredBigEndian_; + } + + void PrepareStorageClass(const std::string& sopClassUid, + DicomTransferSyntax syntax) + { + StorageClasses::iterator found = storageClasses_.find(sopClassUid); + + if (found == storageClasses_.end()) + { + std::set ts; + ts.insert(syntax); + storageClasses_[sopClassUid] = ts; + } + else + { + found->second.insert(syntax); + } + } + + + void Toto(const std::string& sopClassUid, + DicomTransferSyntax transferSyntax) + { + uint8_t id; + + if (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 (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"); + } + } + } }; } @@ -4085,23 +4361,46 @@ printf("[%s] => %d\n", GetTransferSyntaxUid(it->first), it->second); } #else - DicomControlUserConnection assoc(params); - - try { + 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()); } - catch (OrthancException&) - { - } - - params.SetRemoteApplicationEntityTitle("PACS"); - params.SetRemotePort(2000); - assoc.SetParameters(params); - printf(">> %d\n", assoc.Echo()); #endif } +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); + assoc.Toto(UID_MRImageStorage, DicomTransferSyntax_JPEG2000); + //assoc.Toto(UID_MRImageStorage, DicomTransferSyntax_LittleEndianExplicit); +} + #endif