comparison OrthancServer/Sources/DicomInstanceToStore.cpp @ 4508:8f9090b137f1

Optimization in C-STORE SCP by avoiding an unnecessary DICOM parsing
author Sebastien Jodogne <s.jodogne@gmail.com>
date Thu, 11 Feb 2021 11:00:05 +0100
parents b4c58795f3a8
children a3c6678aa7b1
comparison
equal deleted inserted replaced
4507:b4c58795f3a8 4508:8f9090b137f1
35 #include "DicomInstanceToStore.h" 35 #include "DicomInstanceToStore.h"
36 36
37 #include "OrthancConfiguration.h" 37 #include "OrthancConfiguration.h"
38 38
39 #include "../../OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.h" 39 #include "../../OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.h"
40 #include "../../OrthancFramework/Sources/DicomParsing/Internals/DicomFrameIndex.h"
41 #include "../../OrthancFramework/Sources/DicomParsing/Internals/DicomImageDecoder.h"
40 #include "../../OrthancFramework/Sources/DicomParsing/ParsedDicomFile.h" 42 #include "../../OrthancFramework/Sources/DicomParsing/ParsedDicomFile.h"
41 #include "../../OrthancFramework/Sources/Logging.h" 43 #include "../../OrthancFramework/Sources/Logging.h"
42 #include "../../OrthancFramework/Sources/OrthancException.h" 44 #include "../../OrthancFramework/Sources/OrthancException.h"
43 45
44 #include <dcmtk/dcmdata/dcfilefo.h> 46 #include <dcmtk/dcmdata/dcfilefo.h>
45 #include <dcmtk/dcmdata/dcdeftag.h> 47 #include <dcmtk/dcmdata/dcdeftag.h>
46 48
47 49
48 namespace Orthanc 50 namespace Orthanc
49 { 51 {
50 // Anonymous namespace to avoid clashes between compilation modules 52 class DicomInstanceToStore::FromBuffer : public DicomInstanceToStore
51 namespace 53 {
52 { 54 private:
53 template <typename T> 55 const void* buffer_;
54 class SmartContainer 56 size_t size_;
55 { 57 std::unique_ptr<ParsedDicomFile> parsed_;
56 private: 58
57 T* content_; 59 public:
58 bool toDelete_; 60 FromBuffer(const void* buffer,
59 bool isReadOnly_; 61 size_t size) :
60 62 buffer_(buffer),
61 void Deallocate() 63 size_(size)
62 { 64 {
63 if (content_ && toDelete_) 65 }
66
67 virtual ParsedDicomFile& GetParsedDicomFile() const ORTHANC_OVERRIDE
68 {
69 if (parsed_.get() == NULL)
70 {
71 const_cast<FromBuffer&>(*this).parsed_.reset(new ParsedDicomFile(buffer_, size_));
72 }
73
74 return *parsed_;
75 }
76
77 virtual const void* GetBufferData() const ORTHANC_OVERRIDE
78 {
79 return buffer_;
80 }
81
82 virtual size_t GetBufferSize() const ORTHANC_OVERRIDE
83 {
84 return size_;
85 }
86 };
87
88
89 class DicomInstanceToStore::FromParsedDicomFile : public DicomInstanceToStore
90 {
91 private:
92 ParsedDicomFile& parsed_;
93 std::unique_ptr<std::string> buffer_;
94
95 void SerializeToBuffer()
96 {
97 if (buffer_.get() == NULL)
98 {
99 buffer_.reset(new std::string);
100 parsed_.SaveToMemoryBuffer(*buffer_);
101 }
102 }
103
104 public:
105 FromParsedDicomFile(ParsedDicomFile& parsed) :
106 parsed_(parsed)
107 {
108 }
109
110 virtual ParsedDicomFile& GetParsedDicomFile() const ORTHANC_OVERRIDE
111 {
112 return parsed_;
113 }
114
115 virtual const void* GetBufferData() const ORTHANC_OVERRIDE
116 {
117 const_cast<FromParsedDicomFile&>(*this).SerializeToBuffer();
118
119 assert(buffer_.get() != NULL);
120 return (buffer_->empty() ? NULL : buffer_->c_str());
121 }
122
123 virtual size_t GetBufferSize() const ORTHANC_OVERRIDE
124 {
125 const_cast<FromParsedDicomFile&>(*this).SerializeToBuffer();
126
127 assert(buffer_.get() != NULL);
128 return buffer_->size();
129 }
130 };
131
132
133 class DicomInstanceToStore::FromDcmDataset : public DicomInstanceToStore
134 {
135 private:
136 DcmDataset& dataset_;
137 std::unique_ptr<std::string> buffer_;
138 std::unique_ptr<ParsedDicomFile> parsed_;
139
140 void SerializeToBuffer()
141 {
142 if (buffer_.get() == NULL)
143 {
144 buffer_.reset(new std::string);
145
146 if (!FromDcmtkBridge::SaveToMemoryBuffer(*buffer_, dataset_))
64 { 147 {
65 delete content_; 148 throw OrthancException(ErrorCode_InternalError, "Cannot write DICOM file to memory");
66 toDelete_ = false;
67 content_ = NULL;
68 } 149 }
69 } 150 }
70 151 }
71 public: 152
72 SmartContainer() : content_(NULL), toDelete_(false), isReadOnly_(true)
73 {
74 }
75
76 ~SmartContainer()
77 {
78 Deallocate();
79 }
80
81 void Allocate()
82 {
83 Deallocate();
84 content_ = new T;
85 toDelete_ = true;
86 isReadOnly_ = false;
87 }
88
89 void TakeOwnership(T* content)
90 {
91 if (content == NULL)
92 {
93 throw OrthancException(ErrorCode_ParameterOutOfRange);
94 }
95
96 Deallocate();
97 content_ = content;
98 toDelete_ = true;
99 isReadOnly_ = false;
100 }
101
102 void SetReference(T& content) // Read and write assign, without transfering ownership
103 {
104 Deallocate();
105 content_ = &content;
106 toDelete_ = false;
107 isReadOnly_ = false;
108 }
109
110 void SetConstReference(const T& content) // Read-only assign, without transfering ownership
111 {
112 Deallocate();
113 content_ = &const_cast<T&>(content);
114 toDelete_ = false;
115 isReadOnly_ = true;
116 }
117
118 bool HasContent() const
119 {
120 return content_ != NULL;
121 }
122
123 T& GetContent()
124 {
125 if (content_ == NULL)
126 {
127 throw OrthancException(ErrorCode_BadSequenceOfCalls);
128 }
129
130 if (isReadOnly_)
131 {
132 throw OrthancException(ErrorCode_ReadOnly);
133 }
134
135 return *content_;
136 }
137
138 const T& GetConstContent() const
139 {
140 if (content_ == NULL)
141 {
142 throw OrthancException(ErrorCode_BadSequenceOfCalls);
143 }
144
145 return *content_;
146 }
147 };
148 }
149
150
151 class DicomInstanceToStore::PImpl
152 {
153 public: 153 public:
154 DicomInstanceOrigin origin_; 154 FromDcmDataset(DcmDataset& dataset) :
155 bool hasBuffer_; 155 dataset_(dataset)
156 std::unique_ptr<std::string> ownBuffer_; 156 {
157 const void* bufferData_;
158 size_t bufferSize_;
159 SmartContainer<ParsedDicomFile> parsed_;
160 MetadataMap metadata_;
161
162 PImpl() :
163 hasBuffer_(false),
164 bufferData_(NULL),
165 bufferSize_(0)
166 {
167 }
168
169 private:
170 void ComputeParsedDicomFileIfMissing()
171 {
172 if (!parsed_.HasContent())
173 {
174 if (!hasBuffer_)
175 {
176 throw OrthancException(ErrorCode_InternalError);
177 }
178
179 if (ownBuffer_.get() != NULL)
180 {
181 parsed_.TakeOwnership(new ParsedDicomFile(*ownBuffer_));
182 }
183 else
184 {
185 parsed_.TakeOwnership(new ParsedDicomFile(bufferData_, bufferSize_));
186 }
187 }
188 }
189
190 void ComputeDicomBufferIfMissing()
191 {
192 if (!hasBuffer_)
193 {
194 if (!parsed_.HasContent())
195 {
196 throw OrthancException(ErrorCode_NotImplemented);
197 }
198
199 // Serialize the parsed DICOM file
200 ownBuffer_.reset(new std::string);
201 if (!FromDcmtkBridge::SaveToMemoryBuffer(*ownBuffer_,
202 *parsed_.GetContent().GetDcmtkObject().getDataset()))
203 {
204 throw OrthancException(ErrorCode_InternalError,
205 "Unable to serialize a DICOM file to a memory buffer");
206 }
207
208 hasBuffer_ = true;
209 }
210 }
211
212
213 public:
214 void SetBuffer(const void* data,
215 size_t size)
216 {
217 ownBuffer_.reset(NULL);
218 bufferData_ = data;
219 bufferSize_ = size;
220 hasBuffer_ = true;
221 } 157 }
222 158
223 const void* GetBufferData() 159 virtual ParsedDicomFile& GetParsedDicomFile() const ORTHANC_OVERRIDE
224 { 160 {
225 ComputeDicomBufferIfMissing(); 161 if (parsed_.get() == NULL)
226 162 {
227 if (!hasBuffer_) 163 // This operation is costly, as it creates a clone of the
228 { 164 // dataset. This explains why the default implementations
229 throw OrthancException(ErrorCode_InternalError); 165 // are overridden below to use "dataset_" as much as possible
230 } 166 const_cast<FromDcmDataset&>(*this).parsed_.reset(new ParsedDicomFile(dataset_));
231 167 }
232 if (ownBuffer_.get() != NULL) 168
233 { 169 return *parsed_;
234 if (ownBuffer_->empty()) 170 }
235 { 171
236 return NULL; 172 virtual const void* GetBufferData() const ORTHANC_OVERRIDE
237 } 173 {
238 else 174 const_cast<FromDcmDataset&>(*this).SerializeToBuffer();
239 { 175
240 return ownBuffer_->c_str(); 176 assert(buffer_.get() != NULL);
241 } 177 return (buffer_->empty() ? NULL : buffer_->c_str());
242 } 178 }
243 else 179
244 { 180 virtual size_t GetBufferSize() const ORTHANC_OVERRIDE
245 return bufferData_; 181 {
246 } 182 const_cast<FromDcmDataset&>(*this).SerializeToBuffer();
247 } 183
248 184 assert(buffer_.get() != NULL);
249 185 return buffer_->size();
250 size_t GetBufferSize() 186 }
251 { 187
252 ComputeDicomBufferIfMissing(); 188 virtual bool HasPixelData() const ORTHANC_OVERRIDE
189 {
190 DcmTag key(DICOM_TAG_PIXEL_DATA.GetGroup(),
191 DICOM_TAG_PIXEL_DATA.GetElement());
192 return dataset_.tagExists(key);
193 }
194
195 virtual void GetSummary(DicomMap& summary) const ORTHANC_OVERRIDE
196 {
197 OrthancConfiguration::DefaultExtractDicomSummary(summary, dataset_);
198 }
199
200 virtual void GetDicomAsJson(Json::Value& dicomAsJson) const ORTHANC_OVERRIDE
201 {
202 OrthancConfiguration::DefaultDicomDatasetToJson(dicomAsJson, dataset_);
203 }
204
205 virtual void DatasetToJson(Json::Value& target,
206 DicomToJsonFormat format,
207 DicomToJsonFlags flags,
208 unsigned int maxStringLength) const ORTHANC_OVERRIDE
209 {
210 std::set<DicomTag> ignoreTagLength;
211 FromDcmtkBridge::ExtractDicomAsJson(
212 target, dataset_, format, flags, maxStringLength, ignoreTagLength);
213 }
214
215 virtual unsigned int GetFramesCount() const ORTHANC_OVERRIDE
216 {
217 return DicomFrameIndex::GetFramesCount(dataset_);
218 }
253 219
254 if (!hasBuffer_) 220 virtual ImageAccessor* DecodeFrame(unsigned int frame) const ORTHANC_OVERRIDE
255 { 221 {
256 throw OrthancException(ErrorCode_InternalError); 222 return DicomImageDecoder::Decode(dataset_, frame);
257 }
258
259 if (ownBuffer_.get() != NULL)
260 {
261 return ownBuffer_->size();
262 }
263 else
264 {
265 return bufferSize_;
266 }
267 }
268
269
270 bool LookupTransferSyntax(DicomTransferSyntax& result)
271 {
272 DicomMap header;
273 if (DicomMap::ParseDicomMetaInformation(header, GetBufferData(), GetBufferSize()))
274 {
275 const DicomValue* value = header.TestAndGetValue(DICOM_TAG_TRANSFER_SYNTAX_UID);
276 if (value != NULL &&
277 !value->IsBinary() &&
278 !value->IsNull())
279 {
280 return ::Orthanc::LookupTransferSyntax(result, Toolbox::StripSpaces(value->GetContent()));
281 }
282 }
283 else
284 {
285 // This is a DICOM file without a proper meta-header. Fallback
286 // to DCMTK, which will fully parse the dataset to retrieve
287 // the transfer syntax. Added in Orthanc 1.8.2.
288 return GetParsedDicomFile().LookupTransferSyntax(result);
289 }
290
291 return false;
292 }
293
294
295 ParsedDicomFile& GetParsedDicomFile()
296 {
297 ComputeParsedDicomFileIfMissing();
298
299 if (parsed_.HasContent())
300 {
301 return parsed_.GetContent();
302 }
303 else
304 {
305 throw OrthancException(ErrorCode_InternalError);
306 }
307 } 223 }
308 }; 224 };
309 225
310 226
311 DicomInstanceToStore::DicomInstanceToStore() : 227 DicomInstanceToStore* DicomInstanceToStore::CreateFromBuffer(const void* buffer,
312 pimpl_(new PImpl) 228 size_t size)
313 { 229 {
314 } 230 return new FromBuffer(buffer, size);
315 231 }
316 232
317 void DicomInstanceToStore::SetOrigin(const DicomInstanceOrigin& origin) 233
318 { 234 DicomInstanceToStore* DicomInstanceToStore::CreateFromBuffer(const std::string& buffer)
319 pimpl_->origin_ = origin; 235 {
320 } 236 return new FromBuffer(buffer.empty() ? NULL : buffer.c_str(), buffer.size());
321 237 }
238
239
240 DicomInstanceToStore* DicomInstanceToStore::CreateFromParsedDicomFile(ParsedDicomFile& dicom)
241 {
242 return new FromParsedDicomFile(dicom);
243 }
244
245
246 DicomInstanceToStore* DicomInstanceToStore::CreateFromDcmDataset(DcmDataset& dataset)
247 {
248 return new FromDcmDataset(dataset);
249 }
250
251
252 bool DicomInstanceToStore::LookupTransferSyntax(DicomTransferSyntax& result) const
253 {
254 DicomMap header;
255 if (DicomMap::ParseDicomMetaInformation(header, GetBufferData(), GetBufferSize()))
256 {
257 const DicomValue* value = header.TestAndGetValue(DICOM_TAG_TRANSFER_SYNTAX_UID);
258 if (value != NULL &&
259 !value->IsBinary() &&
260 !value->IsNull())
261 {
262 return ::Orthanc::LookupTransferSyntax(result, Toolbox::StripSpaces(value->GetContent()));
263 }
264 }
265 else
266 {
267 // This is a DICOM file without a proper meta-header. Fallback
268 // to DCMTK, which will fully parse the dataset to retrieve
269 // the transfer syntax. Added in Orthanc 1.8.2.
270 return GetParsedDicomFile().LookupTransferSyntax(result);
271 }
322 272
323 const DicomInstanceOrigin& DicomInstanceToStore::GetOrigin() const 273 return false;
324 {
325 return pimpl_->origin_;
326 }
327
328
329 void DicomInstanceToStore::SetBuffer(const void* dicom,
330 size_t size)
331 {
332 pimpl_->SetBuffer(dicom, size);
333 }
334
335
336 void DicomInstanceToStore::SetParsedDicomFile(ParsedDicomFile& parsed)
337 {
338 pimpl_->parsed_.SetReference(parsed);
339 }
340
341
342 const DicomInstanceToStore::MetadataMap& DicomInstanceToStore::GetMetadata() const
343 {
344 return pimpl_->metadata_;
345 }
346
347
348 void DicomInstanceToStore::ClearMetadata()
349 {
350 pimpl_->metadata_.clear();
351 }
352
353
354 void DicomInstanceToStore::AddMetadata(ResourceType level,
355 MetadataType metadata,
356 const std::string& value)
357 {
358 pimpl_->metadata_[std::make_pair(level, metadata)] = value;
359 }
360
361
362 const void* DicomInstanceToStore::GetBufferData() const
363 {
364 return const_cast<PImpl&>(*pimpl_).GetBufferData();
365 }
366
367
368 size_t DicomInstanceToStore::GetBufferSize() const
369 {
370 return const_cast<PImpl&>(*pimpl_).GetBufferSize();
371 }
372
373
374 bool DicomInstanceToStore::LookupTransferSyntax(DicomTransferSyntax& result) const
375 {
376 return const_cast<PImpl&>(*pimpl_).LookupTransferSyntax(result);
377 } 274 }
378 275
379 276
380 bool DicomInstanceToStore::HasPixelData() const 277 bool DicomInstanceToStore::HasPixelData() const
381 { 278 {
382 return const_cast<PImpl&>(*pimpl_).GetParsedDicomFile().HasTag(DICOM_TAG_PIXEL_DATA); 279 return GetParsedDicomFile().HasTag(DICOM_TAG_PIXEL_DATA);
383 } 280 }
384 281
385 ParsedDicomFile& DicomInstanceToStore::GetParsedDicomFile() const 282
386 {
387 return const_cast<PImpl&>(*pimpl_).GetParsedDicomFile();
388 }
389
390 void DicomInstanceToStore::GetSummary(DicomMap& summary) const 283 void DicomInstanceToStore::GetSummary(DicomMap& summary) const
391 { 284 {
392 OrthancConfiguration::DefaultExtractDicomSummary(summary, GetParsedDicomFile()); 285 OrthancConfiguration::DefaultExtractDicomSummary(summary, GetParsedDicomFile());
393 } 286 }
394 287
288
395 void DicomInstanceToStore::GetDicomAsJson(Json::Value& dicomAsJson) const 289 void DicomInstanceToStore::GetDicomAsJson(Json::Value& dicomAsJson) const
396 { 290 {
397 OrthancConfiguration::DefaultDicomDatasetToJson(dicomAsJson, GetParsedDicomFile()); 291 OrthancConfiguration::DefaultDicomDatasetToJson(dicomAsJson, GetParsedDicomFile());
398 } 292 }
293
399 294
400 void DicomInstanceToStore::DatasetToJson(Json::Value& target, 295 void DicomInstanceToStore::DatasetToJson(Json::Value& target,
401 DicomToJsonFormat format, 296 DicomToJsonFormat format,
402 DicomToJsonFlags flags, 297 DicomToJsonFlags flags,
403 unsigned int maxStringLength) const 298 unsigned int maxStringLength) const
404 { 299 {
405 return GetParsedDicomFile().DatasetToJson(target, format, flags, maxStringLength); 300 return GetParsedDicomFile().DatasetToJson(target, format, flags, maxStringLength);
406 } 301 }
407 302
303
408 unsigned int DicomInstanceToStore::GetFramesCount() const 304 unsigned int DicomInstanceToStore::GetFramesCount() const
409 { 305 {
410 return GetParsedDicomFile().GetFramesCount(); 306 return GetParsedDicomFile().GetFramesCount();
411 } 307 }
412 308
309
413 ImageAccessor* DicomInstanceToStore::DecodeFrame(unsigned int frame) const 310 ImageAccessor* DicomInstanceToStore::DecodeFrame(unsigned int frame) const
414 { 311 {
415 return GetParsedDicomFile().DecodeFrame(frame); 312 return GetParsedDicomFile().DecodeFrame(frame);
416 } 313 }
417 } 314 }