Mercurial > hg > orthanc
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 } |