Mercurial > hg > orthanc-stone
annotate Applications/Samples/SingleVolumeApplication.h @ 103:474d85e76499 wasm
mpr
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Wed, 31 May 2017 18:05:33 +0200 |
parents | fcec0ab44054 |
children | eccd64f8e297 |
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:
16
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 #pragma once | |
23 | |
102 | 24 #include "SampleApplicationBase.h" |
25 #include "../../Framework/dev.h" | |
26 //#include "SampleInteractor.h" | |
27 #include "../../Framework/Widgets/LayerWidget.h" | |
0 | 28 |
51 | 29 #include "../../Resources/Orthanc/Core/Toolbox.h" |
30 #include "../../Framework/Layers/LineMeasureTracker.h" | |
31 #include "../../Framework/Layers/CircleMeasureTracker.h" | |
32 #include "../../Resources/Orthanc/Core/Logging.h" | |
0 | 33 |
34 namespace OrthancStone | |
35 { | |
36 namespace Samples | |
37 { | |
102 | 38 class SingleVolumeApplication : |
39 public SampleApplicationBase, | |
40 private ILayerSource::IObserver | |
0 | 41 { |
42 private: | |
102 | 43 class Interactor : public IWorldSceneInteractor |
44 { | |
45 private: | |
46 SingleVolumeApplication& application_; | |
47 | |
48 public: | |
49 Interactor(SingleVolumeApplication& application) : | |
50 application_(application) | |
51 { | |
52 } | |
53 | |
54 virtual IWorldSceneMouseTracker* CreateMouseTracker(WorldSceneWidget& widget, | |
55 const ViewportGeometry& view, | |
56 MouseButton button, | |
57 double x, | |
58 double y, | |
59 IStatusBar* statusBar) | |
60 { | |
61 return NULL; | |
62 } | |
63 | |
64 virtual void MouseOver(CairoContext& context, | |
65 WorldSceneWidget& widget, | |
66 const ViewportGeometry& view, | |
67 double x, | |
68 double y, | |
69 IStatusBar* statusBar) | |
70 { | |
71 if (statusBar != NULL) | |
72 { | |
73 Vector p = dynamic_cast<LayerWidget&>(widget).GetSlice().MapSliceToWorldCoordinates(x, y); | |
74 | |
75 char buf[64]; | |
76 sprintf(buf, "X = %.02f Y = %.02f Z = %.02f (in cm)", | |
77 p[0] / 10.0, p[1] / 10.0, p[2] / 10.0); | |
78 statusBar->SetMessage(buf); | |
79 } | |
80 } | |
81 | |
82 virtual void MouseWheel(WorldSceneWidget& widget, | |
83 MouseWheelDirection direction, | |
84 KeyboardModifiers modifiers, | |
85 IStatusBar* statusBar) | |
86 { | |
87 int scale = (modifiers & KeyboardModifiers_Control ? 10 : 1); | |
88 | |
89 switch (direction) | |
90 { | |
91 case MouseWheelDirection_Up: | |
92 application_.OffsetSlice(-scale); | |
93 break; | |
94 | |
95 case MouseWheelDirection_Down: | |
96 application_.OffsetSlice(scale); | |
97 break; | |
98 | |
99 default: | |
100 break; | |
101 } | |
102 } | |
103 | |
104 virtual void KeyPressed(WorldSceneWidget& widget, | |
105 char key, | |
106 KeyboardModifiers modifiers, | |
107 IStatusBar* statusBar) | |
108 { | |
109 switch (key) | |
110 { | |
111 case 's': | |
112 widget.SetDefaultView(); | |
113 break; | |
114 | |
115 default: | |
116 break; | |
117 } | |
118 } | |
119 }; | |
120 | |
121 | |
122 LayerWidget* widget_; | |
103 | 123 const OrthancVolumeImage* volume_; |
102 | 124 VolumeProjection projection_; |
125 std::auto_ptr<VolumeImageGeometry> slices_; | |
126 size_t slice_; | |
127 | |
128 void OffsetSlice(int offset) | |
129 { | |
130 if (slices_.get() != NULL) | |
131 { | |
132 int slice = static_cast<int>(slice_) + offset; | |
133 | |
134 if (slice < 0) | |
135 { | |
136 slice = 0; | |
137 } | |
138 | |
139 if (slice >= static_cast<int>(slices_->GetSliceCount())) | |
140 { | |
141 slice = slices_->GetSliceCount() - 1; | |
142 } | |
143 | |
144 if (slice != static_cast<int>(slice_)) | |
145 { | |
146 SetSlice(slice); | |
147 } | |
148 } | |
149 } | |
150 | |
151 void SetSlice(size_t slice) | |
152 { | |
153 if (slices_.get() != NULL) | |
154 { | |
155 slice_ = slice; | |
156 widget_->SetSlice(slices_->GetSlice(slice_).GetGeometry()); | |
157 } | |
158 } | |
159 | |
160 virtual void NotifyGeometryReady(const ILayerSource& source) | |
161 { | |
162 if (slices_.get() == NULL) | |
163 { | |
164 slices_.reset(new VolumeImageGeometry(*volume_, projection_)); | |
165 SetSlice(slices_->GetSliceCount() / 2); | |
166 | |
167 widget_->SetDefaultView(); | |
168 } | |
169 } | |
170 | |
171 virtual void NotifyGeometryError(const ILayerSource& source) | |
172 { | |
173 } | |
174 | |
175 virtual void NotifyContentChange(const ILayerSource& source) | |
176 { | |
177 } | |
178 | |
179 virtual void NotifySliceChange(const ILayerSource& source, | |
180 const Slice& slice) | |
181 { | |
182 } | |
183 | |
184 virtual void NotifyLayerReady(std::auto_ptr<ILayerRenderer>& layer, | |
185 const ILayerSource& source, | |
186 const Slice& slice, | |
187 bool isError) | |
188 { | |
189 } | |
190 | |
191 #if 0 | |
0 | 192 class Interactor : public SampleInteractor |
193 { | |
194 private: | |
195 enum MouseMode | |
196 { | |
197 MouseMode_None, | |
198 MouseMode_TrackCoordinates, | |
199 MouseMode_LineMeasure, | |
200 MouseMode_CircleMeasure | |
201 }; | |
202 | |
203 MouseMode mouseMode_; | |
204 | |
205 void SetMouseMode(MouseMode mode, | |
206 IStatusBar* statusBar) | |
207 { | |
208 if (mouseMode_ == mode) | |
209 { | |
210 mouseMode_ = MouseMode_None; | |
211 } | |
212 else | |
213 { | |
214 mouseMode_ = mode; | |
215 } | |
216 | |
217 if (statusBar) | |
218 { | |
219 switch (mouseMode_) | |
220 { | |
221 case MouseMode_None: | |
222 statusBar->SetMessage("Disabling the mouse tools"); | |
223 break; | |
224 | |
225 case MouseMode_TrackCoordinates: | |
226 statusBar->SetMessage("Tracking the mouse coordinates"); | |
227 break; | |
228 | |
229 case MouseMode_LineMeasure: | |
230 statusBar->SetMessage("Mouse clicks will now measure the distances"); | |
231 break; | |
232 | |
233 case MouseMode_CircleMeasure: | |
234 statusBar->SetMessage("Mouse clicks will now draw circles"); | |
235 break; | |
236 | |
237 default: | |
238 break; | |
239 } | |
240 } | |
241 } | |
242 | |
243 public: | |
244 Interactor(VolumeImage& volume, | |
245 VolumeProjection projection, | |
246 bool reverse) : | |
247 SampleInteractor(volume, projection, reverse), | |
248 mouseMode_(MouseMode_None) | |
249 { | |
250 } | |
251 | |
252 virtual IWorldSceneMouseTracker* CreateMouseTracker(WorldSceneWidget& widget, | |
253 const SliceGeometry& slice, | |
254 const ViewportGeometry& view, | |
255 MouseButton button, | |
256 double x, | |
257 double y, | |
258 IStatusBar* statusBar) | |
259 { | |
260 if (button == MouseButton_Left) | |
261 { | |
262 switch (mouseMode_) | |
263 { | |
264 case MouseMode_LineMeasure: | |
265 return new LineMeasureTracker(NULL, slice, x, y, 255, 0, 0, 14 /* font size */); | |
266 | |
267 case MouseMode_CircleMeasure: | |
268 return new CircleMeasureTracker(NULL, slice, x, y, 255, 0, 0, 14 /* font size */); | |
269 | |
270 default: | |
271 break; | |
272 } | |
273 } | |
274 | |
275 return NULL; | |
276 } | |
277 | |
278 virtual void MouseOver(CairoContext& context, | |
279 WorldSceneWidget& widget, | |
280 const SliceGeometry& slice, | |
281 const ViewportGeometry& view, | |
282 double x, | |
283 double y, | |
284 IStatusBar* statusBar) | |
285 { | |
286 if (mouseMode_ == MouseMode_TrackCoordinates && | |
287 statusBar != NULL) | |
288 { | |
289 Vector p = slice.MapSliceToWorldCoordinates(x, y); | |
290 | |
291 char buf[64]; | |
292 sprintf(buf, "X = %.02f Y = %.02f Z = %.02f (in cm)", p[0] / 10.0, p[1] / 10.0, p[2] / 10.0); | |
293 statusBar->SetMessage(buf); | |
294 } | |
295 } | |
296 | |
297 | |
298 virtual void KeyPressed(WorldSceneWidget& widget, | |
299 char key, | |
300 KeyboardModifiers modifiers, | |
301 IStatusBar* statusBar) | |
302 { | |
303 switch (key) | |
304 { | |
305 case 't': | |
306 SetMouseMode(MouseMode_TrackCoordinates, statusBar); | |
307 break; | |
308 | |
309 case 'm': | |
310 SetMouseMode(MouseMode_LineMeasure, statusBar); | |
311 break; | |
312 | |
313 case 'c': | |
314 SetMouseMode(MouseMode_CircleMeasure, statusBar); | |
315 break; | |
316 | |
317 case 'b': | |
318 { | |
319 if (statusBar) | |
320 { | |
321 statusBar->SetMessage("Setting Hounsfield window to bones"); | |
322 } | |
323 | |
324 RenderStyle style; | |
325 style.windowing_ = ImageWindowing_Bone; | |
326 dynamic_cast<LayeredSceneWidget&>(widget).SetLayerStyle(0, style); | |
327 break; | |
328 } | |
329 | |
330 case 'l': | |
331 { | |
332 if (statusBar) | |
333 { | |
334 statusBar->SetMessage("Setting Hounsfield window to lung"); | |
335 } | |
336 | |
337 RenderStyle style; | |
338 style.windowing_ = ImageWindowing_Lung; | |
339 dynamic_cast<LayeredSceneWidget&>(widget).SetLayerStyle(0, style); | |
340 break; | |
341 } | |
342 | |
343 case 'd': | |
344 { | |
345 if (statusBar) | |
346 { | |
347 statusBar->SetMessage("Setting Hounsfield window to what is written in the DICOM file"); | |
348 } | |
349 | |
350 RenderStyle style; | |
351 style.windowing_ = ImageWindowing_Default; | |
352 dynamic_cast<LayeredSceneWidget&>(widget).SetLayerStyle(0, style); | |
353 break; | |
354 } | |
355 | |
356 default: | |
357 break; | |
358 } | |
359 } | |
360 }; | |
102 | 361 #endif |
0 | 362 |
102 | 363 |
0 | 364 public: |
102 | 365 SingleVolumeApplication() : |
366 widget_(NULL), | |
367 volume_(NULL) | |
368 { | |
369 } | |
370 | |
0 | 371 virtual void DeclareCommandLineOptions(boost::program_options::options_description& options) |
372 { | |
373 boost::program_options::options_description generic("Sample options"); | |
374 generic.add_options() | |
375 ("series", boost::program_options::value<std::string>(), | |
376 "Orthanc ID of the series") | |
377 ("threads", boost::program_options::value<unsigned int>()->default_value(3), | |
378 "Number of download threads") | |
379 ("projection", boost::program_options::value<std::string>()->default_value("axial"), | |
380 "Projection of interest (can be axial, sagittal or coronal)") | |
381 ("reverse", boost::program_options::value<bool>()->default_value(false), | |
382 "Reverse the normal direction of the volume") | |
383 ; | |
384 | |
385 options.add(generic); | |
386 } | |
387 | |
388 virtual void Initialize(BasicApplicationContext& context, | |
389 IStatusBar& statusBar, | |
390 const boost::program_options::variables_map& parameters) | |
391 { | |
392 using namespace OrthancStone; | |
393 | |
394 if (parameters.count("series") != 1) | |
395 { | |
396 LOG(ERROR) << "The series ID is missing"; | |
397 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); | |
398 } | |
399 | |
400 std::string series = parameters["series"].as<std::string>(); | |
401 unsigned int threads = parameters["threads"].as<unsigned int>(); | |
402 bool reverse = parameters["reverse"].as<bool>(); | |
403 | |
404 std::string tmp = parameters["projection"].as<std::string>(); | |
405 Orthanc::Toolbox::ToLowerCase(tmp); | |
406 | |
407 if (tmp == "axial") | |
408 { | |
102 | 409 projection_ = VolumeProjection_Axial; |
0 | 410 } |
411 else if (tmp == "sagittal") | |
412 { | |
102 | 413 projection_ = VolumeProjection_Sagittal; |
0 | 414 } |
415 else if (tmp == "coronal") | |
416 { | |
102 | 417 projection_ = VolumeProjection_Coronal; |
0 | 418 } |
419 else | |
420 { | |
421 LOG(ERROR) << "Unknown projection: " << tmp; | |
422 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); | |
423 } | |
424 | |
102 | 425 std::auto_ptr<LayerWidget> widget(new LayerWidget); |
426 widget_ = widget.get(); | |
427 | |
428 #if 0 | |
429 std::auto_ptr<OrthancVolumeImage> volume(new OrthancVolumeImage(context.GetWebService())); | |
430 volume->ScheduleLoadSeries(series); | |
0 | 431 |
102 | 432 volume_ = volume.get(); |
433 | |
434 { | |
435 std::auto_ptr<VolumeImageSource> source(new VolumeImageSource(*volume)); | |
436 source->Register(*this); | |
437 widget->AddLayer(source.release()); | |
438 } | |
439 | |
440 context.AddVolume(volume.release()); | |
441 #else | |
442 std::auto_ptr<OrthancVolumeImage> ct(new OrthancVolumeImage(context.GetWebService())); | |
443 ct->ScheduleLoadSeries("dd069910-4f090474-7d2bba07-e5c10783-f9e4fb1d"); | |
444 | |
445 std::auto_ptr<OrthancVolumeImage> pet(new OrthancVolumeImage(context.GetWebService())); | |
446 pet->ScheduleLoadSeries("aabad2e7-80702b5d-e599d26c-4f13398e-38d58a9e"); | |
0 | 447 |
102 | 448 volume_ = pet.get(); |
449 | |
450 { | |
451 std::auto_ptr<VolumeImageSource> source(new VolumeImageSource(*ct)); | |
452 //source->Register(*this); | |
453 widget->AddLayer(source.release()); | |
454 } | |
455 | |
456 { | |
457 std::auto_ptr<VolumeImageSource> source(new VolumeImageSource(*pet)); | |
458 source->Register(*this); | |
459 widget->AddLayer(source.release()); | |
460 } | |
461 | |
462 context.AddVolume(ct.release()); | |
463 context.AddVolume(pet.release()); | |
0 | 464 |
102 | 465 { |
466 RenderStyle s; | |
467 //s.drawGrid_ = true; | |
468 s.alpha_ = 1; | |
469 widget->SetLayerStyle(0, s); | |
470 } | |
471 | |
472 { | |
473 RenderStyle s; | |
474 //s.drawGrid_ = true; | |
475 s.SetColor(255, 0, 0); // Draw missing PET layer in red | |
476 s.alpha_ = 0.5; | |
477 s.applyLut_ = true; | |
478 s.lut_ = Orthanc::EmbeddedResources::COLORMAP_JET; | |
479 s.interpolation_ = ImageInterpolation_Linear; | |
480 widget->SetLayerStyle(1, s); | |
481 } | |
482 #endif | |
483 | |
0 | 484 |
485 statusBar.SetMessage("Use the keys \"b\", \"l\" and \"d\" to change Hounsfield windowing"); | |
486 statusBar.SetMessage("Use the keys \"t\" to track the (X,Y,Z) mouse coordinates"); | |
487 statusBar.SetMessage("Use the keys \"m\" to measure distances"); | |
488 statusBar.SetMessage("Use the keys \"c\" to draw circles"); | |
102 | 489 |
490 widget->SetTransmitMouseOver(true); | |
491 widget->SetInteractor(context.AddInteractor(new Interactor(*this))); | |
492 context.SetCentralWidget(widget.release()); | |
0 | 493 } |
494 }; | |
495 } | |
496 } |