comparison OrthancServer/Plugins/Samples/Housekeeper/Plugin.cpp @ 5375:984300e70069

updated Housekeeper plugin to handle DicomWeb cache
author Alain Mazy <am@osimis.io>
date Tue, 22 Aug 2023 12:35:32 +0200
parents 49477780e25a
children 4ab905749aed
comparison
equal deleted inserted replaced
5374:b216b57bbe6a 5375:984300e70069
43 static bool workerThreadShouldStop_ = false; 43 static bool workerThreadShouldStop_ = false;
44 static bool triggerOnStorageCompressionChange_ = true; 44 static bool triggerOnStorageCompressionChange_ = true;
45 static bool triggerOnMainDicomTagsChange_ = true; 45 static bool triggerOnMainDicomTagsChange_ = true;
46 static bool triggerOnUnnecessaryDicomAsJsonFiles_ = true; 46 static bool triggerOnUnnecessaryDicomAsJsonFiles_ = true;
47 static bool triggerOnIngestTranscodingChange_ = true; 47 static bool triggerOnIngestTranscodingChange_ = true;
48 48 static bool triggerOnDicomWebCacheChange_ = true;
49 49
50 struct RunningPeriod 50 struct RunningPeriod
51 { 51 {
52 int fromHour_; 52 int fromHour_;
53 int toHour_; 53 int toHour_;
164 std::string patientsMainDicomTagsSignature; 164 std::string patientsMainDicomTagsSignature;
165 std::string studiesMainDicomTagsSignature; 165 std::string studiesMainDicomTagsSignature;
166 std::string seriesMainDicomTagsSignature; 166 std::string seriesMainDicomTagsSignature;
167 std::string instancesMainDicomTagsSignature; 167 std::string instancesMainDicomTagsSignature;
168 std::string ingestTranscoding; 168 std::string ingestTranscoding;
169 std::string dicomWebVersion;
169 bool storageCompressionEnabled; 170 bool storageCompressionEnabled;
170 171
171 DbConfiguration() 172 DbConfiguration()
172 : storageCompressionEnabled(false) 173 : storageCompressionEnabled(false)
173 { 174 {
184 patientsMainDicomTagsSignature.clear(); 185 patientsMainDicomTagsSignature.clear();
185 studiesMainDicomTagsSignature.clear(); 186 studiesMainDicomTagsSignature.clear();
186 seriesMainDicomTagsSignature.clear(); 187 seriesMainDicomTagsSignature.clear();
187 instancesMainDicomTagsSignature.clear(); 188 instancesMainDicomTagsSignature.clear();
188 ingestTranscoding.clear(); 189 ingestTranscoding.clear();
190 dicomWebVersion.clear();
189 } 191 }
190 192
191 void ToJson(Json::Value& target) 193 void ToJson(Json::Value& target)
192 { 194 {
193 if (!IsDefined()) 195 if (!IsDefined())
208 210
209 target["MainDicomTagsSignature"] = signatures; 211 target["MainDicomTagsSignature"] = signatures;
210 target["OrthancVersion"] = orthancVersion; 212 target["OrthancVersion"] = orthancVersion;
211 target["StorageCompressionEnabled"] = storageCompressionEnabled; 213 target["StorageCompressionEnabled"] = storageCompressionEnabled;
212 target["IngestTranscoding"] = ingestTranscoding; 214 target["IngestTranscoding"] = ingestTranscoding;
215 target["DicomWebVersion"] = dicomWebVersion;
213 } 216 }
214 } 217 }
215 218
216 void FromJson(Json::Value& source) 219 void FromJson(Json::Value& source)
217 { 220 {
218 if (!source.isNull()) 221 if (!source.isNull())
219 { 222 {
220 orthancVersion = source["OrthancVersion"].asString(); 223 orthancVersion = source["OrthancVersion"].asString();
224 if (source.isMember("DicomWebVersion"))
225 {
226 dicomWebVersion = source["DicomWebVersion"].asString();
227 }
228 else
229 {
230 dicomWebVersion = "1.14"; // the first change that requires processing has been introduced between 1.14 & 1.15
231 }
221 232
222 const Json::Value& signatures = source["MainDicomTagsSignature"]; 233 const Json::Value& signatures = source["MainDicomTagsSignature"];
223 patientsMainDicomTagsSignature = signatures["Patient"].asString(); 234 patientsMainDicomTagsSignature = signatures["Patient"].asString();
224 studiesMainDicomTagsSignature = signatures["Study"].asString(); 235 studiesMainDicomTagsSignature = signatures["Study"].asString();
225 seriesMainDicomTagsSignature = signatures["Series"].asString(); 236 seriesMainDicomTagsSignature = signatures["Series"].asString();
319 pluginStatus_.lastProcessedChange = -1; 330 pluginStatus_.lastProcessedChange = -1;
320 pluginStatus_.lastChangeToProcess = -1; 331 pluginStatus_.lastChangeToProcess = -1;
321 pluginStatus_.lastTimeStarted = boost::date_time::not_a_date_time; 332 pluginStatus_.lastTimeStarted = boost::date_time::not_a_date_time;
322 333
323 pluginStatus_.lastProcessedConfiguration.orthancVersion = "1.9.0"; // when we don't know, we assume some files were stored with Orthanc 1.9.0 (last version saving the dicom-as-json files) 334 pluginStatus_.lastProcessedConfiguration.orthancVersion = "1.9.0"; // when we don't know, we assume some files were stored with Orthanc 1.9.0 (last version saving the dicom-as-json files)
335 pluginStatus_.lastProcessedConfiguration.dicomWebVersion = "1.14"; // the first change that requires processing has been introduced between 1.14 & 1.15
324 336
325 // default main dicom tags signature are the one from Orthanc 1.4.2 (last time the list was changed): 337 // default main dicom tags signature are the one from Orthanc 1.4.2 (last time the list was changed):
326 pluginStatus_.lastProcessedConfiguration.patientsMainDicomTagsSignature = "0010,0010;0010,0020;0010,0030;0010,0040;0010,1000"; 338 pluginStatus_.lastProcessedConfiguration.patientsMainDicomTagsSignature = "0010,0010;0010,0020;0010,0030;0010,0040;0010,1000";
327 pluginStatus_.lastProcessedConfiguration.studiesMainDicomTagsSignature = "0008,0020;0008,0030;0008,0050;0008,0080;0008,0090;0008,1030;0020,000d;0020,0010;0032,1032;0032,1060"; 339 pluginStatus_.lastProcessedConfiguration.studiesMainDicomTagsSignature = "0008,0020;0008,0030;0008,0050;0008,0080;0008,0090;0008,1030;0020,000d;0020,0010;0032,1032;0032,1060";
328 pluginStatus_.lastProcessedConfiguration.seriesMainDicomTagsSignature = "0008,0021;0008,0031;0008,0060;0008,0070;0008,1010;0008,103e;0008,1070;0018,0010;0018,0015;0018,0024;0018,1030;0018,1090;0018,1400;0020,000e;0020,0011;0020,0037;0020,0105;0020,1002;0040,0254;0054,0081;0054,0101;0054,1000"; 340 pluginStatus_.lastProcessedConfiguration.seriesMainDicomTagsSignature = "0008,0021;0008,0031;0008,0060;0008,0070;0008,1010;0008,103e;0008,1070;0018,0010;0018,0015;0018,0024;0018,1030;0018,1090;0018,1400;0020,000e;0020,0011;0020,0037;0020,0105;0020,1002;0040,0254;0054,0081;0054,0101;0054,1000";
358 configuration.instancesMainDicomTagsSignature = systemInfo["MainDicomTags"]["Instance"].asString(); 370 configuration.instancesMainDicomTagsSignature = systemInfo["MainDicomTags"]["Instance"].asString();
359 configuration.storageCompressionEnabled = systemInfo["StorageCompression"].asBool(); 371 configuration.storageCompressionEnabled = systemInfo["StorageCompression"].asBool();
360 configuration.ingestTranscoding = systemInfo["IngestTranscoding"].asString(); 372 configuration.ingestTranscoding = systemInfo["IngestTranscoding"].asString();
361 373
362 configuration.orthancVersion = OrthancPlugins::GetGlobalContext()->orthancVersion; 374 configuration.orthancVersion = OrthancPlugins::GetGlobalContext()->orthancVersion;
375
376 Json::Value pluginInfo;
377 if (OrthancPlugins::RestApiGet(pluginInfo, "/plugins/dicom-web", false))
378 {
379 configuration.dicomWebVersion = pluginInfo["Version"].asString();
380 }
363 } 381 }
364 382
365 static void CheckNeedsProcessing(bool& needsReconstruct, bool& needsReingest, const DbConfiguration& current, const DbConfiguration& last) 383 static void CheckNeedsProcessing(bool& needsReconstruct, bool& needsReingest, bool& needsDicomWebCaching, const DbConfiguration& current, const DbConfiguration& last)
366 { 384 {
367 needsReconstruct = false; 385 needsReconstruct = false;
368 needsReingest = false; 386 needsReingest = false;
387 needsDicomWebCaching = false;
369 388
370 if (!last.IsDefined()) 389 if (!last.IsDefined())
371 { 390 {
372 return; 391 return;
373 } 392 }
472 { 491 {
473 OrthancPlugins::LogWarning("Housekeeper: ingest transcoding has changed but the trigger is disabled"); 492 OrthancPlugins::LogWarning("Housekeeper: ingest transcoding has changed but the trigger is disabled");
474 } 493 }
475 } 494 }
476 495
496 if (!current.dicomWebVersion.empty())
497 {
498 if (last.dicomWebVersion.empty())
499 {
500 if (triggerOnDicomWebCacheChange_)
501 {
502 OrthancPlugins::LogWarning("Housekeeper: DicomWEB plugin is enabled and the housekeeper has never run, you might miss series metadata cache -> will perform housekeeping");
503 }
504 needsDicomWebCaching = triggerOnDicomWebCacheChange_;
505 }
506 else
507 {
508 const char* lastDicomWebVersion = last.dicomWebVersion.c_str();
509
510 if (!OrthancPlugins::CheckMinimalVersion(lastDicomWebVersion, 1, 15, 0))
511 {
512 if (triggerOnDicomWebCacheChange_)
513 {
514 OrthancPlugins::LogWarning("Housekeeper: DicomWEB plugin might miss series metadata cache -> will perform housekeeping");
515 needsDicomWebCaching = true;
516 }
517 else
518 {
519 OrthancPlugins::LogWarning("Housekeeper: DicomWEB plugin might miss series metadata cache but the trigger has been disabled");
520 }
521 }
522 }
523 }
477 } 524 }
478 525
479 static bool ProcessChanges(bool needsReconstruct, bool needsReingest, const DbConfiguration& currentDbConfiguration) 526 static bool ProcessChanges(bool needsReconstruct, bool needsReingest, bool needsDicomWebCaching, const DbConfiguration& currentDbConfiguration)
480 { 527 {
481 Json::Value changes; 528 Json::Value changes;
482 529
483 { 530 {
484 boost::recursive_mutex::scoped_lock lock(pluginStatusMutex_); 531 boost::recursive_mutex::scoped_lock lock(pluginStatusMutex_);
496 int64_t seq = change["Seq"].asInt64(); 543 int64_t seq = change["Seq"].asInt64();
497 544
498 if (change["ChangeType"] == "NewStudy") // some StableStudy might be missing if orthanc was shutdown during a StableAge -> consider only the NewStudy events that can not be missed 545 if (change["ChangeType"] == "NewStudy") // some StableStudy might be missing if orthanc was shutdown during a StableAge -> consider only the NewStudy events that can not be missed
499 { 546 {
500 Json::Value result; 547 Json::Value result;
501 Json::Value request; 548
502 if (needsReingest) 549 if (needsReconstruct)
503 { 550 {
504 request["ReconstructFiles"] = true; 551 Json::Value request;
552 if (needsReingest)
553 {
554 request["ReconstructFiles"] = true;
555 }
556 OrthancPlugins::RestApiPost(result, "/studies/" + change["ID"].asString() + "/reconstruct", request, false);
505 } 557 }
506 OrthancPlugins::RestApiPost(result, "/studies/" + change["ID"].asString() + "/reconstruct", request, false); 558
559 if (needsDicomWebCaching)
560 {
561 Json::Value request;
562 OrthancPlugins::RestApiPost(result, "/studies/" + change["ID"].asString() + "/update-dicomweb-cache", request, true);
563 }
507 } 564 }
508 565
509 { 566 {
510 boost::recursive_mutex::scoped_lock lock(pluginStatusMutex_); 567 boost::recursive_mutex::scoped_lock lock(pluginStatusMutex_);
511 568
552 609
553 bool needsReconstruct = false; 610 bool needsReconstruct = false;
554 bool needsReingest = false; 611 bool needsReingest = false;
555 bool needsFullProcessing = false; 612 bool needsFullProcessing = false;
556 bool needsProcessing = false; 613 bool needsProcessing = false;
614 bool needsDicomWebCaching = false;
557 615
558 { 616 {
559 boost::recursive_mutex::scoped_lock lock(pluginStatusMutex_); 617 boost::recursive_mutex::scoped_lock lock(pluginStatusMutex_);
560 618
561 // compare with last full processed configuration 619 // compare with last full processed configuration
562 CheckNeedsProcessing(needsReconstruct, needsReingest, currentDbConfiguration, pluginStatus_.lastProcessedConfiguration); 620 CheckNeedsProcessing(needsReconstruct, needsReingest, needsDicomWebCaching, currentDbConfiguration, pluginStatus_.lastProcessedConfiguration);
563 needsFullProcessing = needsReconstruct || needsReingest; 621 needsFullProcessing = needsReconstruct || needsReingest || needsDicomWebCaching;
564 needsProcessing = needsFullProcessing; 622 needsProcessing = needsFullProcessing;
565 623
566 // if a processing was in progress, check if the config has changed since 624 // if a processing was in progress, check if the config has changed since
567 if (pluginStatus_.currentlyProcessingConfiguration.IsDefined()) 625 if (pluginStatus_.currentlyProcessingConfiguration.IsDefined())
568 { 626 {
569 needsProcessing = true; // since a processing was in progress, we need at least a partial processing 627 needsProcessing = true; // since a processing was in progress, we need at least a partial processing
570 628
571 bool needsReconstruct2 = false; 629 bool needsReconstruct2 = false;
572 bool needsReingest2 = false; 630 bool needsReingest2 = false;
573 631 bool needsDicomWebCaching2 = false;
574 CheckNeedsProcessing(needsReconstruct2, needsReingest2, currentDbConfiguration, pluginStatus_.currentlyProcessingConfiguration); 632
575 needsFullProcessing = needsReconstruct2 || needsReingest2; // if the configuration has changed compared to the config being processed, we need a full processing again 633 CheckNeedsProcessing(needsReconstruct2, needsReingest2, needsDicomWebCaching2, currentDbConfiguration, pluginStatus_.currentlyProcessingConfiguration);
634 needsFullProcessing = needsReconstruct2 || needsReingest2 || needsDicomWebCaching2; // if the configuration has changed compared to the config being processed, we need a full processing again
576 } 635 }
577 } 636 }
578 637
579 if (!needsProcessing) 638 if (!needsProcessing)
580 { 639 {
619 678
620 while (!workerThreadShouldStop_ && !completed) 679 while (!workerThreadShouldStop_ && !completed)
621 { 680 {
622 if (runningPeriods_.isInPeriod()) 681 if (runningPeriods_.isInPeriod())
623 { 682 {
624 completed = ProcessChanges(needsReconstruct, needsReingest, currentDbConfiguration); 683 completed = ProcessChanges(needsReconstruct, needsReingest, needsDicomWebCaching, currentDbConfiguration);
625 SaveStatusInDb(); 684 SaveStatusInDb();
626 685
627 if (!completed) 686 if (!completed)
628 { 687 {
629 boost::recursive_mutex::scoped_lock lock(pluginStatusMutex_); 688 boost::recursive_mutex::scoped_lock lock(pluginStatusMutex_);
792 triggerOnStorageCompressionChange_ = housekeeper.GetBooleanValue("StorageCompressionChange", true); 851 triggerOnStorageCompressionChange_ = housekeeper.GetBooleanValue("StorageCompressionChange", true);
793 852
794 triggerOnMainDicomTagsChange_ = housekeeper.GetBooleanValue("MainDicomTagsChange", true); 853 triggerOnMainDicomTagsChange_ = housekeeper.GetBooleanValue("MainDicomTagsChange", true);
795 triggerOnUnnecessaryDicomAsJsonFiles_ = housekeeper.GetBooleanValue("UnnecessaryDicomAsJsonFiles", true); 854 triggerOnUnnecessaryDicomAsJsonFiles_ = housekeeper.GetBooleanValue("UnnecessaryDicomAsJsonFiles", true);
796 triggerOnIngestTranscodingChange_ = housekeeper.GetBooleanValue("IngestTranscodingChange", true); 855 triggerOnIngestTranscodingChange_ = housekeeper.GetBooleanValue("IngestTranscodingChange", true);
856 triggerOnDicomWebCacheChange_ = housekeeper.GetBooleanValue("DicomWebCacheChange", true);
797 } 857 }
798 858
799 if (housekeeper.GetJson().isMember("Schedule")) 859 if (housekeeper.GetJson().isMember("Schedule"))
800 { 860 {
801 runningPeriods_.load(housekeeper.GetJson()["Schedule"]); 861 runningPeriods_.load(housekeeper.GetJson()["Schedule"]);
802 } 862 }
803 863
804 OrthancPluginRegisterOnChangeCallback(c, OnChangeCallback); 864 OrthancPluginRegisterOnChangeCallback(c, OnChangeCallback);
805 OrthancPluginRegisterRestCallback(c, "/housekeeper/status", GetPluginStatus); // for bacward compatiblity with version 1.11.0 865 OrthancPluginRegisterRestCallback(c, "/housekeeper/status", GetPluginStatus); // for backward compatiblity with version 1.11.0
806 OrthancPluginRegisterRestCallback(c, "/plugins/housekeeper/status", GetPluginStatus); 866 OrthancPluginRegisterRestCallback(c, "/plugins/housekeeper/status", GetPluginStatus);
807 } 867 }
808 else 868 else
809 { 869 {
810 OrthancPlugins::LogWarning("Housekeeper plugin is disabled by the configuration file"); 870 OrthancPlugins::LogWarning("Housekeeper plugin is disabled by the configuration file");