Mercurial > hg > orthanc-stone
comparison OrthancStone/Sources/Deprecated/dev.h @ 1512:244ad1e4e76a
reorganization of folders
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Tue, 07 Jul 2020 16:21:02 +0200 |
parents | Framework/Deprecated/dev.h@30deba7bc8e2 |
children |
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 #pragma once | |
23 | |
24 #include "Layers/FrameRenderer.h" | |
25 #include "Layers/LineLayerRenderer.h" | |
26 #include "Layers/SliceOutlineRenderer.h" | |
27 #include "Toolbox/DownloadStack.h" | |
28 #include "Toolbox/GeometryToolbox.h" | |
29 #include "Toolbox/OrthancSlicesLoader.h" | |
30 #include "Volumes/ISlicedVolume.h" | |
31 #include "Volumes/ImageBuffer3D.h" | |
32 #include "Widgets/SliceViewerWidget.h" | |
33 | |
34 #include <Logging.h> | |
35 #include <Images/ImageProcessing.h> | |
36 #include <OrthancException.h> | |
37 | |
38 #include <boost/math/special_functions/round.hpp> | |
39 | |
40 | |
41 namespace Deprecated | |
42 { | |
43 // TODO: Handle errors while loading | |
44 class OrthancVolumeImage : | |
45 public ISlicedVolume, | |
46 public OrthancStone::IObserver | |
47 { | |
48 private: | |
49 OrthancSlicesLoader loader_; | |
50 std::unique_ptr<OrthancStone::ImageBuffer3D> image_; | |
51 std::unique_ptr<DownloadStack> downloadStack_; | |
52 bool computeRange_; | |
53 size_t pendingSlices_; | |
54 | |
55 void ScheduleSliceDownload() | |
56 { | |
57 assert(downloadStack_.get() != NULL); | |
58 | |
59 unsigned int slice; | |
60 if (downloadStack_->Pop(slice)) | |
61 { | |
62 loader_.ScheduleLoadSliceImage(slice, OrthancStone::SliceImageQuality_Jpeg90); | |
63 } | |
64 } | |
65 | |
66 | |
67 static bool IsCompatible(const Slice& a, | |
68 const Slice& b) | |
69 { | |
70 if (!OrthancStone::GeometryToolbox::IsParallel(a.GetGeometry().GetNormal(), | |
71 b.GetGeometry().GetNormal())) | |
72 { | |
73 LOG(ERROR) << "A slice in the volume image is not parallel to the others."; | |
74 return false; | |
75 } | |
76 | |
77 if (a.GetConverter().GetExpectedPixelFormat() != b.GetConverter().GetExpectedPixelFormat()) | |
78 { | |
79 LOG(ERROR) << "The pixel format changes across the slices of the volume image."; | |
80 return false; | |
81 } | |
82 | |
83 if (a.GetWidth() != b.GetWidth() || | |
84 a.GetHeight() != b.GetHeight()) | |
85 { | |
86 LOG(ERROR) << "The slices dimensions (width/height) are varying throughout the volume image"; | |
87 return false; | |
88 } | |
89 | |
90 if (!OrthancStone::LinearAlgebra::IsNear(a.GetPixelSpacingX(), b.GetPixelSpacingX()) || | |
91 !OrthancStone::LinearAlgebra::IsNear(a.GetPixelSpacingY(), b.GetPixelSpacingY())) | |
92 { | |
93 LOG(ERROR) << "The pixel spacing of the slices change across the volume image"; | |
94 return false; | |
95 } | |
96 | |
97 return true; | |
98 } | |
99 | |
100 | |
101 static double GetDistance(const Slice& a, | |
102 const Slice& b) | |
103 { | |
104 return fabs(a.GetGeometry().ProjectAlongNormal(a.GetGeometry().GetOrigin()) - | |
105 a.GetGeometry().ProjectAlongNormal(b.GetGeometry().GetOrigin())); | |
106 } | |
107 | |
108 | |
109 void OnSliceGeometryReady(const OrthancSlicesLoader::SliceGeometryReadyMessage& message) | |
110 { | |
111 assert(&message.GetOrigin() == &loader_); | |
112 | |
113 if (loader_.GetSlicesCount() == 0) | |
114 { | |
115 LOG(ERROR) << "Empty volume image"; | |
116 BroadcastMessage(ISlicedVolume::GeometryErrorMessage(*this)); | |
117 return; | |
118 } | |
119 | |
120 for (size_t i = 1; i < loader_.GetSlicesCount(); i++) | |
121 { | |
122 if (!IsCompatible(loader_.GetSlice(0), loader_.GetSlice(i))) | |
123 { | |
124 BroadcastMessage(ISlicedVolume::GeometryErrorMessage(*this)); | |
125 return; | |
126 } | |
127 } | |
128 | |
129 double spacingZ; | |
130 | |
131 if (loader_.GetSlicesCount() > 1) | |
132 { | |
133 spacingZ = GetDistance(loader_.GetSlice(0), loader_.GetSlice(1)); | |
134 } | |
135 else | |
136 { | |
137 // This is a volume with one single slice: Choose a dummy | |
138 // z-dimension for voxels | |
139 spacingZ = 1; | |
140 } | |
141 | |
142 for (size_t i = 1; i < loader_.GetSlicesCount(); i++) | |
143 { | |
144 if (!OrthancStone::LinearAlgebra::IsNear(spacingZ, GetDistance(loader_.GetSlice(i - 1), loader_.GetSlice(i)), | |
145 0.001 /* this is expressed in mm */)) | |
146 { | |
147 LOG(ERROR) << "The distance between successive slices is not constant in a volume image"; | |
148 BroadcastMessage(ISlicedVolume::GeometryErrorMessage(*this)); | |
149 return; | |
150 } | |
151 } | |
152 | |
153 unsigned int width = loader_.GetSlice(0).GetWidth(); | |
154 unsigned int height = loader_.GetSlice(0).GetHeight(); | |
155 Orthanc::PixelFormat format = loader_.GetSlice(0).GetConverter().GetExpectedPixelFormat(); | |
156 LOG(INFO) << "Creating a volume image of size " << width << "x" << height | |
157 << "x" << loader_.GetSlicesCount() << " in " << Orthanc::EnumerationToString(format); | |
158 | |
159 image_.reset(new OrthancStone::ImageBuffer3D(format, width, height, static_cast<unsigned int>(loader_.GetSlicesCount()), computeRange_)); | |
160 image_->GetGeometry().SetAxialGeometry(loader_.GetSlice(0).GetGeometry()); | |
161 image_->GetGeometry().SetVoxelDimensions(loader_.GetSlice(0).GetPixelSpacingX(), | |
162 loader_.GetSlice(0).GetPixelSpacingY(), spacingZ); | |
163 image_->Clear(); | |
164 | |
165 downloadStack_.reset(new DownloadStack(static_cast<unsigned int>(loader_.GetSlicesCount()))); | |
166 pendingSlices_ = loader_.GetSlicesCount(); | |
167 | |
168 for (unsigned int i = 0; i < 4; i++) // Limit to 4 simultaneous downloads | |
169 { | |
170 ScheduleSliceDownload(); | |
171 } | |
172 | |
173 // TODO Check the DicomFrameConverter are constant | |
174 | |
175 BroadcastMessage(ISlicedVolume::GeometryReadyMessage(*this)); | |
176 } | |
177 | |
178 | |
179 void OnSliceGeometryError(const OrthancSlicesLoader::SliceGeometryErrorMessage& message) | |
180 { | |
181 assert(&message.GetOrigin() == &loader_); | |
182 | |
183 LOG(ERROR) << "Unable to download a volume image"; | |
184 BroadcastMessage(ISlicedVolume::GeometryErrorMessage(*this)); | |
185 } | |
186 | |
187 | |
188 void OnSliceImageReady(const OrthancSlicesLoader::SliceImageReadyMessage& message) | |
189 { | |
190 assert(&message.GetOrigin() == &loader_); | |
191 | |
192 { | |
193 OrthancStone::ImageBuffer3D::SliceWriter writer(*image_, OrthancStone::VolumeProjection_Axial, message.GetSliceIndex()); | |
194 Orthanc::ImageProcessing::Copy(writer.GetAccessor(), message.GetImage()); | |
195 } | |
196 | |
197 BroadcastMessage(ISlicedVolume::SliceContentChangedMessage | |
198 (*this, message.GetSliceIndex(), message.GetSlice())); | |
199 | |
200 if (pendingSlices_ == 1) | |
201 { | |
202 BroadcastMessage(ISlicedVolume::VolumeReadyMessage(*this)); | |
203 pendingSlices_ = 0; | |
204 } | |
205 else if (pendingSlices_ > 1) | |
206 { | |
207 pendingSlices_ -= 1; | |
208 } | |
209 | |
210 ScheduleSliceDownload(); | |
211 } | |
212 | |
213 | |
214 void OnSliceImageError(const OrthancSlicesLoader::SliceImageErrorMessage& message) | |
215 { | |
216 assert(&message.GetOrigin() == &loader_); | |
217 | |
218 LOG(ERROR) << "Cannot download slice " << message.GetSliceIndex() << " in a volume image"; | |
219 ScheduleSliceDownload(); | |
220 } | |
221 | |
222 | |
223 public: | |
224 OrthancVolumeImage(OrthancStone::MessageBroker& broker, | |
225 OrthancApiClient& orthanc, | |
226 bool computeRange) : | |
227 ISlicedVolume(broker), | |
228 IObserver(broker), | |
229 loader_(broker, orthanc), | |
230 computeRange_(computeRange), | |
231 pendingSlices_(0) | |
232 { | |
233 loader_.RegisterObserverCallback( | |
234 new OrthancStone::Callable<OrthancVolumeImage, OrthancSlicesLoader::SliceGeometryReadyMessage> | |
235 (*this, &OrthancVolumeImage::OnSliceGeometryReady)); | |
236 | |
237 loader_.RegisterObserverCallback( | |
238 new OrthancStone::Callable<OrthancVolumeImage, OrthancSlicesLoader::SliceGeometryErrorMessage> | |
239 (*this, &OrthancVolumeImage::OnSliceGeometryError)); | |
240 | |
241 loader_.RegisterObserverCallback( | |
242 new OrthancStone::Callable<OrthancVolumeImage, OrthancSlicesLoader::SliceImageReadyMessage> | |
243 (*this, &OrthancVolumeImage::OnSliceImageReady)); | |
244 | |
245 loader_.RegisterObserverCallback( | |
246 new OrthancStone::Callable<OrthancVolumeImage, OrthancSlicesLoader::SliceImageErrorMessage> | |
247 (*this, &OrthancVolumeImage::OnSliceImageError)); | |
248 } | |
249 | |
250 void ScheduleLoadSeries(const std::string& seriesId) | |
251 { | |
252 loader_.ScheduleLoadSeries(seriesId); | |
253 } | |
254 | |
255 void ScheduleLoadInstance(const std::string& instanceId) | |
256 { | |
257 loader_.ScheduleLoadInstance(instanceId); | |
258 } | |
259 | |
260 void ScheduleLoadFrame(const std::string& instanceId, | |
261 unsigned int frame) | |
262 { | |
263 loader_.ScheduleLoadFrame(instanceId, frame); | |
264 } | |
265 | |
266 virtual size_t GetSlicesCount() const | |
267 { | |
268 return loader_.GetSlicesCount(); | |
269 } | |
270 | |
271 virtual const Slice& GetSlice(size_t index) const | |
272 { | |
273 return loader_.GetSlice(index); | |
274 } | |
275 | |
276 OrthancStone::ImageBuffer3D& GetImage() const | |
277 { | |
278 if (image_.get() == NULL) | |
279 { | |
280 // The geometry is not ready yet | |
281 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); | |
282 } | |
283 else | |
284 { | |
285 return *image_; | |
286 } | |
287 } | |
288 | |
289 bool FitWindowingToRange(RenderStyle& style, | |
290 const DicomFrameConverter& converter) const | |
291 { | |
292 if (image_.get() == NULL) | |
293 { | |
294 return false; | |
295 } | |
296 else | |
297 { | |
298 return image_->FitWindowingToRange(style, converter); | |
299 } | |
300 } | |
301 }; | |
302 | |
303 | |
304 class VolumeImageGeometry | |
305 { | |
306 private: | |
307 unsigned int width_; | |
308 unsigned int height_; | |
309 size_t depth_; | |
310 double pixelSpacingX_; | |
311 double pixelSpacingY_; | |
312 double sliceThickness_; | |
313 OrthancStone::CoordinateSystem3D reference_; | |
314 DicomFrameConverter converter_; | |
315 | |
316 double ComputeAxialThickness(const OrthancVolumeImage& volume) const | |
317 { | |
318 double thickness; | |
319 | |
320 size_t n = volume.GetSlicesCount(); | |
321 if (n > 1) | |
322 { | |
323 const Slice& a = volume.GetSlice(0); | |
324 const Slice& b = volume.GetSlice(n - 1); | |
325 thickness = ((reference_.ProjectAlongNormal(b.GetGeometry().GetOrigin()) - | |
326 reference_.ProjectAlongNormal(a.GetGeometry().GetOrigin())) / | |
327 (static_cast<double>(n) - 1.0)); | |
328 } | |
329 else | |
330 { | |
331 thickness = volume.GetSlice(0).GetThickness(); | |
332 } | |
333 | |
334 if (thickness <= 0) | |
335 { | |
336 // The slices should have been sorted with increasing Z | |
337 // (along the normal) by the OrthancSlicesLoader | |
338 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); | |
339 } | |
340 else | |
341 { | |
342 return thickness; | |
343 } | |
344 } | |
345 | |
346 void SetupAxial(const OrthancVolumeImage& volume) | |
347 { | |
348 const Slice& axial = volume.GetSlice(0); | |
349 | |
350 width_ = axial.GetWidth(); | |
351 height_ = axial.GetHeight(); | |
352 depth_ = volume.GetSlicesCount(); | |
353 | |
354 pixelSpacingX_ = axial.GetPixelSpacingX(); | |
355 pixelSpacingY_ = axial.GetPixelSpacingY(); | |
356 sliceThickness_ = ComputeAxialThickness(volume); | |
357 | |
358 reference_ = axial.GetGeometry(); | |
359 } | |
360 | |
361 void SetupCoronal(const OrthancVolumeImage& volume) | |
362 { | |
363 const Slice& axial = volume.GetSlice(0); | |
364 double axialThickness = ComputeAxialThickness(volume); | |
365 | |
366 width_ = axial.GetWidth(); | |
367 height_ = static_cast<unsigned int>(volume.GetSlicesCount()); | |
368 depth_ = axial.GetHeight(); | |
369 | |
370 pixelSpacingX_ = axial.GetPixelSpacingX(); | |
371 pixelSpacingY_ = axialThickness; | |
372 sliceThickness_ = axial.GetPixelSpacingY(); | |
373 | |
374 OrthancStone::Vector origin = axial.GetGeometry().GetOrigin(); | |
375 origin += (static_cast<double>(volume.GetSlicesCount() - 1) * | |
376 axialThickness * axial.GetGeometry().GetNormal()); | |
377 | |
378 reference_ = OrthancStone::CoordinateSystem3D(origin, | |
379 axial.GetGeometry().GetAxisX(), | |
380 - axial.GetGeometry().GetNormal()); | |
381 } | |
382 | |
383 void SetupSagittal(const OrthancVolumeImage& volume) | |
384 { | |
385 const Slice& axial = volume.GetSlice(0); | |
386 double axialThickness = ComputeAxialThickness(volume); | |
387 | |
388 width_ = axial.GetHeight(); | |
389 height_ = static_cast<unsigned int>(volume.GetSlicesCount()); | |
390 depth_ = axial.GetWidth(); | |
391 | |
392 pixelSpacingX_ = axial.GetPixelSpacingY(); | |
393 pixelSpacingY_ = axialThickness; | |
394 sliceThickness_ = axial.GetPixelSpacingX(); | |
395 | |
396 OrthancStone::Vector origin = axial.GetGeometry().GetOrigin(); | |
397 origin += (static_cast<double>(volume.GetSlicesCount() - 1) * | |
398 axialThickness * axial.GetGeometry().GetNormal()); | |
399 | |
400 reference_ = OrthancStone::CoordinateSystem3D(origin, | |
401 axial.GetGeometry().GetAxisY(), | |
402 axial.GetGeometry().GetNormal()); | |
403 } | |
404 | |
405 public: | |
406 VolumeImageGeometry(const OrthancVolumeImage& volume, | |
407 OrthancStone::VolumeProjection projection) | |
408 { | |
409 if (volume.GetSlicesCount() == 0) | |
410 { | |
411 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); | |
412 } | |
413 | |
414 converter_ = volume.GetSlice(0).GetConverter(); | |
415 | |
416 switch (projection) | |
417 { | |
418 case OrthancStone::VolumeProjection_Axial: | |
419 SetupAxial(volume); | |
420 break; | |
421 | |
422 case OrthancStone::VolumeProjection_Coronal: | |
423 SetupCoronal(volume); | |
424 break; | |
425 | |
426 case OrthancStone::VolumeProjection_Sagittal: | |
427 SetupSagittal(volume); | |
428 break; | |
429 | |
430 default: | |
431 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); | |
432 } | |
433 } | |
434 | |
435 size_t GetSlicesCount() const | |
436 { | |
437 return depth_; | |
438 } | |
439 | |
440 const OrthancStone::Vector& GetNormal() const | |
441 { | |
442 return reference_.GetNormal(); | |
443 } | |
444 | |
445 bool LookupSlice(size_t& index, | |
446 const OrthancStone::CoordinateSystem3D& slice) const | |
447 { | |
448 bool opposite; | |
449 if (!OrthancStone::GeometryToolbox::IsParallelOrOpposite(opposite, | |
450 reference_.GetNormal(), | |
451 slice.GetNormal())) | |
452 { | |
453 return false; | |
454 } | |
455 | |
456 double z = (reference_.ProjectAlongNormal(slice.GetOrigin()) - | |
457 reference_.ProjectAlongNormal(reference_.GetOrigin())) / sliceThickness_; | |
458 | |
459 int s = static_cast<int>(boost::math::iround(z)); | |
460 | |
461 if (s < 0 || | |
462 s >= static_cast<int>(depth_)) | |
463 { | |
464 return false; | |
465 } | |
466 else | |
467 { | |
468 index = static_cast<size_t>(s); | |
469 return true; | |
470 } | |
471 } | |
472 | |
473 Slice* GetSlice(size_t slice) const | |
474 { | |
475 if (slice >= depth_) | |
476 { | |
477 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); | |
478 } | |
479 else | |
480 { | |
481 OrthancStone::CoordinateSystem3D origin(reference_.GetOrigin() + | |
482 static_cast<double>(slice) * sliceThickness_ * reference_.GetNormal(), | |
483 reference_.GetAxisX(), | |
484 reference_.GetAxisY()); | |
485 | |
486 return new Slice(origin, pixelSpacingX_, pixelSpacingY_, sliceThickness_, | |
487 width_, height_, converter_); | |
488 } | |
489 } | |
490 }; | |
491 | |
492 | |
493 | |
494 class VolumeImageMPRSlicer : | |
495 public IVolumeSlicer, | |
496 public OrthancStone::IObserver | |
497 { | |
498 private: | |
499 class RendererFactory : public LayerReadyMessage::IRendererFactory | |
500 { | |
501 private: | |
502 const Orthanc::ImageAccessor& frame_; | |
503 const Slice& slice_; | |
504 bool isFullQuality_; | |
505 | |
506 public: | |
507 RendererFactory(const Orthanc::ImageAccessor& frame, | |
508 const Slice& slice, | |
509 bool isFullQuality) : | |
510 frame_(frame), | |
511 slice_(slice), | |
512 isFullQuality_(isFullQuality) | |
513 { | |
514 } | |
515 | |
516 virtual ILayerRenderer* CreateRenderer() const | |
517 { | |
518 return FrameRenderer::CreateRenderer(frame_, slice_, isFullQuality_); | |
519 } | |
520 }; | |
521 | |
522 | |
523 OrthancVolumeImage& volume_; | |
524 std::unique_ptr<VolumeImageGeometry> axialGeometry_; | |
525 std::unique_ptr<VolumeImageGeometry> coronalGeometry_; | |
526 std::unique_ptr<VolumeImageGeometry> sagittalGeometry_; | |
527 | |
528 | |
529 bool IsGeometryReady() const | |
530 { | |
531 return axialGeometry_.get() != NULL; | |
532 } | |
533 | |
534 void OnGeometryReady(const ISlicedVolume::GeometryReadyMessage& message) | |
535 { | |
536 assert(&message.GetOrigin() == &volume_); | |
537 | |
538 // These 3 values are only used to speed up the IVolumeSlicer | |
539 axialGeometry_.reset(new VolumeImageGeometry(volume_, OrthancStone::VolumeProjection_Axial)); | |
540 coronalGeometry_.reset(new VolumeImageGeometry(volume_, OrthancStone::VolumeProjection_Coronal)); | |
541 sagittalGeometry_.reset(new VolumeImageGeometry(volume_, OrthancStone::VolumeProjection_Sagittal)); | |
542 | |
543 BroadcastMessage(IVolumeSlicer::GeometryReadyMessage(*this)); | |
544 } | |
545 | |
546 void OnGeometryError(const ISlicedVolume::GeometryErrorMessage& message) | |
547 { | |
548 assert(&message.GetOrigin() == &volume_); | |
549 | |
550 BroadcastMessage(IVolumeSlicer::GeometryErrorMessage(*this)); | |
551 } | |
552 | |
553 void OnContentChanged(const ISlicedVolume::ContentChangedMessage& message) | |
554 { | |
555 assert(&message.GetOrigin() == &volume_); | |
556 | |
557 BroadcastMessage(IVolumeSlicer::ContentChangedMessage(*this)); | |
558 } | |
559 | |
560 void OnSliceContentChanged(const ISlicedVolume::SliceContentChangedMessage& message) | |
561 { | |
562 assert(&message.GetOrigin() == &volume_); | |
563 | |
564 //IVolumeSlicer::OnSliceContentChange(slice); | |
565 | |
566 // TODO Improve this? | |
567 BroadcastMessage(IVolumeSlicer::ContentChangedMessage(*this)); | |
568 } | |
569 | |
570 const VolumeImageGeometry& GetProjectionGeometry(OrthancStone::VolumeProjection projection) | |
571 { | |
572 if (!IsGeometryReady()) | |
573 { | |
574 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); | |
575 } | |
576 | |
577 switch (projection) | |
578 { | |
579 case OrthancStone::VolumeProjection_Axial: | |
580 return *axialGeometry_; | |
581 | |
582 case OrthancStone::VolumeProjection_Sagittal: | |
583 return *sagittalGeometry_; | |
584 | |
585 case OrthancStone::VolumeProjection_Coronal: | |
586 return *coronalGeometry_; | |
587 | |
588 default: | |
589 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); | |
590 } | |
591 } | |
592 | |
593 | |
594 bool DetectProjection(OrthancStone::VolumeProjection& projection, | |
595 const OrthancStone::CoordinateSystem3D& viewportSlice) | |
596 { | |
597 bool isOpposite; // Ignored | |
598 | |
599 if (OrthancStone::GeometryToolbox::IsParallelOrOpposite(isOpposite, | |
600 viewportSlice.GetNormal(), | |
601 axialGeometry_->GetNormal())) | |
602 { | |
603 projection = OrthancStone::VolumeProjection_Axial; | |
604 return true; | |
605 } | |
606 else if (OrthancStone::GeometryToolbox::IsParallelOrOpposite(isOpposite, | |
607 viewportSlice.GetNormal(), | |
608 sagittalGeometry_->GetNormal())) | |
609 { | |
610 projection = OrthancStone::VolumeProjection_Sagittal; | |
611 return true; | |
612 } | |
613 else if (OrthancStone::GeometryToolbox::IsParallelOrOpposite(isOpposite, | |
614 viewportSlice.GetNormal(), | |
615 coronalGeometry_->GetNormal())) | |
616 { | |
617 projection = OrthancStone::VolumeProjection_Coronal; | |
618 return true; | |
619 } | |
620 else | |
621 { | |
622 return false; | |
623 } | |
624 } | |
625 | |
626 | |
627 public: | |
628 VolumeImageMPRSlicer(OrthancStone::MessageBroker& broker, | |
629 OrthancVolumeImage& volume) : | |
630 IVolumeSlicer(broker), | |
631 IObserver(broker), | |
632 volume_(volume) | |
633 { | |
634 volume_.RegisterObserverCallback( | |
635 new OrthancStone::Callable<VolumeImageMPRSlicer, ISlicedVolume::GeometryReadyMessage> | |
636 (*this, &VolumeImageMPRSlicer::OnGeometryReady)); | |
637 | |
638 volume_.RegisterObserverCallback( | |
639 new OrthancStone::Callable<VolumeImageMPRSlicer, ISlicedVolume::GeometryErrorMessage> | |
640 (*this, &VolumeImageMPRSlicer::OnGeometryError)); | |
641 | |
642 volume_.RegisterObserverCallback( | |
643 new OrthancStone::Callable<VolumeImageMPRSlicer, ISlicedVolume::ContentChangedMessage> | |
644 (*this, &VolumeImageMPRSlicer::OnContentChanged)); | |
645 | |
646 volume_.RegisterObserverCallback( | |
647 new OrthancStone::Callable<VolumeImageMPRSlicer, ISlicedVolume::SliceContentChangedMessage> | |
648 (*this, &VolumeImageMPRSlicer::OnSliceContentChanged)); | |
649 } | |
650 | |
651 virtual bool GetExtent(std::vector<OrthancStone::Vector>& points, | |
652 const OrthancStone::CoordinateSystem3D& viewportSlice) ORTHANC_OVERRIDE | |
653 { | |
654 OrthancStone::VolumeProjection projection; | |
655 | |
656 if (!IsGeometryReady() || | |
657 !DetectProjection(projection, viewportSlice)) | |
658 { | |
659 return false; | |
660 } | |
661 else | |
662 { | |
663 // As the slices of the volumic image are arranged in a box, | |
664 // we only consider one single reference slice (the one with index 0). | |
665 std::unique_ptr<Slice> slice(GetProjectionGeometry(projection).GetSlice(0)); | |
666 slice->GetExtent(points); | |
667 | |
668 return true; | |
669 } | |
670 } | |
671 | |
672 virtual void ScheduleLayerCreation(const OrthancStone::CoordinateSystem3D& viewportSlice) ORTHANC_OVERRIDE | |
673 { | |
674 OrthancStone::VolumeProjection projection; | |
675 | |
676 if (IsGeometryReady() && | |
677 DetectProjection(projection, viewportSlice)) | |
678 { | |
679 const VolumeImageGeometry& geometry = GetProjectionGeometry(projection); | |
680 | |
681 size_t closest; | |
682 | |
683 if (geometry.LookupSlice(closest, viewportSlice)) | |
684 { | |
685 bool isFullQuality = true; // TODO | |
686 | |
687 std::unique_ptr<Orthanc::Image> frame; | |
688 | |
689 { | |
690 OrthancStone::ImageBuffer3D::SliceReader reader(volume_.GetImage(), projection, static_cast<unsigned int>(closest)); | |
691 | |
692 // TODO Transfer ownership if non-axial, to avoid memcpy | |
693 frame.reset(Orthanc::Image::Clone(reader.GetAccessor())); | |
694 } | |
695 | |
696 std::unique_ptr<Slice> slice(geometry.GetSlice(closest)); | |
697 | |
698 RendererFactory factory(*frame, *slice, isFullQuality); | |
699 | |
700 BroadcastMessage(IVolumeSlicer::LayerReadyMessage(*this, factory, slice->GetGeometry())); | |
701 return; | |
702 } | |
703 } | |
704 | |
705 // Error | |
706 OrthancStone::CoordinateSystem3D slice; | |
707 BroadcastMessage(IVolumeSlicer::LayerErrorMessage(*this, slice)); | |
708 } | |
709 }; | |
710 | |
711 | |
712 class VolumeImageInteractor : | |
713 public IWorldSceneInteractor, | |
714 public OrthancStone::IObserver | |
715 { | |
716 private: | |
717 SliceViewerWidget& widget_; | |
718 OrthancStone::VolumeProjection projection_; | |
719 std::unique_ptr<VolumeImageGeometry> slices_; | |
720 size_t slice_; | |
721 | |
722 protected: | |
723 void OnGeometryReady(const ISlicedVolume::GeometryReadyMessage& message) | |
724 { | |
725 if (slices_.get() == NULL) | |
726 { | |
727 const OrthancVolumeImage& image = | |
728 dynamic_cast<const OrthancVolumeImage&>(message.GetOrigin()); | |
729 | |
730 slices_.reset(new VolumeImageGeometry(image, projection_)); | |
731 SetSlice(slices_->GetSlicesCount() / 2); | |
732 | |
733 widget_.FitContent(); | |
734 } | |
735 } | |
736 | |
737 virtual IWorldSceneMouseTracker* CreateMouseTracker(WorldSceneWidget& widget, | |
738 const ViewportGeometry& view, | |
739 OrthancStone::MouseButton button, | |
740 OrthancStone::KeyboardModifiers modifiers, | |
741 int viewportX, | |
742 int viewportY, | |
743 double x, | |
744 double y, | |
745 IStatusBar* statusBar, | |
746 const std::vector<Touch>& touches) ORTHANC_OVERRIDE | |
747 { | |
748 return NULL; | |
749 } | |
750 | |
751 virtual void MouseOver(OrthancStone::CairoContext& context, | |
752 WorldSceneWidget& widget, | |
753 const ViewportGeometry& view, | |
754 double x, | |
755 double y, | |
756 IStatusBar* statusBar) ORTHANC_OVERRIDE | |
757 { | |
758 } | |
759 | |
760 virtual void MouseWheel(WorldSceneWidget& widget, | |
761 OrthancStone::MouseWheelDirection direction, | |
762 OrthancStone::KeyboardModifiers modifiers, | |
763 IStatusBar* statusBar) ORTHANC_OVERRIDE | |
764 { | |
765 int scale = (modifiers & OrthancStone::KeyboardModifiers_Control ? 10 : 1); | |
766 | |
767 switch (direction) | |
768 { | |
769 case OrthancStone::MouseWheelDirection_Up: | |
770 OffsetSlice(-scale); | |
771 break; | |
772 | |
773 case OrthancStone::MouseWheelDirection_Down: | |
774 OffsetSlice(scale); | |
775 break; | |
776 | |
777 default: | |
778 break; | |
779 } | |
780 } | |
781 | |
782 virtual void KeyPressed(WorldSceneWidget& widget, | |
783 OrthancStone::KeyboardKeys key, | |
784 char keyChar, | |
785 OrthancStone::KeyboardModifiers modifiers, | |
786 IStatusBar* statusBar) ORTHANC_OVERRIDE | |
787 { | |
788 switch (keyChar) | |
789 { | |
790 case 's': | |
791 widget.FitContent(); | |
792 break; | |
793 | |
794 default: | |
795 break; | |
796 } | |
797 } | |
798 | |
799 public: | |
800 VolumeImageInteractor(OrthancStone::MessageBroker& broker, | |
801 OrthancVolumeImage& volume, | |
802 SliceViewerWidget& widget, | |
803 OrthancStone::VolumeProjection projection) : | |
804 IObserver(broker), | |
805 widget_(widget), | |
806 projection_(projection) | |
807 { | |
808 widget.SetInteractor(*this); | |
809 | |
810 volume.RegisterObserverCallback( | |
811 new OrthancStone::Callable<VolumeImageInteractor, ISlicedVolume::GeometryReadyMessage> | |
812 (*this, &VolumeImageInteractor::OnGeometryReady)); | |
813 } | |
814 | |
815 bool IsGeometryReady() const | |
816 { | |
817 return slices_.get() != NULL; | |
818 } | |
819 | |
820 size_t GetSlicesCount() const | |
821 { | |
822 if (slices_.get() == NULL) | |
823 { | |
824 return 0; | |
825 } | |
826 else | |
827 { | |
828 return slices_->GetSlicesCount(); | |
829 } | |
830 } | |
831 | |
832 void OffsetSlice(int offset) | |
833 { | |
834 if (slices_.get() != NULL) | |
835 { | |
836 int slice = static_cast<int>(slice_) + offset; | |
837 | |
838 if (slice < 0) | |
839 { | |
840 slice = 0; | |
841 } | |
842 | |
843 if (slice >= static_cast<int>(slices_->GetSlicesCount())) | |
844 { | |
845 slice = static_cast<unsigned int>(slices_->GetSlicesCount()) - 1; | |
846 } | |
847 | |
848 if (slice != static_cast<int>(slice_)) | |
849 { | |
850 SetSlice(slice); | |
851 } | |
852 } | |
853 } | |
854 | |
855 void SetSlice(size_t slice) | |
856 { | |
857 if (slices_.get() != NULL) | |
858 { | |
859 slice_ = slice; | |
860 | |
861 std::unique_ptr<Slice> tmp(slices_->GetSlice(slice_)); | |
862 widget_.SetSlice(tmp->GetGeometry()); | |
863 } | |
864 } | |
865 }; | |
866 | |
867 | |
868 | |
869 class ReferenceLineSource : public IVolumeSlicer | |
870 { | |
871 private: | |
872 class RendererFactory : public LayerReadyMessage::IRendererFactory | |
873 { | |
874 private: | |
875 double x1_; | |
876 double y1_; | |
877 double x2_; | |
878 double y2_; | |
879 const OrthancStone::CoordinateSystem3D& slice_; | |
880 | |
881 public: | |
882 RendererFactory(double x1, | |
883 double y1, | |
884 double x2, | |
885 double y2, | |
886 const OrthancStone::CoordinateSystem3D& slice) : | |
887 x1_(x1), | |
888 y1_(y1), | |
889 x2_(x2), | |
890 y2_(y2), | |
891 slice_(slice) | |
892 { | |
893 } | |
894 | |
895 virtual ILayerRenderer* CreateRenderer() const | |
896 { | |
897 return new LineLayerRenderer(x1_, y1_, x2_, y2_, slice_); | |
898 } | |
899 }; | |
900 | |
901 SliceViewerWidget& otherPlane_; | |
902 | |
903 public: | |
904 ReferenceLineSource(OrthancStone::MessageBroker& broker, | |
905 SliceViewerWidget& otherPlane) : | |
906 IVolumeSlicer(broker), | |
907 otherPlane_(otherPlane) | |
908 { | |
909 BroadcastMessage(IVolumeSlicer::GeometryReadyMessage(*this)); | |
910 } | |
911 | |
912 virtual bool GetExtent(std::vector<OrthancStone::Vector>& points, | |
913 const OrthancStone::CoordinateSystem3D& viewportSlice) | |
914 { | |
915 return false; | |
916 } | |
917 | |
918 virtual void ScheduleLayerCreation(const OrthancStone::CoordinateSystem3D& viewportSlice) | |
919 { | |
920 Slice reference(viewportSlice, 0.001); | |
921 | |
922 OrthancStone::Vector p, d; | |
923 | |
924 const OrthancStone::CoordinateSystem3D& slice = otherPlane_.GetSlice(); | |
925 | |
926 // Compute the line of intersection between the two slices | |
927 if (!OrthancStone::GeometryToolbox::IntersectTwoPlanes(p, d, | |
928 slice.GetOrigin(), slice.GetNormal(), | |
929 viewportSlice.GetOrigin(), viewportSlice.GetNormal())) | |
930 { | |
931 // The two slice are parallel, don't try and display the intersection | |
932 BroadcastMessage(IVolumeSlicer::LayerErrorMessage(*this, reference.GetGeometry())); | |
933 } | |
934 else | |
935 { | |
936 double x1, y1, x2, y2; | |
937 viewportSlice.ProjectPoint(x1, y1, p); | |
938 viewportSlice.ProjectPoint(x2, y2, p + 1000.0 * d); | |
939 | |
940 const OrthancStone::Extent2D extent = otherPlane_.GetSceneExtent(); | |
941 | |
942 if (OrthancStone::GeometryToolbox::ClipLineToRectangle(x1, y1, x2, y2, | |
943 x1, y1, x2, y2, | |
944 extent.GetX1(), extent.GetY1(), | |
945 extent.GetX2(), extent.GetY2())) | |
946 { | |
947 RendererFactory factory(x1, y1, x2, y2, slice); | |
948 BroadcastMessage(IVolumeSlicer::LayerReadyMessage(*this, factory, reference.GetGeometry())); | |
949 } | |
950 else | |
951 { | |
952 // Error: Parallel slices | |
953 BroadcastMessage(IVolumeSlicer::LayerErrorMessage(*this, reference.GetGeometry())); | |
954 } | |
955 } | |
956 } | |
957 }; | |
958 } |