Mercurial > hg > orthanc
comparison OrthancServer/Sources/ServerContext.cpp @ 5807:8279eaab0d1d attach-custom-data
merged default -> attach-custom-data
author | Alain Mazy <am@orthanc.team> |
---|---|
date | Tue, 24 Sep 2024 11:39:52 +0200 |
parents | d7274e43ea7c b4fbd9d2c907 |
children | 023a99146dd0 |
comparison
equal
deleted
inserted
replaced
5085:79f98ee4f04b | 5807:8279eaab0d1d |
---|---|
1 /** | 1 /** |
2 * Orthanc - A Lightweight, RESTful DICOM Store | 2 * Orthanc - A Lightweight, RESTful DICOM Store |
3 * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics | 3 * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics |
4 * Department, University Hospital of Liege, Belgium | 4 * Department, University Hospital of Liege, Belgium |
5 * Copyright (C) 2017-2022 Osimis S.A., Belgium | 5 * Copyright (C) 2017-2023 Osimis S.A., Belgium |
6 * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium | 6 * Copyright (C) 2024-2024 Orthanc Team SRL, Belgium |
7 * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium | |
7 * | 8 * |
8 * This program is free software: you can redistribute it and/or | 9 * This program is free software: you can redistribute it and/or |
9 * modify it under the terms of the GNU General Public License as | 10 * modify it under the terms of the GNU General Public License as |
10 * published by the Free Software Foundation, either version 3 of the | 11 * published by the Free Software Foundation, either version 3 of the |
11 * License, or (at your option) any later version. | 12 * License, or (at your option) any later version. |
47 #include "StorageCommitmentReports.h" | 48 #include "StorageCommitmentReports.h" |
48 | 49 |
49 #include <dcmtk/dcmdata/dcfilefo.h> | 50 #include <dcmtk/dcmdata/dcfilefo.h> |
50 #include <dcmtk/dcmnet/dimse.h> | 51 #include <dcmtk/dcmnet/dimse.h> |
51 | 52 |
53 #if HAVE_MALLOC_TRIM == 1 | |
54 # include <malloc.h> | |
55 #endif | |
52 | 56 |
53 static size_t DICOM_CACHE_SIZE = 128 * 1024 * 1024; // 128 MB | 57 static size_t DICOM_CACHE_SIZE = 128 * 1024 * 1024; // 128 MB |
54 | 58 |
55 | 59 |
56 /** | 60 /** |
102 status_(StoreStatus_Failure), | 106 status_(StoreStatus_Failure), |
103 cstoreStatusCode_(0) | 107 cstoreStatusCode_(0) |
104 { | 108 { |
105 } | 109 } |
106 | 110 |
111 | |
112 #if HAVE_MALLOC_TRIM == 1 | |
113 void ServerContext::MemoryTrimmingThread(ServerContext* that, | |
114 unsigned int intervalInSeconds) | |
115 { | |
116 Logging::SetCurrentThreadName("MEMORY-TRIM"); | |
117 | |
118 boost::posix_time::ptime lastExecution = boost::posix_time::second_clock::universal_time(); | |
119 | |
120 // This thread is started only if malloc_trim is defined | |
121 while (!that->done_) | |
122 { | |
123 boost::posix_time::ptime now = boost::posix_time::second_clock::universal_time(); | |
124 boost::posix_time::time_duration elapsed = now - lastExecution; | |
125 | |
126 if (elapsed.total_seconds() > intervalInSeconds) | |
127 { | |
128 // If possible, gives memory back to the system | |
129 // (see OrthancServer/Resources/ImplementationNotes/memory_consumption.txt) | |
130 { | |
131 MetricsRegistry::Timer timer(that->GetMetricsRegistry(), "orthanc_memory_trimming_duration_ms"); | |
132 malloc_trim(128*1024); | |
133 } | |
134 lastExecution = boost::posix_time::second_clock::universal_time(); | |
135 } | |
136 | |
137 boost::this_thread::sleep(boost::posix_time::milliseconds(100)); | |
138 } | |
139 } | |
140 #endif | |
141 | |
107 | 142 |
108 void ServerContext::ChangeThread(ServerContext* that, | 143 void ServerContext::ChangeThread(ServerContext* that, |
109 unsigned int sleepDelay) | 144 unsigned int sleepDelay) |
110 { | 145 { |
146 Logging::SetCurrentThreadName("CHANGES"); | |
147 | |
111 while (!that->done_) | 148 while (!that->done_) |
112 { | 149 { |
113 std::unique_ptr<IDynamicObject> obj(that->pendingChanges_.Dequeue(sleepDelay)); | 150 std::unique_ptr<IDynamicObject> obj(that->pendingChanges_.Dequeue(sleepDelay)); |
114 | 151 |
115 if (obj.get() != NULL) | 152 if (obj.get() != NULL) |
130 { | 167 { |
131 LOG(ERROR) << "Not enough memory while signaling a change"; | 168 LOG(ERROR) << "Not enough memory while signaling a change"; |
132 } | 169 } |
133 catch (...) | 170 catch (...) |
134 { | 171 { |
135 throw OrthancException(ErrorCode_InternalError); | 172 throw OrthancException(ErrorCode_InternalError, "Error while signaling a change"); |
136 } | 173 } |
137 } | 174 } |
138 catch (OrthancException& e) | 175 catch (OrthancException& e) |
139 { | 176 { |
140 LOG(ERROR) << "Error in the " << it->GetDescription() | 177 LOG(ERROR) << "Error in the " << it->GetDescription() |
145 } | 182 } |
146 } | 183 } |
147 } | 184 } |
148 | 185 |
149 | 186 |
187 void ServerContext::JobEventsThread(ServerContext* that, | |
188 unsigned int sleepDelay) | |
189 { | |
190 Logging::SetCurrentThreadName("JOB-EVENTS"); | |
191 | |
192 while (!that->done_) | |
193 { | |
194 std::unique_ptr<IDynamicObject> obj(that->pendingJobEvents_.Dequeue(sleepDelay)); | |
195 | |
196 if (obj.get() != NULL) | |
197 { | |
198 const JobEvent& event = dynamic_cast<const JobEvent&>(*obj.get()); | |
199 | |
200 boost::shared_lock<boost::shared_mutex> lock(that->listenersMutex_); | |
201 for (ServerListeners::iterator it = that->listeners_.begin(); | |
202 it != that->listeners_.end(); ++it) | |
203 { | |
204 try | |
205 { | |
206 try | |
207 { | |
208 it->GetListener().SignalJobEvent(event); | |
209 } | |
210 catch (std::bad_alloc&) | |
211 { | |
212 LOG(ERROR) << "Not enough memory while signaling a job event"; | |
213 } | |
214 catch (...) | |
215 { | |
216 throw OrthancException(ErrorCode_InternalError, "Error while signaling a job event"); | |
217 } | |
218 } | |
219 catch (OrthancException& e) | |
220 { | |
221 LOG(ERROR) << "Error in the " << it->GetDescription() | |
222 << " callback while signaling a job event: " << e.What() | |
223 << " (code " << e.GetErrorCode() << ")"; | |
224 } | |
225 } | |
226 } | |
227 } | |
228 } | |
229 | |
230 | |
150 void ServerContext::SaveJobsThread(ServerContext* that, | 231 void ServerContext::SaveJobsThread(ServerContext* that, |
151 unsigned int sleepDelay) | 232 unsigned int sleepDelay) |
152 { | 233 { |
234 Logging::SetCurrentThreadName("SAVE-JOBS"); | |
235 | |
153 static const boost::posix_time::time_duration PERIODICITY = | 236 static const boost::posix_time::time_duration PERIODICITY = |
154 boost::posix_time::seconds(10); | 237 boost::posix_time::seconds(10); |
155 | 238 |
156 boost::posix_time::ptime next = | 239 boost::posix_time::ptime next = |
157 boost::posix_time::microsec_clock::universal_time() + PERIODICITY; | 240 boost::posix_time::microsec_clock::universal_time() + PERIODICITY; |
172 | 255 |
173 | 256 |
174 void ServerContext::SignalJobSubmitted(const std::string& jobId) | 257 void ServerContext::SignalJobSubmitted(const std::string& jobId) |
175 { | 258 { |
176 haveJobsChanged_ = true; | 259 haveJobsChanged_ = true; |
177 mainLua_.SignalJobSubmitted(jobId); | 260 pendingJobEvents_.Enqueue(new JobEvent(JobEventType_Submitted, jobId)); |
178 | |
179 #if ORTHANC_ENABLE_PLUGINS == 1 | |
180 if (HasPlugins()) | |
181 { | |
182 GetPlugins().SignalJobSubmitted(jobId); | |
183 } | |
184 #endif | |
185 } | 261 } |
186 | 262 |
187 | 263 |
188 void ServerContext::SignalJobSuccess(const std::string& jobId) | 264 void ServerContext::SignalJobSuccess(const std::string& jobId) |
189 { | 265 { |
190 haveJobsChanged_ = true; | 266 haveJobsChanged_ = true; |
191 mainLua_.SignalJobSuccess(jobId); | 267 pendingJobEvents_.Enqueue(new JobEvent(JobEventType_Success, jobId)); |
192 | |
193 #if ORTHANC_ENABLE_PLUGINS == 1 | |
194 if (HasPlugins()) | |
195 { | |
196 GetPlugins().SignalJobSuccess(jobId); | |
197 } | |
198 #endif | |
199 } | 268 } |
200 | 269 |
201 | 270 |
202 void ServerContext::SignalJobFailure(const std::string& jobId) | 271 void ServerContext::SignalJobFailure(const std::string& jobId) |
203 { | 272 { |
204 haveJobsChanged_ = true; | 273 haveJobsChanged_ = true; |
205 mainLua_.SignalJobFailure(jobId); | 274 pendingJobEvents_.Enqueue(new JobEvent(JobEventType_Failure, jobId)); |
206 | |
207 #if ORTHANC_ENABLE_PLUGINS == 1 | |
208 if (HasPlugins()) | |
209 { | |
210 GetPlugins().SignalJobFailure(jobId); | |
211 } | |
212 #endif | |
213 } | 275 } |
214 | 276 |
215 | 277 |
216 void ServerContext::SetupJobsEngine(bool unitTesting, | 278 void ServerContext::SetupJobsEngine(bool unitTesting, |
217 bool loadJobsFromDatabase) | 279 bool loadJobsFromDatabase) |
229 jobsEngine_.LoadRegistryFromString(unserializer, serialized); | 291 jobsEngine_.LoadRegistryFromString(unserializer, serialized); |
230 } | 292 } |
231 catch (OrthancException& e) | 293 catch (OrthancException& e) |
232 { | 294 { |
233 LOG(WARNING) << "Cannot unserialize the jobs engine, starting anyway: " << e.What(); | 295 LOG(WARNING) << "Cannot unserialize the jobs engine, starting anyway: " << e.What(); |
296 } | |
297 catch (const std::string& s) | |
298 { | |
299 LOG(WARNING) << "Cannot unserialize the jobs engine, starting anyway: \"" << s << "\""; | |
300 } | |
301 catch (...) | |
302 { | |
303 LOG(WARNING) << "Cannot unserialize the jobs engine, starting anyway"; | |
234 } | 304 } |
235 } | 305 } |
236 else | 306 else |
237 { | 307 { |
238 LOG(INFO) << "The last execution of Orthanc has archived no job"; | 308 LOG(INFO) << "The last execution of Orthanc has archived no job"; |
273 } | 343 } |
274 } | 344 } |
275 } | 345 } |
276 | 346 |
277 | 347 |
278 void ServerContext::PublishDicomCacheMetrics() | 348 void ServerContext::PublishCacheMetrics() |
279 { | 349 { |
280 metricsRegistry_->SetValue("orthanc_dicom_cache_size", | 350 metricsRegistry_->SetFloatValue("orthanc_dicom_cache_size_mb", |
281 static_cast<float>(dicomCache_.GetCurrentSize()) / static_cast<float>(1024 * 1024)); | 351 static_cast<float>(dicomCache_.GetCurrentSize()) / static_cast<float>(1024 * 1024)); |
282 metricsRegistry_->SetValue("orthanc_dicom_cache_count", | 352 metricsRegistry_->SetIntegerValue("orthanc_dicom_cache_count", dicomCache_.GetNumberOfItems()); |
283 static_cast<float>(dicomCache_.GetNumberOfItems())); | 353 |
354 metricsRegistry_->SetFloatValue("orthanc_storage_cache_size_mb", | |
355 static_cast<float>(storageCache_.GetCurrentSize()) / static_cast<float>(1024 * 1024)); | |
356 metricsRegistry_->SetIntegerValue("orthanc_storage_cache_count", storageCache_.GetNumberOfItems()); | |
284 } | 357 } |
285 | 358 |
286 | 359 |
287 ServerContext::ServerContext(IDatabaseWrapper& database, | 360 ServerContext::ServerContext(IDatabaseWrapper& database, |
288 IStorageArea& area, | 361 IStorageArea& area, |
305 haveJobsChanged_(false), | 378 haveJobsChanged_(false), |
306 isJobsEngineUnserialized_(false), | 379 isJobsEngineUnserialized_(false), |
307 metricsRegistry_(new MetricsRegistry), | 380 metricsRegistry_(new MetricsRegistry), |
308 isHttpServerSecure_(true), | 381 isHttpServerSecure_(true), |
309 isExecuteLuaEnabled_(false), | 382 isExecuteLuaEnabled_(false), |
383 isRestApiWriteToFileSystemEnabled_(false), | |
310 overwriteInstances_(false), | 384 overwriteInstances_(false), |
311 dcmtkTranscoder_(new DcmtkTranscoder), | 385 dcmtkTranscoder_(new DcmtkTranscoder), |
312 isIngestTranscoding_(false), | 386 isIngestTranscoding_(false), |
313 ingestTranscodingOfUncompressed_(true), | 387 ingestTranscodingOfUncompressed_(true), |
314 ingestTranscodingOfCompressed_(true), | 388 ingestTranscodingOfCompressed_(true), |
315 preferredTransferSyntax_(DicomTransferSyntax_LittleEndianExplicit), | 389 preferredTransferSyntax_(DicomTransferSyntax_LittleEndianExplicit), |
316 deidentifyLogs_(false) | 390 deidentifyLogs_(false), |
391 serverStartTimeUtc_(boost::posix_time::second_clock::universal_time()) | |
317 { | 392 { |
318 try | 393 try |
319 { | 394 { |
320 unsigned int lossyQuality; | 395 unsigned int lossyQuality; |
321 | 396 |
382 { | 457 { |
383 deidentifyLogs_ = true; | 458 deidentifyLogs_ = true; |
384 CLOG(INFO, DICOM) << "Deidentification of log contents (notably for DIMSE queries) is enabled"; | 459 CLOG(INFO, DICOM) << "Deidentification of log contents (notably for DIMSE queries) is enabled"; |
385 | 460 |
386 DicomVersion version = StringToDicomVersion( | 461 DicomVersion version = StringToDicomVersion( |
387 lock.GetConfiguration().GetStringParameter("DeidentifyLogsDicomVersion", "2021b")); | 462 lock.GetConfiguration().GetStringParameter("DeidentifyLogsDicomVersion", "2023b")); |
388 CLOG(INFO, DICOM) << "Version of DICOM standard used for deidentification is " | 463 CLOG(INFO, DICOM) << "Version of DICOM standard used for deidentification is " |
389 << EnumerationToString(version); | 464 << EnumerationToString(version); |
390 | 465 |
391 logsDeidentifierRules_.SetupAnonymization(version); | 466 logsDeidentifierRules_.SetupAnonymization(version); |
392 } | 467 } |
414 | 489 |
415 jobsEngine_.SetThreadSleep(unitTesting ? 20 : 200); | 490 jobsEngine_.SetThreadSleep(unitTesting ? 20 : 200); |
416 | 491 |
417 listeners_.push_back(ServerListener(luaListener_, "Lua")); | 492 listeners_.push_back(ServerListener(luaListener_, "Lua")); |
418 changeThread_ = boost::thread(ChangeThread, this, (unitTesting ? 20 : 100)); | 493 changeThread_ = boost::thread(ChangeThread, this, (unitTesting ? 20 : 100)); |
419 | 494 jobEventsThread_ = boost::thread(JobEventsThread, this, (unitTesting ? 20 : 100)); |
495 | |
496 #if HAVE_MALLOC_TRIM == 1 | |
497 LOG(INFO) << "Starting memory trimming thread at 30 seconds interval"; | |
498 memoryTrimmingThread_ = boost::thread(MemoryTrimmingThread, this, 30); | |
499 #else | |
500 LOG(INFO) << "Your platform does not support malloc_trim(), not starting the memory trimming thread"; | |
501 #endif | |
502 | |
420 dynamic_cast<DcmtkTranscoder&>(*dcmtkTranscoder_).SetLossyQuality(lossyQuality); | 503 dynamic_cast<DcmtkTranscoder&>(*dcmtkTranscoder_).SetLossyQuality(lossyQuality); |
421 } | 504 } |
422 catch (OrthancException&) | 505 catch (OrthancException&) |
423 { | 506 { |
424 Stop(); | 507 Stop(); |
452 if (changeThread_.joinable()) | 535 if (changeThread_.joinable()) |
453 { | 536 { |
454 changeThread_.join(); | 537 changeThread_.join(); |
455 } | 538 } |
456 | 539 |
540 if (jobEventsThread_.joinable()) | |
541 { | |
542 jobEventsThread_.join(); | |
543 } | |
544 | |
457 if (saveJobsThread_.joinable()) | 545 if (saveJobsThread_.joinable()) |
458 { | 546 { |
459 saveJobsThread_.join(); | 547 saveJobsThread_.join(); |
548 } | |
549 | |
550 if (memoryTrimmingThread_.joinable()) | |
551 { | |
552 memoryTrimmingThread_.join(); | |
460 } | 553 } |
461 | 554 |
462 jobsEngine_.GetRegistry().ResetObserver(); | 555 jobsEngine_.GetRegistry().ResetObserver(); |
463 | 556 |
464 if (isJobsEngineUnserialized_) | 557 if (isJobsEngineUnserialized_) |
487 | 580 |
488 void ServerContext::RemoveFile(const std::string& fileUuid, | 581 void ServerContext::RemoveFile(const std::string& fileUuid, |
489 FileContentType type, | 582 FileContentType type, |
490 const std::string& customData) | 583 const std::string& customData) |
491 { | 584 { |
492 StorageAccessor accessor(area_, &storageCache_, GetMetricsRegistry()); | 585 StorageAccessor accessor(area_, storageCache_, GetMetricsRegistry()); |
493 accessor.Remove(fileUuid, type, customData); | 586 accessor.Remove(fileUuid, type, customData); |
494 } | 587 } |
495 | 588 |
496 | 589 |
497 ServerContext::StoreResult ServerContext::StoreAfterTranscoding(std::string& resultPublicId, | 590 ServerContext::StoreResult ServerContext::StoreAfterTranscoding(std::string& resultPublicId, |
518 throw OrthancException(ErrorCode_ParameterOutOfRange); | 611 throw OrthancException(ErrorCode_ParameterOutOfRange); |
519 } | 612 } |
520 | 613 |
521 bool hasPixelDataOffset; | 614 bool hasPixelDataOffset; |
522 uint64_t pixelDataOffset; | 615 uint64_t pixelDataOffset; |
616 ValueRepresentation pixelDataVR; | |
523 hasPixelDataOffset = DicomStreamReader::LookupPixelDataOffset( | 617 hasPixelDataOffset = DicomStreamReader::LookupPixelDataOffset( |
524 pixelDataOffset, dicom.GetBufferData(), dicom.GetBufferSize()); | 618 pixelDataOffset, pixelDataVR, dicom.GetBufferData(), dicom.GetBufferSize()); |
525 | 619 |
526 DicomTransferSyntax transferSyntax; | 620 DicomTransferSyntax transferSyntax; |
527 bool hasTransferSyntax = dicom.LookupTransferSyntax(transferSyntax); | 621 bool hasTransferSyntax = dicom.LookupTransferSyntax(transferSyntax); |
528 | 622 |
529 DicomMap summary; | 623 DicomMap summary; |
530 dicom.GetSummary(summary); // -> from Orthanc 1.11.1, this includes the leaf nodes and sequences | 624 dicom.GetSummary(summary); // -> from Orthanc 1.11.1, this includes the leaf nodes and sequences |
531 | 625 |
532 std::set<DicomTag> allMainDicomTags = DicomMap::GetAllMainDicomTags(); | 626 std::set<DicomTag> allMainDicomTags; |
627 DicomMap::GetAllMainDicomTags(allMainDicomTags); | |
533 | 628 |
534 try | 629 try |
535 { | 630 { |
536 MetricsRegistry::Timer timer(GetMetricsRegistry(), "orthanc_store_dicom_duration_ms"); | 631 MetricsRegistry::Timer timer(GetMetricsRegistry(), "orthanc_store_dicom_duration_ms"); |
537 StorageAccessor accessor(area_, &storageCache_, GetMetricsRegistry()); | 632 StorageAccessor accessor(area_, storageCache_, GetMetricsRegistry()); |
538 | 633 |
539 DicomInstanceHasher hasher(summary); | 634 DicomInstanceHasher hasher(summary); |
540 resultPublicId = hasher.HashInstance(); | 635 resultPublicId = hasher.HashInstance(); |
541 | 636 |
542 Json::Value dicomAsJson; | 637 Json::Value dicomAsJson; |
593 } | 688 } |
594 | 689 |
595 // Remove the file from the DicomCache (useful if | 690 // Remove the file from the DicomCache (useful if |
596 // "OverwriteInstances" is set to "true") | 691 // "OverwriteInstances" is set to "true") |
597 dicomCache_.Invalidate(resultPublicId); | 692 dicomCache_.Invalidate(resultPublicId); |
598 PublishDicomCacheMetrics(); | |
599 | 693 |
600 // TODO Should we use "gzip" instead? | 694 // TODO Should we use "gzip" instead? |
601 CompressionType compression = (compressionEnabled_ ? CompressionType_ZlibWithSize : CompressionType_None); | 695 CompressionType compression = (compressionEnabled_ ? CompressionType_ZlibWithSize : CompressionType_None); |
602 | 696 |
603 std::string dicomCustomData; | 697 std::string dicomCustomData; |
622 attachments.push_back(dicomUntilPixelData); | 716 attachments.push_back(dicomUntilPixelData); |
623 } | 717 } |
624 | 718 |
625 typedef std::map<MetadataType, std::string> InstanceMetadata; | 719 typedef std::map<MetadataType, std::string> InstanceMetadata; |
626 InstanceMetadata instanceMetadata; | 720 InstanceMetadata instanceMetadata; |
627 result.SetStatus(index_.Store( | 721 |
628 instanceMetadata, summary, attachments, dicom.GetMetadata(), dicom.GetOrigin(), overwrite, | 722 try |
629 hasTransferSyntax, transferSyntax, hasPixelDataOffset, pixelDataOffset, isReconstruct)); | 723 { |
724 result.SetStatus(index_.Store( | |
725 instanceMetadata, summary, attachments, dicom.GetMetadata(), dicom.GetOrigin(), overwrite, | |
726 hasTransferSyntax, transferSyntax, hasPixelDataOffset, pixelDataOffset, pixelDataVR, isReconstruct)); | |
727 } | |
728 catch (OrthancException& ex) | |
729 { | |
730 if (ex.GetErrorCode() == ErrorCode_DuplicateResource) | |
731 { | |
732 LOG(WARNING) << "Duplicate instance, deleting the attachments"; | |
733 } | |
734 else | |
735 { | |
736 LOG(ERROR) << "Unexpected error while storing an instance in DB, cancelling and deleting the attachments: " << ex.GetDetails(); | |
737 } | |
738 | |
739 accessor.Remove(dicomInfo); | |
740 | |
741 if (dicomUntilPixelData.IsValid()) | |
742 { | |
743 accessor.Remove(dicomUntilPixelData); | |
744 } | |
745 | |
746 throw; | |
747 } | |
630 | 748 |
631 // Only keep the metadata for the "instance" level | 749 // Only keep the metadata for the "instance" level |
632 dicom.ClearMetadata(); | 750 dicom.ClearMetadata(); |
633 | 751 |
634 for (InstanceMetadata::const_iterator it = instanceMetadata.begin(); | 752 for (InstanceMetadata::const_iterator it = instanceMetadata.begin(); |
651 { | 769 { |
652 // skip logs in case of reconstruction | 770 // skip logs in case of reconstruction |
653 switch (result.GetStatus()) | 771 switch (result.GetStatus()) |
654 { | 772 { |
655 case StoreStatus_Success: | 773 case StoreStatus_Success: |
656 LOG(INFO) << "New instance stored"; | 774 LOG(INFO) << "New instance stored (" << resultPublicId << ")"; |
657 break; | 775 break; |
658 | 776 |
659 case StoreStatus_AlreadyStored: | 777 case StoreStatus_AlreadyStored: |
660 LOG(INFO) << "Already stored"; | 778 LOG(INFO) << "Instance already stored (" << resultPublicId << ")"; |
661 break; | 779 break; |
662 | 780 |
663 case StoreStatus_Failure: | 781 case StoreStatus_Failure: |
664 LOG(ERROR) << "Store failure"; | 782 LOG(ERROR) << "Unknown store failure while storing instance " << resultPublicId; |
665 break; | 783 throw OrthancException(ErrorCode_InternalError, HttpStatus_500_InternalServerError); |
784 | |
785 case StoreStatus_StorageFull: | |
786 LOG(ERROR) << "Storage full while storing instance " << resultPublicId; | |
787 throw OrthancException(ErrorCode_FullStorage, HttpStatus_507_InsufficientStorage); | |
666 | 788 |
667 default: | 789 default: |
668 // This should never happen | 790 // This should never happen |
669 break; | 791 break; |
670 } | 792 } |
766 DicomInstanceToStore* dicom, | 888 DicomInstanceToStore* dicom, |
767 StoreInstanceMode mode, | 889 StoreInstanceMode mode, |
768 bool isReconstruct) | 890 bool isReconstruct) |
769 { | 891 { |
770 | 892 |
771 if (!isIngestTranscoding_) | 893 if (!isIngestTranscoding_ || dicom->SkipIngestTranscoding()) |
772 { | 894 { |
773 // No automated transcoding. This was the only path in Orthanc <= 1.6.1. | 895 // No automated transcoding. This was the only path in Orthanc <= 1.6.1. |
774 return StoreAfterTranscoding(resultPublicId, *dicom, mode, isReconstruct); | 896 return StoreAfterTranscoding(resultPublicId, *dicom, mode, isReconstruct); |
775 } | 897 } |
776 else | 898 else |
823 { | 945 { |
824 std::unique_ptr<ParsedDicomFile> tmp(transcoded.ReleaseAsParsedDicomFile()); | 946 std::unique_ptr<ParsedDicomFile> tmp(transcoded.ReleaseAsParsedDicomFile()); |
825 | 947 |
826 std::unique_ptr<DicomInstanceToStore> toStore(DicomInstanceToStore::CreateFromParsedDicomFile(*tmp)); | 948 std::unique_ptr<DicomInstanceToStore> toStore(DicomInstanceToStore::CreateFromParsedDicomFile(*tmp)); |
827 toStore->SetOrigin(dicom->GetOrigin()); | 949 toStore->SetOrigin(dicom->GetOrigin()); |
828 | 950 toStore->CopyMetadata(dicom->GetMetadata()); |
829 if (isReconstruct) // the initial instance to store already has its own metadata | |
830 { | |
831 toStore->CopyMetadata(dicom->GetMetadata()); | |
832 } | |
833 | 951 |
834 StoreResult result = StoreAfterTranscoding(resultPublicId, *toStore, mode, isReconstruct); | 952 StoreResult result = StoreAfterTranscoding(resultPublicId, *toStore, mode, isReconstruct); |
835 assert(resultPublicId == tmp->GetHasher().HashInstance()); | 953 assert(resultPublicId == tmp->GetHasher().HashInstance()); |
836 | 954 |
837 return result; | 955 return result; |
856 { | 974 { |
857 throw OrthancException(ErrorCode_UnknownResource); | 975 throw OrthancException(ErrorCode_UnknownResource); |
858 } | 976 } |
859 else | 977 else |
860 { | 978 { |
861 StorageAccessor accessor(area_, &storageCache_, GetMetricsRegistry()); | 979 StorageAccessor accessor(area_, storageCache_, GetMetricsRegistry()); |
862 accessor.AnswerFile(output, attachment, GetFileContentMime(content)); | 980 accessor.AnswerFile(output, attachment, GetFileContentMime(content)); |
863 } | 981 } |
864 } | 982 } |
865 | 983 |
866 | 984 |
887 return; | 1005 return; |
888 } | 1006 } |
889 | 1007 |
890 std::string content; | 1008 std::string content; |
891 | 1009 |
892 StorageAccessor accessor(area_, &storageCache_, GetMetricsRegistry()); | 1010 StorageAccessor accessor(area_, storageCache_, GetMetricsRegistry()); |
893 accessor.Read(content, attachment); | 1011 accessor.Read(content, attachment); |
894 | 1012 |
895 std::string newUuid = Toolbox::GenerateUuid(); | 1013 std::string newUuid = Toolbox::GenerateUuid(); |
896 std::string newCustomData; | 1014 std::string newCustomData; |
897 FileInfo modified; | 1015 FileInfo modified; |
960 if (index_.LookupAttachment(attachment, revision, instancePublicId, FileContentType_DicomUntilPixelData)) | 1078 if (index_.LookupAttachment(attachment, revision, instancePublicId, FileContentType_DicomUntilPixelData)) |
961 { | 1079 { |
962 std::string dicom; | 1080 std::string dicom; |
963 | 1081 |
964 { | 1082 { |
965 StorageAccessor accessor(area_, &storageCache_, GetMetricsRegistry()); | 1083 StorageAccessor accessor(area_, storageCache_, GetMetricsRegistry()); |
966 accessor.Read(dicom, attachment); | 1084 accessor.Read(dicom, attachment); |
967 } | 1085 } |
968 | 1086 |
969 ParsedDicomFile parsed(dicom); | 1087 ParsedDicomFile parsed(dicom); |
970 OrthancConfiguration::DefaultDicomDatasetToJson(result, parsed, ignoreTagLength); | 1088 OrthancConfiguration::DefaultDicomDatasetToJson(result, parsed, ignoreTagLength); |
1026 **/ | 1144 **/ |
1027 | 1145 |
1028 std::string dicom; | 1146 std::string dicom; |
1029 | 1147 |
1030 { | 1148 { |
1031 StorageAccessor accessor(area_, &storageCache_, GetMetricsRegistry()); | 1149 StorageAccessor accessor(area_, storageCache_, GetMetricsRegistry()); |
1032 accessor.ReadStartRange(dicom, attachment.GetUuid(), FileContentType_Dicom, pixelDataOffset, attachment.GetCustomData()); | 1150 accessor.ReadStartRange(dicom, attachment, pixelDataOffset); |
1033 } | 1151 } |
1034 | 1152 |
1035 assert(dicom.size() == pixelDataOffset); | 1153 assert(dicom.size() == pixelDataOffset); |
1036 ParsedDicomFile parsed(dicom); | 1154 ParsedDicomFile parsed(dicom); |
1037 OrthancConfiguration::DefaultDicomDatasetToJson(result, parsed, ignoreTagLength); | 1155 OrthancConfiguration::DefaultDicomDatasetToJson(result, parsed, ignoreTagLength); |
1049 **/ | 1167 **/ |
1050 | 1168 |
1051 std::string dicomAsJson; | 1169 std::string dicomAsJson; |
1052 | 1170 |
1053 { | 1171 { |
1054 StorageAccessor accessor(area_, &storageCache_, GetMetricsRegistry()); | 1172 StorageAccessor accessor(area_, storageCache_, GetMetricsRegistry()); |
1055 accessor.Read(dicomAsJson, attachment); | 1173 accessor.Read(dicomAsJson, attachment); |
1056 } | 1174 } |
1057 | 1175 |
1058 if (!Toolbox::ReadJson(result, dicomAsJson)) | 1176 if (!Toolbox::ReadJson(result, dicomAsJson)) |
1059 { | 1177 { |
1083 * Orthanc <= 1.9.0, or that calls to | 1201 * Orthanc <= 1.9.0, or that calls to |
1084 * "LookupPixelDataOffset()" from earlier versions of | 1202 * "LookupPixelDataOffset()" from earlier versions of |
1085 * Orthanc have failed. Try again this precomputation now | 1203 * Orthanc have failed. Try again this precomputation now |
1086 * for future calls. | 1204 * for future calls. |
1087 **/ | 1205 **/ |
1088 if (DicomStreamReader::LookupPixelDataOffset(pixelDataOffset, dicom) && | 1206 ValueRepresentation pixelDataVR; |
1207 if (DicomStreamReader::LookupPixelDataOffset(pixelDataOffset, pixelDataVR, dicom) && | |
1089 pixelDataOffset < dicom.size()) | 1208 pixelDataOffset < dicom.size()) |
1090 { | 1209 { |
1091 index_.OverwriteMetadata(instancePublicId, MetadataType_Instance_PixelDataOffset, | 1210 index_.OverwriteMetadata(instancePublicId, MetadataType_Instance_PixelDataOffset, |
1092 boost::lexical_cast<std::string>(pixelDataOffset)); | 1211 boost::lexical_cast<std::string>(pixelDataOffset)); |
1093 | 1212 |
1113 ReadDicomAsJson(result, instancePublicId, ignoreTagLength); | 1232 ReadDicomAsJson(result, instancePublicId, ignoreTagLength); |
1114 } | 1233 } |
1115 | 1234 |
1116 | 1235 |
1117 void ServerContext::ReadDicom(std::string& dicom, | 1236 void ServerContext::ReadDicom(std::string& dicom, |
1237 std::string& attachmentId, | |
1118 const std::string& instancePublicId) | 1238 const std::string& instancePublicId) |
1119 { | 1239 { |
1120 int64_t revision; | 1240 int64_t revision; |
1121 ReadAttachment(dicom, revision, instancePublicId, FileContentType_Dicom, true /* uncompress */); | 1241 ReadAttachment(dicom, revision, attachmentId, instancePublicId, FileContentType_Dicom, true /* uncompress */); |
1242 } | |
1243 | |
1244 | |
1245 void ServerContext::ReadDicom(std::string& dicom, | |
1246 const std::string& instancePublicId) | |
1247 { | |
1248 std::string attachmentId; | |
1249 ReadDicom(dicom, attachmentId, instancePublicId); | |
1122 } | 1250 } |
1123 | 1251 |
1124 void ServerContext::ReadDicomForHeader(std::string& dicom, | 1252 void ServerContext::ReadDicomForHeader(std::string& dicom, |
1125 const std::string& instancePublicId) | 1253 const std::string& instancePublicId) |
1126 { | 1254 { |
1131 } | 1259 } |
1132 | 1260 |
1133 bool ServerContext::ReadDicomUntilPixelData(std::string& dicom, | 1261 bool ServerContext::ReadDicomUntilPixelData(std::string& dicom, |
1134 const std::string& instancePublicId) | 1262 const std::string& instancePublicId) |
1135 { | 1263 { |
1136 if (!area_.HasReadRange()) | |
1137 { | |
1138 return false; | |
1139 } | |
1140 | |
1141 FileInfo attachment; | 1264 FileInfo attachment; |
1142 int64_t revision; // Ignored | 1265 int64_t revision; // Ignored |
1266 if (index_.LookupAttachment(attachment, revision, instancePublicId, FileContentType_DicomUntilPixelData)) | |
1267 { | |
1268 StorageAccessor accessor(area_, storageCache_, GetMetricsRegistry()); | |
1269 | |
1270 accessor.Read(dicom, attachment); | |
1271 assert(dicom.size() == attachment.GetUncompressedSize()); | |
1272 | |
1273 return true; | |
1274 } | |
1275 | |
1276 if (!area_.HasReadRange()) | |
1277 { | |
1278 return false; | |
1279 } | |
1280 | |
1143 if (!index_.LookupAttachment(attachment, revision, instancePublicId, FileContentType_Dicom)) | 1281 if (!index_.LookupAttachment(attachment, revision, instancePublicId, FileContentType_Dicom)) |
1144 { | 1282 { |
1145 throw OrthancException(ErrorCode_InternalError, | 1283 throw OrthancException(ErrorCode_InternalError, |
1146 "Unable to read the DICOM file of instance " + instancePublicId); | 1284 "Unable to read the DICOM file of instance " + instancePublicId); |
1147 } | 1285 } |
1155 { | 1293 { |
1156 try | 1294 try |
1157 { | 1295 { |
1158 uint64_t pixelDataOffset = boost::lexical_cast<uint64_t>(s); | 1296 uint64_t pixelDataOffset = boost::lexical_cast<uint64_t>(s); |
1159 | 1297 |
1160 StorageAccessor accessor(area_, &storageCache_, GetMetricsRegistry()); | 1298 StorageAccessor accessor(area_, storageCache_, GetMetricsRegistry()); |
1161 | 1299 |
1162 accessor.ReadStartRange(dicom, attachment.GetUuid(), attachment.GetContentType(), pixelDataOffset, attachment.GetCustomData()); | 1300 accessor.ReadStartRange(dicom, attachment, pixelDataOffset); |
1163 assert(dicom.size() == pixelDataOffset); | 1301 assert(dicom.size() == pixelDataOffset); |
1164 | 1302 |
1165 return true; // Success | 1303 return true; // Success |
1166 } | 1304 } |
1167 catch (boost::bad_lexical_cast&) | 1305 catch (boost::bad_lexical_cast&) |
1174 } | 1312 } |
1175 | 1313 |
1176 | 1314 |
1177 void ServerContext::ReadAttachment(std::string& result, | 1315 void ServerContext::ReadAttachment(std::string& result, |
1178 int64_t& revision, | 1316 int64_t& revision, |
1317 std::string& attachmentId, | |
1179 const std::string& instancePublicId, | 1318 const std::string& instancePublicId, |
1180 FileContentType content, | 1319 FileContentType content, |
1181 bool uncompressIfNeeded, | 1320 bool uncompressIfNeeded, |
1182 bool skipCache) | 1321 bool skipCache) |
1183 { | 1322 { |
1188 "Unable to read attachment " + EnumerationToString(content) + | 1327 "Unable to read attachment " + EnumerationToString(content) + |
1189 " of instance " + instancePublicId); | 1328 " of instance " + instancePublicId); |
1190 } | 1329 } |
1191 | 1330 |
1192 assert(attachment.GetContentType() == content); | 1331 assert(attachment.GetContentType() == content); |
1193 | 1332 attachmentId = attachment.GetUuid(); |
1194 { | 1333 |
1195 StorageCache* cache = NULL; | 1334 { |
1196 | 1335 std::unique_ptr<StorageAccessor> accessor; |
1197 if (!skipCache) | 1336 |
1198 { | 1337 if (skipCache) |
1199 cache = &storageCache_; | 1338 { |
1200 } | 1339 accessor.reset(new StorageAccessor(area_, GetMetricsRegistry())); |
1201 | 1340 } |
1202 StorageAccessor accessor(area_, cache, GetMetricsRegistry()); | 1341 else |
1342 { | |
1343 accessor.reset(new StorageAccessor(area_, storageCache_, GetMetricsRegistry())); | |
1344 } | |
1203 | 1345 |
1204 if (uncompressIfNeeded) | 1346 if (uncompressIfNeeded) |
1205 { | 1347 { |
1206 accessor.Read(result, attachment); | 1348 accessor->Read(result, attachment); |
1207 } | 1349 } |
1208 else | 1350 else |
1209 { | 1351 { |
1210 // Do not uncompress the content of the storage area, return the | 1352 // Do not uncompress the content of the storage area, return the |
1211 // raw data | 1353 // raw data |
1212 accessor.ReadRaw(result, attachment); | 1354 accessor->ReadRaw(result, attachment); |
1213 } | 1355 } |
1214 } | 1356 } |
1215 } | 1357 } |
1216 | 1358 |
1217 | 1359 |
1227 accessor_.reset(NULL); | 1369 accessor_.reset(NULL); |
1228 | 1370 |
1229 // Throttle to avoid loading several large DICOM files simultaneously | 1371 // Throttle to avoid loading several large DICOM files simultaneously |
1230 largeDicomLocker_.reset(new Semaphore::Locker(context.largeDicomThrottler_)); | 1372 largeDicomLocker_.reset(new Semaphore::Locker(context.largeDicomThrottler_)); |
1231 | 1373 |
1232 std::string content; | 1374 context_.ReadDicom(buffer_, instancePublicId_); |
1233 context_.ReadDicom(content, instancePublicId); | |
1234 | 1375 |
1235 // Release the throttle if loading "small" DICOM files (under | 1376 // Release the throttle if loading "small" DICOM files (under |
1236 // 50MB, which is an arbitrary value) | 1377 // 50MB, which is an arbitrary value) |
1237 if (content.size() < 50 * 1024 * 1024) | 1378 if (buffer_.size() < 50 * 1024 * 1024) |
1238 { | 1379 { |
1239 largeDicomLocker_.reset(NULL); | 1380 largeDicomLocker_.reset(NULL); |
1240 } | 1381 } |
1241 | 1382 |
1242 dicom_.reset(new ParsedDicomFile(content)); | 1383 dicom_.reset(new ParsedDicomFile(buffer_)); |
1243 dicomSize_ = content.size(); | 1384 dicomSize_ = buffer_.size(); |
1244 } | 1385 } |
1245 | 1386 |
1246 assert(accessor_.get() != NULL || | 1387 assert(accessor_.get() != NULL || |
1247 dicom_.get() != NULL); | 1388 dicom_.get() != NULL); |
1248 } | 1389 } |
1253 if (dicom_.get() != NULL) | 1394 if (dicom_.get() != NULL) |
1254 { | 1395 { |
1255 try | 1396 try |
1256 { | 1397 { |
1257 context_.dicomCache_.Acquire(instancePublicId_, dicom_.release(), dicomSize_); | 1398 context_.dicomCache_.Acquire(instancePublicId_, dicom_.release(), dicomSize_); |
1258 context_.PublishDicomCacheMetrics(); | |
1259 } | 1399 } |
1260 catch (OrthancException&) | 1400 catch (OrthancException&) |
1261 { | 1401 { |
1262 } | 1402 } |
1263 } | 1403 } |
1275 assert(accessor_.get() != NULL); | 1415 assert(accessor_.get() != NULL); |
1276 return accessor_->GetDicom(); | 1416 return accessor_->GetDicom(); |
1277 } | 1417 } |
1278 } | 1418 } |
1279 | 1419 |
1420 const std::string& ServerContext::DicomCacheLocker::GetBuffer() | |
1421 { | |
1422 if (buffer_.size() > 0) | |
1423 { | |
1424 return buffer_; | |
1425 } | |
1426 else | |
1427 { | |
1428 context_.ReadDicom(buffer_, instancePublicId_); | |
1429 return buffer_; | |
1430 } | |
1431 } | |
1280 | 1432 |
1281 void ServerContext::SetStoreMD5ForAttachments(bool storeMD5) | 1433 void ServerContext::SetStoreMD5ForAttachments(bool storeMD5) |
1282 { | 1434 { |
1283 LOG(INFO) << "Storing MD5 for attachments: " << (storeMD5 ? "yes" : "no"); | 1435 LOG(INFO) << "Storing MD5 for attachments: " << (storeMD5 ? "yes" : "no"); |
1284 storeMD5_ = storeMD5; | 1436 storeMD5_ = storeMD5; |
1298 LOG(INFO) << "Adding attachment " << EnumerationToString(attachmentType) << " to resource " << resourceId; | 1450 LOG(INFO) << "Adding attachment " << EnumerationToString(attachmentType) << " to resource " << resourceId; |
1299 | 1451 |
1300 // TODO Should we use "gzip" instead? | 1452 // TODO Should we use "gzip" instead? |
1301 CompressionType compression = (compressionEnabled_ ? CompressionType_ZlibWithSize : CompressionType_None); | 1453 CompressionType compression = (compressionEnabled_ ? CompressionType_ZlibWithSize : CompressionType_None); |
1302 | 1454 |
1303 StorageAccessor accessor(area_, &storageCache_, GetMetricsRegistry()); | 1455 StorageAccessor accessor(area_, storageCache_, GetMetricsRegistry()); |
1304 | 1456 |
1305 std::string uuid = Toolbox::GenerateUuid(); | 1457 std::string uuid = Toolbox::GenerateUuid(); |
1306 std::string customData; | 1458 std::string customData; |
1307 | 1459 |
1308 assert(attachmentType != FileContentType_Dicom && attachmentType != FileContentType_DicomUntilPixelData); // this method can not be used to store instances | 1460 assert(attachmentType != FileContentType_Dicom && attachmentType != FileContentType_DicomUntilPixelData); // this method can not be used to store instances |
1309 | 1461 |
1338 { | 1490 { |
1339 if (expectedType == ResourceType_Instance) | 1491 if (expectedType == ResourceType_Instance) |
1340 { | 1492 { |
1341 // remove the file from the DicomCache | 1493 // remove the file from the DicomCache |
1342 dicomCache_.Invalidate(uuid); | 1494 dicomCache_.Invalidate(uuid); |
1343 PublishDicomCacheMetrics(); | |
1344 } | 1495 } |
1345 | 1496 |
1346 return index_.DeleteResource(remainingAncestor, uuid, expectedType); | 1497 return index_.DeleteResource(remainingAncestor, uuid, expectedType); |
1347 } | 1498 } |
1348 | 1499 |
1351 { | 1502 { |
1352 if (change.GetResourceType() == ResourceType_Instance && | 1503 if (change.GetResourceType() == ResourceType_Instance && |
1353 change.GetChangeType() == ChangeType_Deleted) | 1504 change.GetChangeType() == ChangeType_Deleted) |
1354 { | 1505 { |
1355 dicomCache_.Invalidate(change.GetPublicId()); | 1506 dicomCache_.Invalidate(change.GetPublicId()); |
1356 PublishDicomCacheMetrics(); | |
1357 } | 1507 } |
1358 | 1508 |
1359 pendingChanges_.Enqueue(change.Clone()); | 1509 pendingChanges_.Enqueue(change.Clone()); |
1360 } | 1510 } |
1361 | 1511 |
1424 | 1574 |
1425 | 1575 |
1426 void ServerContext::Apply(ILookupVisitor& visitor, | 1576 void ServerContext::Apply(ILookupVisitor& visitor, |
1427 const DatabaseLookup& lookup, | 1577 const DatabaseLookup& lookup, |
1428 ResourceType queryLevel, | 1578 ResourceType queryLevel, |
1579 const std::set<std::string>& labels, | |
1580 LabelsConstraint labelsConstraint, | |
1429 size_t since, | 1581 size_t since, |
1430 size_t limit) | 1582 size_t limit) |
1431 { | 1583 { |
1432 unsigned int databaseLimit = (queryLevel == ResourceType_Instance ? | 1584 unsigned int databaseLimit = (queryLevel == ResourceType_Instance ? |
1433 limitFindInstances_ : limitFindResults_); | 1585 limitFindInstances_ : limitFindResults_); |
1446 { | 1598 { |
1447 fastLookup->RemoveConstraint(DICOM_TAG_MODALITIES_IN_STUDY); | 1599 fastLookup->RemoveConstraint(DICOM_TAG_MODALITIES_IN_STUDY); |
1448 } | 1600 } |
1449 | 1601 |
1450 { | 1602 { |
1451 const size_t lookupLimit = (databaseLimit == 0 ? 0 : databaseLimit + 1); | 1603 const size_t lookupLimit = (databaseLimit == 0 ? 0 : databaseLimit + 1); |
1452 GetIndex().ApplyLookupResources(resources, &instances, *fastLookup, queryLevel, lookupLimit); | 1604 GetIndex().ApplyLookupResources(resources, &instances, *fastLookup, queryLevel, labels, labelsConstraint, lookupLimit); |
1453 } | 1605 } |
1454 | 1606 |
1455 bool complete = (databaseLimit == 0 || | 1607 bool complete = (databaseLimit == 0 || |
1456 resources.size() <= databaseLimit); | 1608 resources.size() <= databaseLimit); |
1457 | 1609 |
1540 requestedTags.insert(DICOM_TAG_MODALITIES_IN_STUDY); | 1692 requestedTags.insert(DICOM_TAG_MODALITIES_IN_STUDY); |
1541 ExpandedResource resource; | 1693 ExpandedResource resource; |
1542 ComputeStudyTags(resource, *this, resources[i], requestedTags); | 1694 ComputeStudyTags(resource, *this, resources[i], requestedTags); |
1543 | 1695 |
1544 std::vector<std::string> modalities; | 1696 std::vector<std::string> modalities; |
1545 Toolbox::TokenizeString(modalities, resource.tags_.GetValue(DICOM_TAG_MODALITIES_IN_STUDY).GetContent(), '\\'); | 1697 Toolbox::TokenizeString(modalities, resource.GetMainDicomTags().GetValue(DICOM_TAG_MODALITIES_IN_STUDY).GetContent(), '\\'); |
1546 bool hasAtLeastOneModalityMatching = false; | 1698 bool hasAtLeastOneModalityMatching = false; |
1547 for (size_t m = 0; m < modalities.size(); m++) | 1699 for (size_t m = 0; m < modalities.size(); m++) |
1548 { | 1700 { |
1549 hasAtLeastOneModalityMatching |= dicomModalitiesConstraint->IsMatch(modalities[m]); | 1701 hasAtLeastOneModalityMatching |= dicomModalitiesConstraint->IsMatch(modalities[m]); |
1550 } | 1702 } |
1551 | 1703 |
1552 isMatch = isMatch && hasAtLeastOneModalityMatching; | 1704 isMatch = isMatch && hasAtLeastOneModalityMatching; |
1553 // copy the value of ModalitiesInStudy such that it can be reused to build the answer | 1705 // copy the value of ModalitiesInStudy such that it can be reused to build the answer |
1554 allMainDicomTagsFromDB.SetValue(DICOM_TAG_MODALITIES_IN_STUDY, resource.tags_.GetValue(DICOM_TAG_MODALITIES_IN_STUDY)); | 1706 allMainDicomTagsFromDB.SetValue(DICOM_TAG_MODALITIES_IN_STUDY, resource.GetMainDicomTags().GetValue(DICOM_TAG_MODALITIES_IN_STUDY)); |
1555 } | 1707 } |
1556 | 1708 |
1557 if (isMatch) | 1709 if (isMatch) |
1558 { | 1710 { |
1559 if (skipped < since) | 1711 if (skipped < since) |
1736 | 1888 |
1737 return NULL; | 1889 return NULL; |
1738 } | 1890 } |
1739 | 1891 |
1740 | 1892 |
1893 | |
1894 | |
1895 | |
1741 ImageAccessor* ServerContext::DecodeDicomFrame(const std::string& publicId, | 1896 ImageAccessor* ServerContext::DecodeDicomFrame(const std::string& publicId, |
1742 unsigned int frameIndex) | 1897 unsigned int frameIndex) |
1743 { | 1898 { |
1899 ServerContext::DicomCacheLocker locker(*this, publicId); | |
1900 std::unique_ptr<ImageAccessor> decoded(DecodeDicomFrame(locker.GetDicom(), locker.GetBuffer().c_str(), locker.GetBuffer().size(), frameIndex)); | |
1901 | |
1902 if (decoded.get() == NULL) | |
1903 { | |
1904 OrthancConfiguration::ReaderLock configLock; | |
1905 if (configLock.GetConfiguration().IsWarningEnabled(Warnings_003_DecoderFailure)) | |
1906 { | |
1907 LOG(WARNING) << "W003: Unable to decode frame " << frameIndex << " from instance " << publicId; | |
1908 } | |
1909 return NULL; | |
1910 } | |
1911 | |
1912 return decoded.release(); | |
1913 } | |
1914 | |
1915 | |
1916 ImageAccessor* ServerContext::DecodeDicomFrame(const ParsedDicomFile& parsedDicom, | |
1917 const void* buffer, // actually the buffer that is the source of the ParsedDicomFile | |
1918 size_t size, | |
1919 unsigned int frameIndex) | |
1920 { | |
1921 std::unique_ptr<ImageAccessor> decoded; | |
1922 | |
1744 if (builtinDecoderTranscoderOrder_ == BuiltinDecoderTranscoderOrder_Before) | 1923 if (builtinDecoderTranscoderOrder_ == BuiltinDecoderTranscoderOrder_Before) |
1745 { | 1924 { |
1746 // Use Orthanc's built-in decoder, using the cache to speed-up | 1925 // Use Orthanc's built-in decoder |
1747 // things on multi-frame images | 1926 |
1748 | |
1749 std::unique_ptr<ImageAccessor> decoded; | |
1750 try | 1927 try |
1751 { | 1928 { |
1752 ServerContext::DicomCacheLocker locker(*this, publicId); | 1929 decoded.reset(parsedDicom.DecodeFrame(frameIndex)); |
1753 decoded.reset(locker.GetDicom().DecodeFrame(frameIndex)); | 1930 if (decoded.get() != NULL) |
1931 { | |
1932 return decoded.release(); | |
1933 } | |
1754 } | 1934 } |
1755 catch (OrthancException& e) | 1935 catch (OrthancException& e) |
1756 { | 1936 { // ignore, we'll try other alternatives |
1757 } | |
1758 | |
1759 if (decoded.get() != NULL) | |
1760 { | |
1761 return decoded.release(); | |
1762 } | 1937 } |
1763 } | 1938 } |
1764 | 1939 |
1765 #if ORTHANC_ENABLE_PLUGINS == 1 | 1940 #if ORTHANC_ENABLE_PLUGINS == 1 |
1766 if (HasPlugins() && | 1941 if (HasPlugins() && |
1767 GetPlugins().HasCustomImageDecoder()) | 1942 GetPlugins().HasCustomImageDecoder()) |
1768 { | 1943 { |
1769 // TODO: Store the raw buffer in the DicomCacheLocker | |
1770 std::string dicomContent; | |
1771 ReadDicom(dicomContent, publicId); | |
1772 | |
1773 std::unique_ptr<ImageAccessor> decoded; | |
1774 try | 1944 try |
1775 { | 1945 { |
1776 decoded.reset(GetPlugins().Decode(dicomContent.c_str(), dicomContent.size(), frameIndex)); | 1946 decoded.reset(GetPlugins().Decode(buffer, size, frameIndex)); |
1777 } | 1947 } |
1778 catch (OrthancException& e) | 1948 catch (OrthancException& e) |
1779 { | 1949 { |
1780 } | 1950 } |
1781 | 1951 |
1791 } | 1961 } |
1792 #endif | 1962 #endif |
1793 | 1963 |
1794 if (builtinDecoderTranscoderOrder_ == BuiltinDecoderTranscoderOrder_After) | 1964 if (builtinDecoderTranscoderOrder_ == BuiltinDecoderTranscoderOrder_After) |
1795 { | 1965 { |
1796 ServerContext::DicomCacheLocker locker(*this, publicId); | 1966 try |
1797 return locker.GetDicom().DecodeFrame(frameIndex); | 1967 { |
1798 } | 1968 decoded.reset(parsedDicom.DecodeFrame(frameIndex)); |
1799 else | 1969 if (decoded.get() != NULL) |
1800 { | 1970 { |
1801 return NULL; // Built-in decoder is disabled | 1971 return decoded.release(); |
1802 } | 1972 } |
1973 } | |
1974 catch (OrthancException& e) | |
1975 { | |
1976 LOG(INFO) << e.GetDetails(); | |
1977 } | |
1978 } | |
1979 | |
1980 #if ORTHANC_ENABLE_PLUGINS == 1 | |
1981 if (HasPlugins() && GetPlugins().HasCustomTranscoder()) | |
1982 { | |
1983 LOG(INFO) << "The plugins and built-in image decoders failed to decode a frame, " | |
1984 << "trying to transcode the file to LittleEndianExplicit using the plugins."; | |
1985 DicomImage explicitTemporaryImage; | |
1986 DicomImage source; | |
1987 std::set<DicomTransferSyntax> allowedSyntaxes; | |
1988 | |
1989 source.SetExternalBuffer(buffer, size); | |
1990 allowedSyntaxes.insert(DicomTransferSyntax_LittleEndianExplicit); | |
1991 | |
1992 if (Transcode(explicitTemporaryImage, source, allowedSyntaxes, true)) | |
1993 { | |
1994 std::unique_ptr<ParsedDicomFile> file(explicitTemporaryImage.ReleaseAsParsedDicomFile()); | |
1995 return file->DecodeFrame(frameIndex); | |
1996 } | |
1997 } | |
1998 #endif | |
1999 | |
2000 return NULL; | |
1803 } | 2001 } |
1804 | 2002 |
1805 | 2003 |
1806 ImageAccessor* ServerContext::DecodeDicomFrame(const DicomInstanceToStore& dicom, | 2004 ImageAccessor* ServerContext::DecodeDicomFrame(const DicomInstanceToStore& dicom, |
1807 unsigned int frameIndex) | 2005 unsigned int frameIndex) |
1808 { | 2006 { |
1809 if (builtinDecoderTranscoderOrder_ == BuiltinDecoderTranscoderOrder_Before) | 2007 return DecodeDicomFrame(dicom.GetParsedDicomFile(), |
1810 { | 2008 dicom.GetBufferData(), |
1811 std::unique_ptr<ImageAccessor> decoded; | 2009 dicom.GetBufferSize(), |
1812 try | 2010 frameIndex); |
1813 { | 2011 |
1814 decoded.reset(dicom.DecodeFrame(frameIndex)); | |
1815 } | |
1816 catch (OrthancException& e) | |
1817 { | |
1818 } | |
1819 | |
1820 if (decoded.get() != NULL) | |
1821 { | |
1822 return decoded.release(); | |
1823 } | |
1824 } | |
1825 | |
1826 #if ORTHANC_ENABLE_PLUGINS == 1 | |
1827 if (HasPlugins() && | |
1828 GetPlugins().HasCustomImageDecoder()) | |
1829 { | |
1830 std::unique_ptr<ImageAccessor> decoded; | |
1831 try | |
1832 { | |
1833 decoded.reset(GetPlugins().Decode(dicom.GetBufferData(), dicom.GetBufferSize(), frameIndex)); | |
1834 } | |
1835 catch (OrthancException& e) | |
1836 { | |
1837 } | |
1838 | |
1839 if (decoded.get() != NULL) | |
1840 { | |
1841 return decoded.release(); | |
1842 } | |
1843 else if (builtinDecoderTranscoderOrder_ == BuiltinDecoderTranscoderOrder_After) | |
1844 { | |
1845 LOG(INFO) << "The installed image decoding plugins cannot handle an image, " | |
1846 << "fallback to the built-in DCMTK decoder"; | |
1847 } | |
1848 } | |
1849 #endif | |
1850 | |
1851 if (builtinDecoderTranscoderOrder_ == BuiltinDecoderTranscoderOrder_After) | |
1852 { | |
1853 return dicom.DecodeFrame(frameIndex); | |
1854 } | |
1855 else | |
1856 { | |
1857 return NULL; | |
1858 } | |
1859 } | 2012 } |
1860 | 2013 |
1861 | 2014 |
1862 ImageAccessor* ServerContext::DecodeDicomFrame(const void* dicom, | 2015 ImageAccessor* ServerContext::DecodeDicomFrame(const void* dicom, |
1863 size_t size, | 2016 size_t size, |
1864 unsigned int frameIndex) | 2017 unsigned int frameIndex) |
1865 { | 2018 { |
1866 std::unique_ptr<DicomInstanceToStore> instance(DicomInstanceToStore::CreateFromBuffer(dicom, size)); | 2019 std::unique_ptr<ParsedDicomFile> instance(new ParsedDicomFile(dicom, size)); |
1867 return DecodeDicomFrame(*instance, frameIndex); | 2020 return DecodeDicomFrame(*instance, dicom, size, frameIndex); |
1868 } | 2021 } |
1869 | 2022 |
1870 | 2023 |
1871 void ServerContext::StoreWithTranscoding(std::string& sopClassUid, | 2024 void ServerContext::StoreWithTranscoding(std::string& sopClassUid, |
1872 std::string& sopInstanceUid, | 2025 std::string& sopInstanceUid, |
1875 bool hasMoveOriginator, | 2028 bool hasMoveOriginator, |
1876 const std::string& moveOriginatorAet, | 2029 const std::string& moveOriginatorAet, |
1877 uint16_t moveOriginatorId) | 2030 uint16_t moveOriginatorId) |
1878 { | 2031 { |
1879 const void* data = dicom.empty() ? NULL : dicom.c_str(); | 2032 const void* data = dicom.empty() ? NULL : dicom.c_str(); |
1880 | 2033 const RemoteModalityParameters& modality = connection.GetParameters().GetRemoteModality(); |
2034 | |
1881 if (!transcodeDicomProtocol_ || | 2035 if (!transcodeDicomProtocol_ || |
1882 !connection.GetParameters().GetRemoteModality().IsTranscodingAllowed()) | 2036 !modality.IsTranscodingAllowed()) |
1883 { | 2037 { |
1884 connection.Store(sopClassUid, sopInstanceUid, data, dicom.size(), | 2038 connection.Store(sopClassUid, sopInstanceUid, data, dicom.size(), |
1885 hasMoveOriginator, moveOriginatorAet, moveOriginatorId); | 2039 hasMoveOriginator, moveOriginatorAet, moveOriginatorId); |
1886 } | 2040 } |
1887 else | 2041 else |
1888 { | 2042 { |
1889 connection.Transcode(sopClassUid, sopInstanceUid, *this, data, dicom.size(), preferredTransferSyntax_, | 2043 connection.Transcode(sopClassUid, sopInstanceUid, *this, data, dicom.size(), preferredTransferSyntax_, |
1890 hasMoveOriginator, moveOriginatorAet, moveOriginatorId); | 2044 hasMoveOriginator, moveOriginatorAet, moveOriginatorId); |
1891 } | 2045 } |
2046 } | |
2047 | |
2048 | |
2049 bool ServerContext::TranscodeWithCache(std::string& target, | |
2050 const std::string& source, | |
2051 const std::string& sourceInstanceId, | |
2052 const std::string& attachmentId, | |
2053 DicomTransferSyntax targetSyntax) | |
2054 { | |
2055 StorageCache::Accessor cacheAccessor(storageCache_); | |
2056 | |
2057 if (!cacheAccessor.FetchTranscodedInstance(target, attachmentId, targetSyntax)) | |
2058 { | |
2059 IDicomTranscoder::DicomImage sourceDicom; | |
2060 sourceDicom.SetExternalBuffer(source); | |
2061 | |
2062 IDicomTranscoder::DicomImage targetDicom; | |
2063 std::set<DicomTransferSyntax> syntaxes; | |
2064 syntaxes.insert(targetSyntax); | |
2065 | |
2066 if (Transcode(targetDicom, sourceDicom, syntaxes, true)) | |
2067 { | |
2068 cacheAccessor.AddTranscodedInstance(attachmentId, targetSyntax, reinterpret_cast<const char*>(targetDicom.GetBufferData()), targetDicom.GetBufferSize()); | |
2069 target = std::string(reinterpret_cast<const char*>(targetDicom.GetBufferData()), targetDicom.GetBufferSize()); | |
2070 return true; | |
2071 } | |
2072 | |
2073 return false; | |
2074 } | |
2075 | |
2076 return true; | |
1892 } | 2077 } |
1893 | 2078 |
1894 | 2079 |
1895 bool ServerContext::Transcode(DicomImage& target, | 2080 bool ServerContext::Transcode(DicomImage& target, |
1896 DicomImage& source /* in, "GetParsed()" possibly modified */, | 2081 DicomImage& source /* in, "GetParsed()" possibly modified */, |
1988 DicomToJsonFormat format, | 2173 DicomToJsonFormat format, |
1989 const std::set<DicomTag>& requestedTags) | 2174 const std::set<DicomTag>& requestedTags) |
1990 { | 2175 { |
1991 target = Json::objectValue; | 2176 target = Json::objectValue; |
1992 | 2177 |
1993 target["Type"] = GetResourceTypeText(resource.type_, false, true); | 2178 target["Type"] = GetResourceTypeText(resource.GetLevel(), false, true); |
1994 target["ID"] = resource.id_; | 2179 target["ID"] = resource.GetPublicId(); |
1995 | 2180 |
1996 switch (resource.type_) | 2181 switch (resource.GetLevel()) |
1997 { | 2182 { |
1998 case ResourceType_Patient: | 2183 case ResourceType_Patient: |
1999 break; | 2184 break; |
2000 | 2185 |
2001 case ResourceType_Study: | 2186 case ResourceType_Study: |
2012 | 2197 |
2013 default: | 2198 default: |
2014 throw OrthancException(ErrorCode_InternalError); | 2199 throw OrthancException(ErrorCode_InternalError); |
2015 } | 2200 } |
2016 | 2201 |
2017 switch (resource.type_) | 2202 switch (resource.GetLevel()) |
2018 { | 2203 { |
2019 case ResourceType_Patient: | 2204 case ResourceType_Patient: |
2020 case ResourceType_Study: | 2205 case ResourceType_Study: |
2021 case ResourceType_Series: | 2206 case ResourceType_Series: |
2022 { | 2207 { |
2026 it = resource.childrenIds_.begin(); it != resource.childrenIds_.end(); ++it) | 2211 it = resource.childrenIds_.begin(); it != resource.childrenIds_.end(); ++it) |
2027 { | 2212 { |
2028 c.append(*it); | 2213 c.append(*it); |
2029 } | 2214 } |
2030 | 2215 |
2031 if (resource.type_ == ResourceType_Patient) | 2216 if (resource.GetLevel() == ResourceType_Patient) |
2032 { | 2217 { |
2033 target["Studies"] = c; | 2218 target["Studies"] = c; |
2034 } | 2219 } |
2035 else if (resource.type_ == ResourceType_Study) | 2220 else if (resource.GetLevel() == ResourceType_Study) |
2036 { | 2221 { |
2037 target["Series"] = c; | 2222 target["Series"] = c; |
2038 } | 2223 } |
2039 else | 2224 else |
2040 { | 2225 { |
2048 | 2233 |
2049 default: | 2234 default: |
2050 throw OrthancException(ErrorCode_InternalError); | 2235 throw OrthancException(ErrorCode_InternalError); |
2051 } | 2236 } |
2052 | 2237 |
2053 switch (resource.type_) | 2238 switch (resource.GetLevel()) |
2054 { | 2239 { |
2055 case ResourceType_Patient: | 2240 case ResourceType_Patient: |
2056 case ResourceType_Study: | 2241 case ResourceType_Study: |
2057 break; | 2242 break; |
2058 | 2243 |
2097 if (!resource.modifiedFrom_.empty()) | 2282 if (!resource.modifiedFrom_.empty()) |
2098 { | 2283 { |
2099 target["ModifiedFrom"] = resource.modifiedFrom_; | 2284 target["ModifiedFrom"] = resource.modifiedFrom_; |
2100 } | 2285 } |
2101 | 2286 |
2102 if (resource.type_ == ResourceType_Patient || | 2287 if (resource.GetLevel() == ResourceType_Patient || |
2103 resource.type_ == ResourceType_Study || | 2288 resource.GetLevel() == ResourceType_Study || |
2104 resource.type_ == ResourceType_Series) | 2289 resource.GetLevel() == ResourceType_Series) |
2105 { | 2290 { |
2106 target["IsStable"] = resource.isStable_; | 2291 target["IsStable"] = resource.isStable_; |
2107 | 2292 |
2108 if (!resource.lastUpdate_.empty()) | 2293 if (!resource.lastUpdate_.empty()) |
2109 { | 2294 { |
2115 | 2300 |
2116 static const char* const MAIN_DICOM_TAGS = "MainDicomTags"; | 2301 static const char* const MAIN_DICOM_TAGS = "MainDicomTags"; |
2117 static const char* const PATIENT_MAIN_DICOM_TAGS = "PatientMainDicomTags"; | 2302 static const char* const PATIENT_MAIN_DICOM_TAGS = "PatientMainDicomTags"; |
2118 | 2303 |
2119 DicomMap mainDicomTags; | 2304 DicomMap mainDicomTags; |
2120 resource.tags_.ExtractResourceInformation(mainDicomTags, resource.type_); | 2305 resource.GetMainDicomTags().ExtractResourceInformation(mainDicomTags, resource.GetLevel()); |
2121 | 2306 |
2122 target[MAIN_DICOM_TAGS] = Json::objectValue; | 2307 target[MAIN_DICOM_TAGS] = Json::objectValue; |
2123 FromDcmtkBridge::ToJson(target[MAIN_DICOM_TAGS], mainDicomTags, format); | 2308 FromDcmtkBridge::ToJson(target[MAIN_DICOM_TAGS], mainDicomTags, format); |
2124 | 2309 |
2125 if (resource.type_ == ResourceType_Study) | 2310 if (resource.GetLevel() == ResourceType_Study) |
2126 { | 2311 { |
2127 DicomMap patientMainDicomTags; | 2312 DicomMap patientMainDicomTags; |
2128 resource.tags_.ExtractPatientInformation(patientMainDicomTags); | 2313 resource.GetMainDicomTags().ExtractPatientInformation(patientMainDicomTags); |
2129 | 2314 |
2130 target[PATIENT_MAIN_DICOM_TAGS] = Json::objectValue; | 2315 target[PATIENT_MAIN_DICOM_TAGS] = Json::objectValue; |
2131 FromDcmtkBridge::ToJson(target[PATIENT_MAIN_DICOM_TAGS], patientMainDicomTags, format); | 2316 FromDcmtkBridge::ToJson(target[PATIENT_MAIN_DICOM_TAGS], patientMainDicomTags, format); |
2132 } | 2317 } |
2133 | 2318 |
2134 if (requestedTags.size() > 0) | 2319 if (requestedTags.size() > 0) |
2135 { | 2320 { |
2136 static const char* const REQUESTED_TAGS = "RequestedTags"; | 2321 static const char* const REQUESTED_TAGS = "RequestedTags"; |
2137 | 2322 |
2138 DicomMap tags; | 2323 DicomMap tags; |
2139 resource.tags_.ExtractTags(tags, requestedTags); | 2324 resource.GetMainDicomTags().ExtractTags(tags, requestedTags); |
2140 | 2325 |
2141 target[REQUESTED_TAGS] = Json::objectValue; | 2326 target[REQUESTED_TAGS] = Json::objectValue; |
2142 FromDcmtkBridge::ToJson(target[REQUESTED_TAGS], tags, format); | 2327 FromDcmtkBridge::ToJson(target[REQUESTED_TAGS], tags, format); |
2143 | 2328 |
2144 } | 2329 } |
2145 | 2330 |
2331 { | |
2332 Json::Value labels = Json::arrayValue; | |
2333 | |
2334 for (std::set<std::string>::const_iterator it = resource.labels_.begin(); it != resource.labels_.end(); ++it) | |
2335 { | |
2336 labels.append(*it); | |
2337 } | |
2338 | |
2339 target["Labels"] = labels; | |
2340 } | |
2146 } | 2341 } |
2147 | 2342 |
2148 | 2343 |
2149 static void ComputeInstanceTags(ExpandedResource& resource, | 2344 static void ComputeInstanceTags(ExpandedResource& resource, |
2150 ServerContext& context, | 2345 ServerContext& context, |
2151 const std::string& instancePublicId, | 2346 const std::string& instancePublicId, |
2152 const std::set<DicomTag>& requestedTags) | 2347 const std::set<DicomTag>& requestedTags) |
2153 { | 2348 { |
2154 if (requestedTags.count(DICOM_TAG_INSTANCE_AVAILABILITY) > 0) | 2349 if (requestedTags.count(DICOM_TAG_INSTANCE_AVAILABILITY) > 0) |
2155 { | 2350 { |
2156 resource.tags_.SetValue(DICOM_TAG_INSTANCE_AVAILABILITY, "ONLINE", false); | 2351 resource.GetMainDicomTags().SetValue(DICOM_TAG_INSTANCE_AVAILABILITY, "ONLINE", false); |
2157 resource.missingRequestedTags_.erase(DICOM_TAG_INSTANCE_AVAILABILITY); | 2352 resource.missingRequestedTags_.erase(DICOM_TAG_INSTANCE_AVAILABILITY); |
2158 } | 2353 } |
2159 } | 2354 } |
2160 | 2355 |
2161 | 2356 |
2169 ServerIndex& index = context.GetIndex(); | 2364 ServerIndex& index = context.GetIndex(); |
2170 std::list<std::string> instances; | 2365 std::list<std::string> instances; |
2171 | 2366 |
2172 index.GetChildren(instances, seriesPublicId); | 2367 index.GetChildren(instances, seriesPublicId); |
2173 | 2368 |
2174 resource.tags_.SetValue(DICOM_TAG_NUMBER_OF_SERIES_RELATED_INSTANCES, | 2369 resource.GetMainDicomTags().SetValue(DICOM_TAG_NUMBER_OF_SERIES_RELATED_INSTANCES, |
2175 boost::lexical_cast<std::string>(instances.size()), false); | 2370 boost::lexical_cast<std::string>(instances.size()), false); |
2176 resource.missingRequestedTags_.erase(DICOM_TAG_NUMBER_OF_SERIES_RELATED_INSTANCES); | 2371 resource.missingRequestedTags_.erase(DICOM_TAG_NUMBER_OF_SERIES_RELATED_INSTANCES); |
2177 } | 2372 } |
2178 } | 2373 } |
2179 | 2374 |
2214 } | 2409 } |
2215 | 2410 |
2216 std::string modalities; | 2411 std::string modalities; |
2217 Toolbox::JoinStrings(modalities, values, "\\"); | 2412 Toolbox::JoinStrings(modalities, values, "\\"); |
2218 | 2413 |
2219 resource.tags_.SetValue(DICOM_TAG_MODALITIES_IN_STUDY, modalities, false); | 2414 resource.GetMainDicomTags().SetValue(DICOM_TAG_MODALITIES_IN_STUDY, modalities, false); |
2220 resource.missingRequestedTags_.erase(DICOM_TAG_MODALITIES_IN_STUDY); | 2415 resource.missingRequestedTags_.erase(DICOM_TAG_MODALITIES_IN_STUDY); |
2221 } | 2416 } |
2222 | 2417 |
2223 if (hasNbRelatedSeries) | 2418 if (hasNbRelatedSeries) |
2224 { | 2419 { |
2225 resource.tags_.SetValue(DICOM_TAG_NUMBER_OF_STUDY_RELATED_SERIES, | 2420 resource.GetMainDicomTags().SetValue(DICOM_TAG_NUMBER_OF_STUDY_RELATED_SERIES, |
2226 boost::lexical_cast<std::string>(series.size()), false); | 2421 boost::lexical_cast<std::string>(series.size()), false); |
2227 resource.missingRequestedTags_.erase(DICOM_TAG_NUMBER_OF_STUDY_RELATED_SERIES); | 2422 resource.missingRequestedTags_.erase(DICOM_TAG_NUMBER_OF_STUDY_RELATED_SERIES); |
2228 } | 2423 } |
2229 | 2424 |
2230 if (hasNbRelatedInstances || hasSopClassesInStudy) | 2425 if (hasNbRelatedInstances || hasSopClassesInStudy) |
2238 instances.splice(instances.end(), seriesInstancesIds); | 2433 instances.splice(instances.end(), seriesInstancesIds); |
2239 } | 2434 } |
2240 | 2435 |
2241 if (hasNbRelatedInstances) | 2436 if (hasNbRelatedInstances) |
2242 { | 2437 { |
2243 resource.tags_.SetValue(DICOM_TAG_NUMBER_OF_STUDY_RELATED_INSTANCES, | 2438 resource.GetMainDicomTags().SetValue(DICOM_TAG_NUMBER_OF_STUDY_RELATED_INSTANCES, |
2244 boost::lexical_cast<std::string>(instances.size()), false); | 2439 boost::lexical_cast<std::string>(instances.size()), false); |
2245 resource.missingRequestedTags_.erase(DICOM_TAG_NUMBER_OF_STUDY_RELATED_INSTANCES); | 2440 resource.missingRequestedTags_.erase(DICOM_TAG_NUMBER_OF_STUDY_RELATED_INSTANCES); |
2246 } | 2441 } |
2247 | 2442 |
2248 if (hasSopClassesInStudy) | 2443 if (hasSopClassesInStudy) |
2262 | 2457 |
2263 if (values.size() > 0) | 2458 if (values.size() > 0) |
2264 { | 2459 { |
2265 std::string sopClassUids; | 2460 std::string sopClassUids; |
2266 Toolbox::JoinStrings(sopClassUids, values, "\\"); | 2461 Toolbox::JoinStrings(sopClassUids, values, "\\"); |
2267 resource.tags_.SetValue(DICOM_TAG_SOP_CLASSES_IN_STUDY, sopClassUids, false); | 2462 resource.GetMainDicomTags().SetValue(DICOM_TAG_SOP_CLASSES_IN_STUDY, sopClassUids, false); |
2268 } | 2463 } |
2269 | 2464 |
2270 resource.missingRequestedTags_.erase(DICOM_TAG_SOP_CLASSES_IN_STUDY); | 2465 resource.missingRequestedTags_.erase(DICOM_TAG_SOP_CLASSES_IN_STUDY); |
2271 } | 2466 } |
2272 } | 2467 } |
2289 | 2484 |
2290 index.GetChildren(studies, patientPublicId); | 2485 index.GetChildren(studies, patientPublicId); |
2291 | 2486 |
2292 if (hasNbRelatedStudies) | 2487 if (hasNbRelatedStudies) |
2293 { | 2488 { |
2294 resource.tags_.SetValue(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_STUDIES, | 2489 resource.GetMainDicomTags().SetValue(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_STUDIES, |
2295 boost::lexical_cast<std::string>(studies.size()), false); | 2490 boost::lexical_cast<std::string>(studies.size()), false); |
2296 resource.missingRequestedTags_.erase(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_STUDIES); | 2491 resource.missingRequestedTags_.erase(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_STUDIES); |
2297 } | 2492 } |
2298 | 2493 |
2299 if (hasNbRelatedSeries || hasNbRelatedInstances) | 2494 if (hasNbRelatedSeries || hasNbRelatedInstances) |
2306 series.splice(series.end(), thisSeriesIds); | 2501 series.splice(series.end(), thisSeriesIds); |
2307 } | 2502 } |
2308 | 2503 |
2309 if (hasNbRelatedSeries) | 2504 if (hasNbRelatedSeries) |
2310 { | 2505 { |
2311 resource.tags_.SetValue(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_SERIES, | 2506 resource.GetMainDicomTags().SetValue(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_SERIES, |
2312 boost::lexical_cast<std::string>(series.size()), false); | 2507 boost::lexical_cast<std::string>(series.size()), false); |
2313 resource.missingRequestedTags_.erase(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_SERIES); | 2508 resource.missingRequestedTags_.erase(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_SERIES); |
2314 } | 2509 } |
2315 } | 2510 } |
2316 | 2511 |
2322 std::list<std::string> thisInstancesIds; | 2517 std::list<std::string> thisInstancesIds; |
2323 index.GetChildren(thisInstancesIds, *it); | 2518 index.GetChildren(thisInstancesIds, *it); |
2324 instances.splice(instances.end(), thisInstancesIds); | 2519 instances.splice(instances.end(), thisInstancesIds); |
2325 } | 2520 } |
2326 | 2521 |
2327 resource.tags_.SetValue(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_INSTANCES, | 2522 resource.GetMainDicomTags().SetValue(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_INSTANCES, |
2328 boost::lexical_cast<std::string>(instances.size()), false); | 2523 boost::lexical_cast<std::string>(instances.size()), false); |
2329 resource.missingRequestedTags_.erase(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_INSTANCES); | 2524 resource.missingRequestedTags_.erase(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_INSTANCES); |
2330 } | 2525 } |
2331 } | 2526 } |
2332 | 2527 |
2386 const std::set<DicomTag>& requestedTags, | 2581 const std::set<DicomTag>& requestedTags, |
2387 bool allowStorageAccess) | 2582 bool allowStorageAccess) |
2388 { | 2583 { |
2389 ExpandedResource resource; | 2584 ExpandedResource resource; |
2390 | 2585 |
2391 if (ExpandResource(resource, publicId, mainDicomTags, instanceId, dicomAsJson, level, requestedTags, ExpandResourceDbFlags_Default, allowStorageAccess)) | 2586 if (ExpandResource(resource, publicId, mainDicomTags, instanceId, dicomAsJson, level, requestedTags, ExpandResourceFlags_Default, allowStorageAccess)) |
2392 { | 2587 { |
2393 SerializeExpandedResource(target, resource, format, requestedTags); | 2588 SerializeExpandedResource(target, resource, format, requestedTags); |
2394 return true; | 2589 return true; |
2395 } | 2590 } |
2396 | 2591 |
2402 const DicomMap& mainDicomTags, // optional: the main dicom tags for the resource (if already available) | 2597 const DicomMap& mainDicomTags, // optional: the main dicom tags for the resource (if already available) |
2403 const std::string& instanceId, // optional: the id of an instance for the resource (if already available) | 2598 const std::string& instanceId, // optional: the id of an instance for the resource (if already available) |
2404 const Json::Value* dicomAsJson, // optional: the dicom-as-json for the resource (if already available) | 2599 const Json::Value* dicomAsJson, // optional: the dicom-as-json for the resource (if already available) |
2405 ResourceType level, | 2600 ResourceType level, |
2406 const std::set<DicomTag>& requestedTags, | 2601 const std::set<DicomTag>& requestedTags, |
2407 ExpandResourceDbFlags expandFlags, | 2602 ExpandResourceFlags expandFlags, |
2408 bool allowStorageAccess) | 2603 bool allowStorageAccess) |
2409 { | 2604 { |
2410 // first try to get the tags from what is already available | 2605 // first try to get the tags from what is already available |
2411 | 2606 |
2412 if ((expandFlags & ExpandResourceDbFlags_IncludeMainDicomTags) | 2607 if ((expandFlags & ExpandResourceFlags_IncludeMainDicomTags) && |
2413 && (mainDicomTags.GetSize() > 0) | 2608 mainDicomTags.GetSize() > 0 && |
2414 && (dicomAsJson != NULL)) | 2609 dicomAsJson != NULL) |
2415 { | 2610 { |
2416 | 2611 |
2417 resource.tags_.Merge(mainDicomTags); | 2612 resource.GetMainDicomTags().Merge(mainDicomTags); |
2418 | 2613 |
2419 if (dicomAsJson->isObject()) | 2614 if (dicomAsJson->isObject()) |
2420 { | 2615 { |
2421 resource.tags_.FromDicomAsJson(*dicomAsJson); | 2616 resource.GetMainDicomTags().FromDicomAsJson(*dicomAsJson); |
2422 } | 2617 } |
2423 | 2618 |
2424 std::set<DicomTag> retrievedTags; | 2619 std::set<DicomTag> retrievedTags; |
2425 std::set<DicomTag> missingTags; | 2620 std::set<DicomTag> missingTags; |
2426 resource.tags_.GetTags(retrievedTags); | 2621 resource.GetMainDicomTags().GetTags(retrievedTags); |
2427 | 2622 |
2428 Toolbox::GetMissingsFromSet(missingTags, requestedTags, retrievedTags); | 2623 Toolbox::GetMissingsFromSet(missingTags, requestedTags, retrievedTags); |
2429 | 2624 |
2430 // if all possible tags have been read, no need to get them from DB anymore | 2625 // if all possible tags have been read, no need to get them from DB anymore |
2431 if (missingTags.size() == 0 || DicomMap::HasOnlyComputedTags(missingTags)) | 2626 if (missingTags.size() > 0 && DicomMap::HasOnlyComputedTags(missingTags)) |
2432 { | 2627 { |
2433 expandFlags = static_cast<ExpandResourceDbFlags>(expandFlags & ~ExpandResourceDbFlags_IncludeMainDicomTags); | 2628 resource.missingRequestedTags_ = missingTags; |
2434 } | 2629 ComputeTags(resource, *this, publicId, level, requestedTags); |
2435 | |
2436 if (missingTags.size() == 0 && expandFlags == ExpandResourceDbFlags_None) // we have already retrieved anything we need | |
2437 { | |
2438 return true; | 2630 return true; |
2439 } | 2631 } |
2440 } | 2632 else if (missingTags.size() == 0) |
2441 | 2633 { |
2442 if (expandFlags != ExpandResourceDbFlags_None | 2634 expandFlags = static_cast<ExpandResourceFlags>(expandFlags & ~ExpandResourceFlags_IncludeMainDicomTags); |
2443 && GetIndex().ExpandResource(resource, publicId, level, requestedTags, static_cast<ExpandResourceDbFlags>(expandFlags | ExpandResourceDbFlags_IncludeMetadata))) // we always need the metadata to get the mainDicomTagsSignature | 2635 } |
2636 | |
2637 if (missingTags.size() == 0 && expandFlags == ExpandResourceFlags_None) // we have already retrieved anything we need | |
2638 { | |
2639 return true; | |
2640 } | |
2641 } | |
2642 | |
2643 if (expandFlags != ExpandResourceFlags_None && | |
2644 GetIndex().ExpandResource(resource, publicId, level, requestedTags, | |
2645 static_cast<ExpandResourceFlags>(expandFlags | ExpandResourceFlags_IncludeMetadata))) // we always need the metadata to get the mainDicomTagsSignature | |
2444 { | 2646 { |
2445 // check the main dicom tags list has not changed since the resource was stored | 2647 // check the main dicom tags list has not changed since the resource was stored |
2446 if (resource.mainDicomTagsSignature_ != DicomMap::GetMainDicomTagsSignature(resource.type_)) | 2648 if (resource.mainDicomTagsSignature_ != DicomMap::GetMainDicomTagsSignature(resource.GetLevel())) |
2447 { | 2649 { |
2448 OrthancConfiguration::ReaderLock lock; | 2650 OrthancConfiguration::ReaderLock lock; |
2449 if (lock.GetConfiguration().IsWarningEnabled(Warnings_002_InconsistentDicomTagsInDb)) | 2651 if (lock.GetConfiguration().IsWarningEnabled(Warnings_002_InconsistentDicomTagsInDb)) |
2450 { | 2652 { |
2451 LOG(WARNING) << "W002: " << Orthanc::GetResourceTypeText(resource.type_, false , false) << " has been stored with another version of Main Dicom Tags list, you should POST to /" << Orthanc::GetResourceTypeText(resource.type_, true, false) << "/" << resource.id_ << "/reconstruct to update the list of tags saved in DB. Some MainDicomTags might be missing from this answer."; | 2653 LOG(WARNING) << "W002: " << Orthanc::GetResourceTypeText(resource.GetLevel(), false , false) |
2654 << " has been stored with another version of Main Dicom Tags list, you should POST to /" | |
2655 << Orthanc::GetResourceTypeText(resource.GetLevel(), true, false) | |
2656 << "/" << resource.GetPublicId() | |
2657 << "/reconstruct to update the list of tags saved in DB. Some MainDicomTags might be missing from this answer."; | |
2452 } | 2658 } |
2453 } | 2659 } |
2454 | 2660 |
2455 // possibly merge missing requested tags from dicom-as-json | 2661 // possibly merge missing requested tags from dicom-as-json |
2456 if (allowStorageAccess | 2662 if (allowStorageAccess && |
2457 && !resource.missingRequestedTags_.empty() && !DicomMap::HasOnlyComputedTags(resource.missingRequestedTags_)) | 2663 !resource.missingRequestedTags_.empty() && |
2664 !DicomMap::HasOnlyComputedTags(resource.missingRequestedTags_)) | |
2458 { | 2665 { |
2459 OrthancConfiguration::ReaderLock lock; | 2666 OrthancConfiguration::ReaderLock lock; |
2460 if (lock.GetConfiguration().IsWarningEnabled(Warnings_001_TagsBeingReadFromStorage)) | 2667 if (lock.GetConfiguration().IsWarningEnabled(Warnings_001_TagsBeingReadFromStorage)) |
2461 { | 2668 { |
2462 std::set<DicomTag> missingTags; | 2669 std::set<DicomTag> missingTags; |
2470 } | 2677 } |
2471 | 2678 |
2472 std::string missings; | 2679 std::string missings; |
2473 FromDcmtkBridge::FormatListOfTags(missings, missingTags); | 2680 FromDcmtkBridge::FormatListOfTags(missings, missingTags); |
2474 | 2681 |
2475 LOG(WARNING) << "W001: Accessing Dicom tags from storage when accessing " << Orthanc::GetResourceTypeText(resource.type_, false , false) << " : " << missings; | 2682 LOG(WARNING) << "W001: Accessing Dicom tags from storage when accessing " |
2683 << Orthanc::GetResourceTypeText(resource.GetLevel(), false, false) | |
2684 << " : " << missings; | |
2476 } | 2685 } |
2477 | 2686 |
2478 | 2687 |
2479 std::string instanceId_ = instanceId; | 2688 std::string instanceId_ = instanceId; |
2480 DicomMap tagsFromJson; | 2689 DicomMap tagsFromJson; |
2506 else | 2715 else |
2507 { | 2716 { |
2508 tagsFromJson.FromDicomAsJson(*dicomAsJson, false /* append */, true /* parseSequences*/); | 2717 tagsFromJson.FromDicomAsJson(*dicomAsJson, false /* append */, true /* parseSequences*/); |
2509 } | 2718 } |
2510 | 2719 |
2511 resource.tags_.Merge(tagsFromJson); | 2720 resource.GetMainDicomTags().Merge(tagsFromJson); |
2512 } | 2721 } |
2513 | 2722 |
2514 // compute the requested tags | 2723 // compute the requested tags |
2515 ComputeTags(resource, *this, publicId, level, requestedTags); | 2724 ComputeTags(resource, *this, publicId, level, requestedTags); |
2516 } | 2725 } |
2520 } | 2729 } |
2521 | 2730 |
2522 return true; | 2731 return true; |
2523 } | 2732 } |
2524 | 2733 |
2734 int64_t ServerContext::GetServerUpTime() const | |
2735 { | |
2736 boost::posix_time::ptime nowUtc = boost::posix_time::second_clock::universal_time(); | |
2737 boost::posix_time::time_duration elapsed = nowUtc - serverStartTimeUtc_; | |
2738 | |
2739 return elapsed.total_seconds(); | |
2740 } | |
2741 | |
2525 } | 2742 } |