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 }