comparison Applications/Samples/WebAssembly/SingleFrameViewer/SingleFrameViewerApplication.h @ 1538:d1806b4e4839

moving OrthancStone/Samples/ as Applications/Samples/
author Sebastien Jodogne <s.jodogne@gmail.com>
date Tue, 11 Aug 2020 13:24:38 +0200
parents OrthancStone/Samples/WebAssembly/SingleFrameViewer/SingleFrameViewerApplication.h@244ad1e4e76a
children 4cfdaf4ef3fe
comparison
equal deleted inserted replaced
1537:de8cf5859e84 1538:d1806b4e4839
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-2020 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 "../../../Sources/Loaders/DicomResourcesLoader.h"
25 #include "../../../Sources/Loaders/ILoadersContext.h"
26 #include "../../../Sources/Loaders/SeriesFramesLoader.h"
27 #include "../../../Sources/Loaders/SeriesThumbnailsLoader.h"
28 #include "../../../Sources/Viewport/IViewport.h"
29
30 #include <Compatibility.h> // For std::unique_ptr<>
31
32 #include <boost/make_shared.hpp>
33
34
35 namespace OrthancStone
36 {
37 class Application : public ObserverBase<Application>
38 {
39 private:
40 ILoadersContext& context_;
41 boost::shared_ptr<IViewport> viewport_;
42 boost::shared_ptr<DicomResourcesLoader> dicomLoader_;
43 boost::shared_ptr<SeriesFramesLoader> framesLoader_;
44
45 Application(ILoadersContext& context,
46 boost::shared_ptr<IViewport> viewport) :
47 context_(context),
48 viewport_(viewport)
49 {
50 }
51
52 void Handle(const SeriesFramesLoader::FrameLoadedMessage& message)
53 {
54 LOG(INFO) << "Frame decoded! "
55 << message.GetImage().GetWidth() << "x" << message.GetImage().GetHeight()
56 << " " << Orthanc::EnumerationToString(message.GetImage().GetFormat());
57
58 std::unique_ptr<TextureBaseSceneLayer> layer(
59 message.GetInstanceParameters().CreateTexture(message.GetImage()));
60 layer->SetLinearInterpolation(true);
61
62 {
63 std::unique_ptr<IViewport::ILock> lock(viewport_->Lock());
64 lock->GetController().GetScene().SetLayer(0, layer.release());
65 lock->GetCompositor().FitContent(lock->GetController().GetScene());
66 lock->Invalidate();
67 }
68 }
69
70 void Handle(const DicomResourcesLoader::SuccessMessage& message)
71 {
72 if (message.GetResources()->GetSize() != 1)
73 {
74 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
75 }
76
77 //message.GetResources()->GetResource(0).Print(stdout);
78
79 {
80 std::unique_ptr<ILoadersContext::ILock> lock(context_.Lock());
81 SeriesFramesLoader::Factory f(*message.GetResources());
82
83 framesLoader_ = boost::dynamic_pointer_cast<SeriesFramesLoader>(f.Create(*lock));
84 Register<SeriesFramesLoader::FrameLoadedMessage>(*framesLoader_, &Application::Handle);
85
86 assert(message.HasUserPayload());
87 const Orthanc::SingleValueObject<unsigned int>& payload =
88 dynamic_cast<const Orthanc::SingleValueObject<unsigned int>&>(message.GetUserPayload());
89
90 LOG(INFO) << "Loading pixel data of frame: " << payload.GetValue();
91 framesLoader_->ScheduleLoadFrame(
92 0, message.GetDicomSource(), payload.GetValue(),
93 message.GetDicomSource().GetQualityCount() - 1 /* download best quality available */,
94 NULL);
95 }
96 }
97
98 public:
99 static boost::shared_ptr<Application> Create(ILoadersContext& context,
100 boost::shared_ptr<IViewport> viewport)
101 {
102 boost::shared_ptr<Application> application(new Application(context, viewport));
103
104 {
105 std::unique_ptr<ILoadersContext::ILock> lock(context.Lock());
106 application->dicomLoader_ = DicomResourcesLoader::Create(*lock);
107 }
108
109 application->Register<DicomResourcesLoader::SuccessMessage>(*application->dicomLoader_, &Application::Handle);
110
111 return application;
112 }
113
114 void LoadOrthancFrame(const DicomSource& source,
115 const std::string& instanceId,
116 unsigned int frame)
117 {
118 std::unique_ptr<ILoadersContext::ILock> lock(context_.Lock());
119
120 dicomLoader_->ScheduleLoadOrthancResource(
121 boost::make_shared<LoadedDicomResources>(Orthanc::DICOM_TAG_SOP_INSTANCE_UID),
122 0, source, Orthanc::ResourceType_Instance, instanceId,
123 new Orthanc::SingleValueObject<unsigned int>(frame));
124 }
125
126 void LoadDicomWebFrame(const DicomSource& source,
127 const std::string& studyInstanceUid,
128 const std::string& seriesInstanceUid,
129 const std::string& sopInstanceUid,
130 unsigned int frame)
131 {
132 std::unique_ptr<ILoadersContext::ILock> lock(context_.Lock());
133
134 // We first must load the "/metadata" to know the number of frames
135 dicomLoader_->ScheduleGetDicomWeb(
136 boost::make_shared<LoadedDicomResources>(Orthanc::DICOM_TAG_SOP_INSTANCE_UID), 0, source,
137 "/studies/" + studyInstanceUid + "/series/" + seriesInstanceUid + "/instances/" + sopInstanceUid + "/metadata",
138 new Orthanc::SingleValueObject<unsigned int>(frame));
139 }
140
141 void FitContent()
142 {
143 std::unique_ptr<IViewport::ILock> lock(viewport_->Lock());
144 lock->GetCompositor().FitContent(lock->GetController().GetScene());
145 lock->Invalidate();
146 }
147 };
148
149
150
151 class IWebViewerLoadersObserver : public boost::noncopyable
152 {
153 public:
154 virtual ~IWebViewerLoadersObserver()
155 {
156 }
157
158 virtual void SignalSeriesUpdated(LoadedDicomResources& series) = 0;
159
160 virtual void SignalThumbnailLoaded(const std::string& studyInstanceUid,
161 const std::string& seriesInstanceUid,
162 SeriesThumbnailType type) = 0;
163 };
164
165
166 class WebViewerLoaders : public ObserverBase<WebViewerLoaders>
167 {
168 private:
169 static const int PRIORITY_ADD_RESOURCES = 0;
170 static const int PRIORITY_THUMBNAILS = OracleScheduler::PRIORITY_LOW + 100;
171
172 enum Type
173 {
174 Type_Orthanc = 1,
175 Type_DicomWeb = 2
176 };
177
178 ILoadersContext& context_;
179 std::unique_ptr<IWebViewerLoadersObserver> observer_;
180 bool loadThumbnails_;
181 DicomSource source_;
182 std::set<std::string> scheduledSeries_;
183 std::set<std::string> scheduledThumbnails_;
184 std::set<std::string> scheduledStudies_;
185 boost::shared_ptr<LoadedDicomResources> loadedSeries_;
186 boost::shared_ptr<LoadedDicomResources> loadedStudies_;
187 boost::shared_ptr<DicomResourcesLoader> resourcesLoader_;
188 boost::shared_ptr<SeriesThumbnailsLoader> thumbnailsLoader_;
189
190 WebViewerLoaders(ILoadersContext& context,
191 IWebViewerLoadersObserver* observer) :
192 context_(context),
193 observer_(observer),
194 loadThumbnails_(false)
195 {
196 loadedSeries_ = boost::make_shared<LoadedDicomResources>(Orthanc::DICOM_TAG_SERIES_INSTANCE_UID);
197 loadedStudies_ = boost::make_shared<LoadedDicomResources>(Orthanc::DICOM_TAG_STUDY_INSTANCE_UID);
198 }
199
200 static Orthanc::IDynamicObject* CreatePayload(Type type)
201 {
202 return new Orthanc::SingleValueObject<Type>(type);
203 }
204
205 void HandleThumbnail(const SeriesThumbnailsLoader::SuccessMessage& message)
206 {
207 if (observer_.get() != NULL)
208 {
209 observer_->SignalThumbnailLoaded(message.GetStudyInstanceUid(),
210 message.GetSeriesInstanceUid(),
211 message.GetType());
212 }
213 }
214
215 void HandleLoadedResources(const DicomResourcesLoader::SuccessMessage& message)
216 {
217 LoadedDicomResources series(Orthanc::DICOM_TAG_SERIES_INSTANCE_UID);
218
219 switch (dynamic_cast<const Orthanc::SingleValueObject<Type>&>(message.GetUserPayload()).GetValue())
220 {
221 case Type_DicomWeb:
222 {
223 for (size_t i = 0; i < loadedSeries_->GetSize(); i++)
224 {
225 std::string study;
226 if (loadedSeries_->GetResource(i).LookupStringValue(
227 study, Orthanc::DICOM_TAG_STUDY_INSTANCE_UID, false) &&
228 loadedStudies_->HasResource(study))
229 {
230 Orthanc::DicomMap m;
231 m.Assign(loadedSeries_->GetResource(i));
232 loadedStudies_->MergeResource(m, study);
233 series.AddResource(m);
234 }
235 }
236
237 break;
238 }
239
240 case Type_Orthanc:
241 {
242 for (size_t i = 0; i < message.GetResources()->GetSize(); i++)
243 {
244 series.AddResource(message.GetResources()->GetResource(i));
245 }
246
247 break;
248 }
249
250 default:
251 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
252 }
253
254 if (loadThumbnails_ &&
255 (!source_.IsDicomWeb() ||
256 source_.HasDicomWebRendered()))
257 {
258 for (size_t i = 0; i < series.GetSize(); i++)
259 {
260 std::string patientId, studyInstanceUid, seriesInstanceUid;
261 if (series.GetResource(i).LookupStringValue(patientId, Orthanc::DICOM_TAG_PATIENT_ID, false) &&
262 series.GetResource(i).LookupStringValue(studyInstanceUid, Orthanc::DICOM_TAG_STUDY_INSTANCE_UID, false) &&
263 series.GetResource(i).LookupStringValue(seriesInstanceUid, Orthanc::DICOM_TAG_SERIES_INSTANCE_UID, false) &&
264 scheduledThumbnails_.find(seriesInstanceUid) == scheduledThumbnails_.end())
265 {
266 scheduledThumbnails_.insert(seriesInstanceUid);
267 thumbnailsLoader_->ScheduleLoadThumbnail(source_, patientId, studyInstanceUid, seriesInstanceUid);
268 }
269 }
270 }
271
272 if (observer_.get() != NULL &&
273 series.GetSize() > 0)
274 {
275 observer_->SignalSeriesUpdated(series);
276 }
277 }
278
279 void HandleOrthancRestApi(const OrthancRestApiCommand::SuccessMessage& message)
280 {
281 Json::Value body;
282 message.ParseJsonBody(body);
283
284 if (body.type() != Json::arrayValue)
285 {
286 throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
287 }
288 else
289 {
290 for (Json::Value::ArrayIndex i = 0; i < body.size(); i++)
291 {
292 if (body[i].type() == Json::stringValue)
293 {
294 AddOrthancSeries(body[i].asString());
295 }
296 else
297 {
298 throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
299 }
300 }
301 }
302 }
303
304 public:
305 static boost::shared_ptr<WebViewerLoaders> Create(ILoadersContext& context,
306 const DicomSource& source,
307 bool loadThumbnails,
308 IWebViewerLoadersObserver* observer)
309 {
310 boost::shared_ptr<WebViewerLoaders> application(new WebViewerLoaders(context, observer));
311 application->source_ = source;
312 application->loadThumbnails_ = loadThumbnails;
313
314 {
315 std::unique_ptr<ILoadersContext::ILock> lock(context.Lock());
316
317 application->resourcesLoader_ = DicomResourcesLoader::Create(*lock);
318
319 {
320 SeriesThumbnailsLoader::Factory f;
321 f.SetPriority(PRIORITY_THUMBNAILS);
322 application->thumbnailsLoader_ = boost::dynamic_pointer_cast<SeriesThumbnailsLoader>(f.Create(*lock));
323 }
324
325 application->Register<OrthancRestApiCommand::SuccessMessage>(
326 lock->GetOracleObservable(), &WebViewerLoaders::HandleOrthancRestApi);
327
328 application->Register<DicomResourcesLoader::SuccessMessage>(
329 *application->resourcesLoader_, &WebViewerLoaders::HandleLoadedResources);
330
331 application->Register<SeriesThumbnailsLoader::SuccessMessage>(
332 *application->thumbnailsLoader_, &WebViewerLoaders::HandleThumbnail);
333
334 lock->AddLoader(application);
335 }
336
337 return application;
338 }
339
340 void AddDicomAllSeries()
341 {
342 std::unique_ptr<ILoadersContext::ILock> lock(context_.Lock());
343
344 if (source_.IsDicomWeb())
345 {
346 resourcesLoader_->ScheduleGetDicomWeb(loadedSeries_, PRIORITY_ADD_RESOURCES, source_,
347 "/series", CreatePayload(Type_DicomWeb));
348 resourcesLoader_->ScheduleGetDicomWeb(loadedStudies_, PRIORITY_ADD_RESOURCES, source_,
349 "/studies", CreatePayload(Type_DicomWeb));
350 }
351 else if (source_.IsOrthanc())
352 {
353 std::unique_ptr<OrthancRestApiCommand> command(new OrthancRestApiCommand);
354 command->SetMethod(Orthanc::HttpMethod_Get);
355 command->SetUri("/series");
356 lock->Schedule(GetSharedObserver(), PRIORITY_ADD_RESOURCES, command.release());
357 }
358 else
359 {
360 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
361 }
362 }
363
364 void AddDicomStudy(const std::string& studyInstanceUid)
365 {
366 // Avoid adding twice the same study
367 if (scheduledStudies_.find(studyInstanceUid) == scheduledStudies_.end())
368 {
369 scheduledStudies_.insert(studyInstanceUid);
370
371 if (source_.IsDicomWeb())
372 {
373 Orthanc::DicomMap filter;
374 filter.SetValue(Orthanc::DICOM_TAG_STUDY_INSTANCE_UID, studyInstanceUid, false);
375
376 std::set<Orthanc::DicomTag> tags;
377
378 {
379 std::unique_ptr<ILoadersContext::ILock> lock(context_.Lock());
380
381 resourcesLoader_->ScheduleQido(loadedStudies_, PRIORITY_ADD_RESOURCES, source_,
382 Orthanc::ResourceType_Study, filter, tags, CreatePayload(Type_DicomWeb));
383
384 resourcesLoader_->ScheduleQido(loadedSeries_, PRIORITY_ADD_RESOURCES, source_,
385 Orthanc::ResourceType_Series, filter, tags, CreatePayload(Type_DicomWeb));
386 }
387 }
388 else if (source_.IsOrthanc())
389 {
390 std::unique_ptr<OrthancRestApiCommand> command(new OrthancRestApiCommand);
391 command->SetMethod(Orthanc::HttpMethod_Post);
392 command->SetUri("/tools/find");
393
394 Json::Value body;
395 body["Level"] = "Series";
396 body["Query"] = Json::objectValue;
397 body["Query"]["StudyInstanceUID"] = studyInstanceUid;
398 command->SetBody(body);
399
400 {
401 std::unique_ptr<ILoadersContext::ILock> lock(context_.Lock());
402 lock->Schedule(GetSharedObserver(), PRIORITY_ADD_RESOURCES, command.release());
403 }
404 }
405 else
406 {
407 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
408 }
409 }
410 }
411
412 void AddDicomSeries(const std::string& studyInstanceUid,
413 const std::string& seriesInstanceUid)
414 {
415 std::set<Orthanc::DicomTag> tags;
416
417 std::unique_ptr<ILoadersContext::ILock> lock(context_.Lock());
418
419 if (scheduledStudies_.find(studyInstanceUid) == scheduledStudies_.end())
420 {
421 scheduledStudies_.insert(studyInstanceUid);
422
423 if (source_.IsDicomWeb())
424 {
425 Orthanc::DicomMap filter;
426 filter.SetValue(Orthanc::DICOM_TAG_STUDY_INSTANCE_UID, studyInstanceUid, false);
427
428 resourcesLoader_->ScheduleQido(loadedStudies_, PRIORITY_ADD_RESOURCES, source_,
429 Orthanc::ResourceType_Study, filter, tags, CreatePayload(Type_DicomWeb));
430 }
431 }
432
433 if (scheduledSeries_.find(seriesInstanceUid) == scheduledSeries_.end())
434 {
435 scheduledSeries_.insert(seriesInstanceUid);
436
437 if (source_.IsDicomWeb())
438 {
439 Orthanc::DicomMap filter;
440 filter.SetValue(Orthanc::DICOM_TAG_STUDY_INSTANCE_UID, studyInstanceUid, false);
441 filter.SetValue(Orthanc::DICOM_TAG_SERIES_INSTANCE_UID, seriesInstanceUid, false);
442
443 resourcesLoader_->ScheduleQido(loadedSeries_, PRIORITY_ADD_RESOURCES, source_,
444 Orthanc::ResourceType_Series, filter, tags, CreatePayload(Type_DicomWeb));
445 }
446 else if (source_.IsOrthanc())
447 {
448 std::unique_ptr<OrthancRestApiCommand> command(new OrthancRestApiCommand);
449 command->SetMethod(Orthanc::HttpMethod_Post);
450 command->SetUri("/tools/find");
451
452 Json::Value body;
453 body["Level"] = "Series";
454 body["Query"] = Json::objectValue;
455 body["Query"]["StudyInstanceUID"] = studyInstanceUid;
456 body["Query"]["SeriesInstanceUID"] = seriesInstanceUid;
457 command->SetBody(body);
458
459 lock->Schedule(GetSharedObserver(), PRIORITY_ADD_RESOURCES, command.release());
460 }
461 else
462 {
463 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
464 }
465 }
466 }
467
468 void AddOrthancStudy(const std::string& orthancId)
469 {
470 if (source_.IsOrthanc())
471 {
472 std::unique_ptr<ILoadersContext::ILock> lock(context_.Lock());
473 resourcesLoader_->ScheduleLoadOrthancResources(
474 loadedSeries_, PRIORITY_ADD_RESOURCES, source_,
475 Orthanc::ResourceType_Study, orthancId, Orthanc::ResourceType_Series,
476 CreatePayload(Type_Orthanc));
477 }
478 else
479 {
480 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadParameterType,
481 "Only applicable to Orthanc DICOM sources");
482 }
483 }
484
485 void AddOrthancSeries(const std::string& orthancId)
486 {
487 if (source_.IsOrthanc())
488 {
489 std::unique_ptr<ILoadersContext::ILock> lock(context_.Lock());
490 resourcesLoader_->ScheduleLoadOrthancResource(
491 loadedSeries_, PRIORITY_ADD_RESOURCES,
492 source_, Orthanc::ResourceType_Series, orthancId,
493 CreatePayload(Type_Orthanc));
494 }
495 else
496 {
497 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadParameterType,
498 "Only applicable to Orthanc DICOM sources");
499 }
500 }
501 };
502 }