1 /**
2  * Orthanc - A Lightweight, RESTful DICOM Store
3  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
4  * Department, University Hospital of Liege, Belgium
5  * Copyright (C) 2017-2021 Osimis S.A., Belgium
6  *
7  * This program is free software: you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public License
9  * as published by the Free Software Foundation, either version 3 of
10  * the License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful, but
13  * WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with this program. If not, see
19  * <http://www.gnu.org/licenses/>.
20  **/
21 
22 
23 #include "../PrecompiledHeaders.h"
24 #include "DicomStoreUserConnection.h"
25 
26 #include "../DicomParsing/FromDcmtkBridge.h"
27 #include "../DicomParsing/ParsedDicomFile.h"
28 #include "../Logging.h"
29 #include "../OrthancException.h"
30 #include "DicomAssociation.h"
31 
32 #include <dcmtk/dcmdata/dcdeftag.h>
33 
34 #include <list>
35 
36 
37 namespace Orthanc
38 {
ProgressCallback(void *,T_DIMSE_StoreProgress * progress,T_DIMSE_C_StoreRQ * req)39   static void ProgressCallback(void * /*callbackData*/,
40                                T_DIMSE_StoreProgress *progress,
41                                T_DIMSE_C_StoreRQ * req)
42   {
43     if (req != NULL &&
44         progress->state == DIMSE_StoreBegin)
45     {
46       OFString str;
47       CLOG(TRACE, DICOM) << "Sending Store Request:" << std::endl
48                          << DIMSE_dumpMessage(str, *req, DIMSE_OUTGOING);
49     }
50   }
51 
52 
ProposeStorageClass(const std::string & sopClassUid,const std::set<DicomTransferSyntax> & sourceSyntaxes,bool hasPreferred,DicomTransferSyntax preferred)53   bool DicomStoreUserConnection::ProposeStorageClass(const std::string& sopClassUid,
54                                                      const std::set<DicomTransferSyntax>& sourceSyntaxes,
55                                                      bool hasPreferred,
56                                                      DicomTransferSyntax preferred)
57   {
58     typedef std::list< std::set<DicomTransferSyntax> >  GroupsOfSyntaxes;
59 
60     GroupsOfSyntaxes  groups;
61 
62     // Firstly, add one group for each individual transfer syntax
63     for (std::set<DicomTransferSyntax>::const_iterator
64            it = sourceSyntaxes.begin(); it != sourceSyntaxes.end(); ++it)
65     {
66       std::set<DicomTransferSyntax> group;
67       group.insert(*it);
68       groups.push_back(group);
69     }
70 
71     // Secondly, add one group with the preferred transfer syntax
72     if (hasPreferred &&
73         sourceSyntaxes.find(preferred) == sourceSyntaxes.end())
74     {
75       std::set<DicomTransferSyntax> group;
76       group.insert(preferred);
77       groups.push_back(group);
78     }
79 
80     // Thirdly, add all the uncompressed transfer syntaxes as one single group
81     if (proposeUncompressedSyntaxes_)
82     {
83       static const size_t N = 3;
84       static const DicomTransferSyntax UNCOMPRESSED_SYNTAXES[N] = {
85         DicomTransferSyntax_LittleEndianImplicit,
86         DicomTransferSyntax_LittleEndianExplicit,
87         DicomTransferSyntax_BigEndianExplicit
88       };
89 
90       std::set<DicomTransferSyntax> group;
91 
92       for (size_t i = 0; i < N; i++)
93       {
94         DicomTransferSyntax syntax = UNCOMPRESSED_SYNTAXES[i];
95         if (sourceSyntaxes.find(syntax) == sourceSyntaxes.end() &&
96             (!hasPreferred || preferred != syntax))
97         {
98           group.insert(syntax);
99         }
100       }
101 
102       if (!group.empty())
103       {
104         groups.push_back(group);
105       }
106     }
107 
108     // Now, propose each of these groups of transfer syntaxes
109     if (association_->GetRemainingPropositions() <= groups.size())
110     {
111       return false;  // Not enough room
112     }
113     else
114     {
115       for (GroupsOfSyntaxes::const_iterator it = groups.begin(); it != groups.end(); ++it)
116       {
117         association_->ProposePresentationContext(sopClassUid, *it);
118 
119         // Remember the syntaxes that were individually proposed, in
120         // order to avoid renegociation if they are seen again (**)
121         if (it->size() == 1)
122         {
123           DicomTransferSyntax syntax = *it->begin();
124           proposedOriginalClasses_.insert(std::make_pair(sopClassUid, syntax));
125         }
126       }
127 
128       return true;
129     }
130   }
131 
132 
LookupPresentationContext(uint8_t & presentationContextId,const std::string & sopClassUid,DicomTransferSyntax transferSyntax)133   bool DicomStoreUserConnection::LookupPresentationContext(
134     uint8_t& presentationContextId,
135     const std::string& sopClassUid,
136     DicomTransferSyntax transferSyntax)
137   {
138     typedef std::map<DicomTransferSyntax, uint8_t>  PresentationContexts;
139 
140     PresentationContexts pc;
141     if (association_->IsOpen() &&
142         association_->LookupAcceptedPresentationContext(pc, sopClassUid))
143     {
144       PresentationContexts::const_iterator found = pc.find(transferSyntax);
145       if (found != pc.end())
146       {
147         presentationContextId = found->second;
148         return true;
149       }
150     }
151 
152     return false;
153   }
154 
155 
DicomStoreUserConnection(const DicomAssociationParameters & params)156   DicomStoreUserConnection::DicomStoreUserConnection(
157     const DicomAssociationParameters& params) :
158     parameters_(params),
159     association_(new DicomAssociation),
160     proposeCommonClasses_(true),
161     proposeUncompressedSyntaxes_(true),
162     proposeRetiredBigEndian_(false)
163   {
164   }
165 
GetParameters() const166   const DicomAssociationParameters &DicomStoreUserConnection::GetParameters() const
167   {
168     return parameters_;
169   }
170 
SetCommonClassesProposed(bool proposed)171   void DicomStoreUserConnection::SetCommonClassesProposed(bool proposed)
172   {
173     proposeCommonClasses_ = proposed;
174   }
175 
IsCommonClassesProposed() const176   bool DicomStoreUserConnection::IsCommonClassesProposed() const
177   {
178     return proposeCommonClasses_;
179   }
180 
SetUncompressedSyntaxesProposed(bool proposed)181   void DicomStoreUserConnection::SetUncompressedSyntaxesProposed(bool proposed)
182   {
183     proposeUncompressedSyntaxes_ = proposed;
184   }
185 
IsUncompressedSyntaxesProposed() const186   bool DicomStoreUserConnection::IsUncompressedSyntaxesProposed() const
187   {
188     return proposeUncompressedSyntaxes_;
189   }
190 
SetRetiredBigEndianProposed(bool propose)191   void DicomStoreUserConnection::SetRetiredBigEndianProposed(bool propose)
192   {
193     proposeRetiredBigEndian_ = propose;
194   }
195 
IsRetiredBigEndianProposed() const196   bool DicomStoreUserConnection::IsRetiredBigEndianProposed() const
197   {
198     return proposeRetiredBigEndian_;
199   }
200 
201 
RegisterStorageClass(const std::string & sopClassUid,DicomTransferSyntax syntax)202   void DicomStoreUserConnection::RegisterStorageClass(const std::string& sopClassUid,
203                                                       DicomTransferSyntax syntax)
204   {
205     RegisteredClasses::iterator found = registeredClasses_.find(sopClassUid);
206 
207     if (found == registeredClasses_.end())
208     {
209       std::set<DicomTransferSyntax> ts;
210       ts.insert(syntax);
211       registeredClasses_[sopClassUid] = ts;
212     }
213     else
214     {
215       found->second.insert(syntax);
216     }
217   }
218 
219 
LookupParameters(std::string & sopClassUid,std::string & sopInstanceUid,DicomTransferSyntax & transferSyntax,DcmFileFormat & dicom)220   void DicomStoreUserConnection::LookupParameters(std::string& sopClassUid,
221                                                   std::string& sopInstanceUid,
222                                                   DicomTransferSyntax& transferSyntax,
223                                                   DcmFileFormat& dicom)
224   {
225     if (dicom.getDataset() == NULL)
226     {
227       throw OrthancException(ErrorCode_InternalError);
228     }
229 
230     OFString a, b;
231     if (!dicom.getDataset()->findAndGetOFString(DCM_SOPClassUID, a).good() ||
232         !dicom.getDataset()->findAndGetOFString(DCM_SOPInstanceUID, b).good())
233     {
234       throw OrthancException(ErrorCode_NoSopClassOrInstance,
235                              "Unable to determine the SOP class/instance for C-STORE with AET " +
236                              parameters_.GetRemoteModality().GetApplicationEntityTitle());
237     }
238 
239     sopClassUid.assign(a.c_str());
240     sopInstanceUid.assign(b.c_str());
241 
242     if (!FromDcmtkBridge::LookupOrthancTransferSyntax(transferSyntax, dicom))
243     {
244       throw OrthancException(ErrorCode_InternalError,
245                              "Unknown transfer syntax from DCMTK");
246     }
247   }
248 
249 
NegotiatePresentationContext(uint8_t & presentationContextId,const std::string & sopClassUid,DicomTransferSyntax transferSyntax,bool hasPreferred,DicomTransferSyntax preferred)250   bool DicomStoreUserConnection::NegotiatePresentationContext(
251     uint8_t& presentationContextId,
252     const std::string& sopClassUid,
253     DicomTransferSyntax transferSyntax,
254     bool hasPreferred,
255     DicomTransferSyntax preferred)
256   {
257     /**
258      * Step 1: Check whether this presentation context is already
259      * available in the previously negotiated assocation.
260      **/
261 
262     if (LookupPresentationContext(presentationContextId, sopClassUid, transferSyntax))
263     {
264       return true;
265     }
266 
267     // The association must be re-negotiated
268     if (association_->IsOpen())
269     {
270       CLOG(INFO, DICOM) << "Re-negotiating DICOM association with "
271                         << parameters_.GetRemoteModality().GetApplicationEntityTitle();
272 
273       // Don't renegociate if we know that the remote modality was
274       // already proposed this individual transfer syntax (**)
275       if (proposedOriginalClasses_.find(std::make_pair(sopClassUid, transferSyntax)) !=
276           proposedOriginalClasses_.end())
277       {
278         CLOG(INFO, DICOM) << "The remote modality has already rejected SOP class UID \""
279                           << sopClassUid << "\" with transfer syntax \""
280                           << GetTransferSyntaxUid(transferSyntax) << "\", don't renegotiate";
281         return false;
282       }
283     }
284 
285     association_->ClearPresentationContexts();
286     proposedOriginalClasses_.clear();
287     RegisterStorageClass(sopClassUid, transferSyntax);  // (*)
288 
289 
290     /**
291      * Step 2: Propose at least the mandatory SOP class.
292      **/
293 
294     {
295       RegisteredClasses::const_iterator mandatory = registeredClasses_.find(sopClassUid);
296 
297       if (mandatory == registeredClasses_.end() ||
298           mandatory->second.find(transferSyntax) == mandatory->second.end())
299       {
300         // Should never fail because of (*)
301         throw OrthancException(ErrorCode_InternalError);
302       }
303 
304       if (!ProposeStorageClass(sopClassUid, mandatory->second, hasPreferred, preferred))
305       {
306         // Should never happen in real life: There are no more than
307         // 128 transfer syntaxes in DICOM!
308         throw OrthancException(ErrorCode_InternalError,
309                                "Too many transfer syntaxes for SOP class UID: " + sopClassUid);
310       }
311     }
312 
313 
314     /**
315      * Step 3: Propose all the previously spotted SOP classes, as
316      * registered through the "RegisterStorageClass()" method.
317      **/
318 
319     for (RegisteredClasses::const_iterator it = registeredClasses_.begin();
320          it != registeredClasses_.end(); ++it)
321     {
322       if (it->first != sopClassUid)
323       {
324         ProposeStorageClass(it->first, it->second, hasPreferred, preferred);
325       }
326     }
327 
328 
329     /**
330      * Step 4: As long as there is room left in the proposed
331      * presentation contexts, propose the uncompressed transfer syntaxes
332      * for the most common SOP classes, as can be found in the
333      * "dcmShortSCUStorageSOPClassUIDs" array from DCMTK. The
334      * preferred transfer syntax is "LittleEndianImplicit".
335      **/
336 
337     if (proposeCommonClasses_)
338     {
339       // The method "ProposeStorageClass()" will automatically add
340       // "LittleEndianImplicit"
341       std::set<DicomTransferSyntax> ts;
342 
343       for (int i = 0; i < numberOfDcmShortSCUStorageSOPClassUIDs; i++)
344       {
345         std::string c(dcmShortSCUStorageSOPClassUIDs[i]);
346 
347         if (c != sopClassUid &&
348             registeredClasses_.find(c) == registeredClasses_.end())
349         {
350           ProposeStorageClass(c, ts, hasPreferred, preferred);
351         }
352       }
353     }
354 
355 
356     /**
357      * Step 5: Open the association, and check whether the pair (SOP
358      * class UID, transfer syntax) was accepted by the remote host.
359      **/
360 
361     association_->Open(parameters_);
362     return LookupPresentationContext(presentationContextId, sopClassUid, transferSyntax);
363   }
364 
365 
Store(std::string & sopClassUid,std::string & sopInstanceUid,DcmFileFormat & dicom,bool hasMoveOriginator,const std::string & moveOriginatorAET,uint16_t moveOriginatorID)366   void DicomStoreUserConnection::Store(std::string& sopClassUid,
367                                        std::string& sopInstanceUid,
368                                        DcmFileFormat& dicom,
369                                        bool hasMoveOriginator,
370                                        const std::string& moveOriginatorAET,
371                                        uint16_t moveOriginatorID)
372   {
373     DicomTransferSyntax transferSyntax;
374     LookupParameters(sopClassUid, sopInstanceUid, transferSyntax, dicom);
375 
376     uint8_t presID;
377     if (!NegotiatePresentationContext(presID, sopClassUid, transferSyntax, proposeUncompressedSyntaxes_,
378                                       DicomTransferSyntax_LittleEndianExplicit))
379     {
380       throw OrthancException(ErrorCode_NetworkProtocol,
381                              "No valid presentation context was negotiated for "
382                              "SOP class UID [" + sopClassUid + "] and transfer "
383                              "syntax [" + GetTransferSyntaxUid(transferSyntax) + "] "
384                              "while sending to modality [" +
385                              parameters_.GetRemoteModality().GetApplicationEntityTitle() + "]");
386     }
387 
388     // Prepare the transmission of data
389     T_DIMSE_C_StoreRQ request;
390     memset(&request, 0, sizeof(request));
391     request.MessageID = association_->GetDcmtkAssociation().nextMsgID++;
392     strncpy(request.AffectedSOPClassUID, sopClassUid.c_str(), DIC_UI_LEN);
393     request.Priority = DIMSE_PRIORITY_MEDIUM;
394     request.DataSetType = DIMSE_DATASET_PRESENT;
395     strncpy(request.AffectedSOPInstanceUID, sopInstanceUid.c_str(), DIC_UI_LEN);
396 
397     if (hasMoveOriginator)
398     {
399       strncpy(request.MoveOriginatorApplicationEntityTitle,
400               moveOriginatorAET.c_str(), DIC_AE_LEN);
401       request.opts = O_STORE_MOVEORIGINATORAETITLE;
402 
403       request.MoveOriginatorID = moveOriginatorID;  // The type DIC_US is an alias for uint16_t
404       request.opts |= O_STORE_MOVEORIGINATORID;
405     }
406 
407     if (dicom.getDataset() == NULL)
408     {
409       throw OrthancException(ErrorCode_InternalError);
410     }
411 
412     // Finally conduct transmission of data
413     T_DIMSE_C_StoreRSP response;
414     DcmDataset* statusDetail = NULL;
415     DicomAssociation::CheckCondition(
416       DIMSE_storeUser(&association_->GetDcmtkAssociation(), presID, &request,
417                       NULL, dicom.getDataset(), ProgressCallback, NULL,
418                       /*opt_blockMode*/ (GetParameters().HasTimeout() ? DIMSE_NONBLOCKING : DIMSE_BLOCKING),
419                       /*opt_dimse_timeout*/ GetParameters().GetTimeout(),
420                       &response, &statusDetail, NULL),
421       GetParameters(), "C-STORE");
422 
423     if (statusDetail != NULL)
424     {
425       delete statusDetail;
426     }
427 
428     {
429       OFString str;
430       CLOG(TRACE, DICOM) << "Received Store Response:" << std::endl
431                          << DIMSE_dumpMessage(str, response, DIMSE_INCOMING, NULL, presID);
432     }
433 
434     /**
435      * New in Orthanc 1.6.0: Deal with failures during C-STORE.
436      * http://dicom.nema.org/medical/dicom/current/output/chtml/part04/sect_B.2.3.html#table_B.2-1
437      **/
438 
439     if (response.DimseStatus != 0x0000 &&  // Success
440         response.DimseStatus != 0xB000 &&  // Warning - Coercion of Data Elements
441         response.DimseStatus != 0xB007 &&  // Warning - Data Set does not match SOP Class
442         response.DimseStatus != 0xB006)    // Warning - Elements Discarded
443     {
444       char buf[16];
445       sprintf(buf, "%04X", response.DimseStatus);
446       throw OrthancException(ErrorCode_NetworkProtocol,
447                              "C-STORE SCU to AET \"" +
448                              GetParameters().GetRemoteModality().GetApplicationEntityTitle() +
449                              "\" has failed with DIMSE status 0x" + buf);
450     }
451   }
452 
453 
Store(std::string & sopClassUid,std::string & sopInstanceUid,const void * buffer,size_t size,bool hasMoveOriginator,const std::string & moveOriginatorAET,uint16_t moveOriginatorID)454   void DicomStoreUserConnection::Store(std::string& sopClassUid,
455                                        std::string& sopInstanceUid,
456                                        const void* buffer,
457                                        size_t size,
458                                        bool hasMoveOriginator,
459                                        const std::string& moveOriginatorAET,
460                                        uint16_t moveOriginatorID)
461   {
462     std::unique_ptr<DcmFileFormat> dicom(
463       FromDcmtkBridge::LoadFromMemoryBuffer(buffer, size));
464 
465     if (dicom.get() == NULL)
466     {
467       throw OrthancException(ErrorCode_InternalError);
468     }
469 
470     Store(sopClassUid, sopInstanceUid, *dicom, hasMoveOriginator, moveOriginatorAET, moveOriginatorID);
471   }
472 
473 
LookupTranscoding(std::set<DicomTransferSyntax> & acceptedSyntaxes,const std::string & sopClassUid,DicomTransferSyntax sourceSyntax,bool hasPreferred,DicomTransferSyntax preferred)474   void DicomStoreUserConnection::LookupTranscoding(std::set<DicomTransferSyntax>& acceptedSyntaxes,
475                                                    const std::string& sopClassUid,
476                                                    DicomTransferSyntax sourceSyntax,
477                                                    bool hasPreferred,
478                                                    DicomTransferSyntax preferred)
479   {
480     acceptedSyntaxes.clear();
481 
482     // Make sure a negotiation has already occurred for this transfer
483     // syntax. We don't use the return code: Transcoding is possible
484     // even if the "sourceSyntax" is not supported.
485     uint8_t presID;
486     NegotiatePresentationContext(presID, sopClassUid, sourceSyntax, hasPreferred, preferred);
487 
488     std::map<DicomTransferSyntax, uint8_t> contexts;
489     if (association_->LookupAcceptedPresentationContext(contexts, sopClassUid))
490     {
491       for (std::map<DicomTransferSyntax, uint8_t>::const_iterator
492              it = contexts.begin(); it != contexts.end(); ++it)
493       {
494         acceptedSyntaxes.insert(it->first);
495       }
496     }
497   }
498 
499 
Transcode(std::string & sopClassUid,std::string & sopInstanceUid,IDicomTranscoder & transcoder,const void * buffer,size_t size,DicomTransferSyntax preferredTransferSyntax,bool hasMoveOriginator,const std::string & moveOriginatorAET,uint16_t moveOriginatorID)500   void DicomStoreUserConnection::Transcode(std::string& sopClassUid /* out */,
501                                            std::string& sopInstanceUid /* out */,
502                                            IDicomTranscoder& transcoder,
503                                            const void* buffer,
504                                            size_t size,
505                                            DicomTransferSyntax preferredTransferSyntax,
506                                            bool hasMoveOriginator,
507                                            const std::string& moveOriginatorAET,
508                                            uint16_t moveOriginatorID)
509   {
510     std::unique_ptr<DcmFileFormat> dicom(FromDcmtkBridge::LoadFromMemoryBuffer(buffer, size));
511     if (dicom.get() == NULL ||
512         dicom->getDataset() == NULL)
513     {
514       throw OrthancException(ErrorCode_NullPointer);
515     }
516 
517     DicomTransferSyntax sourceSyntax;
518     LookupParameters(sopClassUid, sopInstanceUid, sourceSyntax, *dicom);
519 
520     std::set<DicomTransferSyntax> accepted;
521     LookupTranscoding(accepted, sopClassUid, sourceSyntax, true, preferredTransferSyntax);
522 
523     if (accepted.find(sourceSyntax) != accepted.end())
524     {
525       // No need for transcoding
526       Store(sopClassUid, sopInstanceUid, *dicom,
527             hasMoveOriginator, moveOriginatorAET, moveOriginatorID);
528     }
529     else
530     {
531       // Transcoding is needed
532       IDicomTranscoder::DicomImage source;
533       source.AcquireParsed(dicom.release());
534       source.SetExternalBuffer(buffer, size);
535 
536       const std::string sourceUid = IDicomTranscoder::GetSopInstanceUid(source.GetParsed());
537 
538       IDicomTranscoder::DicomImage transcoded;
539       bool success = false;
540       bool isDestructiveCompressionAllowed = false;
541       std::set<DicomTransferSyntax> attemptedSyntaxes;
542 
543       if (accepted.find(preferredTransferSyntax) != accepted.end())
544       {
545         // New in Orthanc 1.9.0: The preferred transfer syntax is
546         // accepted by the remote modality => transcode to this syntax
547         std::set<DicomTransferSyntax> targetSyntaxes;
548         targetSyntaxes.insert(preferredTransferSyntax);
549         attemptedSyntaxes.insert(preferredTransferSyntax);
550 
551         success = transcoder.Transcode(transcoded, source, targetSyntaxes, true);
552         isDestructiveCompressionAllowed = true;
553       }
554 
555       if (!success)
556       {
557         // Transcode to either one of the uncompressed transfer
558         // syntaxes that are accepted by the remote modality
559 
560         std::set<DicomTransferSyntax> targetSyntaxes;
561 
562         if (accepted.find(DicomTransferSyntax_LittleEndianImplicit) != accepted.end())
563         {
564           targetSyntaxes.insert(DicomTransferSyntax_LittleEndianImplicit);
565           attemptedSyntaxes.insert(DicomTransferSyntax_LittleEndianImplicit);
566         }
567 
568         if (accepted.find(DicomTransferSyntax_LittleEndianExplicit) != accepted.end())
569         {
570           targetSyntaxes.insert(DicomTransferSyntax_LittleEndianExplicit);
571           attemptedSyntaxes.insert(DicomTransferSyntax_LittleEndianExplicit);
572         }
573 
574         if (accepted.find(DicomTransferSyntax_BigEndianExplicit) != accepted.end())
575         {
576           targetSyntaxes.insert(DicomTransferSyntax_BigEndianExplicit);
577           attemptedSyntaxes.insert(DicomTransferSyntax_BigEndianExplicit);
578         }
579 
580         if (!targetSyntaxes.empty())
581         {
582           success = transcoder.Transcode(transcoded, source, targetSyntaxes, false);
583           isDestructiveCompressionAllowed = false;
584         }
585       }
586 
587       if (success)
588       {
589         std::string targetUid = IDicomTranscoder::GetSopInstanceUid(transcoded.GetParsed());
590         if (sourceUid != targetUid)
591         {
592           if (isDestructiveCompressionAllowed)
593           {
594             LOG(WARNING) << "Because of the use of a preferred transfer syntax that corresponds to "
595                          << "a destructive compression, C-STORE SCU has hanged the SOP Instance UID "
596                          << "of a DICOM instance from \"" << sourceUid << "\" to \"" << targetUid << "\"";
597           }
598           else
599           {
600             throw OrthancException(ErrorCode_Plugin, "The transcoder has changed the SOP "
601                                    "Instance UID while transcoding to an uncompressed transfer syntax");
602           }
603         }
604 
605         DicomTransferSyntax transcodedSyntax;
606 
607         // Sanity check
608         if (!FromDcmtkBridge::LookupOrthancTransferSyntax(transcodedSyntax, transcoded.GetParsed()) ||
609             accepted.find(transcodedSyntax) == accepted.end())
610         {
611           throw OrthancException(ErrorCode_InternalError);
612         }
613         else
614         {
615           Store(sopClassUid, sopInstanceUid, transcoded.GetParsed(),
616                 hasMoveOriginator, moveOriginatorAET, moveOriginatorID);
617         }
618       }
619       else
620       {
621         std::string s;
622         for (std::set<DicomTransferSyntax>::const_iterator
623                it = attemptedSyntaxes.begin(); it != attemptedSyntaxes.end(); ++it)
624         {
625           s += " " + std::string(GetTransferSyntaxUid(*it));
626         }
627 
628         throw OrthancException(ErrorCode_NotImplemented, "Cannot transcode from " +
629                                std::string(GetTransferSyntaxUid(sourceSyntax)) +
630                                " to one of [" + s + " ]");
631       }
632     }
633   }
634 
635 
Transcode(std::string & sopClassUid,std::string & sopInstanceUid,IDicomTranscoder & transcoder,const void * buffer,size_t size,bool hasMoveOriginator,const std::string & moveOriginatorAET,uint16_t moveOriginatorID)636   void DicomStoreUserConnection::Transcode(std::string& sopClassUid /* out */,
637                                            std::string& sopInstanceUid /* out */,
638                                            IDicomTranscoder& transcoder,
639                                            const void* buffer,
640                                            size_t size,
641                                            bool hasMoveOriginator,
642                                            const std::string& moveOriginatorAET,
643                                            uint16_t moveOriginatorID)
644   {
645     Transcode(sopClassUid, sopInstanceUid, transcoder, buffer, size, DicomTransferSyntax_LittleEndianExplicit,
646               hasMoveOriginator, moveOriginatorAET, moveOriginatorID);
647   }
648 }
649