Mercurial > hg > orthanc
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 } |