comparison UnitTestsSources/FromDcmtkTests.cpp @ 3823:897ca3103253 transcoding

new class: DicomStoreUserConnection
author Sebastien Jodogne <s.jodogne@gmail.com>
date Fri, 10 Apr 2020 14:39:20 +0200
parents 0d5f3a438e14
children 6762506ef4fb
comparison
equal deleted inserted replaced
3822:0d5f3a438e14 3823:897ca3103253
2678 // http://dicom.nema.org/medical/dicom/2019e/output/chtml/part08/sect_9.3.2.2.html 2678 // http://dicom.nema.org/medical/dicom/2019e/output/chtml/part08/sect_9.3.2.2.html
2679 static const size_t MAX_PROPOSED_PRESENTATIONS = 128; 2679 static const size_t MAX_PROPOSED_PRESENTATIONS = 128;
2680 2680
2681 struct ProposedPresentationContext 2681 struct ProposedPresentationContext
2682 { 2682 {
2683 std::string sopClassUid_; 2683 std::string abstractSyntax_;
2684 std::set<DicomTransferSyntax> transferSyntaxes_; 2684 std::set<DicomTransferSyntax> transferSyntaxes_;
2685 }; 2685 };
2686 2686
2687 typedef std::map<std::string, std::map<DicomTransferSyntax, uint8_t> > AcceptedPresentationContexts; 2687 typedef std::map<std::string, std::map<DicomTransferSyntax, uint8_t> > AcceptedPresentationContexts;
2688 2688
2746 2746
2747 accepted_.clear(); 2747 accepted_.clear();
2748 isOpen_ = false; 2748 isOpen_ = false;
2749 } 2749 }
2750 2750
2751 void AddAccepted(const std::string& sopClassUid, 2751 void AddAccepted(const std::string& abstractSyntax,
2752 DicomTransferSyntax syntax, 2752 DicomTransferSyntax syntax,
2753 uint8_t presentationContextId) 2753 uint8_t presentationContextId)
2754 { 2754 {
2755 AcceptedPresentationContexts::iterator found = accepted_.find(sopClassUid); 2755 AcceptedPresentationContexts::iterator found = accepted_.find(abstractSyntax);
2756 2756
2757 if (found == accepted_.end()) 2757 if (found == accepted_.end())
2758 { 2758 {
2759 std::map<DicomTransferSyntax, uint8_t> syntaxes; 2759 std::map<DicomTransferSyntax, uint8_t> syntaxes;
2760 syntaxes[syntax] = presentationContextId; 2760 syntaxes[syntax] = presentationContextId;
2761 accepted_[sopClassUid] = syntaxes; 2761 accepted_[abstractSyntax] = syntaxes;
2762 } 2762 }
2763 else 2763 else
2764 { 2764 {
2765 if (found->second.find(syntax) != found->second.end()) 2765 if (found->second.find(syntax) != found->second.end())
2766 { 2766 {
2767 LOG(WARNING) << "The same transfer syntax (" 2767 LOG(WARNING) << "The same transfer syntax ("
2768 << GetTransferSyntaxUid(syntax) 2768 << GetTransferSyntaxUid(syntax)
2769 << ") was accepted twice for the same SOP class UID (" 2769 << ") was accepted twice for the same abstract syntax UID ("
2770 << sopClassUid << ")"; 2770 << abstractSyntax << ")";
2771 } 2771 }
2772 else 2772 else
2773 { 2773 {
2774 found->second[syntax] = presentationContextId; 2774 found->second[syntax] = presentationContextId;
2775 } 2775 }
2905 // Setup the list of proposed presentation contexts 2905 // Setup the list of proposed presentation contexts
2906 unsigned int presentationContextId = 1; 2906 unsigned int presentationContextId = 1;
2907 for (size_t i = 0; i < proposed_.size(); i++) 2907 for (size_t i = 0; i < proposed_.size(); i++)
2908 { 2908 {
2909 assert(presentationContextId <= 255); 2909 assert(presentationContextId <= 255);
2910 const char* sopClassUid = proposed_[i].sopClassUid_.c_str(); 2910 const char* abstractSyntax = proposed_[i].abstractSyntax_.c_str();
2911 2911
2912 const std::set<DicomTransferSyntax>& source = proposed_[i].transferSyntaxes_; 2912 const std::set<DicomTransferSyntax>& source = proposed_[i].transferSyntaxes_;
2913 2913
2914 std::vector<const char*> transferSyntaxes; 2914 std::vector<const char*> transferSyntaxes;
2915 transferSyntaxes.reserve(source.size()); 2915 transferSyntaxes.reserve(source.size());
2920 transferSyntaxes.push_back(GetTransferSyntaxUid(*it)); 2920 transferSyntaxes.push_back(GetTransferSyntaxUid(*it));
2921 } 2921 }
2922 2922
2923 assert(!transferSyntaxes.empty()); 2923 assert(!transferSyntaxes.empty());
2924 CheckConnecting(parameters, ASC_addPresentationContext( 2924 CheckConnecting(parameters, ASC_addPresentationContext(
2925 params_, presentationContextId, sopClassUid, 2925 params_, presentationContextId, abstractSyntax,
2926 &transferSyntaxes[0], transferSyntaxes.size(), dcmtkRole)); 2926 &transferSyntaxes[0], transferSyntaxes.size(), dcmtkRole));
2927 2927
2928 presentationContextId += 2; 2928 presentationContextId += 2;
2929 } 2929 }
2930 2930
2974 CloseInternal(); 2974 CloseInternal();
2975 } 2975 }
2976 } 2976 }
2977 2977
2978 bool LookupAcceptedPresentationContext(std::map<DicomTransferSyntax, uint8_t>& target, 2978 bool LookupAcceptedPresentationContext(std::map<DicomTransferSyntax, uint8_t>& target,
2979 const std::string& sopClassUid) const 2979 const std::string& abstractSyntax) const
2980 { 2980 {
2981 if (!IsOpen()) 2981 if (!IsOpen())
2982 { 2982 {
2983 throw OrthancException(ErrorCode_BadSequenceOfCalls, "Connection not opened"); 2983 throw OrthancException(ErrorCode_BadSequenceOfCalls, "Connection not opened");
2984 } 2984 }
2985 2985
2986 AcceptedPresentationContexts::const_iterator found = accepted_.find(sopClassUid); 2986 AcceptedPresentationContexts::const_iterator found = accepted_.find(abstractSyntax);
2987 2987
2988 if (found == accepted_.end()) 2988 if (found == accepted_.end())
2989 { 2989 {
2990 return false; 2990 return false;
2991 } 2991 }
2994 target = found->second; 2994 target = found->second;
2995 return true; 2995 return true;
2996 } 2996 }
2997 } 2997 }
2998 2998
2999 void ProposeGenericPresentationContext(const std::string& sopClassUid) 2999 void ProposeGenericPresentationContext(const std::string& abstractSyntax)
3000 { 3000 {
3001 std::set<DicomTransferSyntax> ts; 3001 std::set<DicomTransferSyntax> ts;
3002 ts.insert(DicomTransferSyntax_LittleEndianImplicit); 3002 ts.insert(DicomTransferSyntax_LittleEndianImplicit);
3003 ts.insert(DicomTransferSyntax_LittleEndianExplicit); 3003 ts.insert(DicomTransferSyntax_LittleEndianExplicit);
3004 ts.insert(DicomTransferSyntax_BigEndianExplicit); 3004 ts.insert(DicomTransferSyntax_BigEndianExplicit); // Retired
3005 ProposePresentationContext(sopClassUid, ts); 3005 ProposePresentationContext(abstractSyntax, ts);
3006 } 3006 }
3007 3007
3008 void ProposePresentationContext(const std::string& sopClassUid, 3008 void ProposePresentationContext(const std::string& abstractSyntax,
3009 DicomTransferSyntax transferSyntax) 3009 DicomTransferSyntax transferSyntax)
3010 { 3010 {
3011 std::set<DicomTransferSyntax> ts; 3011 std::set<DicomTransferSyntax> ts;
3012 ts.insert(transferSyntax); 3012 ts.insert(transferSyntax);
3013 ProposePresentationContext(sopClassUid, ts); 3013 ProposePresentationContext(abstractSyntax, ts);
3014 } 3014 }
3015 3015
3016 size_t GetRemainingPropositions() const 3016 size_t GetRemainingPropositions() const
3017 { 3017 {
3018 assert(proposed_.size() <= MAX_PROPOSED_PRESENTATIONS); 3018 assert(proposed_.size() <= MAX_PROPOSED_PRESENTATIONS);
3019 return MAX_PROPOSED_PRESENTATIONS - proposed_.size(); 3019 return MAX_PROPOSED_PRESENTATIONS - proposed_.size();
3020 } 3020 }
3021 3021
3022 void ProposePresentationContext(const std::string& sopClassUid, 3022 void ProposePresentationContext(const std::string& abstractSyntax,
3023 const std::set<DicomTransferSyntax>& transferSyntaxes) 3023 const std::set<DicomTransferSyntax>& transferSyntaxes)
3024 { 3024 {
3025 if (transferSyntaxes.empty()) 3025 if (transferSyntaxes.empty())
3026 { 3026 {
3027 throw OrthancException(ErrorCode_ParameterOutOfRange, 3027 throw OrthancException(ErrorCode_ParameterOutOfRange,
3038 { 3038 {
3039 Close(); 3039 Close();
3040 } 3040 }
3041 3041
3042 ProposedPresentationContext context; 3042 ProposedPresentationContext context;
3043 context.sopClassUid_ = sopClassUid; 3043 context.abstractSyntax_ = abstractSyntax;
3044 context.transferSyntaxes_ = transferSyntaxes; 3044 context.transferSyntaxes_ = transferSyntaxes;
3045 3045
3046 proposed_.push_back(context); 3046 proposed_.push_back(context);
3047 } 3047 }
3048 3048
3789 } 3789 }
3790 } 3790 }
3791 } 3791 }
3792 3792
3793 public: 3793 public:
3794 DicomControlUserConnection()
3795 {
3796 SetupPresentationContexts();
3797 }
3798
3799 DicomControlUserConnection(const DicomAssociationParameters& params) : 3794 DicomControlUserConnection(const DicomAssociationParameters& params) :
3800 parameters_(params) 3795 parameters_(params)
3801 { 3796 {
3802 SetupPresentationContexts(); 3797 SetupPresentationContexts();
3803 } 3798 }
3804 3799
3805 void SetParameters(const DicomAssociationParameters& params)
3806 {
3807 if (!parameters_.IsEqual(params))
3808 {
3809 association_.Close();
3810 parameters_ = params;
3811 }
3812 }
3813
3814 const DicomAssociationParameters& GetParameters() const 3800 const DicomAssociationParameters& GetParameters() const
3815 { 3801 {
3816 return parameters_; 3802 return parameters_;
3817 } 3803 }
3818 3804
4041 FindInternal(result, dataset, sopClass, true, NULL); 4027 FindInternal(result, dataset, sopClass, true, NULL);
4042 } 4028 }
4043 }; 4029 };
4044 4030
4045 4031
4046 class DicomStorageUserConnection : public boost::noncopyable 4032 class DicomStoreUserConnection : public boost::noncopyable
4047 { 4033 {
4048 private: 4034 private:
4049 std::unique_ptr<DicomAssociation> association_; 4035 typedef std::map<std::string, std::set<DicomTransferSyntax> > StorageClasses;
4050 4036
4037 DicomAssociationParameters parameters_;
4038 DicomAssociation association_;
4039 StorageClasses storageClasses_;
4040 bool proposeCommonClasses_;
4041 bool proposeUncompressedSyntaxes_;
4042 bool proposeRetiredBigEndian_;
4043
4044
4045 /**
4046
4047 Orthanc < 1.7.0:
4048
4049 Input | Output
4050 -------------+---------------------------------------------
4051 Compressed | Same transfer syntax
4052 Uncompressed | Same transfer syntax, or other uncompressed
4053
4054 Orthanc >= 1.7.0:
4055
4056 Input | Output
4057 -------------+---------------------------------------------
4058 Compressed | Same transfer syntax, or uncompressed
4059 Uncompressed | Same transfer syntax, or other uncompressed
4060
4061 **/
4062
4063
4064 // Return "false" if there is not enough room remaining in the association
4065 bool ProposeStorageClass(const std::string& sopClassUid,
4066 const std::set<DicomTransferSyntax>& syntaxes)
4067 {
4068 size_t requiredCount = syntaxes.size();
4069 if (proposeUncompressedSyntaxes_)
4070 {
4071 requiredCount += 1;
4072 }
4073
4074 if (association_.GetRemainingPropositions() <= requiredCount)
4075 {
4076 return false; // Not enough room
4077 }
4078
4079 for (std::set<DicomTransferSyntax>::const_iterator
4080 it = syntaxes.begin(); it != syntaxes.end(); ++it)
4081 {
4082 association_.ProposePresentationContext(sopClassUid, *it);
4083 }
4084
4085 if (proposeUncompressedSyntaxes_)
4086 {
4087 std::set<DicomTransferSyntax> uncompressed;
4088
4089 if (syntaxes.find(DicomTransferSyntax_LittleEndianImplicit) == syntaxes.end())
4090 {
4091 uncompressed.insert(DicomTransferSyntax_LittleEndianImplicit);
4092 }
4093
4094 if (syntaxes.find(DicomTransferSyntax_LittleEndianExplicit) == syntaxes.end())
4095 {
4096 uncompressed.insert(DicomTransferSyntax_LittleEndianExplicit);
4097 }
4098
4099 if (proposeRetiredBigEndian_ &&
4100 syntaxes.find(DicomTransferSyntax_BigEndianExplicit) == syntaxes.end())
4101 {
4102 uncompressed.insert(DicomTransferSyntax_BigEndianExplicit);
4103 }
4104
4105 if (!uncompressed.empty())
4106 {
4107 association_.ProposePresentationContext(sopClassUid, uncompressed);
4108 }
4109 }
4110
4111 return true;
4112 }
4113
4114
4115 bool LookupPresentationContext(uint8_t& presentationContextId,
4116 const std::string& sopClassUid,
4117 DicomTransferSyntax transferSyntax)
4118 {
4119 typedef std::map<DicomTransferSyntax, uint8_t> PresentationContexts;
4120
4121 PresentationContexts pc;
4122 if (association_.IsOpen() &&
4123 association_.LookupAcceptedPresentationContext(pc, sopClassUid))
4124 {
4125 PresentationContexts::const_iterator found = pc.find(transferSyntax);
4126 if (found != pc.end())
4127 {
4128 presentationContextId = found->second;
4129 return true;
4130 }
4131 }
4132
4133 return false;
4134 }
4135
4136
4137 bool NegotiatePresentationContext(uint8_t& presentationContextId,
4138 const std::string& sopClassUid,
4139 DicomTransferSyntax transferSyntax)
4140 {
4141 /**
4142 * Step 1: Check whether this presentation context is already
4143 * available in the previously negociated assocation.
4144 **/
4145
4146 if (LookupPresentationContext(presentationContextId, sopClassUid, transferSyntax))
4147 {
4148 return true;
4149 }
4150
4151 // The association must be re-negotiated
4152 association_.ClearPresentationContexts();
4153 PrepareStorageClass(sopClassUid, transferSyntax);
4154
4155
4156 /**
4157 * Step 2: Propose at least the mandatory SOP class.
4158 **/
4159
4160 {
4161 StorageClasses::const_iterator mandatory = storageClasses_.find(sopClassUid);
4162
4163 if (mandatory == storageClasses_.end() ||
4164 mandatory->second.find(transferSyntax) == mandatory->second.end())
4165 {
4166 throw OrthancException(ErrorCode_InternalError);
4167 }
4168
4169 if (!ProposeStorageClass(sopClassUid, mandatory->second))
4170 {
4171 // Should never happen in real life: There are no more than
4172 // 128 transfer syntaxes in DICOM!
4173 throw OrthancException(ErrorCode_InternalError,
4174 "Too many transfer syntaxes for SOP class UID: " + sopClassUid);
4175 }
4176 }
4177
4178
4179 /**
4180 * Step 3: Propose all the previously spotted SOP classes, as
4181 * registered through the "PrepareStorageClass()" method.
4182 **/
4183
4184 for (StorageClasses::const_iterator it = storageClasses_.begin();
4185 it != storageClasses_.end(); ++it)
4186 {
4187 if (it->first != sopClassUid)
4188 {
4189 ProposeStorageClass(it->first, it->second);
4190 }
4191 }
4192
4193
4194 /**
4195 * Step 4: As long as there is room left in the proposed
4196 * presentation contexts, propose the uncompressed transfer syntaxes
4197 * for the most common SOP classes, as can be found in the
4198 * "dcmShortSCUStorageSOPClassUIDs" array from DCMTK. The
4199 * preferred transfer syntax is "LittleEndianImplicit".
4200 **/
4201
4202 if (proposeCommonClasses_)
4203 {
4204 std::set<DicomTransferSyntax> ts;
4205 ts.insert(DicomTransferSyntax_LittleEndianImplicit);
4206
4207 for (int i = 0; i < numberOfDcmShortSCUStorageSOPClassUIDs; i++)
4208 {
4209 std::string c(dcmShortSCUStorageSOPClassUIDs[i]);
4210
4211 if (c != sopClassUid &&
4212 storageClasses_.find(c) == storageClasses_.end())
4213 {
4214 ProposeStorageClass(c, ts);
4215 }
4216 }
4217 }
4218
4219
4220 /**
4221 * Step 5: Open the association, and check whether the pair (SOP
4222 * class UID, transfer syntax) was accepted by the remote host.
4223 **/
4224
4225 association_.Open(parameters_);
4226 return LookupPresentationContext(presentationContextId, sopClassUid, transferSyntax);
4227 }
4228
4051 public: 4229 public:
4230 DicomStoreUserConnection(const DicomAssociationParameters& params) :
4231 parameters_(params),
4232 proposeCommonClasses_(true),
4233 proposeUncompressedSyntaxes_(true),
4234 proposeRetiredBigEndian_(false)
4235 {
4236 }
4237
4238 const DicomAssociationParameters& GetParameters() const
4239 {
4240 return parameters_;
4241 }
4242
4243 void SetCommonClassesProposed(bool proposed)
4244 {
4245 proposeCommonClasses_ = proposed;
4246 }
4247
4248 bool IsCommonClassesProposed() const
4249 {
4250 return proposeCommonClasses_;
4251 }
4252
4253 void SetUncompressedSyntaxesProposed(bool proposed)
4254 {
4255 proposeUncompressedSyntaxes_ = proposed;
4256 }
4257
4258 bool IsUncompressedSyntaxesProposed() const
4259 {
4260 return proposeUncompressedSyntaxes_;
4261 }
4262
4263 void SetRetiredBigEndianProposed(bool propose)
4264 {
4265 proposeRetiredBigEndian_ = propose;
4266 }
4267
4268 bool IsRetiredBigEndianProposed() const
4269 {
4270 return proposeRetiredBigEndian_;
4271 }
4272
4273 void PrepareStorageClass(const std::string& sopClassUid,
4274 DicomTransferSyntax syntax)
4275 {
4276 StorageClasses::iterator found = storageClasses_.find(sopClassUid);
4277
4278 if (found == storageClasses_.end())
4279 {
4280 std::set<DicomTransferSyntax> ts;
4281 ts.insert(syntax);
4282 storageClasses_[sopClassUid] = ts;
4283 }
4284 else
4285 {
4286 found->second.insert(syntax);
4287 }
4288 }
4289
4290
4291 void Toto(const std::string& sopClassUid,
4292 DicomTransferSyntax transferSyntax)
4293 {
4294 uint8_t id;
4295
4296 if (NegotiatePresentationContext(id, sopClassUid, transferSyntax))
4297 {
4298 printf("**** OK, without transcoding !! %d\n", id);
4299 }
4300 else
4301 {
4302 // Transcoding - only in Orthanc >= 1.7.0
4303
4304 const DicomTransferSyntax uncompressed[] = {
4305 DicomTransferSyntax_LittleEndianImplicit, // Default transfer syntax
4306 DicomTransferSyntax_LittleEndianExplicit,
4307 DicomTransferSyntax_BigEndianExplicit
4308 };
4309
4310 bool found = false;
4311 for (size_t i = 0; i < 3; i++)
4312 {
4313 if (LookupPresentationContext(id, sopClassUid, uncompressed[i]))
4314 {
4315 printf("**** TRANSCODING to %s => %d\n",
4316 GetTransferSyntaxUid(uncompressed[i]), id);
4317 found = true;
4318 break;
4319 }
4320 }
4321
4322 if (!found)
4323 {
4324 printf("**** KO KO KO\n");
4325 }
4326 }
4327 }
4052 }; 4328 };
4053 } 4329 }
4054 4330
4055 4331
4056 TEST(Toto, DISABLED_DicomAssociation) 4332 TEST(Toto, DISABLED_DicomAssociation)
4083 it = pc.begin(); it != pc.end(); ++it) 4359 it = pc.begin(); it != pc.end(); ++it)
4084 { 4360 {
4085 printf("[%s] => %d\n", GetTransferSyntaxUid(it->first), it->second); 4361 printf("[%s] => %d\n", GetTransferSyntaxUid(it->first), it->second);
4086 } 4362 }
4087 #else 4363 #else
4088 DicomControlUserConnection assoc(params); 4364 {
4089 4365 DicomControlUserConnection assoc(params);
4090 try 4366
4091 { 4367 try
4092 printf(">> %d\n", assoc.Echo()); 4368 {
4093 } 4369 printf(">> %d\n", assoc.Echo());
4094 catch (OrthancException&) 4370 }
4095 { 4371 catch (OrthancException&)
4096 } 4372 {
4097 4373 }
4374 }
4375
4098 params.SetRemoteApplicationEntityTitle("PACS"); 4376 params.SetRemoteApplicationEntityTitle("PACS");
4099 params.SetRemotePort(2000); 4377 params.SetRemotePort(2000);
4100 assoc.SetParameters(params); 4378
4101 printf(">> %d\n", assoc.Echo()); 4379 {
4380 DicomControlUserConnection assoc(params);
4381 printf(">> %d\n", assoc.Echo());
4382 }
4102 4383
4103 #endif 4384 #endif
4104 } 4385 }
4105 4386
4106 4387
4388 TEST(Toto, DISABLED_Store)
4389 {
4390 DicomAssociationParameters params;
4391 params.SetLocalApplicationEntityTitle("ORTHANC");
4392 params.SetRemoteApplicationEntityTitle("PACS");
4393 params.SetRemotePort(2000);
4394
4395 DicomStoreUserConnection assoc(params);
4396 assoc.PrepareStorageClass(UID_MRImageStorage, DicomTransferSyntax_JPEGProcess1);
4397 assoc.PrepareStorageClass(UID_MRImageStorage, DicomTransferSyntax_JPEGProcess2_4);
4398 //assoc.PrepareStorageClass(UID_MRImageStorage, DicomTransferSyntax_LittleEndianExplicit);
4399
4400 //assoc.SetUncompressedSyntaxesProposed(false);
4401 //assoc.SetCommonClassesProposed(false);
4402 assoc.Toto(UID_MRImageStorage, DicomTransferSyntax_JPEG2000);
4403 //assoc.Toto(UID_MRImageStorage, DicomTransferSyntax_LittleEndianExplicit);
4404 }
4405
4107 #endif 4406 #endif