Mercurial > hg > orthanc-stone
comparison Framework/Toolbox/SortedFrames.cpp @ 1478:fab6c6e795a3
Framework/Toolbox/SortedFrames.cpp
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Thu, 18 Jun 2020 16:01:00 +0200 |
parents | |
children | 5e3cfe87a873 |
comparison
equal
deleted
inserted
replaced
1477:5732edec7cbd | 1478:fab6c6e795a3 |
---|---|
1 /** | |
2 * Stone of Orthanc | |
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 Affero General Public License | |
9 * as published by the Free Software Foundation, either version 3 of | |
10 * the License, or (at your option) any later version. | |
11 * | |
12 * This program is distributed in the hope that it will be useful, but | |
13 * WITHOUT ANY WARRANTY; without even the implied warranty of | |
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
15 * Affero General Public License for more details. | |
16 * | |
17 * You should have received a copy of the GNU Affero General Public License | |
18 * along with this program. If not, see <http://www.gnu.org/licenses/>. | |
19 **/ | |
20 | |
21 | |
22 #include "SortedFrames.h" | |
23 | |
24 #include <OrthancException.h> | |
25 | |
26 #include "GeometryToolbox.h" | |
27 | |
28 namespace OrthancStone | |
29 { | |
30 SortedFrames::Instance::Instance(const Orthanc::DicomMap& tags) | |
31 { | |
32 tags_.Assign(tags); | |
33 | |
34 if (!tags.LookupStringValue(sopInstanceUid_, Orthanc::DICOM_TAG_SOP_INSTANCE_UID, false)) | |
35 { | |
36 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); | |
37 } | |
38 | |
39 uint32_t tmp; | |
40 if (tags.ParseUnsignedInteger32(tmp, Orthanc::DICOM_TAG_NUMBER_OF_FRAMES)) | |
41 { | |
42 numberOfFrames_ = tmp; | |
43 } | |
44 else | |
45 { | |
46 numberOfFrames_ = 1; | |
47 } | |
48 | |
49 hasPosition_ = ( | |
50 LinearAlgebra::ParseVector(position_, tags, Orthanc::DICOM_TAG_IMAGE_POSITION_PATIENT) && | |
51 position_.size() == 3 && | |
52 GeometryToolbox::ComputeNormal(normal_, tags)); | |
53 } | |
54 | |
55 | |
56 const Vector& SortedFrames::Instance::GetNormal() const | |
57 { | |
58 if (hasPosition_) | |
59 { | |
60 return normal_; | |
61 } | |
62 else | |
63 { | |
64 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); | |
65 } | |
66 } | |
67 | |
68 | |
69 const Vector& SortedFrames::Instance::GetPosition() const | |
70 { | |
71 if (hasPosition_) | |
72 { | |
73 return position_; | |
74 } | |
75 else | |
76 { | |
77 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); | |
78 } | |
79 } | |
80 | |
81 | |
82 SortedFrames::Frame::Frame(const Instance& instance, | |
83 unsigned int frameIndex) : | |
84 instance_(instance), | |
85 frameIndex_(frameIndex) | |
86 { | |
87 if (frameIndex >= instance.GetNumberOfFrames()) | |
88 { | |
89 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); | |
90 } | |
91 } | |
92 | |
93 | |
94 const SortedFrames::Instance& SortedFrames::GetInstance(size_t index) const | |
95 { | |
96 if (index >= instances_.size()) | |
97 { | |
98 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); | |
99 } | |
100 else | |
101 { | |
102 assert(instances_[index] != NULL); | |
103 return *instances_[index]; | |
104 } | |
105 } | |
106 | |
107 | |
108 const SortedFrames::Frame& SortedFrames::GetFrame(size_t index) const | |
109 { | |
110 if (!sorted_) | |
111 { | |
112 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls, | |
113 "Sort() has not been called"); | |
114 } | |
115 if (index >= frames_.size()) | |
116 { | |
117 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); | |
118 } | |
119 else | |
120 { | |
121 return frames_[index]; | |
122 } | |
123 } | |
124 | |
125 | |
126 SortedFrames::~SortedFrames() | |
127 { | |
128 for (size_t i = 0; i < instances_.size(); i++) | |
129 { | |
130 assert(instances_[i] != NULL); | |
131 delete instances_[i]; | |
132 } | |
133 } | |
134 | |
135 | |
136 void SortedFrames::AddInstance(const Orthanc::DicomMap& tags) | |
137 { | |
138 std::unique_ptr<Instance> instance(new Instance(tags)); | |
139 | |
140 std::string studyInstanceUid, seriesInstanceUid; | |
141 if (!tags.LookupStringValue(studyInstanceUid, Orthanc::DICOM_TAG_STUDY_INSTANCE_UID, false) || | |
142 !tags.LookupStringValue(seriesInstanceUid, Orthanc::DICOM_TAG_SERIES_INSTANCE_UID, false)) | |
143 { | |
144 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); | |
145 } | |
146 | |
147 if (instances_.empty()) | |
148 { | |
149 studyInstanceUid_ = studyInstanceUid; | |
150 seriesInstanceUid_ = seriesInstanceUid; | |
151 } | |
152 else | |
153 { | |
154 if (studyInstanceUid_ != studyInstanceUid || | |
155 seriesInstanceUid_ != seriesInstanceUid) | |
156 { | |
157 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange, | |
158 "Mixing instances from different series"); | |
159 } | |
160 } | |
161 | |
162 instances_.push_back(instance.release()); | |
163 sorted_ = false; | |
164 frames_.clear(); | |
165 } | |
166 | |
167 | |
168 void SortedFrames::AddFramesOfInstance(std::set<size_t>& remainingInstances, | |
169 size_t index) | |
170 { | |
171 assert(instances_[index] != NULL); | |
172 const Instance& instance = *instances_[index]; | |
173 | |
174 for (unsigned int i = 0; i < instance.GetNumberOfFrames(); i++) | |
175 { | |
176 frames_.push_back(Frame(instance, i)); | |
177 } | |
178 | |
179 assert(remainingInstances.find(index) != remainingInstances.end()); | |
180 remainingInstances.erase(index); | |
181 } | |
182 | |
183 | |
184 namespace | |
185 { | |
186 template<typename T> | |
187 class SortableItem | |
188 { | |
189 private: | |
190 T value_; | |
191 size_t instance_; | |
192 std::string sopInstanceUid_; | |
193 | |
194 public: | |
195 SortableItem(const T& value, | |
196 size_t instance, | |
197 const std::string& sopInstanceUid) : | |
198 value_(value), | |
199 instance_(instance), | |
200 sopInstanceUid_(sopInstanceUid) | |
201 { | |
202 } | |
203 | |
204 size_t GetInstanceIndex() const | |
205 { | |
206 return instance_; | |
207 } | |
208 | |
209 bool operator< (const SortableItem& other) const | |
210 { | |
211 return (value_ < other.value_ || | |
212 (value_ == other.value_ && | |
213 sopInstanceUid_ < other.sopInstanceUid_)); | |
214 } | |
215 }; | |
216 } | |
217 | |
218 | |
219 void SortedFrames::SortUsingIntegerTag(std::set<size_t>& remainingInstances, | |
220 const Orthanc::DicomTag& tag) | |
221 { | |
222 std::vector< SortableItem<int32_t> > items; | |
223 items.reserve(remainingInstances.size()); | |
224 | |
225 for (std::set<size_t>::const_iterator it = remainingInstances.begin(); | |
226 it != remainingInstances.end(); ++it) | |
227 { | |
228 assert(instances_[*it] != NULL); | |
229 const Instance& instance = *instances_[*it]; | |
230 | |
231 int32_t value; | |
232 std::string sopInstanceUid; | |
233 if (instance.GetTags().ParseInteger32(value, tag) && | |
234 instance.GetTags().LookupStringValue( | |
235 sopInstanceUid, Orthanc::DICOM_TAG_SOP_INSTANCE_UID, false)) | |
236 { | |
237 items.push_back(SortableItem<int32_t>(value, *it, sopInstanceUid)); | |
238 } | |
239 } | |
240 | |
241 std::sort(items.begin(), items.end()); | |
242 | |
243 for (size_t i = 0; i < items.size(); i++) | |
244 { | |
245 AddFramesOfInstance(remainingInstances, items[i].GetInstanceIndex()); | |
246 } | |
247 } | |
248 | |
249 | |
250 void SortedFrames::SortUsingSopInstanceUid(std::set<size_t>& remainingInstances) | |
251 { | |
252 std::vector<SortableItem<int32_t> > items; | |
253 items.reserve(remainingInstances.size()); | |
254 | |
255 for (std::set<size_t>::const_iterator it = remainingInstances.begin(); | |
256 it != remainingInstances.end(); ++it) | |
257 { | |
258 assert(instances_[*it] != NULL); | |
259 const Instance& instance = *instances_[*it]; | |
260 | |
261 std::string sopInstanceUid; | |
262 if (instance.GetTags().LookupStringValue( | |
263 sopInstanceUid, Orthanc::DICOM_TAG_SOP_INSTANCE_UID, false)) | |
264 { | |
265 items.push_back(SortableItem<int32_t>(0 /* arbitrary value */, *it, sopInstanceUid)); | |
266 } | |
267 } | |
268 | |
269 std::sort(items.begin(), items.end()); | |
270 | |
271 for (size_t i = 0; i < items.size(); i++) | |
272 { | |
273 AddFramesOfInstance(remainingInstances, items[i].GetInstanceIndex()); | |
274 } | |
275 } | |
276 | |
277 | |
278 void SortedFrames::SortUsing3DLocation(std::set<size_t>& remainingInstances) | |
279 { | |
280 /** | |
281 * Compute the mean of the normal vectors, using the recursive | |
282 * formula for arithmetic means for numerical stability. | |
283 * https://diego.assencio.com/?index=c34d06f4f4de2375658ed41f70177d59 | |
284 **/ | |
285 | |
286 Vector meanNormal; | |
287 LinearAlgebra::AssignVector(meanNormal, 0, 0, 0); | |
288 | |
289 unsigned int n = 0; | |
290 | |
291 for (std::set<size_t>::const_iterator it = remainingInstances.begin(); | |
292 it != remainingInstances.end(); ++it) | |
293 { | |
294 assert(instances_[*it] != NULL); | |
295 const Instance& instance = *instances_[*it]; | |
296 | |
297 if (instance.HasPosition()) | |
298 { | |
299 n += 1; | |
300 meanNormal += (instance.GetNormal() - meanNormal) / static_cast<float>(n); | |
301 } | |
302 } | |
303 | |
304 std::vector<SortableItem<float> > items; | |
305 items.reserve(n); | |
306 | |
307 for (std::set<size_t>::const_iterator it = remainingInstances.begin(); | |
308 it != remainingInstances.end(); ++it) | |
309 { | |
310 assert(instances_[*it] != NULL); | |
311 const Instance& instance = *instances_[*it]; | |
312 | |
313 std::string sopInstanceUid; | |
314 if (instance.HasPosition() && | |
315 instance.GetTags().LookupStringValue( | |
316 sopInstanceUid, Orthanc::DICOM_TAG_SOP_INSTANCE_UID, false)) | |
317 { | |
318 double p = LinearAlgebra::DotProduct(meanNormal, instance.GetPosition()); | |
319 items.push_back(SortableItem<float>(p, *it, sopInstanceUid)); | |
320 } | |
321 } | |
322 | |
323 assert(items.size() <= n); | |
324 | |
325 std::sort(items.begin(), items.end()); | |
326 | |
327 for (size_t i = 0; i < items.size(); i++) | |
328 { | |
329 AddFramesOfInstance(remainingInstances, items[i].GetInstanceIndex()); | |
330 } | |
331 } | |
332 | |
333 | |
334 size_t SortedFrames::GetFramesCount() const | |
335 { | |
336 if (sorted_) | |
337 { | |
338 return frames_.size(); | |
339 } | |
340 else | |
341 { | |
342 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls, | |
343 "Sort() has not been called"); | |
344 } | |
345 } | |
346 | |
347 | |
348 void SortedFrames::Sort() | |
349 { | |
350 if (!sorted_) | |
351 { | |
352 size_t totalFrames = 0; | |
353 std::set<size_t> remainingInstances; | |
354 | |
355 for (size_t i = 0; i < instances_.size(); i++) | |
356 { | |
357 assert(instances_[i] != NULL); | |
358 totalFrames += instances_[i]->GetNumberOfFrames(); | |
359 | |
360 remainingInstances.insert(i); | |
361 } | |
362 | |
363 frames_.clear(); | |
364 frames_.reserve(totalFrames); | |
365 | |
366 SortUsingIntegerTag(remainingInstances, Orthanc::DICOM_TAG_INSTANCE_NUMBER); // VR is "IS" | |
367 SortUsingIntegerTag(remainingInstances, Orthanc::DICOM_TAG_IMAGE_INDEX); // VR is "US" | |
368 SortUsing3DLocation(remainingInstances); | |
369 SortUsingSopInstanceUid(remainingInstances); | |
370 | |
371 // The following could in theory happen if several instances | |
372 // have the same SOPInstanceUID, no ordering is available | |
373 for (std::set<size_t>::const_iterator it = remainingInstances.begin(); | |
374 it != remainingInstances.end(); it++) | |
375 { | |
376 AddFramesOfInstance(remainingInstances, *it); | |
377 } | |
378 | |
379 if (frames_.size() != totalFrames || | |
380 !remainingInstances.empty()) | |
381 { | |
382 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); | |
383 } | |
384 | |
385 sorted_ = true; | |
386 } | |
387 } | |
388 } |