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