0
|
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
|
16
|
41 #include "../Resources/Orthanc/Core/Logging.h"
|
0
|
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;
|
25
|
148 style.alpha_ = (layer == 0 ? 1.0f : 0.5f);
|
0
|
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 }
|