comparison OrthancServer/ServerJobs/SplitStudyJob.cpp @ 2844:99863d6245b2

New URI: "/studies/.../split" to split a study
author Sebastien Jodogne <s.jodogne@gmail.com>
date Fri, 28 Sep 2018 16:48:43 +0200
parents
children 218e2c864d1d
comparison
equal deleted inserted replaced
2843:4ee3a759afea 2844:99863d6245b2
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-2018 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/Logging.h"
37 #include "../../Core/DicomParsing/FromDcmtkBridge.h"
38
39 namespace Orthanc
40 {
41 void SplitStudyJob::CheckAllowedTag(const DicomTag& tag) const
42 {
43 if (allowedTags_.find(tag) == allowedTags_.end())
44 {
45 LOG(ERROR) << "Cannot modify the following tag while splitting a study "
46 << "(not in the patient/study modules): "
47 << FromDcmtkBridge::GetTagName(tag, "") << " (" << tag.Format() << ")";
48 throw OrthancException(ErrorCode_ParameterOutOfRange);
49 }
50 }
51
52
53 void SplitStudyJob::Setup(const std::string& sourceStudy)
54 {
55 SetPermissive(false);
56
57 keepSource_ = false;
58 sourceStudy_ = sourceStudy;
59 targetStudyUid_ = FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Study);
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 ResourceType type;
67
68 if (!context_.GetIndex().LookupResourceType(type, sourceStudy) ||
69 type != ResourceType_Study)
70 {
71 LOG(ERROR) << "Cannot split unknown study: " << sourceStudy;
72 throw OrthancException(ErrorCode_UnknownResource);
73 }
74
75 std::list<std::string> children;
76 context_.GetIndex().GetChildren(children, sourceStudy);
77
78 for (std::list<std::string>::const_iterator
79 it = children.begin(); it != children.end(); ++it)
80 {
81 sourceSeries_.insert(*it);
82 }
83 }
84
85
86 bool SplitStudyJob::HandleInstance(const std::string& instance)
87 {
88 /**
89 * Retrieve the DICOM instance to be modified
90 **/
91
92 std::auto_ptr<ParsedDicomFile> modified;
93
94 try
95 {
96 ServerContext::DicomCacheLocker locker(context_, instance);
97 modified.reset(locker.GetDicom().Clone(true));
98 }
99 catch (OrthancException&)
100 {
101 LOG(WARNING) << "An instance was removed after the job was issued: " << instance;
102 return false;
103 }
104
105
106 /**
107 * Chose the target UIDs
108 **/
109
110 std::string series;
111 if (!modified->GetTagValue(series, DICOM_TAG_SERIES_INSTANCE_UID))
112 {
113 throw OrthancException(ErrorCode_BadFileFormat); // Should never happen
114 }
115
116 std::string targetSeriesUid;
117 SeriesUidMap::const_iterator found = targetSeries_.find(series);
118
119 if (found == targetSeries_.end())
120 {
121 // Choose a random SeriesInstanceUID for this series
122 targetSeriesUid = FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Series);
123 targetSeries_[series] = targetSeriesUid;
124 }
125 else
126 {
127 targetSeriesUid = found->second;
128 }
129
130
131 /**
132 * Apply user-specified modifications
133 **/
134
135 for (Removals::const_iterator it = removals_.begin();
136 it != removals_.end(); ++it)
137 {
138 modified->Remove(*it);
139 }
140
141 for (Replacements::const_iterator it = replacements_.begin();
142 it != replacements_.end(); ++it)
143 {
144 modified->ReplacePlainString(it->first, it->second);
145 }
146
147
148 /**
149 * Store the new instance into Orthanc
150 **/
151
152 modified->ReplacePlainString(DICOM_TAG_STUDY_INSTANCE_UID, targetStudyUid_);
153 modified->ReplacePlainString(DICOM_TAG_SERIES_INSTANCE_UID, targetSeriesUid);
154
155 if (targetStudy_.empty())
156 {
157 targetStudy_ = modified->GetHasher().HashStudy();
158 }
159
160 DicomInstanceToStore toStore;
161 toStore.SetOrigin(origin_);
162 toStore.SetParsedDicomFile(*modified);
163
164 std::string modifiedInstance;
165 if (context_.Store(modifiedInstance, toStore) != StoreStatus_Success)
166 {
167 LOG(ERROR) << "Error while storing a modified instance " << instance;
168 return false;
169 }
170
171 return true;
172 }
173
174
175 bool SplitStudyJob::HandleTrailingStep()
176 {
177 if (!keepSource_)
178 {
179 const size_t n = GetInstancesCount();
180
181 for (size_t i = 0; i < n; i++)
182 {
183 Json::Value tmp;
184 context_.DeleteResource(tmp, GetInstance(i), ResourceType_Instance);
185 }
186 }
187
188 return true;
189 }
190
191
192 SplitStudyJob::SplitStudyJob(ServerContext& context,
193 const std::string& sourceStudy) :
194 SetOfInstancesJob(true /* with trailing step */),
195 context_(context)
196 {
197 Setup(sourceStudy);
198 }
199
200
201 SplitStudyJob::SplitStudyJob(ServerContext& context,
202 const Json::Value& serialized) :
203 SetOfInstancesJob(serialized),
204 context_(context)
205 {
206 //assert(HasTrailingStep());
207 //Setup();
208 }
209
210
211 void SplitStudyJob::SetOrigin(const DicomInstanceOrigin& origin)
212 {
213 if (IsStarted())
214 {
215 throw OrthancException(ErrorCode_BadSequenceOfCalls);
216 }
217 else
218 {
219 origin_ = origin;
220 }
221 }
222
223
224 void SplitStudyJob::SetOrigin(const RestApiCall& call)
225 {
226 SetOrigin(DicomInstanceOrigin::FromRest(call));
227 }
228
229
230 void SplitStudyJob::AddSourceSeries(const std::string& series)
231 {
232 if (IsStarted())
233 {
234 throw OrthancException(ErrorCode_BadSequenceOfCalls);
235 }
236 else if (sourceSeries_.find(series) == sourceSeries_.end())
237 {
238 LOG(ERROR) << "This series does not belong to the study to be split: " << series;
239 throw OrthancException(ErrorCode_UnknownResource);
240 }
241 else
242 {
243 // Add all the instances of the series as to be processed
244 std::list<std::string> instances;
245 context_.GetIndex().GetChildren(instances, series);
246
247 for (std::list<std::string>::const_iterator
248 it = instances.begin(); it != instances.end(); ++it)
249 {
250 AddInstance(*it);
251 }
252 }
253 }
254
255
256 void SplitStudyJob::SetKeepSource(bool keep)
257 {
258 if (IsStarted())
259 {
260 throw OrthancException(ErrorCode_BadSequenceOfCalls);
261 }
262
263 keepSource_ = keep;
264 }
265
266
267 void SplitStudyJob::Remove(const DicomTag& tag)
268 {
269 if (IsStarted())
270 {
271 throw OrthancException(ErrorCode_BadSequenceOfCalls);
272 }
273
274 CheckAllowedTag(tag);
275 removals_.insert(tag);
276 }
277
278
279 void SplitStudyJob::Replace(const DicomTag& tag,
280 const std::string& value)
281 {
282 if (IsStarted())
283 {
284 throw OrthancException(ErrorCode_BadSequenceOfCalls);
285 }
286
287 CheckAllowedTag(tag);
288 replacements_[tag] = value;
289 }
290
291
292 void SplitStudyJob::GetPublicContent(Json::Value& value)
293 {
294 SetOfInstancesJob::GetPublicContent(value);
295
296 if (!targetStudy_.empty())
297 {
298 value["TargetStudy"] = targetStudy_;
299 }
300
301 value["TargetStudyUID"] = targetStudyUid_;
302 }
303
304
305 bool SplitStudyJob::Serialize(Json::Value& target)
306 {
307 return true;
308 }
309 }