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 }