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