Mercurial > hg > orthanc
view OrthancServer/Sources/ServerJobs/MergeStudyJob.cpp @ 4640:66109d24d26e
"ETag" headers for metadata and attachments now allow strong comparison (MD5 is included)
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Mon, 26 Apr 2021 15:22:44 +0200 |
parents | 8f9090b137f1 |
children | f0038043fb97 fb98db281d1d |
line wrap: on
line source
/** * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium * Copyright (C) 2017-2021 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * In addition, as a special exception, the copyright holders of this * program give permission to link the code of its release with the * OpenSSL project's "OpenSSL" library (or with modified versions of it * that use the same license as the "OpenSSL" library), and distribute * the linked executables. You must obey the GNU General Public License * in all respects for all of the code used other than "OpenSSL". If you * modify file(s) with this exception, you may extend this exception to * your version of the file(s), but you are not obligated to do so. If * you do not wish to do so, delete this exception statement from your * version. If you delete this exception statement from all source files * in the program, then also delete it here. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. **/ #include "MergeStudyJob.h" #include "../../../OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.h" #include "../../../OrthancFramework/Sources/Logging.h" #include "../../../OrthancFramework/Sources/SerializationToolbox.h" #include "../OrthancConfiguration.h" #include "../ServerContext.h" namespace Orthanc { void MergeStudyJob::AddSourceSeriesInternal(const std::string& series) { // Generate a target SeriesInstanceUID for this series seriesUidMap_[series] = FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Series); // Add all the instances of the series as to be processed std::list<std::string> instances; GetContext().GetIndex().GetChildren(instances, series); for (std::list<std::string>::const_iterator it = instances.begin(); it != instances.end(); ++it) { AddInstance(*it); } } void MergeStudyJob::AddSourceStudyInternal(const std::string& study) { if (study == targetStudy_) { throw OrthancException(ErrorCode_UnknownResource, "Cannot merge a study into the same study: " + study); } else { std::list<std::string> series; GetContext().GetIndex().GetChildren(series, study); for (std::list<std::string>::const_iterator it = series.begin(); it != series.end(); ++it) { AddSourceSeriesInternal(*it); } } } bool MergeStudyJob::HandleInstance(const std::string& instance) { if (!HasTrailingStep()) { throw OrthancException(ErrorCode_BadSequenceOfCalls, "AddTrailingStep() should have been called after AddSourceXXX()"); } /** * Retrieve the DICOM instance to be modified **/ std::unique_ptr<ParsedDicomFile> modified; try { ServerContext::DicomCacheLocker locker(GetContext(), instance); modified.reset(locker.GetDicom().Clone(true)); } catch (OrthancException&) { LOG(WARNING) << "An instance was removed after the job was issued: " << instance; return false; } /** * Chose the target UIDs **/ std::string series = modified->GetHasher().HashSeries(); SeriesUidMap::const_iterator targetSeriesUid = seriesUidMap_.find(series); if (targetSeriesUid == seriesUidMap_.end()) { throw OrthancException(ErrorCode_BadFileFormat); // Should never happen } /** * Copy the tags from the "Patient Module Attributes" and "General * Study Module Attributes" modules of the target study **/ for (std::set<DicomTag>::const_iterator it = removals_.begin(); it != removals_.end(); ++it) { modified->Remove(*it); } for (Replacements::const_iterator it = replacements_.begin(); it != replacements_.end(); ++it) { modified->ReplacePlainString(it->first, it->second); } /** * Store the new instance into Orthanc **/ modified->ReplacePlainString(DICOM_TAG_SERIES_INSTANCE_UID, targetSeriesUid->second); // Fix since Orthanc 1.5.8: Assign new "SOPInstanceUID", as the instance has been modified modified->ReplacePlainString(DICOM_TAG_SOP_INSTANCE_UID, FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Instance)); std::unique_ptr<DicomInstanceToStore> toStore(DicomInstanceToStore::CreateFromParsedDicomFile(*modified)); toStore->SetOrigin(origin_); std::string modifiedInstance; if (GetContext().Store(modifiedInstance, *toStore, StoreInstanceMode_Default) != StoreStatus_Success) { LOG(ERROR) << "Error while storing a modified instance " << instance; return false; } return true; } MergeStudyJob::MergeStudyJob(ServerContext& context, const std::string& targetStudy) : CleaningInstancesJob(context, false /* by default, remove source instances */), targetStudy_(targetStudy) { /** * Check the validity of the input ID **/ ResourceType type; if (!GetContext().GetIndex().LookupResourceType(type, targetStudy) || type != ResourceType_Study) { throw OrthancException(ErrorCode_UnknownResource, "Cannot merge into an unknown study: " + targetStudy); } /** * Detect the tags to be removed/replaced by parsing one child * instance of the study **/ DicomTag::AddTagsForModule(removals_, DicomModule_Patient); DicomTag::AddTagsForModule(removals_, DicomModule_Study); std::list<std::string> instances; GetContext().GetIndex().GetChildInstances(instances, targetStudy); if (instances.empty()) { throw OrthancException(ErrorCode_UnknownResource); } DicomMap dicom; { ServerContext::DicomCacheLocker locker(GetContext(), instances.front()); OrthancConfiguration::DefaultExtractDicomSummary(dicom, locker.GetDicom()); } const std::set<DicomTag> moduleTags = removals_; for (std::set<DicomTag>::const_iterator it = moduleTags.begin(); it != moduleTags.end(); ++it) { const DicomValue* value = dicom.TestAndGetValue(*it); std::string str; if (value != NULL && value->CopyToString(str, false)) { removals_.erase(*it); replacements_.insert(std::make_pair(*it, str)); } } } void MergeStudyJob::SetOrigin(const DicomInstanceOrigin& origin) { if (IsStarted()) { throw OrthancException(ErrorCode_BadSequenceOfCalls); } else { origin_ = origin; } } void MergeStudyJob::SetOrigin(const RestApiCall& call) { SetOrigin(DicomInstanceOrigin::FromRest(call)); } void MergeStudyJob::AddSource(const std::string& studyOrSeries) { ResourceType level; if (IsStarted()) { throw OrthancException(ErrorCode_BadSequenceOfCalls); } else if (!GetContext().GetIndex().LookupResourceType(level, studyOrSeries)) { throw OrthancException(ErrorCode_UnknownResource, "Cannot find this resource: " + studyOrSeries); } else { switch (level) { case ResourceType_Study: AddSourceStudyInternal(studyOrSeries); break; case ResourceType_Series: AddSourceSeries(studyOrSeries); break; default: throw OrthancException(ErrorCode_UnknownResource, "This resource is neither a study, nor a series: " + studyOrSeries + " is a " + std::string(EnumerationToString(level))); } } } void MergeStudyJob::AddSourceSeries(const std::string& series) { std::string parent; if (IsStarted()) { throw OrthancException(ErrorCode_BadSequenceOfCalls); } else if (!GetContext().GetIndex().LookupParent(parent, series, ResourceType_Study)) { throw OrthancException(ErrorCode_UnknownResource, "This resource is not a series: " + series); } else if (parent == targetStudy_) { throw OrthancException(ErrorCode_UnknownResource, "Cannot merge series " + series + " into its parent study " + targetStudy_); } else { AddSourceSeriesInternal(series); } } void MergeStudyJob::AddSourceStudy(const std::string& study) { ResourceType actualLevel; if (IsStarted()) { throw OrthancException(ErrorCode_BadSequenceOfCalls); } else if (!GetContext().GetIndex().LookupResourceType(actualLevel, study) || actualLevel != ResourceType_Study) { throw OrthancException(ErrorCode_UnknownResource, "This resource is not a study: " + study); } else { AddSourceStudyInternal(study); } } void MergeStudyJob::GetPublicContent(Json::Value& value) { CleaningInstancesJob::GetPublicContent(value); value["TargetStudy"] = targetStudy_; } static const char* TARGET_STUDY = "TargetStudy"; static const char* REPLACEMENTS = "Replacements"; static const char* REMOVALS = "Removals"; static const char* SERIES_UID_MAP = "SeriesUIDMap"; static const char* ORIGIN = "Origin"; MergeStudyJob::MergeStudyJob(ServerContext& context, const Json::Value& serialized) : CleaningInstancesJob(context, serialized, false /* by default, remove source instances */) // (*) { if (!HasTrailingStep()) { // Should have been set by (*) throw OrthancException(ErrorCode_InternalError); } targetStudy_ = SerializationToolbox::ReadString(serialized, TARGET_STUDY); SerializationToolbox::ReadMapOfTags(replacements_, serialized, REPLACEMENTS); SerializationToolbox::ReadSetOfTags(removals_, serialized, REMOVALS); SerializationToolbox::ReadMapOfStrings(seriesUidMap_, serialized, SERIES_UID_MAP); origin_ = DicomInstanceOrigin(serialized[ORIGIN]); } bool MergeStudyJob::Serialize(Json::Value& target) { if (!CleaningInstancesJob::Serialize(target)) { return false; } else { target[TARGET_STUDY] = targetStudy_; SerializationToolbox::WriteMapOfTags(target, replacements_, REPLACEMENTS); SerializationToolbox::WriteSetOfTags(target, removals_, REMOVALS); SerializationToolbox::WriteMapOfStrings(target, seriesUidMap_, SERIES_UID_MAP); origin_.Serialize(target[ORIGIN]); return true; } } }