Mercurial > hg > orthanc
comparison OrthancServer/SliceOrdering.cpp @ 1703:b80e76dd1d56 db-changes
ordered-slices continued
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Tue, 13 Oct 2015 16:10:35 +0200 |
parents | 9980875edc7c |
children | ec66a16aa398 |
comparison
equal
deleted
inserted
replaced
1702:9980875edc7c | 1703:b80e76dd1d56 |
---|---|
31 | 31 |
32 | 32 |
33 #include "PrecompiledHeadersServer.h" | 33 #include "PrecompiledHeadersServer.h" |
34 #include "SliceOrdering.h" | 34 #include "SliceOrdering.h" |
35 | 35 |
36 #include "../Core/Logging.h" | |
36 #include "../Core/Toolbox.h" | 37 #include "../Core/Toolbox.h" |
37 | 38 #include "ServerEnumerations.h" |
39 | |
40 #include <algorithm> | |
38 #include <boost/lexical_cast.hpp> | 41 #include <boost/lexical_cast.hpp> |
42 #include <boost/noncopyable.hpp> | |
39 | 43 |
40 | 44 |
41 namespace Orthanc | 45 namespace Orthanc |
42 { | 46 { |
43 static bool TokenizeVector(std::vector<float>& result, | 47 static bool TokenizeVector(std::vector<float>& result, |
87 return TokenizeVector(result, value->AsString(), expectedSize); | 91 return TokenizeVector(result, value->AsString(), expectedSize); |
88 } | 92 } |
89 } | 93 } |
90 | 94 |
91 | 95 |
92 struct SliceOrdering::Instance | 96 struct SliceOrdering::Instance : public boost::noncopyable |
93 { | 97 { |
98 private: | |
94 std::string instanceId_; | 99 std::string instanceId_; |
95 bool hasPosition_; | 100 bool hasPosition_; |
96 Vector position_; | 101 Vector position_; |
97 bool hasIndexInSeries_; | 102 bool hasIndexInSeries_; |
98 size_t indexInSeries_; | 103 size_t indexInSeries_; |
99 | 104 unsigned int framesCount_; |
105 | |
106 public: | |
100 Instance(ServerIndex& index, | 107 Instance(ServerIndex& index, |
101 const std::string& instanceId) : | 108 const std::string& instanceId) : |
102 instanceId_(instanceId) | 109 instanceId_(instanceId), |
110 framesCount_(1) | |
103 { | 111 { |
104 DicomMap instance; | 112 DicomMap instance; |
105 if (!index.GetMainDicomTags(instance, instanceId, ResourceType_Instance, ResourceType_Instance)) | 113 if (!index.GetMainDicomTags(instance, instanceId, ResourceType_Instance, ResourceType_Instance)) |
106 { | 114 { |
107 throw OrthancException(ErrorCode_UnknownResource); | 115 throw OrthancException(ErrorCode_UnknownResource); |
116 } | |
117 | |
118 const DicomValue* frames = instance.TestAndGetValue(DICOM_TAG_NUMBER_OF_FRAMES); | |
119 if (frames != NULL && | |
120 !frames->IsNull()) | |
121 { | |
122 try | |
123 { | |
124 framesCount_ = boost::lexical_cast<unsigned int>(frames->AsString()); | |
125 } | |
126 catch (boost::bad_lexical_cast&) | |
127 { | |
128 } | |
108 } | 129 } |
109 | 130 |
110 std::vector<float> tmp; | 131 std::vector<float> tmp; |
111 hasPosition_ = TokenizeVector(tmp, instance, DICOM_TAG_IMAGE_POSITION_PATIENT, 3); | 132 hasPosition_ = TokenizeVector(tmp, instance, DICOM_TAG_IMAGE_POSITION_PATIENT, 3); |
112 | 133 |
130 } | 151 } |
131 catch (boost::bad_lexical_cast&) | 152 catch (boost::bad_lexical_cast&) |
132 { | 153 { |
133 } | 154 } |
134 } | 155 } |
156 | |
157 const std::string& GetIdentifier() const | |
158 { | |
159 return instanceId_; | |
160 } | |
161 | |
162 bool HasPosition() const | |
163 { | |
164 return hasPosition_; | |
165 } | |
166 | |
167 float ComputeRelativePosition(const Vector& normal) const | |
168 { | |
169 assert(HasPosition()); | |
170 return (normal[0] * position_[0] + | |
171 normal[1] * position_[1] + | |
172 normal[2] * position_[2]); | |
173 } | |
174 | |
175 bool HasIndexInSeries() const | |
176 { | |
177 return hasIndexInSeries_; | |
178 } | |
179 | |
180 size_t GetIndexInSeries() const | |
181 { | |
182 assert(HasIndexInSeries()); | |
183 return indexInSeries_; | |
184 } | |
185 | |
186 unsigned int GetFramesCount() const | |
187 { | |
188 return framesCount_; | |
189 } | |
135 }; | 190 }; |
191 | |
192 | |
193 class SliceOrdering::PositionComparator | |
194 { | |
195 private: | |
196 const Vector& normal_; | |
197 | |
198 public: | |
199 PositionComparator(const Vector& normal) : normal_(normal) | |
200 { | |
201 } | |
202 | |
203 int operator() (const Instance* a, | |
204 const Instance* b) const | |
205 { | |
206 return a->ComputeRelativePosition(normal_) < b->ComputeRelativePosition(normal_); | |
207 } | |
208 }; | |
209 | |
210 | |
211 bool SliceOrdering::IndexInSeriesComparator(const SliceOrdering::Instance* a, | |
212 const SliceOrdering::Instance* b) | |
213 { | |
214 return a->GetIndexInSeries() < b->GetIndexInSeries(); | |
215 } | |
136 | 216 |
137 | 217 |
138 void SliceOrdering::ComputeNormal() | 218 void SliceOrdering::ComputeNormal() |
139 { | 219 { |
140 DicomMap series; | 220 DicomMap series; |
169 } | 249 } |
170 | 250 |
171 | 251 |
172 bool SliceOrdering::SortUsingPositions() | 252 bool SliceOrdering::SortUsingPositions() |
173 { | 253 { |
254 if (instances_.size() <= 1) | |
255 { | |
256 // One single instance: It is sorted by default | |
257 return true; | |
258 } | |
259 | |
174 if (!hasNormal_) | 260 if (!hasNormal_) |
175 { | 261 { |
176 return false; | 262 return false; |
177 } | 263 } |
178 | 264 |
179 for (size_t i = 0; i < instances_.size(); i++) | 265 for (size_t i = 0; i < instances_.size(); i++) |
180 { | 266 { |
181 assert(instances_[i] != NULL); | 267 assert(instances_[i] != NULL); |
182 if (!instances_[i]->hasPosition_) | 268 if (!instances_[i]->HasPosition()) |
183 { | 269 { |
184 return false; | 270 return false; |
185 } | 271 } |
186 } | 272 } |
187 | 273 |
274 PositionComparator comparator(normal_); | |
275 std::sort(instances_.begin(), instances_.end(), comparator); | |
276 | |
277 float a = instances_.front()->ComputeRelativePosition(normal_); | |
278 float b = instances_.back()->ComputeRelativePosition(normal_); | |
279 | |
280 if (std::fabs(b - a) <= 10.0f * std::numeric_limits<float>::epsilon()) | |
281 { | |
282 // Not enough difference between the minimum and maximum | |
283 // positions along the normal of the volume | |
284 return false; | |
285 } | |
286 else | |
287 { | |
288 // This is a 3D volume | |
289 isVolume_ = true; | |
290 return true; | |
291 } | |
292 } | |
293 | |
294 | |
295 bool SliceOrdering::SortUsingIndexInSeries() | |
296 { | |
297 if (instances_.size() <= 1) | |
298 { | |
299 // One single instance: It is sorted by default | |
300 return true; | |
301 } | |
302 | |
303 for (size_t i = 0; i < instances_.size(); i++) | |
304 { | |
305 assert(instances_[i] != NULL); | |
306 if (!instances_[i]->HasIndexInSeries()) | |
307 { | |
308 return false; | |
309 } | |
310 } | |
311 | |
312 std::sort(instances_.begin(), instances_.end(), IndexInSeriesComparator); | |
188 | 313 |
314 for (size_t i = 1; i < instances_.size(); i++) | |
315 { | |
316 if (instances_[i - 1]->GetIndexInSeries() == instances_[i]->GetIndexInSeries()) | |
317 { | |
318 // The current "IndexInSeries" occurs 2 times: Not a proper ordering | |
319 return false; | |
320 } | |
321 } | |
189 | 322 |
190 return true; | 323 return true; |
191 } | 324 } |
192 | 325 |
193 | 326 |
194 SliceOrdering::SliceOrdering(ServerIndex& index, | 327 SliceOrdering::SliceOrdering(ServerIndex& index, |
195 const std::string& seriesId) : | 328 const std::string& seriesId) : |
196 index_(index), | 329 index_(index), |
197 seriesId_(seriesId) | 330 seriesId_(seriesId), |
331 isVolume_(false) | |
198 { | 332 { |
199 ComputeNormal(); | 333 ComputeNormal(); |
200 CreateInstances(); | 334 CreateInstances(); |
335 | |
336 if (!SortUsingPositions() && | |
337 !SortUsingIndexInSeries()) | |
338 { | |
339 LOG(ERROR) << "Unable to order the slices of the series " << seriesId; | |
340 throw OrthancException(ErrorCode_CannotOrderSlices); | |
341 } | |
201 } | 342 } |
202 | 343 |
203 | 344 |
204 SliceOrdering::~SliceOrdering() | 345 SliceOrdering::~SliceOrdering() |
205 { | 346 { |
210 { | 351 { |
211 delete *it; | 352 delete *it; |
212 } | 353 } |
213 } | 354 } |
214 } | 355 } |
356 | |
357 | |
358 const std::string& SliceOrdering::GetInstanceId(size_t index) const | |
359 { | |
360 if (index >= instances_.size()) | |
361 { | |
362 throw OrthancException(ErrorCode_ParameterOutOfRange); | |
363 } | |
364 else | |
365 { | |
366 return instances_[index]->GetIdentifier(); | |
367 } | |
368 } | |
369 | |
370 | |
371 unsigned int SliceOrdering::GetFramesCount(size_t index) const | |
372 { | |
373 if (index >= instances_.size()) | |
374 { | |
375 throw OrthancException(ErrorCode_ParameterOutOfRange); | |
376 } | |
377 else | |
378 { | |
379 return instances_[index]->GetFramesCount(); | |
380 } | |
381 } | |
382 | |
383 | |
384 void SliceOrdering::Format(Json::Value& result) const | |
385 { | |
386 result = Json::objectValue; | |
387 result["Type"] = (isVolume_ ? "Volume" : "Sequence"); | |
388 | |
389 Json::Value tmp = Json::arrayValue; | |
390 for (size_t i = 0; i < GetInstancesCount(); i++) | |
391 { | |
392 tmp.append(GetBasePath(ResourceType_Instance, GetInstanceId(i)) + "/file"); | |
393 } | |
394 | |
395 result["Dicom"] = tmp; | |
396 | |
397 tmp.clear(); | |
398 for (size_t i = 0; i < GetInstancesCount(); i++) | |
399 { | |
400 std::string base = GetBasePath(ResourceType_Instance, GetInstanceId(i)); | |
401 for (size_t j = 0; j < GetFramesCount(i); j++) | |
402 { | |
403 tmp.append(base + "/frames/" + boost::lexical_cast<std::string>(j)); | |
404 } | |
405 } | |
406 | |
407 result["Slices"] = tmp; | |
408 } | |
215 } | 409 } |