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 }