comparison OrthancServer/Sources/ServerJobs/SplitStudyJob.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/ServerJobs/SplitStudyJob.cpp@771dbd9eb3bd
children 05b8fd21089c
comparison
equal deleted inserted replaced
4043:6c6239aec462 4044:d25f4c0fa160
1 /**
2 * Orthanc - A Lightweight, RESTful DICOM Store
3 * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
4 * Department, University Hospital of Liege, Belgium
5 * Copyright (C) 2017-2020 Osimis S.A., Belgium
6 *
7 * This program is free software: you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License as
9 * published by the Free Software Foundation, either version 3 of the
10 * License, or (at your option) any later version.
11 *
12 * In addition, as a special exception, the copyright holders of this
13 * program give permission to link the code of its release with the
14 * OpenSSL project's "OpenSSL" library (or with modified versions of it
15 * that use the same license as the "OpenSSL" library), and distribute
16 * the linked executables. You must obey the GNU General Public License
17 * in all respects for all of the code used other than "OpenSSL". If you
18 * modify file(s) with this exception, you may extend this exception to
19 * your version of the file(s), but you are not obligated to do so. If
20 * you do not wish to do so, delete this exception statement from your
21 * version. If you delete this exception statement from all source files
22 * in the program, then also delete it here.
23 *
24 * This program is distributed in the hope that it will be useful, but
25 * WITHOUT ANY WARRANTY; without even the implied warranty of
26 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
27 * General Public License for more details.
28 *
29 * You should have received a copy of the GNU General Public License
30 * along with this program. If not, see <http://www.gnu.org/licenses/>.
31 **/
32
33
34 #include "SplitStudyJob.h"
35
36 #include "../../Core/DicomParsing/FromDcmtkBridge.h"
37 #include "../../Core/Logging.h"
38 #include "../../Core/SerializationToolbox.h"
39 #include "../ServerContext.h"
40
41
42 namespace Orthanc
43 {
44 void SplitStudyJob::CheckAllowedTag(const DicomTag& tag) const
45 {
46 if (allowedTags_.find(tag) == allowedTags_.end())
47 {
48 throw OrthancException(ErrorCode_ParameterOutOfRange,
49 "Cannot modify the following tag while splitting a study "
50 "(not in the patient/study modules): " +
51 FromDcmtkBridge::GetTagName(tag, "") +
52 " (" + tag.Format() + ")");
53 }
54 }
55
56
57 void SplitStudyJob::Setup()
58 {
59 SetPermissive(false);
60
61 DicomTag::AddTagsForModule(allowedTags_, DicomModule_Patient);
62 DicomTag::AddTagsForModule(allowedTags_, DicomModule_Study);
63 allowedTags_.erase(DICOM_TAG_STUDY_INSTANCE_UID);
64 allowedTags_.erase(DICOM_TAG_SERIES_INSTANCE_UID);
65 }
66
67
68 bool SplitStudyJob::HandleInstance(const std::string& instance)
69 {
70 if (!HasTrailingStep())
71 {
72 throw OrthancException(ErrorCode_BadSequenceOfCalls,
73 "AddTrailingStep() should have been called after AddSourceSeries()");
74 }
75
76 /**
77 * Retrieve the DICOM instance to be modified
78 **/
79
80 std::unique_ptr<ParsedDicomFile> modified;
81
82 try
83 {
84 ServerContext::DicomCacheLocker locker(GetContext(), instance);
85 modified.reset(locker.GetDicom().Clone(true));
86 }
87 catch (OrthancException&)
88 {
89 LOG(WARNING) << "An instance was removed after the job was issued: " << instance;
90 return false;
91 }
92
93
94 /**
95 * Chose the target UIDs
96 **/
97
98 assert(modified->GetHasher().HashStudy() == sourceStudy_);
99
100 std::string series = modified->GetHasher().HashSeries();
101
102 SeriesUidMap::const_iterator targetSeriesUid = seriesUidMap_.find(series);
103
104 if (targetSeriesUid == seriesUidMap_.end())
105 {
106 throw OrthancException(ErrorCode_BadFileFormat); // Should never happen
107 }
108
109
110 /**
111 * Apply user-specified modifications
112 **/
113
114 for (std::set<DicomTag>::const_iterator it = removals_.begin();
115 it != removals_.end(); ++it)
116 {
117 modified->Remove(*it);
118 }
119
120 for (Replacements::const_iterator it = replacements_.begin();
121 it != replacements_.end(); ++it)
122 {
123 modified->ReplacePlainString(it->first, it->second);
124 }
125
126
127 /**
128 * Store the new instance into Orthanc
129 **/
130
131 modified->ReplacePlainString(DICOM_TAG_STUDY_INSTANCE_UID, targetStudyUid_);
132 modified->ReplacePlainString(DICOM_TAG_SERIES_INSTANCE_UID, targetSeriesUid->second);
133
134 // Fix since Orthanc 1.5.8: Assign new "SOPInstanceUID", as the instance has been modified
135 modified->ReplacePlainString(DICOM_TAG_SOP_INSTANCE_UID, FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Instance));
136
137 if (targetStudy_.empty())
138 {
139 targetStudy_ = modified->GetHasher().HashStudy();
140 }
141
142 DicomInstanceToStore toStore;
143 toStore.SetOrigin(origin_);
144 toStore.SetParsedDicomFile(*modified);
145
146 std::string modifiedInstance;
147 if (GetContext().Store(modifiedInstance, toStore,
148 StoreInstanceMode_Default) != StoreStatus_Success)
149 {
150 LOG(ERROR) << "Error while storing a modified instance " << instance;
151 return false;
152 }
153
154 return true;
155 }
156
157
158 SplitStudyJob::SplitStudyJob(ServerContext& context,
159 const std::string& sourceStudy) :
160 CleaningInstancesJob(context, false /* by default, remove source instances */),
161 sourceStudy_(sourceStudy),
162 targetStudyUid_(FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Study))
163 {
164 Setup();
165
166 ResourceType type;
167
168 if (!GetContext().GetIndex().LookupResourceType(type, sourceStudy) ||
169 type != ResourceType_Study)
170 {
171 throw OrthancException(ErrorCode_UnknownResource,
172 "Cannot split unknown study " + sourceStudy);
173 }
174 }
175
176
177 void SplitStudyJob::SetOrigin(const DicomInstanceOrigin& origin)
178 {
179 if (IsStarted())
180 {
181 throw OrthancException(ErrorCode_BadSequenceOfCalls);
182 }
183 else
184 {
185 origin_ = origin;
186 }
187 }
188
189
190 void SplitStudyJob::SetOrigin(const RestApiCall& call)
191 {
192 SetOrigin(DicomInstanceOrigin::FromRest(call));
193 }
194
195
196 void SplitStudyJob::AddSourceSeries(const std::string& series)
197 {
198 std::string parent;
199
200 if (IsStarted())
201 {
202 throw OrthancException(ErrorCode_BadSequenceOfCalls);
203 }
204 else if (!GetContext().GetIndex().LookupParent(parent, series, ResourceType_Study) ||
205 parent != sourceStudy_)
206 {
207 throw OrthancException(ErrorCode_UnknownResource,
208 "This series does not belong to the study to be split: " + series);
209 }
210 else
211 {
212 // Generate a target SeriesInstanceUID for this series
213 seriesUidMap_[series] = FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Series);
214
215 // Add all the instances of the series as to be processed
216 std::list<std::string> instances;
217 GetContext().GetIndex().GetChildren(instances, series);
218
219 for (std::list<std::string>::const_iterator
220 it = instances.begin(); it != instances.end(); ++it)
221 {
222 AddInstance(*it);
223 }
224 }
225 }
226
227
228 bool SplitStudyJob::LookupTargetSeriesUid(std::string& uid,
229 const std::string& series) const
230 {
231 SeriesUidMap::const_iterator found = seriesUidMap_.find(series);
232
233 if (found == seriesUidMap_.end())
234 {
235 return false;
236 }
237 else
238 {
239 uid = found->second;
240 return true;
241 }
242 }
243
244
245 void SplitStudyJob::Remove(const DicomTag& tag)
246 {
247 if (IsStarted())
248 {
249 throw OrthancException(ErrorCode_BadSequenceOfCalls);
250 }
251
252 CheckAllowedTag(tag);
253 removals_.insert(tag);
254 }
255
256
257 void SplitStudyJob::Replace(const DicomTag& tag,
258 const std::string& value)
259 {
260 if (IsStarted())
261 {
262 throw OrthancException(ErrorCode_BadSequenceOfCalls);
263 }
264
265 CheckAllowedTag(tag);
266 replacements_[tag] = value;
267 }
268
269
270 bool SplitStudyJob::LookupReplacement(std::string& value,
271 const DicomTag& tag) const
272 {
273 Replacements::const_iterator found = replacements_.find(tag);
274
275 if (found == replacements_.end())
276 {
277 return false;
278 }
279 else
280 {
281 value = found->second;
282 return true;
283 }
284 }
285
286
287 void SplitStudyJob::GetPublicContent(Json::Value& value)
288 {
289 CleaningInstancesJob::GetPublicContent(value);
290
291 if (!targetStudy_.empty())
292 {
293 value["TargetStudy"] = targetStudy_;
294 }
295
296 value["TargetStudyUID"] = targetStudyUid_;
297 }
298
299
300 static const char* SOURCE_STUDY = "SourceStudy";
301 static const char* TARGET_STUDY = "TargetStudy";
302 static const char* TARGET_STUDY_UID = "TargetStudyUID";
303 static const char* SERIES_UID_MAP = "SeriesUIDMap";
304 static const char* ORIGIN = "Origin";
305 static const char* REPLACEMENTS = "Replacements";
306 static const char* REMOVALS = "Removals";
307
308
309 SplitStudyJob::SplitStudyJob(ServerContext& context,
310 const Json::Value& serialized) :
311 CleaningInstancesJob(context, serialized,
312 false /* by default, remove source instances */) // (*)
313 {
314 if (!HasTrailingStep())
315 {
316 // Should have been set by (*)
317 throw OrthancException(ErrorCode_InternalError);
318 }
319
320 Setup();
321
322 sourceStudy_ = SerializationToolbox::ReadString(serialized, SOURCE_STUDY);
323 targetStudy_ = SerializationToolbox::ReadString(serialized, TARGET_STUDY);
324 targetStudyUid_ = SerializationToolbox::ReadString(serialized, TARGET_STUDY_UID);
325 SerializationToolbox::ReadMapOfStrings(seriesUidMap_, serialized, SERIES_UID_MAP);
326 origin_ = DicomInstanceOrigin(serialized[ORIGIN]);
327 SerializationToolbox::ReadMapOfTags(replacements_, serialized, REPLACEMENTS);
328 SerializationToolbox::ReadSetOfTags(removals_, serialized, REMOVALS);
329 }
330
331
332 bool SplitStudyJob::Serialize(Json::Value& target)
333 {
334 if (!CleaningInstancesJob::Serialize(target))
335 {
336 return false;
337 }
338 else
339 {
340 target[SOURCE_STUDY] = sourceStudy_;
341 target[TARGET_STUDY] = targetStudy_;
342 target[TARGET_STUDY_UID] = targetStudyUid_;
343 SerializationToolbox::WriteMapOfStrings(target, seriesUidMap_, SERIES_UID_MAP);
344 origin_.Serialize(target[ORIGIN]);
345 SerializationToolbox::WriteMapOfTags(target, replacements_, REPLACEMENTS);
346 SerializationToolbox::WriteSetOfTags(target, removals_, REMOVALS);
347
348 return true;
349 }
350 }
351 }