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