Mercurial > hg > orthanc-stone
annotate Samples/LayoutPetCtFusionApplication.h @ 41:ed07f4bbf1a8
notes
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Wed, 22 Mar 2017 15:57:24 +0100 |
parents | 7207a407bcd8 |
children | 28956ed68280 |
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:
25
diff
changeset
|
5 * Copyright (C) 2017 Osimis, Belgium |
0 | 6 * |
7 * This program is free software: you can redistribute it and/or | |
8 * modify it under the terms of the GNU General Public License as | |
9 * published by the Free Software Foundation, either version 3 of the | |
10 * License, or (at your option) any later version. | |
11 * | |
12 * In addition, as a special exception, the copyright holders of this | |
13 * program give permission to link the code of its release with the | |
14 * OpenSSL project's "OpenSSL" library (or with modified versions of it | |
15 * that use the same license as the "OpenSSL" library), and distribute | |
16 * the linked executables. You must obey the GNU General Public License | |
17 * in all respects for all of the code used other than "OpenSSL". If you | |
18 * modify file(s) with this exception, you may extend this exception to | |
19 * your version of the file(s), but you are not obligated to do so. If | |
20 * you do not wish to do so, delete this exception statement from your | |
21 * version. If you delete this exception statement from all source files | |
22 * in the program, then also delete it here. | |
23 * | |
24 * This program is distributed in the hope that it will be useful, but | |
25 * WITHOUT ANY WARRANTY; without even the implied warranty of | |
26 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
27 * General Public License for more details. | |
28 * | |
29 * You should have received a copy of the GNU General Public License | |
30 * along with this program. If not, see <http://www.gnu.org/licenses/>. | |
31 **/ | |
32 | |
33 | |
34 #pragma once | |
35 | |
36 #include "SampleInteractor.h" | |
37 | |
38 #include "../Framework/Layers/SiblingSliceLocationFactory.h" | |
39 #include "../Framework/Layers/DicomStructureSetRendererFactory.h" | |
40 #include "../Framework/Widgets/LayoutWidget.h" | |
41 | |
16 | 42 #include "../Resources/Orthanc/Core/Logging.h" |
0 | 43 |
44 namespace OrthancStone | |
45 { | |
46 namespace Samples | |
47 { | |
48 class LayoutPetCtFusionApplication : | |
49 public SampleApplicationBase, | |
50 public LayeredSceneWidget::ISliceObserver, | |
51 public WorldSceneWidget::IWorldObserver | |
52 { | |
53 private: | |
54 class Interactor : public SampleInteractor | |
55 { | |
56 private: | |
57 LayoutPetCtFusionApplication& that_; | |
58 | |
59 public: | |
60 Interactor(LayoutPetCtFusionApplication& that, | |
61 VolumeImage& volume, | |
62 VolumeProjection projection, | |
63 bool reverse) : | |
64 SampleInteractor(volume, projection, reverse), | |
65 that_(that) | |
66 { | |
67 } | |
68 | |
69 virtual IWorldSceneMouseTracker* CreateMouseTracker(WorldSceneWidget& widget, | |
70 const SliceGeometry& slice, | |
71 const ViewportGeometry& view, | |
72 MouseButton button, | |
73 double x, | |
74 double y, | |
75 IStatusBar* statusBar) | |
76 { | |
77 if (button == MouseButton_Left) | |
78 { | |
79 // Center the sibling views over the clicked point | |
80 Vector p = slice.MapSliceToWorldCoordinates(x, y); | |
81 | |
82 if (statusBar != NULL) | |
83 { | |
84 char buf[64]; | |
85 sprintf(buf, "Click on coordinates (%.02f,%.02f,%.02f) in cm", p[0] / 10.0, p[1] / 10.0, p[2] / 10.0); | |
86 statusBar->SetMessage(buf); | |
87 } | |
88 | |
89 that_.interactorAxial_->LookupSliceContainingPoint(*that_.ctAxial_, p); | |
90 that_.interactorCoronal_->LookupSliceContainingPoint(*that_.ctCoronal_, p); | |
91 that_.interactorSagittal_->LookupSliceContainingPoint(*that_.ctSagittal_, p); | |
92 } | |
93 | |
94 return NULL; | |
95 } | |
96 | |
97 virtual void KeyPressed(WorldSceneWidget& widget, | |
98 char key, | |
99 KeyboardModifiers modifiers, | |
100 IStatusBar* statusBar) | |
101 { | |
102 if (key == 's') | |
103 { | |
104 that_.SetDefaultView(); | |
105 } | |
106 } | |
107 }; | |
108 | |
109 bool processingEvent_; | |
110 Interactor* interactorAxial_; | |
111 Interactor* interactorCoronal_; | |
112 Interactor* interactorSagittal_; | |
113 LayeredSceneWidget* ctAxial_; | |
114 LayeredSceneWidget* ctCoronal_; | |
115 LayeredSceneWidget* ctSagittal_; | |
116 LayeredSceneWidget* petAxial_; | |
117 LayeredSceneWidget* petCoronal_; | |
118 LayeredSceneWidget* petSagittal_; | |
119 LayeredSceneWidget* fusionAxial_; | |
120 LayeredSceneWidget* fusionCoronal_; | |
121 LayeredSceneWidget* fusionSagittal_; | |
122 | |
123 | |
124 void SetDefaultView() | |
125 { | |
126 petAxial_->SetDefaultView(); | |
127 petCoronal_->SetDefaultView(); | |
128 petSagittal_->SetDefaultView(); | |
129 } | |
130 | |
131 | |
132 void AddLayer(LayeredSceneWidget& widget, | |
133 VolumeImage& volume, | |
134 bool isCt) | |
135 { | |
136 size_t layer; | |
137 widget.AddLayer(layer, new VolumeImage::LayerFactory(volume)); | |
138 | |
139 if (isCt) | |
140 { | |
141 RenderStyle style; | |
142 style.windowing_ = ImageWindowing_Bone; | |
143 widget.SetLayerStyle(layer, style); | |
144 } | |
145 else | |
146 { | |
147 RenderStyle style; | |
148 style.applyLut_ = true; | |
25 | 149 style.alpha_ = (layer == 0 ? 1.0f : 0.5f); |
0 | 150 widget.SetLayerStyle(layer, style); |
151 } | |
152 } | |
153 | |
154 | |
155 void ConnectSiblingLocations(LayeredSceneWidget& axial, | |
156 LayeredSceneWidget& coronal, | |
157 LayeredSceneWidget& sagittal) | |
158 { | |
159 SiblingSliceLocationFactory::Configure(axial, coronal); | |
160 SiblingSliceLocationFactory::Configure(axial, sagittal); | |
161 SiblingSliceLocationFactory::Configure(coronal, sagittal); | |
162 } | |
163 | |
164 | |
165 void SynchronizeView(const WorldSceneWidget& source, | |
166 const ViewportGeometry& view, | |
167 LayeredSceneWidget& widget1, | |
168 LayeredSceneWidget& widget2, | |
169 LayeredSceneWidget& widget3) | |
170 { | |
171 if (&source == &widget1 || | |
172 &source == &widget2 || | |
173 &source == &widget3) | |
174 { | |
175 if (&source != &widget1) | |
176 { | |
177 widget1.SetView(view); | |
178 } | |
179 | |
180 if (&source != &widget2) | |
181 { | |
182 widget2.SetView(view); | |
183 } | |
184 | |
185 if (&source != &widget3) | |
186 { | |
187 widget3.SetView(view); | |
188 } | |
189 } | |
190 } | |
191 | |
192 | |
193 void SynchronizeSlice(const LayeredSceneWidget& source, | |
194 const SliceGeometry& slice, | |
195 LayeredSceneWidget& widget1, | |
196 LayeredSceneWidget& widget2, | |
197 LayeredSceneWidget& widget3) | |
198 { | |
199 if (&source == &widget1 || | |
200 &source == &widget2 || | |
201 &source == &widget3) | |
202 { | |
203 if (&source != &widget1) | |
204 { | |
205 widget1.SetSlice(slice); | |
206 } | |
207 | |
208 if (&source != &widget2) | |
209 { | |
210 widget2.SetSlice(slice); | |
211 } | |
212 | |
213 if (&source != &widget3) | |
214 { | |
215 widget3.SetSlice(slice); | |
216 } | |
217 } | |
218 } | |
219 | |
220 | |
221 LayeredSceneWidget* CreateWidget() | |
222 { | |
223 std::auto_ptr<LayeredSceneWidget> widget(new LayeredSceneWidget); | |
224 widget->Register(dynamic_cast<WorldSceneWidget::IWorldObserver&>(*this)); | |
225 widget->Register(dynamic_cast<LayeredSceneWidget::ISliceObserver&>(*this)); | |
226 return widget.release(); | |
227 } | |
228 | |
229 | |
230 void CreateLayout(BasicApplicationContext& context) | |
231 { | |
232 std::auto_ptr<OrthancStone::LayoutWidget> layout(new OrthancStone::LayoutWidget); | |
233 layout->SetBackgroundCleared(true); | |
234 //layout->SetBackgroundColor(255,0,0); | |
235 layout->SetPadding(5); | |
236 | |
237 OrthancStone::LayoutWidget& layoutA = dynamic_cast<OrthancStone::LayoutWidget&> | |
238 (layout->AddWidget(new OrthancStone::LayoutWidget)); | |
239 layoutA.SetPadding(0, 0, 0, 0, 5); | |
240 layoutA.SetVertical(); | |
241 petAxial_ = &dynamic_cast<LayeredSceneWidget&>(layoutA.AddWidget(CreateWidget())); | |
242 OrthancStone::LayoutWidget& layoutA2 = dynamic_cast<OrthancStone::LayoutWidget&> | |
243 (layoutA.AddWidget(new OrthancStone::LayoutWidget)); | |
244 layoutA2.SetPadding(0, 0, 0, 0, 5); | |
245 petSagittal_ = &dynamic_cast<LayeredSceneWidget&>(layoutA2.AddWidget(CreateWidget())); | |
246 petCoronal_ = &dynamic_cast<LayeredSceneWidget&>(layoutA2.AddWidget(CreateWidget())); | |
247 | |
248 OrthancStone::LayoutWidget& layoutB = dynamic_cast<OrthancStone::LayoutWidget&> | |
249 (layout->AddWidget(new OrthancStone::LayoutWidget)); | |
250 layoutB.SetPadding(0, 0, 0, 0, 5); | |
251 layoutB.SetVertical(); | |
252 ctAxial_ = &dynamic_cast<LayeredSceneWidget&>(layoutB.AddWidget(CreateWidget())); | |
253 OrthancStone::LayoutWidget& layoutB2 = dynamic_cast<OrthancStone::LayoutWidget&> | |
254 (layoutB.AddWidget(new OrthancStone::LayoutWidget)); | |
255 layoutB2.SetPadding(0, 0, 0, 0, 5); | |
256 ctSagittal_ = &dynamic_cast<LayeredSceneWidget&>(layoutB2.AddWidget(CreateWidget())); | |
257 ctCoronal_ = &dynamic_cast<LayeredSceneWidget&>(layoutB2.AddWidget(CreateWidget())); | |
258 | |
259 OrthancStone::LayoutWidget& layoutC = dynamic_cast<OrthancStone::LayoutWidget&> | |
260 (layout->AddWidget(new OrthancStone::LayoutWidget)); | |
261 layoutC.SetPadding(0, 0, 0, 0, 5); | |
262 layoutC.SetVertical(); | |
263 fusionAxial_ = &dynamic_cast<LayeredSceneWidget&>(layoutC.AddWidget(CreateWidget())); | |
264 OrthancStone::LayoutWidget& layoutC2 = dynamic_cast<OrthancStone::LayoutWidget&> | |
265 (layoutC.AddWidget(new OrthancStone::LayoutWidget)); | |
266 layoutC2.SetPadding(0, 0, 0, 0, 5); | |
267 fusionSagittal_ = &dynamic_cast<LayeredSceneWidget&>(layoutC2.AddWidget(CreateWidget())); | |
268 fusionCoronal_ = &dynamic_cast<LayeredSceneWidget&>(layoutC2.AddWidget(CreateWidget())); | |
269 | |
270 context.SetCentralWidget(layout.release()); | |
271 } | |
272 | |
273 | |
274 public: | |
275 virtual void DeclareCommandLineOptions(boost::program_options::options_description& options) | |
276 { | |
277 boost::program_options::options_description generic("Sample options"); | |
278 generic.add_options() | |
279 ("ct", boost::program_options::value<std::string>(), | |
280 "Orthanc ID of the CT series") | |
281 ("pet", boost::program_options::value<std::string>(), | |
282 "Orthanc ID of the PET series") | |
283 ("rt", boost::program_options::value<std::string>(), | |
284 "Orthanc ID of the DICOM RT-STRUCT series (optional)") | |
285 ("threads", boost::program_options::value<unsigned int>()->default_value(3), | |
286 "Number of download threads for the CT series") | |
287 ; | |
288 | |
289 options.add(generic); | |
290 } | |
291 | |
292 virtual void Initialize(BasicApplicationContext& context, | |
293 IStatusBar& statusBar, | |
294 const boost::program_options::variables_map& parameters) | |
295 { | |
296 using namespace OrthancStone; | |
297 | |
298 processingEvent_ = true; | |
299 | |
300 if (parameters.count("ct") != 1 || | |
301 parameters.count("pet") != 1) | |
302 { | |
303 LOG(ERROR) << "The series ID is missing"; | |
304 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); | |
305 } | |
306 | |
307 std::string ct = parameters["ct"].as<std::string>(); | |
308 std::string pet = parameters["pet"].as<std::string>(); | |
309 unsigned int threads = parameters["threads"].as<unsigned int>(); | |
310 | |
311 VolumeImage& ctVolume = context.AddSeriesVolume(ct, true /* progressive download */, threads); | |
312 VolumeImage& petVolume = context.AddSeriesVolume(pet, true /* progressive download */, 1); | |
313 | |
314 // Take the PET volume as the reference for the slices | |
315 interactorAxial_ = &dynamic_cast<Interactor&> | |
316 (context.AddInteractor(new Interactor(*this, petVolume, VolumeProjection_Axial, false))); | |
317 interactorCoronal_ = &dynamic_cast<Interactor&> | |
318 (context.AddInteractor(new Interactor(*this, petVolume, VolumeProjection_Coronal, false))); | |
319 interactorSagittal_ = &dynamic_cast<Interactor&> | |
320 (context.AddInteractor(new Interactor(*this, petVolume, VolumeProjection_Sagittal, true))); | |
321 | |
322 CreateLayout(context); | |
323 | |
324 AddLayer(*ctAxial_, ctVolume, true); | |
325 AddLayer(*ctCoronal_, ctVolume, true); | |
326 AddLayer(*ctSagittal_, ctVolume, true); | |
327 | |
328 AddLayer(*petAxial_, petVolume, false); | |
329 AddLayer(*petCoronal_, petVolume, false); | |
330 AddLayer(*petSagittal_, petVolume, false); | |
331 | |
332 AddLayer(*fusionAxial_, ctVolume, true); | |
333 AddLayer(*fusionAxial_, petVolume, false); | |
334 AddLayer(*fusionCoronal_, ctVolume, true); | |
335 AddLayer(*fusionCoronal_, petVolume, false); | |
336 AddLayer(*fusionSagittal_, ctVolume, true); | |
337 AddLayer(*fusionSagittal_, petVolume, false); | |
338 | |
339 if (parameters.count("rt") == 1) | |
340 { | |
341 DicomStructureSet& rtStruct = context.AddStructureSet(parameters["rt"].as<std::string>()); | |
342 | |
343 Vector p = rtStruct.GetStructureCenter(0); | |
344 interactorAxial_->GetCursor().LookupSliceContainingPoint(p); | |
345 | |
346 ctAxial_->AddLayer(new DicomStructureSetRendererFactory(rtStruct)); | |
347 petAxial_->AddLayer(new DicomStructureSetRendererFactory(rtStruct)); | |
348 fusionAxial_->AddLayer(new DicomStructureSetRendererFactory(rtStruct)); | |
349 } | |
350 | |
351 ConnectSiblingLocations(*ctAxial_, *ctCoronal_, *ctSagittal_); | |
352 ConnectSiblingLocations(*petAxial_, *petCoronal_, *petSagittal_); | |
353 ConnectSiblingLocations(*fusionAxial_, *fusionCoronal_, *fusionSagittal_); | |
354 | |
355 interactorAxial_->AddWidget(*ctAxial_); | |
356 interactorAxial_->AddWidget(*petAxial_); | |
357 interactorAxial_->AddWidget(*fusionAxial_); | |
358 | |
359 interactorCoronal_->AddWidget(*ctCoronal_); | |
360 interactorCoronal_->AddWidget(*petCoronal_); | |
361 interactorCoronal_->AddWidget(*fusionCoronal_); | |
362 | |
363 interactorSagittal_->AddWidget(*ctSagittal_); | |
364 interactorSagittal_->AddWidget(*petSagittal_); | |
365 interactorSagittal_->AddWidget(*fusionSagittal_); | |
366 | |
367 processingEvent_ = false; | |
368 | |
369 statusBar.SetMessage("Use the key \"t\" to toggle the fullscreen mode"); | |
370 statusBar.SetMessage("Use the key \"s\" to reinitialize the layout"); | |
371 } | |
372 | |
373 virtual void NotifySizeChange(const WorldSceneWidget& source, | |
374 ViewportGeometry& view) | |
375 { | |
376 view.SetDefaultView(); | |
377 } | |
378 | |
379 virtual void NotifyViewChange(const WorldSceneWidget& source, | |
380 const ViewportGeometry& view) | |
381 { | |
382 if (!processingEvent_) // Avoid reentrant calls | |
383 { | |
384 processingEvent_ = true; | |
385 | |
386 SynchronizeView(source, view, *ctAxial_, *petAxial_, *fusionAxial_); | |
387 SynchronizeView(source, view, *ctCoronal_, *petCoronal_, *fusionCoronal_); | |
388 SynchronizeView(source, view, *ctSagittal_, *petSagittal_, *fusionSagittal_); | |
389 | |
390 processingEvent_ = false; | |
391 } | |
392 } | |
393 | |
394 virtual void NotifySliceChange(const LayeredSceneWidget& source, | |
395 const SliceGeometry& slice) | |
396 { | |
397 if (!processingEvent_) // Avoid reentrant calls | |
398 { | |
399 processingEvent_ = true; | |
400 | |
401 SynchronizeSlice(source, slice, *ctAxial_, *petAxial_, *fusionAxial_); | |
402 SynchronizeSlice(source, slice, *ctCoronal_, *petCoronal_, *fusionCoronal_); | |
403 SynchronizeSlice(source, slice, *ctSagittal_, *petSagittal_, *fusionSagittal_); | |
404 | |
405 processingEvent_ = false; | |
406 } | |
407 } | |
408 }; | |
409 } | |
410 } |