Mercurial > hg > orthanc
diff OrthancServer/Sources/OrthancRestApi/OrthancRestAnonymizeModify.cpp @ 4044:d25f4c0fa160 framework
splitting code into OrthancFramework and OrthancServer
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Wed, 10 Jun 2020 20:30:34 +0200 |
parents | OrthancServer/OrthancRestApi/OrthancRestAnonymizeModify.cpp@5fe8c6d3212e |
children | 05b8fd21089c |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/Sources/OrthancRestApi/OrthancRestAnonymizeModify.cpp Wed Jun 10 20:30:34 2020 +0200 @@ -0,0 +1,855 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 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 "../PrecompiledHeadersServer.h" +#include "OrthancRestApi.h" + +#include "../../Core/DicomParsing/FromDcmtkBridge.h" +#include "../../Core/Logging.h" +#include "../../Core/SerializationToolbox.h" +#include "../OrthancConfiguration.h" +#include "../ServerContext.h" +#include "../ServerJobs/MergeStudyJob.h" +#include "../ServerJobs/ResourceModificationJob.h" +#include "../ServerJobs/SplitStudyJob.h" + +#include <boost/lexical_cast.hpp> +#include <boost/algorithm/string/predicate.hpp> + +namespace Orthanc +{ + // Modification of DICOM instances ------------------------------------------ + + + static std::string GeneratePatientName(ServerContext& context) + { + uint64_t seq = context.GetIndex().IncrementGlobalSequence(GlobalProperty_AnonymizationSequence); + return "Anonymized" + boost::lexical_cast<std::string>(seq); + } + + + static void ParseModifyRequest(Json::Value& request, + DicomModification& target, + const RestApiPostCall& call) + { + // curl http://localhost:8042/series/95a6e2bf-9296e2cc-bf614e2f-22b391ee-16e010e0/modify -X POST -d '{"Replace":{"InstitutionName":"My own clinic"},"Priority":9}' + + { + OrthancConfiguration::ReaderLock lock; + target.SetPrivateCreator(lock.GetConfiguration().GetDefaultPrivateCreator()); + } + + if (call.ParseJsonRequest(request)) + { + target.ParseModifyRequest(request); + } + else + { + throw OrthancException(ErrorCode_BadFileFormat); + } + } + + + static void ParseAnonymizationRequest(Json::Value& request, + DicomModification& target, + RestApiPostCall& call) + { + // curl http://localhost:8042/instances/6e67da51-d119d6ae-c5667437-87b9a8a5-0f07c49f/anonymize -X POST -d '{"Replace":{"PatientName":"hello","0010-0020":"world"},"Keep":["StudyDescription", "SeriesDescription"],"KeepPrivateTags": true,"Remove":["Modality"]}' > Anonymized.dcm + + { + OrthancConfiguration::ReaderLock lock; + target.SetPrivateCreator(lock.GetConfiguration().GetDefaultPrivateCreator()); + } + + if (call.ParseJsonRequest(request) && + request.isObject()) + { + bool patientNameReplaced; + target.ParseAnonymizationRequest(patientNameReplaced, request); + + if (patientNameReplaced) + { + // Overwrite the random Patient's Name by one that is more + // user-friendly (provided none was specified by the user) + target.Replace(DICOM_TAG_PATIENT_NAME, GeneratePatientName(OrthancRestApi::GetContext(call)), true); + } + } + else + { + throw OrthancException(ErrorCode_BadFileFormat); + } + } + + + static void AnonymizeOrModifyInstance(DicomModification& modification, + RestApiPostCall& call, + bool transcode, + DicomTransferSyntax targetSyntax) + { + ServerContext& context = OrthancRestApi::GetContext(call); + std::string id = call.GetUriComponent("id", ""); + + std::unique_ptr<ParsedDicomFile> modified; + + { + ServerContext::DicomCacheLocker locker(context, id); + modified.reset(locker.GetDicom().Clone(true)); + } + + modification.Apply(*modified); + + if (transcode) + { + IDicomTranscoder::DicomImage source; + source.AcquireParsed(*modified); // "modified" is invalid below this point + + IDicomTranscoder::DicomImage transcoded; + + std::set<DicomTransferSyntax> s; + s.insert(targetSyntax); + + if (context.Transcode(transcoded, source, s, true)) + { + call.GetOutput().AnswerBuffer(transcoded.GetBufferData(), + transcoded.GetBufferSize(), MimeType_Dicom); + } + else + { + throw OrthancException(ErrorCode_InternalError, + "Cannot transcode to transfer syntax: " + + std::string(GetTransferSyntaxUid(targetSyntax))); + } + } + else + { + modified->Answer(call.GetOutput()); + } + } + + + static void ModifyInstance(RestApiPostCall& call) + { + DicomModification modification; + modification.SetAllowManualIdentifiers(true); + + Json::Value request; + ParseModifyRequest(request, modification, call); + + if (modification.IsReplaced(DICOM_TAG_PATIENT_ID)) + { + modification.SetLevel(ResourceType_Patient); + } + else if (modification.IsReplaced(DICOM_TAG_STUDY_INSTANCE_UID)) + { + modification.SetLevel(ResourceType_Study); + } + else if (modification.IsReplaced(DICOM_TAG_SERIES_INSTANCE_UID)) + { + modification.SetLevel(ResourceType_Series); + } + else + { + modification.SetLevel(ResourceType_Instance); + } + + static const char* TRANSCODE = "Transcode"; + if (request.isMember(TRANSCODE)) + { + std::string s = SerializationToolbox::ReadString(request, TRANSCODE); + + DicomTransferSyntax syntax; + if (LookupTransferSyntax(syntax, s)) + { + AnonymizeOrModifyInstance(modification, call, true, syntax); + } + else + { + throw OrthancException(ErrorCode_ParameterOutOfRange, "Unknown transfer syntax: " + s); + } + } + else + { + AnonymizeOrModifyInstance(modification, call, false /* no transcoding */, + DicomTransferSyntax_LittleEndianImplicit /* unused */); + } + } + + + static void AnonymizeInstance(RestApiPostCall& call) + { + DicomModification modification; + modification.SetAllowManualIdentifiers(true); + + Json::Value request; + ParseAnonymizationRequest(request, modification, call); + + AnonymizeOrModifyInstance(modification, call, false /* no transcoding */, + DicomTransferSyntax_LittleEndianImplicit /* unused */); + } + + + static void SetKeepSource(CleaningInstancesJob& job, + const Json::Value& body) + { + static const char* KEEP_SOURCE = "KeepSource"; + if (body.isMember(KEEP_SOURCE)) + { + job.SetKeepSource(SerializationToolbox::ReadBoolean(body, KEEP_SOURCE)); + } + } + + + static void SubmitModificationJob(std::unique_ptr<DicomModification>& modification, + bool isAnonymization, + RestApiPostCall& call, + const Json::Value& body, + ResourceType level) + { + ServerContext& context = OrthancRestApi::GetContext(call); + + std::unique_ptr<ResourceModificationJob> job(new ResourceModificationJob(context)); + + job->SetModification(modification.release(), level, isAnonymization); + job->SetOrigin(call); + SetKeepSource(*job, body); + + static const char* TRANSCODE = "Transcode"; + if (body.isMember(TRANSCODE)) + { + job->SetTranscode(SerializationToolbox::ReadString(body, TRANSCODE)); + } + + context.AddChildInstances(*job, call.GetUriComponent("id", "")); + job->AddTrailingStep(); + + OrthancRestApi::GetApi(call).SubmitCommandsJob + (call, job.release(), true /* synchronous by default */, body); + } + + + template <enum ResourceType resourceType> + static void ModifyResource(RestApiPostCall& call) + { + std::unique_ptr<DicomModification> modification(new DicomModification); + + Json::Value body; + ParseModifyRequest(body, *modification, call); + + modification->SetLevel(resourceType); + + SubmitModificationJob(modification, false /* not an anonymization */, + call, body, resourceType); + } + + + template <enum ResourceType resourceType> + static void AnonymizeResource(RestApiPostCall& call) + { + std::unique_ptr<DicomModification> modification(new DicomModification); + + Json::Value body; + ParseAnonymizationRequest(body, *modification, call); + + SubmitModificationJob(modification, true /* anonymization */, + call, body, resourceType); + } + + + static void StoreCreatedInstance(std::string& id /* out */, + RestApiPostCall& call, + ParsedDicomFile& dicom, + bool sendAnswer) + { + DicomInstanceToStore toStore; + toStore.SetOrigin(DicomInstanceOrigin::FromRest(call)); + toStore.SetParsedDicomFile(dicom); + + ServerContext& context = OrthancRestApi::GetContext(call); + StoreStatus status = context.Store(id, toStore, StoreInstanceMode_Default); + + if (status == StoreStatus_Failure) + { + throw OrthancException(ErrorCode_CannotStoreInstance); + } + + if (sendAnswer) + { + OrthancRestApi::GetApi(call).AnswerStoredInstance(call, toStore, status, id); + } + } + + + static void CreateDicomV1(ParsedDicomFile& dicom, + RestApiPostCall& call, + const Json::Value& request) + { + // curl http://localhost:8042/tools/create-dicom -X POST -d '{"PatientName":"Hello^World"}' + // curl http://localhost:8042/tools/create-dicom -X POST -d '{"PatientName":"Hello^World","PixelData":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAAAAAA6mKC9AAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3gUGDDcB53FulQAAAElJREFUGNNtj0sSAEEEQ1+U+185s1CtmRkblQ9CZldsKHJDk6DLGLJa6chjh0ooQmpjXMM86zPwydGEj6Ed/UGykkEM8X+p3u8/8LcOJIWLGeMAAAAASUVORK5CYII="}' + + assert(request.isObject()); + LOG(WARNING) << "Using a deprecated call to /tools/create-dicom"; + + Json::Value::Members members = request.getMemberNames(); + for (size_t i = 0; i < members.size(); i++) + { + const std::string& name = members[i]; + if (request[name].type() != Json::stringValue) + { + throw OrthancException(ErrorCode_CreateDicomNotString); + } + + std::string value = request[name].asString(); + + DicomTag tag = FromDcmtkBridge::ParseTag(name); + if (tag == DICOM_TAG_PIXEL_DATA) + { + dicom.EmbedContent(value); + } + else + { + // This is V1, don't try and decode data URI scheme + dicom.ReplacePlainString(tag, value); + } + } + } + + + static void InjectTags(ParsedDicomFile& dicom, + const Json::Value& tags, + bool decodeBinaryTags, + const std::string& privateCreator) + { + if (tags.type() != Json::objectValue) + { + throw OrthancException(ErrorCode_BadRequest, "Tags field is not an array"); + } + + // Inject the user-specified tags + Json::Value::Members members = tags.getMemberNames(); + for (size_t i = 0; i < members.size(); i++) + { + const std::string& name = members[i]; + DicomTag tag = FromDcmtkBridge::ParseTag(name); + + if (tag != DICOM_TAG_SPECIFIC_CHARACTER_SET) + { + if (tag != DICOM_TAG_PATIENT_ID && + tag != DICOM_TAG_ACQUISITION_DATE && + tag != DICOM_TAG_ACQUISITION_TIME && + tag != DICOM_TAG_CONTENT_DATE && + tag != DICOM_TAG_CONTENT_TIME && + tag != DICOM_TAG_INSTANCE_CREATION_DATE && + tag != DICOM_TAG_INSTANCE_CREATION_TIME && + tag != DICOM_TAG_SERIES_DATE && + tag != DICOM_TAG_SERIES_TIME && + tag != DICOM_TAG_STUDY_DATE && + tag != DICOM_TAG_STUDY_TIME && + dicom.HasTag(tag)) + { + throw OrthancException(ErrorCode_CreateDicomOverrideTag, name); + } + + if (tag == DICOM_TAG_PIXEL_DATA) + { + throw OrthancException(ErrorCode_CreateDicomUseContent); + } + else + { + dicom.Replace(tag, tags[name], decodeBinaryTags, DicomReplaceMode_InsertIfAbsent, privateCreator); + } + } + } + } + + + static void CreateSeries(RestApiPostCall& call, + ParsedDicomFile& base /* in */, + const Json::Value& content, + bool decodeBinaryTags, + const std::string& privateCreator) + { + assert(content.isArray()); + assert(content.size() > 0); + ServerContext& context = OrthancRestApi::GetContext(call); + + base.ReplacePlainString(DICOM_TAG_IMAGES_IN_ACQUISITION, boost::lexical_cast<std::string>(content.size())); + base.ReplacePlainString(DICOM_TAG_NUMBER_OF_TEMPORAL_POSITIONS, "1"); + + std::string someInstance; + + try + { + for (Json::ArrayIndex i = 0; i < content.size(); i++) + { + std::unique_ptr<ParsedDicomFile> dicom(base.Clone(false)); + const Json::Value* payload = NULL; + + if (content[i].type() == Json::stringValue) + { + payload = &content[i]; + } + else if (content[i].type() == Json::objectValue) + { + if (!content[i].isMember("Content")) + { + throw OrthancException(ErrorCode_CreateDicomNoPayload); + } + + payload = &content[i]["Content"]; + + if (content[i].isMember("Tags")) + { + InjectTags(*dicom, content[i]["Tags"], decodeBinaryTags, privateCreator); + } + } + + if (payload == NULL || + payload->type() != Json::stringValue) + { + throw OrthancException(ErrorCode_CreateDicomUseDataUriScheme); + } + + dicom->EmbedContent(payload->asString()); + dicom->ReplacePlainString(DICOM_TAG_INSTANCE_NUMBER, boost::lexical_cast<std::string>(i + 1)); + dicom->ReplacePlainString(DICOM_TAG_IMAGE_INDEX, boost::lexical_cast<std::string>(i + 1)); + + StoreCreatedInstance(someInstance, call, *dicom, false); + } + } + catch (OrthancException&) + { + // Error: Remove the newly-created series + + std::string series; + if (context.GetIndex().LookupParent(series, someInstance)) + { + Json::Value dummy; + context.GetIndex().DeleteResource(dummy, series, ResourceType_Series); + } + + throw; + } + + std::string series; + if (context.GetIndex().LookupParent(series, someInstance)) + { + OrthancRestApi::GetApi(call).AnswerStoredResource(call, series, ResourceType_Series, StoreStatus_Success); + } + } + + + static void CreateDicomV2(RestApiPostCall& call, + const Json::Value& request) + { + assert(request.isObject()); + ServerContext& context = OrthancRestApi::GetContext(call); + + if (!request.isMember("Tags") || + request["Tags"].type() != Json::objectValue) + { + throw OrthancException(ErrorCode_BadRequest); + } + + ParsedDicomFile dicom(true); + + { + Encoding encoding; + + if (request["Tags"].isMember("SpecificCharacterSet")) + { + const char* tmp = request["Tags"]["SpecificCharacterSet"].asCString(); + if (!GetDicomEncoding(encoding, tmp)) + { + throw OrthancException(ErrorCode_ParameterOutOfRange, + "Unknown specific character set: " + std::string(tmp)); + } + } + else + { + encoding = GetDefaultDicomEncoding(); + } + + dicom.SetEncoding(encoding); + } + + ResourceType parentType = ResourceType_Instance; + + if (request.isMember("Parent")) + { + // Locate the parent tags + std::string parent = request["Parent"].asString(); + if (!context.GetIndex().LookupResourceType(parentType, parent)) + { + throw OrthancException(ErrorCode_CreateDicomBadParent); + } + + if (parentType == ResourceType_Instance) + { + throw OrthancException(ErrorCode_CreateDicomParentIsInstance); + } + + // Select one existing child instance of the parent resource, to + // retrieve all its tags + Json::Value siblingTags; + std::string siblingInstanceId; + + { + // Retrieve all the instances of the parent resource + std::list<std::string> siblingInstances; + context.GetIndex().GetChildInstances(siblingInstances, parent); + + if (siblingInstances.empty()) + { + // Error: No instance (should never happen) + throw OrthancException(ErrorCode_InternalError); + } + + siblingInstanceId = siblingInstances.front(); + context.ReadDicomAsJson(siblingTags, siblingInstanceId); + } + + + // Choose the same encoding as the parent resource + { + static const char* SPECIFIC_CHARACTER_SET = "0008,0005"; + + if (siblingTags.isMember(SPECIFIC_CHARACTER_SET)) + { + Encoding encoding; + + if (!siblingTags[SPECIFIC_CHARACTER_SET].isMember("Value") || + siblingTags[SPECIFIC_CHARACTER_SET]["Value"].type() != Json::stringValue || + !GetDicomEncoding(encoding, siblingTags[SPECIFIC_CHARACTER_SET]["Value"].asCString())) + { + LOG(WARNING) << "Instance with an incorrect Specific Character Set, " + << "using the default Orthanc encoding: " << siblingInstanceId; + encoding = GetDefaultDicomEncoding(); + } + + dicom.SetEncoding(encoding); + } + } + + + // Retrieve the tags for all the parent modules + typedef std::set<DicomTag> ModuleTags; + ModuleTags moduleTags; + + ResourceType type = parentType; + for (;;) + { + DicomTag::AddTagsForModule(moduleTags, GetModule(type)); + + if (type == ResourceType_Patient) + { + break; // We're done + } + + // Go up + std::string tmp; + if (!context.GetIndex().LookupParent(tmp, parent)) + { + throw OrthancException(ErrorCode_InternalError); + } + + parent = tmp; + type = GetParentResourceType(type); + } + + for (ModuleTags::const_iterator it = moduleTags.begin(); + it != moduleTags.end(); ++it) + { + std::string t = it->Format(); + if (siblingTags.isMember(t)) + { + const Json::Value& tag = siblingTags[t]; + if (tag["Type"] == "Null") + { + dicom.ReplacePlainString(*it, ""); + } + else if (tag["Type"] == "String") + { + std::string value = tag["Value"].asString(); // This is an UTF-8 value (as it comes from JSON) + dicom.ReplacePlainString(*it, value); + } + } + } + } + + + bool decodeBinaryTags = true; + if (request.isMember("InterpretBinaryTags")) + { + const Json::Value& v = request["InterpretBinaryTags"]; + if (v.type() != Json::booleanValue) + { + throw OrthancException(ErrorCode_BadRequest); + } + + decodeBinaryTags = v.asBool(); + } + + + // New argument in Orthanc 1.6.0 + std::string privateCreator; + if (request.isMember("PrivateCreator")) + { + const Json::Value& v = request["PrivateCreator"]; + if (v.type() != Json::stringValue) + { + throw OrthancException(ErrorCode_BadRequest); + } + + privateCreator = v.asString(); + } + else + { + OrthancConfiguration::ReaderLock lock; + privateCreator = lock.GetConfiguration().GetDefaultPrivateCreator(); + } + + + // Inject time-related information + std::string date, time; + SystemToolbox::GetNowDicom(date, time, true /* use UTC time (not local time) */); + dicom.ReplacePlainString(DICOM_TAG_ACQUISITION_DATE, date); + dicom.ReplacePlainString(DICOM_TAG_ACQUISITION_TIME, time); + dicom.ReplacePlainString(DICOM_TAG_CONTENT_DATE, date); + dicom.ReplacePlainString(DICOM_TAG_CONTENT_TIME, time); + dicom.ReplacePlainString(DICOM_TAG_INSTANCE_CREATION_DATE, date); + dicom.ReplacePlainString(DICOM_TAG_INSTANCE_CREATION_TIME, time); + + if (parentType == ResourceType_Patient || + parentType == ResourceType_Study || + parentType == ResourceType_Instance /* no parent */) + { + dicom.ReplacePlainString(DICOM_TAG_SERIES_DATE, date); + dicom.ReplacePlainString(DICOM_TAG_SERIES_TIME, time); + } + + if (parentType == ResourceType_Patient || + parentType == ResourceType_Instance /* no parent */) + { + dicom.ReplacePlainString(DICOM_TAG_STUDY_DATE, date); + dicom.ReplacePlainString(DICOM_TAG_STUDY_TIME, time); + } + + + InjectTags(dicom, request["Tags"], decodeBinaryTags, privateCreator); + + + // Inject the content (either an image, or a PDF file) + if (request.isMember("Content")) + { + const Json::Value& content = request["Content"]; + + if (content.type() == Json::stringValue) + { + dicom.EmbedContent(request["Content"].asString()); + + } + else if (content.type() == Json::arrayValue) + { + if (content.size() > 0) + { + // Let's create a series instead of a single instance + CreateSeries(call, dicom, content, decodeBinaryTags, privateCreator); + return; + } + } + else + { + throw OrthancException(ErrorCode_CreateDicomUseDataUriScheme); + } + } + + std::string id; + StoreCreatedInstance(id, call, dicom, true); + } + + + static void CreateDicom(RestApiPostCall& call) + { + Json::Value request; + if (!call.ParseJsonRequest(request) || + !request.isObject()) + { + throw OrthancException(ErrorCode_BadRequest); + } + + if (request.isMember("Tags")) + { + CreateDicomV2(call, request); + } + else + { + // Compatibility with Orthanc <= 0.9.3 + ParsedDicomFile dicom(true); + CreateDicomV1(dicom, call, request); + + std::string id; + StoreCreatedInstance(id, call, dicom, true); + } + } + + + static void SplitStudy(RestApiPostCall& call) + { + ServerContext& context = OrthancRestApi::GetContext(call); + + Json::Value request; + if (!call.ParseJsonRequest(request)) + { + // Bad JSON request + throw OrthancException(ErrorCode_BadFileFormat); + } + + const std::string study = call.GetUriComponent("id", ""); + + std::unique_ptr<SplitStudyJob> job(new SplitStudyJob(context, study)); + job->SetOrigin(call); + + std::vector<std::string> series; + SerializationToolbox::ReadArrayOfStrings(series, request, "Series"); + + for (size_t i = 0; i < series.size(); i++) + { + job->AddSourceSeries(series[i]); + } + + job->AddTrailingStep(); + + SetKeepSource(*job, request); + + static const char* REMOVE = "Remove"; + if (request.isMember(REMOVE)) + { + if (request[REMOVE].type() != Json::arrayValue) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + + for (Json::Value::ArrayIndex i = 0; i < request[REMOVE].size(); i++) + { + if (request[REMOVE][i].type() != Json::stringValue) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + else + { + job->Remove(FromDcmtkBridge::ParseTag(request[REMOVE][i].asCString())); + } + } + } + + static const char* REPLACE = "Replace"; + if (request.isMember(REPLACE)) + { + if (request[REPLACE].type() != Json::objectValue) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + + Json::Value::Members tags = request[REPLACE].getMemberNames(); + + for (size_t i = 0; i < tags.size(); i++) + { + const Json::Value& value = request[REPLACE][tags[i]]; + + if (value.type() != Json::stringValue) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + else + { + job->Replace(FromDcmtkBridge::ParseTag(tags[i]), value.asString()); + } + } + } + + OrthancRestApi::GetApi(call).SubmitCommandsJob + (call, job.release(), true /* synchronous by default */, request); + } + + + static void MergeStudy(RestApiPostCall& call) + { + ServerContext& context = OrthancRestApi::GetContext(call); + + Json::Value request; + if (!call.ParseJsonRequest(request)) + { + // Bad JSON request + throw OrthancException(ErrorCode_BadFileFormat); + } + + const std::string study = call.GetUriComponent("id", ""); + + std::unique_ptr<MergeStudyJob> job(new MergeStudyJob(context, study)); + job->SetOrigin(call); + + std::vector<std::string> resources; + SerializationToolbox::ReadArrayOfStrings(resources, request, "Resources"); + + for (size_t i = 0; i < resources.size(); i++) + { + job->AddSource(resources[i]); + } + + job->AddTrailingStep(); + + SetKeepSource(*job, request); + + OrthancRestApi::GetApi(call).SubmitCommandsJob + (call, job.release(), true /* synchronous by default */, request); + } + + + void OrthancRestApi::RegisterAnonymizeModify() + { + Register("/instances/{id}/modify", ModifyInstance); + Register("/series/{id}/modify", ModifyResource<ResourceType_Series>); + Register("/studies/{id}/modify", ModifyResource<ResourceType_Study>); + Register("/patients/{id}/modify", ModifyResource<ResourceType_Patient>); + + Register("/instances/{id}/anonymize", AnonymizeInstance); + Register("/series/{id}/anonymize", AnonymizeResource<ResourceType_Series>); + Register("/studies/{id}/anonymize", AnonymizeResource<ResourceType_Study>); + Register("/patients/{id}/anonymize", AnonymizeResource<ResourceType_Patient>); + + Register("/tools/create-dicom", CreateDicom); + + Register("/studies/{id}/split", SplitStudy); + Register("/studies/{id}/merge", MergeStudy); + } +}