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 "IDicomTranscoder.h"
25 
26 #include "../OrthancException.h"
27 #include "FromDcmtkBridge.h"
28 #include "ParsedDicomFile.h"
29 
30 #include <dcmtk/dcmdata/dcfilefo.h>
31 #include <dcmtk/dcmdata/dcdeftag.h>
32 
33 namespace Orthanc
34 {
GetTranscodingType(DicomTransferSyntax target,DicomTransferSyntax source)35   IDicomTranscoder::TranscodingType IDicomTranscoder::GetTranscodingType(DicomTransferSyntax target,
36                                                                          DicomTransferSyntax source)
37   {
38     if (target == source)
39     {
40       return TranscodingType_Lossless;
41     }
42     else if (target == DicomTransferSyntax_LittleEndianImplicit ||
43              target == DicomTransferSyntax_LittleEndianExplicit ||
44              target == DicomTransferSyntax_BigEndianExplicit ||
45              target == DicomTransferSyntax_DeflatedLittleEndianExplicit ||
46              target == DicomTransferSyntax_JPEGProcess14 ||
47              target == DicomTransferSyntax_JPEGProcess14SV1 ||
48              target == DicomTransferSyntax_JPEGLSLossless ||
49              target == DicomTransferSyntax_JPEG2000LosslessOnly ||
50              target == DicomTransferSyntax_JPEG2000MulticomponentLosslessOnly)
51     {
52       return TranscodingType_Lossless;
53     }
54     else if (target == DicomTransferSyntax_JPEGProcess1 ||
55              target == DicomTransferSyntax_JPEGProcess2_4 ||
56              target == DicomTransferSyntax_JPEGLSLossy ||
57              target == DicomTransferSyntax_JPEG2000 ||
58              target == DicomTransferSyntax_JPEG2000Multicomponent)
59     {
60       return TranscodingType_Lossy;
61     }
62     else
63     {
64       return TranscodingType_Unknown;
65     }
66   }
67 
68 
GetSopInstanceUid(DcmFileFormat & dicom)69   std::string IDicomTranscoder::GetSopInstanceUid(DcmFileFormat& dicom)
70   {
71     if (dicom.getDataset() == NULL)
72     {
73       throw OrthancException(ErrorCode_InternalError);
74     }
75 
76     DcmDataset& dataset = *dicom.getDataset();
77 
78     const char* v = NULL;
79 
80     if (dataset.findAndGetString(DCM_SOPInstanceUID, v).good() &&
81         v != NULL)
82     {
83       return std::string(v);
84     }
85     else
86     {
87       throw OrthancException(ErrorCode_BadFileFormat, "File without SOP instance UID");
88     }
89   }
90 
91 
CheckTranscoding(IDicomTranscoder::DicomImage & transcoded,DicomTransferSyntax sourceSyntax,const std::string & sourceSopInstanceUid,const std::set<DicomTransferSyntax> & allowedSyntaxes,bool allowNewSopInstanceUid)92   void IDicomTranscoder::CheckTranscoding(IDicomTranscoder::DicomImage& transcoded,
93                                           DicomTransferSyntax sourceSyntax,
94                                           const std::string& sourceSopInstanceUid,
95                                           const std::set<DicomTransferSyntax>& allowedSyntaxes,
96                                           bool allowNewSopInstanceUid)
97   {
98     DcmFileFormat& parsed = transcoded.GetParsed();
99 
100     if (parsed.getDataset() == NULL)
101     {
102       throw OrthancException(ErrorCode_InternalError);
103     }
104 
105     std::string targetSopInstanceUid = GetSopInstanceUid(parsed);
106 
107     if (parsed.getDataset()->tagExists(DCM_PixelData))
108     {
109       if (!allowNewSopInstanceUid && (targetSopInstanceUid != sourceSopInstanceUid))
110       {
111         throw OrthancException(ErrorCode_InternalError);
112       }
113     }
114     else
115     {
116       if (targetSopInstanceUid != sourceSopInstanceUid)
117       {
118         throw OrthancException(ErrorCode_InternalError,
119                                "No pixel data: Transcoding must not change the SOP instance UID");
120       }
121     }
122 
123     DicomTransferSyntax targetSyntax;
124     if (!FromDcmtkBridge::LookupOrthancTransferSyntax(targetSyntax, parsed))
125     {
126       return;  // Unknown transfer syntax, cannot do further test
127     }
128 
129     if (allowedSyntaxes.find(sourceSyntax) != allowedSyntaxes.end())
130     {
131       // No transcoding should have happened
132       if (targetSopInstanceUid != sourceSopInstanceUid)
133       {
134         throw OrthancException(ErrorCode_InternalError);
135       }
136     }
137 
138     if (allowedSyntaxes.find(targetSyntax) == allowedSyntaxes.end())
139     {
140       throw OrthancException(ErrorCode_InternalError, "An incorrect output transfer syntax was chosen");
141     }
142 
143     if (parsed.getDataset()->tagExists(DCM_PixelData))
144     {
145       switch (GetTranscodingType(targetSyntax, sourceSyntax))
146       {
147         case TranscodingType_Lossy:
148           if (targetSopInstanceUid == sourceSopInstanceUid)
149           {
150             throw OrthancException(ErrorCode_InternalError);
151           }
152           break;
153 
154         case TranscodingType_Lossless:
155           if (targetSopInstanceUid != sourceSopInstanceUid)
156           {
157             throw OrthancException(ErrorCode_InternalError);
158           }
159           break;
160 
161         default:
162           break;
163       }
164     }
165   }
166 
167 
Parse()168   void IDicomTranscoder::DicomImage::Parse()
169   {
170     if (parsed_.get() != NULL)
171     {
172       // Already parsed
173       throw OrthancException(ErrorCode_BadSequenceOfCalls);
174     }
175     else if (buffer_.get() != NULL)
176     {
177       if (isExternalBuffer_)
178       {
179         throw OrthancException(ErrorCode_InternalError);
180       }
181       else
182       {
183         parsed_.reset(FromDcmtkBridge::LoadFromMemoryBuffer(
184                         buffer_->empty() ? NULL : buffer_->c_str(), buffer_->size()));
185 
186         if (parsed_.get() == NULL)
187         {
188           throw OrthancException(ErrorCode_BadFileFormat);
189         }
190       }
191     }
192     else if (isExternalBuffer_)
193     {
194       parsed_.reset(FromDcmtkBridge::LoadFromMemoryBuffer(externalBuffer_, externalSize_));
195 
196       if (parsed_.get() == NULL)
197       {
198         throw OrthancException(ErrorCode_BadFileFormat);
199       }
200     }
201     else
202     {
203       // No buffer is available
204       throw OrthancException(ErrorCode_BadSequenceOfCalls);
205     }
206   }
207 
208 
Serialize()209   void IDicomTranscoder::DicomImage::Serialize()
210   {
211     if (parsed_.get() == NULL ||
212         buffer_.get() != NULL ||
213         isExternalBuffer_)
214     {
215       throw OrthancException(ErrorCode_BadSequenceOfCalls);
216     }
217     else if (parsed_->getDataset() == NULL)
218     {
219       throw OrthancException(ErrorCode_InternalError);
220     }
221     else
222     {
223       buffer_.reset(new std::string);
224       FromDcmtkBridge::SaveToMemoryBuffer(*buffer_, *parsed_->getDataset());
225     }
226   }
227 
228 
DicomImage()229   IDicomTranscoder::DicomImage::DicomImage() :
230     isExternalBuffer_(false),
231     externalBuffer_(NULL),
232     externalSize_(0)
233   {
234   }
235 
236 
Clear()237   void IDicomTranscoder::DicomImage::Clear()
238   {
239     parsed_.reset(NULL);
240     buffer_.reset(NULL);
241     isExternalBuffer_ = false;
242   }
243 
244 
AcquireParsed(ParsedDicomFile & parsed)245   void IDicomTranscoder::DicomImage::AcquireParsed(ParsedDicomFile& parsed)
246   {
247     AcquireParsed(parsed.ReleaseDcmtkObject());
248   }
249 
250 
AcquireParsed(DcmFileFormat * parsed)251   void IDicomTranscoder::DicomImage::AcquireParsed(DcmFileFormat* parsed)
252   {
253     if (parsed == NULL)
254     {
255       throw OrthancException(ErrorCode_NullPointer);
256     }
257     else if (parsed->getDataset() == NULL)
258     {
259       throw OrthancException(ErrorCode_InternalError);
260     }
261     else if (parsed_.get() != NULL)
262     {
263       throw OrthancException(ErrorCode_BadSequenceOfCalls);
264     }
265     else
266     {
267       parsed_.reset(parsed);
268     }
269   }
270 
271 
AcquireParsed(DicomImage & other)272   void IDicomTranscoder::DicomImage::AcquireParsed(DicomImage& other)
273   {
274     AcquireParsed(other.ReleaseParsed());
275   }
276 
277 
AcquireBuffer(std::string & buffer)278   void IDicomTranscoder::DicomImage::AcquireBuffer(std::string& buffer /* will be swapped */)
279   {
280     if (buffer_.get() != NULL ||
281         isExternalBuffer_)
282     {
283       throw OrthancException(ErrorCode_BadSequenceOfCalls);
284     }
285     else
286     {
287       buffer_.reset(new std::string);
288       buffer_->swap(buffer);
289     }
290   }
291 
292 
AcquireBuffer(DicomImage & other)293   void IDicomTranscoder::DicomImage::AcquireBuffer(DicomImage& other)
294   {
295     if (buffer_.get() != NULL ||
296         isExternalBuffer_)
297     {
298       throw OrthancException(ErrorCode_BadSequenceOfCalls);
299     }
300     else if (other.isExternalBuffer_)
301     {
302       assert(other.buffer_.get() == NULL);
303       isExternalBuffer_ = true;
304       externalBuffer_ = other.externalBuffer_;
305       externalSize_ = other.externalSize_;
306     }
307     else if (other.buffer_.get() != NULL)
308     {
309       buffer_.reset(other.buffer_.release());
310     }
311     else
312     {
313       buffer_.reset(NULL);
314     }
315   }
316 
317 
SetExternalBuffer(const void * buffer,size_t size)318   void IDicomTranscoder::DicomImage::SetExternalBuffer(const void* buffer,
319                                                        size_t size)
320   {
321     if (buffer_.get() != NULL ||
322         isExternalBuffer_)
323     {
324       throw OrthancException(ErrorCode_BadSequenceOfCalls);
325     }
326     else
327     {
328       isExternalBuffer_ = true;
329       externalBuffer_ = buffer;
330       externalSize_ = size;
331     }
332   }
333 
334 
SetExternalBuffer(const std::string & buffer)335   void IDicomTranscoder::DicomImage::SetExternalBuffer(const std::string& buffer)
336   {
337     SetExternalBuffer(buffer.empty() ? NULL : buffer.c_str(), buffer.size());
338   }
339 
340 
GetParsed()341   DcmFileFormat& IDicomTranscoder::DicomImage::GetParsed()
342   {
343     if (parsed_.get() != NULL)
344     {
345       return *parsed_;
346     }
347     else if (buffer_.get() != NULL ||
348              isExternalBuffer_)
349     {
350       Parse();
351       return *parsed_;
352     }
353     else
354     {
355       throw OrthancException(
356         ErrorCode_BadSequenceOfCalls,
357         "AcquireParsed(), AcquireBuffer() or SetExternalBuffer() should have been called");
358     }
359   }
360 
361 
ReleaseParsed()362   DcmFileFormat* IDicomTranscoder::DicomImage::ReleaseParsed()
363   {
364     if (parsed_.get() != NULL)
365     {
366       buffer_.reset(NULL);
367       return parsed_.release();
368     }
369     else if (buffer_.get() != NULL ||
370              isExternalBuffer_)
371     {
372       Parse();
373       buffer_.reset(NULL);
374       return parsed_.release();
375     }
376     else
377     {
378       throw OrthancException(
379         ErrorCode_BadSequenceOfCalls,
380         "AcquireParsed(), AcquireBuffer() or SetExternalBuffer() should have been called");
381     }
382   }
383 
384 
ReleaseAsParsedDicomFile()385   ParsedDicomFile* IDicomTranscoder::DicomImage::ReleaseAsParsedDicomFile()
386   {
387     return ParsedDicomFile::AcquireDcmtkObject(ReleaseParsed());
388   }
389 
390 
GetBufferData()391   const void* IDicomTranscoder::DicomImage::GetBufferData()
392   {
393     if (isExternalBuffer_)
394     {
395       assert(buffer_.get() == NULL);
396       return externalBuffer_;
397     }
398     else
399     {
400       if (buffer_.get() == NULL)
401       {
402         Serialize();
403       }
404 
405       assert(buffer_.get() != NULL);
406       return buffer_->empty() ? NULL : buffer_->c_str();
407     }
408   }
409 
410 
GetBufferSize()411   size_t IDicomTranscoder::DicomImage::GetBufferSize()
412   {
413     if (isExternalBuffer_)
414     {
415       assert(buffer_.get() == NULL);
416       return externalSize_;
417     }
418     else
419     {
420       if (buffer_.get() == NULL)
421       {
422         Serialize();
423       }
424 
425       assert(buffer_.get() != NULL);
426       return buffer_->size();
427     }
428   }
429 }
430