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