Mercurial > hg > orthanc-stone
annotate Framework/Volumes/VolumeImage.cpp @ 44:027418bd1517
todo
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Wed, 19 Apr 2017 10:27:48 +0200 |
parents | 7207a407bcd8 |
children | 28956ed68280 |
rev | line source |
---|---|
0 | 1 /** |
2 * Stone of Orthanc | |
3 * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics | |
4 * Department, University Hospital of Liege, Belgium | |
40
7207a407bcd8
shared copyright with osimis
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
32
diff
changeset
|
5 * Copyright (C) 2017 Osimis, Belgium |
0 | 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 "VolumeImage.h" | |
35 | |
16 | 36 #include "../../Resources/Orthanc/Core/Logging.h" |
0 | 37 #include "../Layers/FrameRenderer.h" |
38 | |
39 namespace OrthancStone | |
40 { | |
41 void VolumeImage::StoreUpdateTime() | |
42 { | |
43 lastUpdate_ = MessagingToolbox::Timestamp(); | |
44 } | |
45 | |
46 | |
47 void VolumeImage::NotifyChange(bool force) | |
48 { | |
49 bool go = false; | |
50 | |
51 if (force) | |
52 { | |
53 go = true; | |
54 } | |
55 else | |
56 { | |
57 // Don't notify the observers more than 5 times per second | |
58 MessagingToolbox::Timestamp now; | |
59 go = (now.GetMillisecondsSince(lastUpdate_) > 200); | |
60 } | |
61 | |
62 if (go) | |
63 { | |
64 StoreUpdateTime(); | |
65 observers_.NotifyChange(this); | |
66 } | |
67 } | |
68 | |
69 | |
70 void VolumeImage::LoadThread(VolumeImage* that) | |
71 { | |
72 while (that->continue_) | |
73 { | |
74 bool complete = false; | |
75 bool done = that->policy_->DownloadStep(complete); | |
76 | |
77 if (complete) | |
78 { | |
79 that->loadingComplete_ = true; | |
80 } | |
81 | |
82 if (done) | |
83 { | |
84 break; | |
85 } | |
86 else | |
87 { | |
88 that->NotifyChange(false); | |
89 } | |
90 } | |
91 | |
92 that->NotifyChange(true); | |
93 } | |
94 | |
95 | |
96 VolumeImage::VolumeImage(ISeriesLoader* loader) : // Takes ownership | |
97 loader_(loader), | |
98 threads_(1), | |
99 started_(false), | |
100 continue_(false), | |
101 loadingComplete_(false) | |
102 { | |
103 if (loader == NULL) | |
104 { | |
105 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); | |
106 } | |
107 | |
108 const size_t depth = loader_->GetGeometry().GetSliceCount(); | |
109 | |
110 if (depth < 2) | |
111 { | |
112 // Empty or flat series | |
113 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); | |
114 } | |
115 | |
116 // TODO Check pixel spacing, slice thickness, and windowing are | |
117 // constant across slices | |
118 referenceDataset_.reset(loader->DownloadDicom(0)); | |
119 | |
120 double spacingZ; | |
121 | |
122 { | |
123 // Project the origin of the first and last slices onto the normal | |
124 const SliceGeometry& s1 = loader_->GetGeometry().GetSlice(0); | |
125 const SliceGeometry& s2 = loader_->GetGeometry().GetSlice(depth - 1); | |
126 const Vector& normal = loader_->GetGeometry().GetNormal(); | |
127 | |
128 double p1 = boost::numeric::ublas::inner_prod(s1.GetOrigin(), normal); | |
129 double p2 = boost::numeric::ublas::inner_prod(s2.GetOrigin(), normal); | |
130 | |
131 spacingZ = fabs(p2 - p1) / static_cast<double>(depth); | |
132 | |
133 // TODO Check that all slices are evenly distributed | |
134 } | |
135 | |
136 buffer_.reset(new ImageBuffer3D(loader_->GetPixelFormat(), | |
137 loader_->GetWidth(), | |
138 loader_->GetHeight(), | |
139 depth)); | |
140 buffer_->Clear(); | |
141 buffer_->SetAxialGeometry(loader_->GetGeometry().GetSlice(0)); | |
142 | |
143 double spacingX, spacingY; | |
32 | 144 GeometryToolbox::GetPixelSpacing(spacingX, spacingY, *referenceDataset_); |
0 | 145 buffer_->SetVoxelDimensions(spacingX, spacingY, spacingZ); |
146 | |
147 // These 3 values are only used to speed up the LayerFactory | |
148 axialGeometry_.reset(buffer_->GetGeometry(VolumeProjection_Axial)); | |
149 coronalGeometry_.reset(buffer_->GetGeometry(VolumeProjection_Coronal)); | |
150 sagittalGeometry_.reset(buffer_->GetGeometry(VolumeProjection_Sagittal)); | |
151 } | |
152 | |
153 | |
154 VolumeImage::~VolumeImage() | |
155 { | |
156 Stop(); | |
157 | |
158 for (size_t i = 0; i < threads_.size(); i++) | |
159 { | |
160 if (threads_[i] != NULL) | |
161 { | |
162 delete threads_[i]; | |
163 } | |
164 } | |
165 } | |
166 | |
167 | |
168 void VolumeImage::SetDownloadPolicy(IDownloadPolicy* policy) // Takes ownership | |
169 { | |
170 if (started_) | |
171 { | |
172 LOG(ERROR) << "Cannot change the number of threads after a call to Start()"; | |
173 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); | |
174 } | |
175 | |
176 policy_.reset(policy); | |
177 } | |
178 | |
179 | |
180 void VolumeImage::SetThreadCount(size_t count) | |
181 { | |
182 if (started_) | |
183 { | |
184 LOG(ERROR) << "Cannot change the number of threads after a call to Start()"; | |
185 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); | |
186 } | |
187 | |
188 if (count <= 0) | |
189 { | |
190 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); | |
191 } | |
192 | |
193 threads_.resize(count); | |
194 } | |
195 | |
196 | |
197 void VolumeImage::Register(IChangeObserver& observer) | |
198 { | |
199 observers_.Register(observer); | |
200 } | |
201 | |
202 | |
203 void VolumeImage::Unregister(IChangeObserver& observer) | |
204 { | |
205 observers_.Unregister(observer); | |
206 } | |
207 | |
208 | |
209 void VolumeImage::Start() | |
210 { | |
211 if (started_) | |
212 { | |
213 LOG(ERROR) << "Cannot call Start() twice"; | |
214 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); | |
215 } | |
216 | |
217 started_ = true; | |
218 StoreUpdateTime(); | |
219 | |
220 if (policy_.get() != NULL && | |
221 threads_.size() > 0) | |
222 { | |
223 continue_ = true; | |
224 policy_->Initialize(*buffer_, *loader_); | |
225 | |
226 for (size_t i = 0; i < threads_.size(); i++) | |
227 { | |
228 assert(threads_[i] == NULL); | |
229 threads_[i] = new boost::thread(LoadThread, this); | |
230 } | |
231 } | |
232 } | |
233 | |
234 | |
235 void VolumeImage::Stop() | |
236 { | |
237 if (!started_) | |
238 { | |
239 LOG(ERROR) << "Cannot call Stop() without calling Start() beforehand"; | |
240 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); | |
241 } | |
242 | |
243 if (continue_) | |
244 { | |
245 continue_ = false; | |
246 | |
247 for (size_t i = 0; i < threads_.size(); i++) | |
248 { | |
249 if (threads_[i]->joinable()) | |
250 { | |
251 threads_[i]->join(); | |
252 } | |
253 } | |
254 | |
255 assert(policy_.get() != NULL); | |
256 policy_->Finalize(); | |
257 } | |
258 } | |
259 | |
260 | |
261 ParallelSlices* VolumeImage::GetGeometry(VolumeProjection projection, | |
262 bool reverse) | |
263 { | |
264 std::auto_ptr<ParallelSlices> slices(buffer_->GetGeometry(projection)); | |
265 | |
266 if (reverse) | |
267 { | |
268 return slices->Reverse(); | |
269 } | |
270 else | |
271 { | |
272 return slices.release(); | |
273 } | |
274 } | |
275 | |
276 | |
277 bool VolumeImage::DetectProjection(VolumeProjection& projection, | |
278 bool& reverse, | |
279 const SliceGeometry& viewportSlice) | |
280 { | |
281 if (GeometryToolbox::IsParallelOrOpposite(reverse, viewportSlice.GetNormal(), axialGeometry_->GetNormal())) | |
282 { | |
283 projection = VolumeProjection_Axial; | |
284 return true; | |
285 } | |
286 else if (GeometryToolbox::IsParallelOrOpposite(reverse, viewportSlice.GetNormal(), sagittalGeometry_->GetNormal())) | |
287 { | |
288 projection = VolumeProjection_Sagittal; | |
289 return true; | |
290 } | |
291 else if (GeometryToolbox::IsParallelOrOpposite(reverse, viewportSlice.GetNormal(), coronalGeometry_->GetNormal())) | |
292 { | |
293 projection = VolumeProjection_Coronal; | |
294 return true; | |
295 } | |
296 else | |
297 { | |
298 return false; | |
299 } | |
300 } | |
301 | |
302 | |
303 const ParallelSlices& VolumeImage::GetGeometryInternal(VolumeProjection projection) | |
304 { | |
305 switch (projection) | |
306 { | |
307 case VolumeProjection_Axial: | |
308 return *axialGeometry_; | |
309 | |
310 case VolumeProjection_Sagittal: | |
311 return *sagittalGeometry_; | |
312 | |
313 case VolumeProjection_Coronal: | |
314 return *coronalGeometry_; | |
315 | |
316 default: | |
317 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); | |
318 } | |
319 } | |
320 | |
321 | |
322 bool VolumeImage::LayerFactory::GetExtent(double& x1, | |
323 double& y1, | |
324 double& x2, | |
325 double& y2, | |
326 const SliceGeometry& viewportSlice) | |
327 { | |
328 VolumeProjection projection; | |
329 bool reverse; | |
330 | |
331 if (that_.buffer_->GetWidth() == 0 || | |
332 that_.buffer_->GetHeight() == 0 || | |
333 that_.buffer_->GetDepth() == 0 || | |
334 !that_.DetectProjection(projection, reverse, viewportSlice)) | |
335 { | |
336 return false; | |
337 } | |
338 else | |
339 { | |
340 Vector spacing = that_.GetVoxelDimensions(projection); | |
341 | |
342 unsigned int width, height; | |
343 that_.buffer_->GetSliceSize(width, height, projection); | |
344 | |
345 // As the slices of the volumic image are arranged in a box, | |
346 // we only consider one single reference slice (the one with index 0). | |
347 const SliceGeometry& volumeSlice = that_.GetGeometryInternal(projection).GetSlice(0); | |
348 | |
349 return FrameRenderer::ComputeFrameExtent(x1, y1, x2, y2, | |
350 viewportSlice, volumeSlice, | |
351 width, height, | |
352 spacing[0], spacing[1]); | |
353 } | |
354 } | |
355 | |
356 | |
357 ILayerRenderer* VolumeImage::LayerFactory::CreateLayerRenderer(const SliceGeometry& viewportSlice) | |
358 { | |
359 VolumeProjection projection; | |
360 bool reverse; | |
361 | |
362 if (that_.buffer_->GetWidth() == 0 || | |
363 that_.buffer_->GetHeight() == 0 || | |
364 that_.buffer_->GetDepth() == 0 || | |
365 !that_.DetectProjection(projection, reverse, viewportSlice)) | |
366 { | |
367 return NULL; | |
368 } | |
369 | |
370 const ParallelSlices& geometry = that_.GetGeometryInternal(projection); | |
371 | |
372 size_t closest; | |
373 double distance; | |
374 | |
375 const Vector spacing = that_.GetVoxelDimensions(projection); | |
376 const double sliceThickness = spacing[2]; | |
377 | |
378 if (geometry.ComputeClosestSlice(closest, distance, viewportSlice.GetOrigin()) && | |
379 distance <= sliceThickness / 2.0) | |
380 { | |
381 bool isFullQuality; | |
382 | |
383 if (projection == VolumeProjection_Axial && | |
384 that_.policy_.get() != NULL) | |
385 { | |
386 isFullQuality = that_.policy_->IsFullQualityAxial(closest); | |
387 } | |
388 else | |
389 { | |
390 isFullQuality = that_.IsLoadingComplete(); | |
391 } | |
392 | |
393 std::auto_ptr<Orthanc::Image> frame; | |
394 SliceGeometry frameSlice = geometry.GetSlice(closest); | |
395 | |
396 { | |
397 ImageBuffer3D::SliceReader reader(*that_.buffer_, projection, closest); | |
398 frame.reset(Orthanc::Image::Clone(reader.GetAccessor())); | |
399 } | |
400 | |
401 return FrameRenderer::CreateRenderer(frame.release(), | |
402 viewportSlice, | |
403 frameSlice, | |
404 *that_.referenceDataset_, | |
405 spacing[0], spacing[1], | |
406 isFullQuality); | |
407 } | |
408 else | |
409 { | |
410 return NULL; | |
411 } | |
412 } | |
413 } |