comparison OrthancServer/Sources/ServerJobs/ResourceModificationJob.cpp @ 5137:15109c3f0f7d

added sanity checks in DicomModificationJob + automatically reconstruct resources at the end of a DicomModificationJob
author Alain Mazy <am@osimis.io>
date Wed, 18 Jan 2023 17:58:51 +0100
parents e71b22a43c0b
children 021d7fdcb659
comparison
equal deleted inserted replaced
5136:e71b22a43c0b 5137:15109c3f0f7d
166 void ResourceModificationJob::Reset() 166 void ResourceModificationJob::Reset()
167 { 167 {
168 boost::recursive_mutex::scoped_lock lock(mutex_); 168 boost::recursive_mutex::scoped_lock lock(mutex_);
169 169
170 // TODO: cleanup the instances that have been generated during the previous run 170 // TODO: cleanup the instances that have been generated during the previous run
171 modifiedInstances_.clear(); 171 modifiedSeries_.clear();
172 instancesToReconstruct_.clear();
172 173
173 ThreadedSetOfInstancesJob::Reset(); 174 ThreadedSetOfInstancesJob::Reset();
174 } 175 }
175 176
176 void ResourceModificationJob::PostProcessInstances() 177 void ResourceModificationJob::PostProcessInstances()
177 { 178 {
178 boost::recursive_mutex::scoped_lock lock(mutex_); 179 boost::recursive_mutex::scoped_lock lock(mutex_);
179 180
180 // reconstruct the parents MainDicomTags in case one of them has changed 181 // reconstruct the parents MainDicomTags in case one of them has changed
181 if (modifiedInstances_.size() > 0) 182 if (instancesToReconstruct_.size() > 0)
182 { 183 {
183 ServerContext::DicomCacheLocker locker(GetContext(), *(modifiedInstances_.begin())); 184 for (std::set<std::string>::const_iterator it = instancesToReconstruct_.begin(); it != instancesToReconstruct_.end(); ++it)
184 ParsedDicomFile& modifiedDicom = locker.GetDicom(); 185 {
185 186 ServerContext::DicomCacheLocker locker(GetContext(), *it);
186 GetContext().GetIndex().ReconstructInstance(modifiedDicom); 187 ParsedDicomFile& modifiedDicom = locker.GetDicom();
188
189 GetContext().GetIndex().ReconstructInstance(modifiedDicom);
190 }
187 } 191 }
188 192
189 } 193 }
190 194
191 bool ResourceModificationJob::HandleInstance(const std::string& instance) 195 bool ResourceModificationJob::HandleInstance(const std::string& instance)
318 322
319 { 323 {
320 boost::recursive_mutex::scoped_lock lock(outputMutex_); 324 boost::recursive_mutex::scoped_lock lock(outputMutex_);
321 325
322 output_->Update(modifiedHasher); 326 output_->Update(modifiedHasher);
323 modifiedInstances_.insert(modifiedInstance); 327 if (modifiedSeries_.find(modifiedHasher.HashSeries()) == modifiedSeries_.end())
328 {
329 modifiedSeries_.insert(modifiedHasher.HashSeries());
330 // add an instance to reconstruct for each series
331 instancesToReconstruct_.insert(modifiedHasher.HashInstance());
332 }
333
324 } 334 }
325 335
326 return true; 336 return true;
327 } 337 }
328 338
611 621
612 if (transcode_) 622 if (transcode_)
613 { 623 {
614 value[TRANSCODE] = GetTransferSyntaxUid(transferSyntax_); 624 value[TRANSCODE] = GetTransferSyntaxUid(transferSyntax_);
615 } 625 }
616 626
617 origin_.Serialize(value[ORIGIN]); 627 origin_.Serialize(value[ORIGIN]);
618 628
619 Json::Value tmp; 629 Json::Value tmp;
620 modification_->Serialize(tmp); 630 modification_->Serialize(tmp);
621 value[MODIFICATION] = tmp; 631 value[MODIFICATION] = tmp;
628 } 638 }
629 639
630 return true; 640 return true;
631 } 641 }
632 } 642 }
643
644 void ResourceModificationJob::PerformSanityChecks()
645 {
646 boost::recursive_mutex::scoped_lock lock(mutex_); // because we access the parentResources_
647
648 std::set<DicomTag> emptyRequestedTags;
649
650 if (modification_.get() == NULL)
651 {
652 throw OrthancException(ErrorCode_BadSequenceOfCalls);
653 }
654
655 bool replacePatientMainDicomTags = false;
656 bool replaceStudyMainDicomTags = false;
657 bool replaceSeriesMainDicomTags = false;
658 bool replaceInstanceMainDicomTags = false;
659
660 ResourceType modificationLevel = modification_->GetLevel();
661 std::set<DicomTag> replacedTags;
662 modification_->GetReplacedTags(replacedTags);
663
664 for (std::set<DicomTag>::const_iterator it = replacedTags.begin(); it != replacedTags.end(); it++)
665 {
666 replacePatientMainDicomTags |= DicomMap::IsMainDicomTag(*it, ResourceType_Patient);
667 replaceStudyMainDicomTags |= DicomMap::IsMainDicomTag(*it, ResourceType_Study);
668 replaceSeriesMainDicomTags |= DicomMap::IsMainDicomTag(*it, ResourceType_Series);
669 replaceInstanceMainDicomTags |= DicomMap::IsMainDicomTag(*it, ResourceType_Instance);
670 }
671
672 if (modification_->IsKept(DICOM_TAG_SOP_INSTANCE_UID) && !IsKeepSource())
673 {
674 // note: we could refine this criteria -> this is valid only if all DicomUIDs are kept identical (but this can happen through Keep or Replace options)
675 throw OrthancException(ErrorCode_BadRequest,
676 "When keeping SOPInstanceUID tag, you must set KeepSource to true to avoid deleting the modified files at the end of the process");
677 }
678
679 if (modificationLevel == ResourceType_Study && replacePatientMainDicomTags)
680 {
681 for (std::set<std::string>::const_iterator studyId = parentResources_.begin(); studyId != parentResources_.end(); ++studyId)
682 {
683 // When modifying a study, you may not modify patient tags as you wish.
684 // - If this is the patient's only study, you may modify all patient tags. This could be performed in 2 steps (modify the patient and then, the study) but,
685 // for many use cases, it's helpful to be able to do it one step (e.g, to modify a name in a study that has just been acquired)
686 // - If the patient already has other studies, you may only 'attach' the study to an existing patient by modifying
687 // all patient tags from the study to match those of the target patient.
688 // - Otherwise, you can't modify the patient tags
689
690 std::string targetPatientId;
691 if (modification_->IsReplaced(DICOM_TAG_PATIENT_ID))
692 {
693 targetPatientId = modification_->GetReplacementAsString(DICOM_TAG_PATIENT_ID);
694 }
695 else
696 {
697 ExpandedResource originalStudy;
698 if (GetContext().GetIndex().ExpandResource(originalStudy, *studyId, ResourceType_Study, emptyRequestedTags, ExpandResourceDbFlags_IncludeMainDicomTags))
699 {
700 targetPatientId = originalStudy.tags_.GetStringValue(DICOM_TAG_PATIENT_ID, "", false);
701 }
702 else
703 {
704 throw OrthancException(ErrorCode_UnknownResource, "Study not found");
705 }
706 }
707
708 // try to find the targetPatient
709 std::vector<std::string> lookupPatientResult;
710 GetContext().GetIndex().LookupIdentifierExact(lookupPatientResult, ResourceType_Patient, DICOM_TAG_PATIENT_ID, targetPatientId);
711
712 // if the patient exists, check how many child studies it has.
713 if (lookupPatientResult.size() >= 1)
714 {
715 ExpandedResource targetPatient;
716
717 if (GetContext().GetIndex().ExpandResource(targetPatient, lookupPatientResult[0], ResourceType_Patient, emptyRequestedTags, static_cast<ExpandResourceDbFlags>(ExpandResourceDbFlags_IncludeMainDicomTags | ExpandResourceDbFlags_IncludeChildren)))
718 {
719 const std::list<std::string> childrenIds = targetPatient.childrenIds_;
720 bool targetPatientHasOtherStudies = childrenIds.size() > 1;
721 if (childrenIds.size() == 1)
722 {
723 targetPatientHasOtherStudies = std::find(childrenIds.begin(), childrenIds.end(), *studyId) == childrenIds.end(); // if the patient has one study that is not the one being modified
724 }
725
726 if (targetPatientHasOtherStudies)
727 {
728 // this is allowed if all patient replacedTags do match the target patient tags
729 DicomMap targetPatientTags;
730 targetPatient.tags_.ExtractPatientInformation(targetPatientTags);
731
732 for (std::set<DicomTag>::const_iterator mainPatientTag = DicomMap::GetMainDicomTags(ResourceType_Patient).begin();
733 mainPatientTag != DicomMap::GetMainDicomTags(ResourceType_Patient).end(); ++mainPatientTag)
734 {
735 if (targetPatientTags.HasTag(*mainPatientTag)
736 && (!modification_->IsReplaced(*mainPatientTag)
737 || modification_->GetReplacementAsString(*mainPatientTag) != targetPatientTags.GetStringValue(*mainPatientTag, "", false)))
738 {
739 throw OrthancException(ErrorCode_BadRequest, std::string("Trying to change patient tags in a study. The Patient already exists and has other studies. All the 'Replace' tags should match the existing patient main dicom tags. Try using /patients/../modify instead to modify the patient. Failing tag: ") + mainPatientTag->Format());
740 }
741 else if (!targetPatientTags.HasTag(*mainPatientTag) && modification_->IsReplaced(*mainPatientTag) )
742 {
743 throw OrthancException(ErrorCode_BadRequest, std::string("Trying to change patient tags in a study. The Patient already exists and has other studies. You are trying to replace a tag that is not defined yet in this patient. Try using /patients/../modify instead to modify the patient. Failing tag: ") + mainPatientTag->Format());
744 }
745 }
746 }
747 // TODO: need reconstruct_
748 }
749 }
750 }
751
752 }
753 }
633 } 754 }