comparison Applications/Samples/SimpleViewerApplicationSingleFile.h @ 319:daa04d15192c am-2

new SimpleViewer sample that has been split in multiple files to be able to scale it
author am@osimis.io
date Thu, 11 Oct 2018 13:16:54 +0200
parents
children 612238b3f3e8
comparison
equal deleted inserted replaced
318:3a4ca166fafa 319:daa04d15192c
1 /**
2 * Stone of Orthanc
3 * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
4 * Department, University Hospital of Liege, Belgium
5 * Copyright (C) 2017-2018 Osimis S.A., Belgium
6 *
7 * This program is free software: you can redistribute it and/or
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.
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
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
18 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 **/
20
21
22 #pragma once
23
24 #include "SampleApplicationBase.h"
25
26 #include "../../Framework/Layers/OrthancFrameLayerSource.h"
27 #include "../../Framework/Layers/CircleMeasureTracker.h"
28 #include "../../Framework/Layers/LineMeasureTracker.h"
29 #include "../../Framework/Widgets/LayerWidget.h"
30 #include "../../Framework/Widgets/LayoutWidget.h"
31 #include "../../Framework/Messages/IObserver.h"
32 #include "../../Framework/SmartLoader.h"
33
34 #if ORTHANC_ENABLE_WASM==1
35 #include "../../Platforms/Wasm/WasmPlatformApplicationAdapter.h"
36 #include "../../Platforms/Wasm/Defaults.h"
37 #endif
38
39 #if ORTHANC_ENABLE_QT==1
40 #include "Qt/SampleMainWindow.h"
41 #endif
42 #include <Core/Logging.h>
43
44 namespace OrthancStone
45 {
46 namespace Samples
47 {
48 class SimpleViewerApplication :
49 public SampleApplicationBase,
50 public IObserver
51 {
52 private:
53 class ThumbnailInteractor : public IWorldSceneInteractor
54 {
55 private:
56 SimpleViewerApplication& application_;
57 public:
58 ThumbnailInteractor(SimpleViewerApplication& application) :
59 application_(application)
60 {
61 }
62
63 virtual IWorldSceneMouseTracker* CreateMouseTracker(WorldSceneWidget& widget,
64 const ViewportGeometry& view,
65 MouseButton button,
66 KeyboardModifiers modifiers,
67 double x,
68 double y,
69 IStatusBar* statusBar)
70 {
71 if (button == MouseButton_Left)
72 {
73 statusBar->SetMessage("selected thumbnail " + widget.GetName());
74 std::string seriesId = widget.GetName().substr(strlen("thumbnail-series-"));
75 application_.SelectSeriesInMainViewport(seriesId);
76 }
77 return NULL;
78 }
79 virtual void MouseOver(CairoContext& context,
80 WorldSceneWidget& widget,
81 const ViewportGeometry& view,
82 double x,
83 double y,
84 IStatusBar* statusBar)
85 {}
86
87 virtual void MouseWheel(WorldSceneWidget& widget,
88 MouseWheelDirection direction,
89 KeyboardModifiers modifiers,
90 IStatusBar* statusBar)
91 {}
92
93 virtual void KeyPressed(WorldSceneWidget& widget,
94 char key,
95 KeyboardModifiers modifiers,
96 IStatusBar* statusBar)
97 {}
98
99 };
100
101 class MainWidgetInteractor : public IWorldSceneInteractor
102 {
103 private:
104 SimpleViewerApplication& application_;
105
106 public:
107 MainWidgetInteractor(SimpleViewerApplication& application) :
108 application_(application)
109 {
110 }
111
112 virtual IWorldSceneMouseTracker* CreateMouseTracker(WorldSceneWidget& widget,
113 const ViewportGeometry& view,
114 MouseButton button,
115 KeyboardModifiers modifiers,
116 double x,
117 double y,
118 IStatusBar* statusBar)
119 {
120 if (button == MouseButton_Left)
121 {
122 if (application_.currentTool_ == Tools_LineMeasure)
123 {
124 return new LineMeasureTracker(statusBar, dynamic_cast<LayerWidget&>(widget).GetSlice(), x, y, 255, 0, 0, 10);
125 }
126 else if (application_.currentTool_ == Tools_CircleMeasure)
127 {
128 return new CircleMeasureTracker(statusBar, dynamic_cast<LayerWidget&>(widget).GetSlice(), x, y, 255, 0, 0, 10);
129 }
130 }
131 return NULL;
132 }
133
134 virtual void MouseOver(CairoContext& context,
135 WorldSceneWidget& widget,
136 const ViewportGeometry& view,
137 double x,
138 double y,
139 IStatusBar* statusBar)
140 {
141 if (statusBar != NULL)
142 {
143 Vector p = dynamic_cast<LayerWidget&>(widget).GetSlice().MapSliceToWorldCoordinates(x, y);
144
145 char buf[64];
146 sprintf(buf, "X = %.02f Y = %.02f Z = %.02f (in cm)",
147 p[0] / 10.0, p[1] / 10.0, p[2] / 10.0);
148 statusBar->SetMessage(buf);
149 }
150 }
151
152 virtual void MouseWheel(WorldSceneWidget& widget,
153 MouseWheelDirection direction,
154 KeyboardModifiers modifiers,
155 IStatusBar* statusBar)
156 {
157 }
158
159 virtual void KeyPressed(WorldSceneWidget& widget,
160 char key,
161 KeyboardModifiers modifiers,
162 IStatusBar* statusBar)
163 {
164 switch (key)
165 {
166 case 's':
167 widget.SetDefaultView();
168 break;
169
170 default:
171 break;
172 }
173 }
174 };
175
176
177 #if ORTHANC_ENABLE_WASM==1
178 class SimpleViewerApplicationAdapter : public WasmPlatformApplicationAdapter
179 {
180 SimpleViewerApplication& viewerApplication_;
181
182 public:
183 SimpleViewerApplicationAdapter(MessageBroker& broker, SimpleViewerApplication& application)
184 : WasmPlatformApplicationAdapter(broker, application),
185 viewerApplication_(application)
186 {
187
188 }
189
190 virtual void HandleMessageFromWeb(std::string& output, const std::string& input) {
191 if (input == "select-tool:line-measure")
192 {
193 viewerApplication_.currentTool_ = Tools_LineMeasure;
194 NotifyStatusUpdateFromCppToWeb("currentTool=line-measure");
195 }
196 else if (input == "select-tool:circle-measure")
197 {
198 viewerApplication_.currentTool_ = Tools_CircleMeasure;
199 NotifyStatusUpdateFromCppToWeb("currentTool=circle-measure");
200 }
201
202 output = "ok";
203 }
204
205 virtual void NotifyStatusUpdateFromCppToWeb(const std::string& statusUpdateMessage) {
206 UpdateStoneApplicationStatusFromCpp(statusUpdateMessage.c_str());
207 }
208
209 };
210 #endif
211 enum Tools {
212 Tools_LineMeasure,
213 Tools_CircleMeasure
214 };
215
216 Tools currentTool_;
217 std::unique_ptr<MainWidgetInteractor> mainWidgetInteractor_;
218 std::unique_ptr<ThumbnailInteractor> thumbnailInteractor_;
219 LayoutWidget* mainLayout_;
220 LayoutWidget* thumbnailsLayout_;
221 LayerWidget* mainWidget_;
222 std::vector<LayerWidget*> thumbnails_;
223 std::map<std::string, std::vector<std::string>> instancesIdsPerSeriesId_;
224 std::map<std::string, Json::Value> seriesTags_;
225
226 unsigned int currentInstanceIndex_;
227 OrthancStone::WidgetViewport* wasmViewport1_;
228 OrthancStone::WidgetViewport* wasmViewport2_;
229
230 IStatusBar* statusBar_;
231 std::unique_ptr<SmartLoader> smartLoader_;
232 std::unique_ptr<OrthancApiClient> orthancApiClient_;
233
234 public:
235 SimpleViewerApplication(MessageBroker& broker) :
236 IObserver(broker),
237 currentTool_(Tools_LineMeasure),
238 mainLayout_(NULL),
239 currentInstanceIndex_(0),
240 wasmViewport1_(NULL),
241 wasmViewport2_(NULL)
242 {
243 // DeclareIgnoredMessage(MessageType_Widget_ContentChanged);
244 }
245
246 virtual void Finalize() {}
247 virtual IWidget* GetCentralWidget() {return mainLayout_;}
248
249 virtual void DeclareStartupOptions(boost::program_options::options_description& options)
250 {
251 boost::program_options::options_description generic("Sample options");
252 generic.add_options()
253 ("studyId", boost::program_options::value<std::string>(),
254 "Orthanc ID of the study")
255 ;
256
257 options.add(generic);
258 }
259
260 virtual void Initialize(StoneApplicationContext* context,
261 IStatusBar& statusBar,
262 const boost::program_options::variables_map& parameters)
263 {
264 using namespace OrthancStone;
265
266 context_ = context;
267 statusBar_ = &statusBar;
268
269 {// initialize viewports and layout
270 mainLayout_ = new LayoutWidget("main-layout");
271 mainLayout_->SetPadding(10);
272 mainLayout_->SetBackgroundCleared(true);
273 mainLayout_->SetBackgroundColor(0, 0, 0);
274 mainLayout_->SetHorizontal();
275
276 thumbnailsLayout_ = new LayoutWidget("thumbnail-layout");
277 thumbnailsLayout_->SetPadding(10);
278 thumbnailsLayout_->SetBackgroundCleared(true);
279 thumbnailsLayout_->SetBackgroundColor(50, 50, 50);
280 thumbnailsLayout_->SetVertical();
281
282 mainWidget_ = new LayerWidget(broker_, "main-viewport");
283 //mainWidget_->RegisterObserver(*this);
284
285 // hierarchy
286 mainLayout_->AddWidget(thumbnailsLayout_);
287 mainLayout_->AddWidget(mainWidget_);
288
289 orthancApiClient_.reset(new OrthancApiClient(IObserver::broker_, context_->GetWebService()));
290
291 // sources
292 smartLoader_.reset(new SmartLoader(IObserver::broker_, *orthancApiClient_));
293 smartLoader_->SetImageQuality(SliceImageQuality_FullPam);
294
295 mainLayout_->SetTransmitMouseOver(true);
296 mainWidgetInteractor_.reset(new MainWidgetInteractor(*this));
297 mainWidget_->SetInteractor(*mainWidgetInteractor_);
298 thumbnailInteractor_.reset(new ThumbnailInteractor(*this));
299 }
300
301 statusBar.SetMessage("Use the key \"s\" to reinitialize the layout");
302 statusBar.SetMessage("Use the key \"n\" to go to next image in the main viewport");
303
304
305 if (parameters.count("studyId") < 1)
306 {
307 LOG(WARNING) << "The study ID is missing, will take the first studyId found in Orthanc";
308 orthancApiClient_->GetJsonAsync("/studies", new Callable<SimpleViewerApplication, OrthancApiClient::JsonResponseReadyMessage>(*this, &SimpleViewerApplication::OnStudyListReceived));
309 }
310 else
311 {
312 SelectStudy(parameters["studyId"].as<std::string>());
313 }
314 }
315
316 void OnStudyListReceived(const OrthancApiClient::JsonResponseReadyMessage& message)
317 {
318 const Json::Value& response = message.Response;
319
320 if (response.isArray() && response.size() > 1)
321 {
322 SelectStudy(response[0].asString());
323 }
324 }
325 void OnStudyReceived(const OrthancApiClient::JsonResponseReadyMessage& message)
326 {
327 const Json::Value& response = message.Response;
328
329 if (response.isObject() && response["Series"].isArray())
330 {
331 for (size_t i=0; i < response["Series"].size(); i++)
332 {
333 orthancApiClient_->GetJsonAsync("/series/" + response["Series"][(int)i].asString(), new Callable<SimpleViewerApplication, OrthancApiClient::JsonResponseReadyMessage>(*this, &SimpleViewerApplication::OnSeriesReceived));
334 }
335 }
336 }
337
338 void OnSeriesReceived(const OrthancApiClient::JsonResponseReadyMessage& message)
339 {
340 const Json::Value& response = message.Response;
341
342 if (response.isObject() && response["Instances"].isArray() && response["Instances"].size() > 0)
343 {
344 // keep track of all instances IDs
345 const std::string& seriesId = response["ID"].asString();
346 seriesTags_[seriesId] = response;
347 instancesIdsPerSeriesId_[seriesId] = std::vector<std::string>();
348 for (size_t i = 0; i < response["Instances"].size(); i++)
349 {
350 const std::string& instanceId = response["Instances"][static_cast<int>(i)].asString();
351 instancesIdsPerSeriesId_[seriesId].push_back(instanceId);
352 }
353
354 // load the first instance in the thumbnail
355 LoadThumbnailForSeries(seriesId, instancesIdsPerSeriesId_[seriesId][0]);
356
357 // if this is the first thumbnail loaded, load the first instance in the mainWidget
358 if (mainWidget_->GetLayerCount() == 0)
359 {
360 smartLoader_->SetFrameInWidget(*mainWidget_, 0, instancesIdsPerSeriesId_[seriesId][0], 0);
361 }
362 }
363 }
364
365 void LoadThumbnailForSeries(const std::string& seriesId, const std::string& instanceId)
366 {
367 LOG(INFO) << "Loading thumbnail for series " << seriesId;
368 LayerWidget* thumbnailWidget = new LayerWidget(IObserver::broker_, "thumbnail-series-" + seriesId);
369 thumbnails_.push_back(thumbnailWidget);
370 thumbnailsLayout_->AddWidget(thumbnailWidget);
371 thumbnailWidget->RegisterObserverCallback(new Callable<SimpleViewerApplication, LayerWidget::GeometryChangedMessage>(*this, &SimpleViewerApplication::OnWidgetGeometryChanged));
372 smartLoader_->SetFrameInWidget(*thumbnailWidget, 0, instanceId, 0);
373 thumbnailWidget->SetInteractor(*thumbnailInteractor_);
374 }
375
376 void SelectStudy(const std::string& studyId)
377 {
378 orthancApiClient_->GetJsonAsync("/studies/" + studyId, new Callable<SimpleViewerApplication, OrthancApiClient::JsonResponseReadyMessage>(*this, &SimpleViewerApplication::OnStudyReceived));
379 }
380
381 void OnWidgetGeometryChanged(const LayerWidget::GeometryChangedMessage& message)
382 {
383 message.origin_.SetDefaultView();
384 }
385
386 void SelectSeriesInMainViewport(const std::string& seriesId)
387 {
388 smartLoader_->SetFrameInWidget(*mainWidget_, 0, instancesIdsPerSeriesId_[seriesId][0], 0);
389 }
390
391 virtual void OnPushButton1Clicked() {}
392 virtual void OnPushButton2Clicked() {}
393 virtual void OnTool1Clicked() { currentTool_ = Tools_LineMeasure;}
394 virtual void OnTool2Clicked() { currentTool_ = Tools_CircleMeasure;}
395
396 virtual void GetButtonNames(std::string& pushButton1,
397 std::string& pushButton2,
398 std::string& tool1,
399 std::string& tool2
400 ) {
401 tool1 = "line";
402 tool2 = "circle";
403 pushButton1 = "action1";
404 pushButton2 = "action2";
405 }
406
407 #if ORTHANC_ENABLE_WASM==1
408 virtual void InitializeWasm() {
409
410 AttachWidgetToWasmViewport("canvas", thumbnailsLayout_);
411 AttachWidgetToWasmViewport("canvas2", mainWidget_);
412 }
413 #endif
414
415 #if ORTHANC_ENABLE_QT==1
416 virtual QStoneMainWindow* CreateQtMainWindow() {
417 return new SampleMainWindow(dynamic_cast<OrthancStone::NativeStoneApplicationContext&>(*context_), *this);
418 }
419 #endif
420 };
421
422
423 }
424 }