Mercurial > hg > orthanc-stone
comparison Samples/LayoutPetCtFusionApplication.h @ 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 #pragma once | |
34 | |
35 #include "SampleInteractor.h" | |
36 | |
37 #include "../Framework/Layers/SiblingSliceLocationFactory.h" | |
38 #include "../Framework/Layers/DicomStructureSetRendererFactory.h" | |
39 #include "../Framework/Widgets/LayoutWidget.h" | |
40 | |
41 #include "../Framework/Orthanc/Core/Logging.h" | |
42 | |
43 namespace OrthancStone | |
44 { | |
45 namespace Samples | |
46 { | |
47 class LayoutPetCtFusionApplication : | |
48 public SampleApplicationBase, | |
49 public LayeredSceneWidget::ISliceObserver, | |
50 public WorldSceneWidget::IWorldObserver | |
51 { | |
52 private: | |
53 class Interactor : public SampleInteractor | |
54 { | |
55 private: | |
56 LayoutPetCtFusionApplication& that_; | |
57 | |
58 public: | |
59 Interactor(LayoutPetCtFusionApplication& that, | |
60 VolumeImage& volume, | |
61 VolumeProjection projection, | |
62 bool reverse) : | |
63 SampleInteractor(volume, projection, reverse), | |
64 that_(that) | |
65 { | |
66 } | |
67 | |
68 virtual IWorldSceneMouseTracker* CreateMouseTracker(WorldSceneWidget& widget, | |
69 const SliceGeometry& slice, | |
70 const ViewportGeometry& view, | |
71 MouseButton button, | |
72 double x, | |
73 double y, | |
74 IStatusBar* statusBar) | |
75 { | |
76 if (button == MouseButton_Left) | |
77 { | |
78 // Center the sibling views over the clicked point | |
79 Vector p = slice.MapSliceToWorldCoordinates(x, y); | |
80 | |
81 if (statusBar != NULL) | |
82 { | |
83 char buf[64]; | |
84 sprintf(buf, "Click on coordinates (%.02f,%.02f,%.02f) in cm", p[0] / 10.0, p[1] / 10.0, p[2] / 10.0); | |
85 statusBar->SetMessage(buf); | |
86 } | |
87 | |
88 that_.interactorAxial_->LookupSliceContainingPoint(*that_.ctAxial_, p); | |
89 that_.interactorCoronal_->LookupSliceContainingPoint(*that_.ctCoronal_, p); | |
90 that_.interactorSagittal_->LookupSliceContainingPoint(*that_.ctSagittal_, p); | |
91 } | |
92 | |
93 return NULL; | |
94 } | |
95 | |
96 virtual void KeyPressed(WorldSceneWidget& widget, | |
97 char key, | |
98 KeyboardModifiers modifiers, | |
99 IStatusBar* statusBar) | |
100 { | |
101 if (key == 's') | |
102 { | |
103 that_.SetDefaultView(); | |
104 } | |
105 } | |
106 }; | |
107 | |
108 bool processingEvent_; | |
109 Interactor* interactorAxial_; | |
110 Interactor* interactorCoronal_; | |
111 Interactor* interactorSagittal_; | |
112 LayeredSceneWidget* ctAxial_; | |
113 LayeredSceneWidget* ctCoronal_; | |
114 LayeredSceneWidget* ctSagittal_; | |
115 LayeredSceneWidget* petAxial_; | |
116 LayeredSceneWidget* petCoronal_; | |
117 LayeredSceneWidget* petSagittal_; | |
118 LayeredSceneWidget* fusionAxial_; | |
119 LayeredSceneWidget* fusionCoronal_; | |
120 LayeredSceneWidget* fusionSagittal_; | |
121 | |
122 | |
123 void SetDefaultView() | |
124 { | |
125 petAxial_->SetDefaultView(); | |
126 petCoronal_->SetDefaultView(); | |
127 petSagittal_->SetDefaultView(); | |
128 } | |
129 | |
130 | |
131 void AddLayer(LayeredSceneWidget& widget, | |
132 VolumeImage& volume, | |
133 bool isCt) | |
134 { | |
135 size_t layer; | |
136 widget.AddLayer(layer, new VolumeImage::LayerFactory(volume)); | |
137 | |
138 if (isCt) | |
139 { | |
140 RenderStyle style; | |
141 style.windowing_ = ImageWindowing_Bone; | |
142 widget.SetLayerStyle(layer, style); | |
143 } | |
144 else | |
145 { | |
146 RenderStyle style; | |
147 style.applyLut_ = true; | |
148 style.alpha_ = (layer == 0 ? 1.0 : 0.5); | |
149 widget.SetLayerStyle(layer, style); | |
150 } | |
151 } | |
152 | |
153 | |
154 void ConnectSiblingLocations(LayeredSceneWidget& axial, | |
155 LayeredSceneWidget& coronal, | |
156 LayeredSceneWidget& sagittal) | |
157 { | |
158 SiblingSliceLocationFactory::Configure(axial, coronal); | |
159 SiblingSliceLocationFactory::Configure(axial, sagittal); | |
160 SiblingSliceLocationFactory::Configure(coronal, sagittal); | |
161 } | |
162 | |
163 | |
164 void SynchronizeView(const WorldSceneWidget& source, | |
165 const ViewportGeometry& view, | |
166 LayeredSceneWidget& widget1, | |
167 LayeredSceneWidget& widget2, | |
168 LayeredSceneWidget& widget3) | |
169 { | |
170 if (&source == &widget1 || | |
171 &source == &widget2 || | |
172 &source == &widget3) | |
173 { | |
174 if (&source != &widget1) | |
175 { | |
176 widget1.SetView(view); | |
177 } | |
178 | |
179 if (&source != &widget2) | |
180 { | |
181 widget2.SetView(view); | |
182 } | |
183 | |
184 if (&source != &widget3) | |
185 { | |
186 widget3.SetView(view); | |
187 } | |
188 } | |
189 } | |
190 | |
191 | |
192 void SynchronizeSlice(const LayeredSceneWidget& source, | |
193 const SliceGeometry& slice, | |
194 LayeredSceneWidget& widget1, | |
195 LayeredSceneWidget& widget2, | |
196 LayeredSceneWidget& widget3) | |
197 { | |
198 if (&source == &widget1 || | |
199 &source == &widget2 || | |
200 &source == &widget3) | |
201 { | |
202 if (&source != &widget1) | |
203 { | |
204 widget1.SetSlice(slice); | |
205 } | |
206 | |
207 if (&source != &widget2) | |
208 { | |
209 widget2.SetSlice(slice); | |
210 } | |
211 | |
212 if (&source != &widget3) | |
213 { | |
214 widget3.SetSlice(slice); | |
215 } | |
216 } | |
217 } | |
218 | |
219 | |
220 LayeredSceneWidget* CreateWidget() | |
221 { | |
222 std::auto_ptr<LayeredSceneWidget> widget(new LayeredSceneWidget); | |
223 widget->Register(dynamic_cast<WorldSceneWidget::IWorldObserver&>(*this)); | |
224 widget->Register(dynamic_cast<LayeredSceneWidget::ISliceObserver&>(*this)); | |
225 return widget.release(); | |
226 } | |
227 | |
228 | |
229 void CreateLayout(BasicApplicationContext& context) | |
230 { | |
231 std::auto_ptr<OrthancStone::LayoutWidget> layout(new OrthancStone::LayoutWidget); | |
232 layout->SetBackgroundCleared(true); | |
233 //layout->SetBackgroundColor(255,0,0); | |
234 layout->SetPadding(5); | |
235 | |
236 OrthancStone::LayoutWidget& layoutA = dynamic_cast<OrthancStone::LayoutWidget&> | |
237 (layout->AddWidget(new OrthancStone::LayoutWidget)); | |
238 layoutA.SetPadding(0, 0, 0, 0, 5); | |
239 layoutA.SetVertical(); | |
240 petAxial_ = &dynamic_cast<LayeredSceneWidget&>(layoutA.AddWidget(CreateWidget())); | |
241 OrthancStone::LayoutWidget& layoutA2 = dynamic_cast<OrthancStone::LayoutWidget&> | |
242 (layoutA.AddWidget(new OrthancStone::LayoutWidget)); | |
243 layoutA2.SetPadding(0, 0, 0, 0, 5); | |
244 petSagittal_ = &dynamic_cast<LayeredSceneWidget&>(layoutA2.AddWidget(CreateWidget())); | |
245 petCoronal_ = &dynamic_cast<LayeredSceneWidget&>(layoutA2.AddWidget(CreateWidget())); | |
246 | |
247 OrthancStone::LayoutWidget& layoutB = dynamic_cast<OrthancStone::LayoutWidget&> | |
248 (layout->AddWidget(new OrthancStone::LayoutWidget)); | |
249 layoutB.SetPadding(0, 0, 0, 0, 5); | |
250 layoutB.SetVertical(); | |
251 ctAxial_ = &dynamic_cast<LayeredSceneWidget&>(layoutB.AddWidget(CreateWidget())); | |
252 OrthancStone::LayoutWidget& layoutB2 = dynamic_cast<OrthancStone::LayoutWidget&> | |
253 (layoutB.AddWidget(new OrthancStone::LayoutWidget)); | |
254 layoutB2.SetPadding(0, 0, 0, 0, 5); | |
255 ctSagittal_ = &dynamic_cast<LayeredSceneWidget&>(layoutB2.AddWidget(CreateWidget())); | |
256 ctCoronal_ = &dynamic_cast<LayeredSceneWidget&>(layoutB2.AddWidget(CreateWidget())); | |
257 | |
258 OrthancStone::LayoutWidget& layoutC = dynamic_cast<OrthancStone::LayoutWidget&> | |
259 (layout->AddWidget(new OrthancStone::LayoutWidget)); | |
260 layoutC.SetPadding(0, 0, 0, 0, 5); | |
261 layoutC.SetVertical(); | |
262 fusionAxial_ = &dynamic_cast<LayeredSceneWidget&>(layoutC.AddWidget(CreateWidget())); | |
263 OrthancStone::LayoutWidget& layoutC2 = dynamic_cast<OrthancStone::LayoutWidget&> | |
264 (layoutC.AddWidget(new OrthancStone::LayoutWidget)); | |
265 layoutC2.SetPadding(0, 0, 0, 0, 5); | |
266 fusionSagittal_ = &dynamic_cast<LayeredSceneWidget&>(layoutC2.AddWidget(CreateWidget())); | |
267 fusionCoronal_ = &dynamic_cast<LayeredSceneWidget&>(layoutC2.AddWidget(CreateWidget())); | |
268 | |
269 context.SetCentralWidget(layout.release()); | |
270 } | |
271 | |
272 | |
273 public: | |
274 virtual void DeclareCommandLineOptions(boost::program_options::options_description& options) | |
275 { | |
276 boost::program_options::options_description generic("Sample options"); | |
277 generic.add_options() | |
278 ("ct", boost::program_options::value<std::string>(), | |
279 "Orthanc ID of the CT series") | |
280 ("pet", boost::program_options::value<std::string>(), | |
281 "Orthanc ID of the PET series") | |
282 ("rt", boost::program_options::value<std::string>(), | |
283 "Orthanc ID of the DICOM RT-STRUCT series (optional)") | |
284 ("threads", boost::program_options::value<unsigned int>()->default_value(3), | |
285 "Number of download threads for the CT series") | |
286 ; | |
287 | |
288 options.add(generic); | |
289 } | |
290 | |
291 virtual void Initialize(BasicApplicationContext& context, | |
292 IStatusBar& statusBar, | |
293 const boost::program_options::variables_map& parameters) | |
294 { | |
295 using namespace OrthancStone; | |
296 | |
297 processingEvent_ = true; | |
298 | |
299 if (parameters.count("ct") != 1 || | |
300 parameters.count("pet") != 1) | |
301 { | |
302 LOG(ERROR) << "The series ID is missing"; | |
303 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); | |
304 } | |
305 | |
306 std::string ct = parameters["ct"].as<std::string>(); | |
307 std::string pet = parameters["pet"].as<std::string>(); | |
308 unsigned int threads = parameters["threads"].as<unsigned int>(); | |
309 | |
310 VolumeImage& ctVolume = context.AddSeriesVolume(ct, true /* progressive download */, threads); | |
311 VolumeImage& petVolume = context.AddSeriesVolume(pet, true /* progressive download */, 1); | |
312 | |
313 // Take the PET volume as the reference for the slices | |
314 interactorAxial_ = &dynamic_cast<Interactor&> | |
315 (context.AddInteractor(new Interactor(*this, petVolume, VolumeProjection_Axial, false))); | |
316 interactorCoronal_ = &dynamic_cast<Interactor&> | |
317 (context.AddInteractor(new Interactor(*this, petVolume, VolumeProjection_Coronal, false))); | |
318 interactorSagittal_ = &dynamic_cast<Interactor&> | |
319 (context.AddInteractor(new Interactor(*this, petVolume, VolumeProjection_Sagittal, true))); | |
320 | |
321 CreateLayout(context); | |
322 | |
323 AddLayer(*ctAxial_, ctVolume, true); | |
324 AddLayer(*ctCoronal_, ctVolume, true); | |
325 AddLayer(*ctSagittal_, ctVolume, true); | |
326 | |
327 AddLayer(*petAxial_, petVolume, false); | |
328 AddLayer(*petCoronal_, petVolume, false); | |
329 AddLayer(*petSagittal_, petVolume, false); | |
330 | |
331 AddLayer(*fusionAxial_, ctVolume, true); | |
332 AddLayer(*fusionAxial_, petVolume, false); | |
333 AddLayer(*fusionCoronal_, ctVolume, true); | |
334 AddLayer(*fusionCoronal_, petVolume, false); | |
335 AddLayer(*fusionSagittal_, ctVolume, true); | |
336 AddLayer(*fusionSagittal_, petVolume, false); | |
337 | |
338 if (parameters.count("rt") == 1) | |
339 { | |
340 DicomStructureSet& rtStruct = context.AddStructureSet(parameters["rt"].as<std::string>()); | |
341 | |
342 Vector p = rtStruct.GetStructureCenter(0); | |
343 interactorAxial_->GetCursor().LookupSliceContainingPoint(p); | |
344 | |
345 ctAxial_->AddLayer(new DicomStructureSetRendererFactory(rtStruct)); | |
346 petAxial_->AddLayer(new DicomStructureSetRendererFactory(rtStruct)); | |
347 fusionAxial_->AddLayer(new DicomStructureSetRendererFactory(rtStruct)); | |
348 } | |
349 | |
350 ConnectSiblingLocations(*ctAxial_, *ctCoronal_, *ctSagittal_); | |
351 ConnectSiblingLocations(*petAxial_, *petCoronal_, *petSagittal_); | |
352 ConnectSiblingLocations(*fusionAxial_, *fusionCoronal_, *fusionSagittal_); | |
353 | |
354 interactorAxial_->AddWidget(*ctAxial_); | |
355 interactorAxial_->AddWidget(*petAxial_); | |
356 interactorAxial_->AddWidget(*fusionAxial_); | |
357 | |
358 interactorCoronal_->AddWidget(*ctCoronal_); | |
359 interactorCoronal_->AddWidget(*petCoronal_); | |
360 interactorCoronal_->AddWidget(*fusionCoronal_); | |
361 | |
362 interactorSagittal_->AddWidget(*ctSagittal_); | |
363 interactorSagittal_->AddWidget(*petSagittal_); | |
364 interactorSagittal_->AddWidget(*fusionSagittal_); | |
365 | |
366 processingEvent_ = false; | |
367 | |
368 statusBar.SetMessage("Use the key \"t\" to toggle the fullscreen mode"); | |
369 statusBar.SetMessage("Use the key \"s\" to reinitialize the layout"); | |
370 } | |
371 | |
372 virtual void NotifySizeChange(const WorldSceneWidget& source, | |
373 ViewportGeometry& view) | |
374 { | |
375 view.SetDefaultView(); | |
376 } | |
377 | |
378 virtual void NotifyViewChange(const WorldSceneWidget& source, | |
379 const ViewportGeometry& view) | |
380 { | |
381 if (!processingEvent_) // Avoid reentrant calls | |
382 { | |
383 processingEvent_ = true; | |
384 | |
385 SynchronizeView(source, view, *ctAxial_, *petAxial_, *fusionAxial_); | |
386 SynchronizeView(source, view, *ctCoronal_, *petCoronal_, *fusionCoronal_); | |
387 SynchronizeView(source, view, *ctSagittal_, *petSagittal_, *fusionSagittal_); | |
388 | |
389 processingEvent_ = false; | |
390 } | |
391 } | |
392 | |
393 virtual void NotifySliceChange(const LayeredSceneWidget& source, | |
394 const SliceGeometry& slice) | |
395 { | |
396 if (!processingEvent_) // Avoid reentrant calls | |
397 { | |
398 processingEvent_ = true; | |
399 | |
400 SynchronizeSlice(source, slice, *ctAxial_, *petAxial_, *fusionAxial_); | |
401 SynchronizeSlice(source, slice, *ctCoronal_, *petCoronal_, *fusionCoronal_); | |
402 SynchronizeSlice(source, slice, *ctSagittal_, *petSagittal_, *fusionSagittal_); | |
403 | |
404 processingEvent_ = false; | |
405 } | |
406 } | |
407 }; | |
408 } | |
409 } |