Mercurial > hg > orthanc-stone
comparison Applications/StoneWebViewer/WebAssembly/StoneWebViewer.cpp @ 1849:023cce3d7844
introduction of the concept of "virtual series"
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Tue, 29 Jun 2021 12:12:46 +0200 |
parents | 21ccc00839f7 |
children | 932dc2265baa |
comparison
equal
deleted
inserted
replaced
1848:3751485f1b2e | 1849:023cce3d7844 |
---|---|
185 static const unsigned int QUALITY_FULL = 1; | 185 static const unsigned int QUALITY_FULL = 1; |
186 | 186 |
187 static const unsigned int DEFAULT_CINE_RATE = 30; | 187 static const unsigned int DEFAULT_CINE_RATE = 30; |
188 | 188 |
189 | 189 |
190 | |
191 class VirtualSeries : public boost::noncopyable | |
192 { | |
193 private: | |
194 class Item | |
195 { | |
196 private: | |
197 std::string seriesInstanceUid_; | |
198 unsigned int numberOfFrames_; | |
199 | |
200 public: | |
201 Item(const std::string& seriesInstanceUid, | |
202 unsigned int numberOfFrames) : | |
203 seriesInstanceUid_(seriesInstanceUid), | |
204 numberOfFrames_(numberOfFrames) | |
205 { | |
206 if (numberOfFrames == 0) | |
207 { | |
208 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); | |
209 } | |
210 } | |
211 | |
212 const std::string& GetSeriesInstanceUid() const | |
213 { | |
214 return seriesInstanceUid_; | |
215 } | |
216 | |
217 unsigned int GetNumberOfFrames() const | |
218 { | |
219 return numberOfFrames_; | |
220 } | |
221 }; | |
222 | |
223 typedef std::map<std::string, Item> Content; | |
224 | |
225 Content content_; | |
226 | |
227 const Item& GetItem(const std::string& id) const | |
228 { | |
229 Content::const_iterator found = content_.find(id); | |
230 | |
231 if (found == content_.end()) | |
232 { | |
233 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); | |
234 } | |
235 else | |
236 { | |
237 return found->second; | |
238 } | |
239 } | |
240 | |
241 public: | |
242 std::string Add(const std::string& seriesInstanceUid, | |
243 const std::string& sopInstanceUid, | |
244 unsigned int numberOfFrames) | |
245 { | |
246 if (content_.find(sopInstanceUid) != content_.end()) | |
247 { | |
248 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); | |
249 } | |
250 else | |
251 { | |
252 content_.insert(std::make_pair(sopInstanceUid, Item(seriesInstanceUid, numberOfFrames))); | |
253 return sopInstanceUid; | |
254 } | |
255 } | |
256 | |
257 const std::string& GetSeriesInstanceUid(const std::string& id) const | |
258 { | |
259 return GetItem(id).GetSeriesInstanceUid(); | |
260 } | |
261 | |
262 unsigned int GetNumberOfFrames(const std::string& id) const | |
263 { | |
264 return GetItem(id).GetNumberOfFrames(); | |
265 } | |
266 }; | |
267 | |
268 | |
269 | |
190 class ResourcesLoader : public OrthancStone::ObserverBase<ResourcesLoader> | 270 class ResourcesLoader : public OrthancStone::ObserverBase<ResourcesLoader> |
191 { | 271 { |
192 public: | 272 public: |
193 class IObserver : public boost::noncopyable | 273 class IObserver : public boost::noncopyable |
194 { | 274 { |
207 | 287 |
208 virtual void SignalSeriesPdfLoaded(const std::string& studyInstanceUid, | 288 virtual void SignalSeriesPdfLoaded(const std::string& studyInstanceUid, |
209 const std::string& seriesInstanceUid, | 289 const std::string& seriesInstanceUid, |
210 const std::string& pdf) = 0; | 290 const std::string& pdf) = 0; |
211 | 291 |
212 virtual void SignalMultiframeInstanceThumbnailLoaded(const std::string& sopInstanceUid, | 292 virtual void SignalVirtualSeriesThumbnailLoaded(const std::string& virtualSeriesId, |
213 const std::string& jpeg) = 0; | 293 const std::string& jpeg) = 0; |
214 }; | 294 }; |
215 | 295 |
216 private: | 296 private: |
217 OrthancStone::ILoadersContext& context_; | 297 OrthancStone::ILoadersContext& context_; |
218 std::unique_ptr<IObserver> observer_; | 298 std::unique_ptr<IObserver> observer_; |
221 boost::shared_ptr<OrthancStone::LoadedDicomResources> studies_; | 301 boost::shared_ptr<OrthancStone::LoadedDicomResources> studies_; |
222 boost::shared_ptr<OrthancStone::LoadedDicomResources> series_; | 302 boost::shared_ptr<OrthancStone::LoadedDicomResources> series_; |
223 boost::shared_ptr<OrthancStone::DicomResourcesLoader> resourcesLoader_; | 303 boost::shared_ptr<OrthancStone::DicomResourcesLoader> resourcesLoader_; |
224 boost::shared_ptr<OrthancStone::SeriesThumbnailsLoader> thumbnailsLoader_; | 304 boost::shared_ptr<OrthancStone::SeriesThumbnailsLoader> thumbnailsLoader_; |
225 boost::shared_ptr<OrthancStone::SeriesMetadataLoader> metadataLoader_; | 305 boost::shared_ptr<OrthancStone::SeriesMetadataLoader> metadataLoader_; |
226 std::set<std::string> scheduledMultiframeInstances_; | 306 std::set<std::string> scheduledVirtualSeriesThumbnails_; |
227 | 307 |
228 explicit ResourcesLoader(OrthancStone::ILoadersContext& context, | 308 explicit ResourcesLoader(OrthancStone::ILoadersContext& context, |
229 const OrthancStone::DicomSource& source) : | 309 const OrthancStone::DicomSource& source) : |
230 context_(context), | 310 context_(context), |
231 source_(source), | 311 source_(source), |
376 LOG(ERROR) << "Unable to extract PDF from series: " << info.GetSeriesInstanceUid(); | 456 LOG(ERROR) << "Unable to extract PDF from series: " << info.GetSeriesInstanceUid(); |
377 } | 457 } |
378 } | 458 } |
379 } | 459 } |
380 | 460 |
381 void FetchInstanceThumbnail(const std::string& studyInstanceUid, | 461 void FetchVirtualSeriesThumbnail(const std::string& virtualSeriesId, |
382 const std::string& seriesInstanceUid, | 462 const std::string& studyInstanceUid, |
383 const std::string& sopInstanceUid) | 463 const std::string& seriesInstanceUid, |
384 { | 464 const std::string& sopInstanceUid) |
385 if (scheduledMultiframeInstances_.find(sopInstanceUid) == scheduledMultiframeInstances_.end()) | 465 { |
386 { | 466 if (scheduledVirtualSeriesThumbnails_.find(virtualSeriesId) == scheduledVirtualSeriesThumbnails_.end()) |
387 scheduledMultiframeInstances_.insert(sopInstanceUid); | 467 { |
468 scheduledVirtualSeriesThumbnails_.insert(virtualSeriesId); | |
388 | 469 |
389 std::map<std::string, std::string> arguments; | 470 std::map<std::string, std::string> arguments; |
390 std::map<std::string, std::string> headers; | 471 std::map<std::string, std::string> headers; |
391 arguments["viewport"] = ( | 472 arguments["viewport"] = ( |
392 boost::lexical_cast<std::string>(thumbnailsLoader_->GetThumbnailWidth()) + "," + | 473 boost::lexical_cast<std::string>(thumbnailsLoader_->GetThumbnailWidth()) + "," + |
398 | 479 |
399 { | 480 { |
400 std::unique_ptr<OrthancStone::ILoadersContext::ILock> lock(context_.Lock()); | 481 std::unique_ptr<OrthancStone::ILoadersContext::ILock> lock(context_.Lock()); |
401 lock->Schedule( | 482 lock->Schedule( |
402 GetSharedObserver(), PRIORITY_LOW + 2, source_.CreateDicomWebCommand( | 483 GetSharedObserver(), PRIORITY_LOW + 2, source_.CreateDicomWebCommand( |
403 uri, arguments, headers, new Orthanc::SingleValueObject<std::string>(sopInstanceUid))); | 484 uri, arguments, headers, new Orthanc::SingleValueObject<std::string>(virtualSeriesId))); |
404 } | 485 } |
405 } | 486 } |
406 } | 487 } |
407 | 488 |
408 void HandleInstanceThumbnail(const OrthancStone::HttpCommand::SuccessMessage& message) | 489 void HandleInstanceThumbnail(const OrthancStone::HttpCommand::SuccessMessage& message) |
409 { | 490 { |
410 if (observer_.get() != NULL) | 491 if (observer_.get() != NULL) |
411 { | 492 { |
412 const std::string& sopInstanceUid = | 493 const std::string& virtualSeriesId = |
413 dynamic_cast<const Orthanc::SingleValueObject<std::string>&>( | 494 dynamic_cast<const Orthanc::SingleValueObject<std::string>&>( |
414 message.GetOrigin().GetPayload()).GetValue(); | 495 message.GetOrigin().GetPayload()).GetValue(); |
415 observer_->SignalMultiframeInstanceThumbnailLoaded(sopInstanceUid, message.GetAnswer()); | 496 observer_->SignalVirtualSeriesThumbnailLoaded(virtualSeriesId, message.GetAnswer()); |
416 } | 497 } |
417 } | 498 } |
418 | 499 |
419 public: | 500 public: |
420 static boost::shared_ptr<ResourcesLoader> Create(OrthancStone::ILoadersContext::ILock& lock, | 501 static boost::shared_ptr<ResourcesLoader> Create(OrthancStone::ILoadersContext::ILock& lock, |
523 { | 604 { |
524 OrthancStone::SeriesMetadataLoader::Accessor accessor(*metadataLoader_, seriesInstanceUid); | 605 OrthancStone::SeriesMetadataLoader::Accessor accessor(*metadataLoader_, seriesInstanceUid); |
525 return accessor.IsComplete(); | 606 return accessor.IsComplete(); |
526 } | 607 } |
527 | 608 |
528 bool LookupMultiframeSeries(std::map<std::string, unsigned int>& numberOfFramesPerInstance, | 609 bool LookupVirtualSeries(VirtualSeries& target /* out */, |
529 const std::string& seriesInstanceUid) | 610 std::set<std::string>& virtualSeriesIds /* out */, |
530 { | 611 const std::string& seriesInstanceUid) |
531 numberOfFramesPerInstance.clear(); | 612 { |
532 | |
533 OrthancStone::SeriesMetadataLoader::Accessor accessor(*metadataLoader_, seriesInstanceUid); | 613 OrthancStone::SeriesMetadataLoader::Accessor accessor(*metadataLoader_, seriesInstanceUid); |
534 if (accessor.IsComplete() && | 614 if (accessor.IsComplete() && |
535 accessor.GetInstancesCount() >= 2) | 615 accessor.GetInstancesCount() >= 2) |
536 { | 616 { |
537 bool isMultiframe = false; | 617 bool hasMultiframe = false; |
538 | 618 |
539 for (size_t i = 0; i < accessor.GetInstancesCount(); i++) | 619 for (size_t i = 0; i < accessor.GetInstancesCount(); i++) |
540 { | 620 { |
541 OrthancStone::DicomInstanceParameters p(accessor.GetInstance(i)); | 621 OrthancStone::DicomInstanceParameters p(accessor.GetInstance(i)); |
542 numberOfFramesPerInstance[p.GetSopInstanceUid()] = p.GetNumberOfFrames(); | |
543 | 622 |
544 if (p.GetNumberOfFrames() > 1) | 623 if (p.GetNumberOfFrames() > 1) |
545 { | 624 { |
546 isMultiframe = true; | 625 hasMultiframe = true; |
547 } | 626 } |
548 } | 627 } |
549 | 628 |
550 if (isMultiframe) | 629 if (hasMultiframe) |
551 { | 630 { |
552 for (size_t i = 0; i < accessor.GetInstancesCount(); i++) | 631 for (size_t i = 0; i < accessor.GetInstancesCount(); i++) |
553 { | 632 { |
554 OrthancStone::DicomInstanceParameters p(accessor.GetInstance(i)); | 633 OrthancStone::DicomInstanceParameters p(accessor.GetInstance(i)); |
555 FetchInstanceThumbnail(p.GetStudyInstanceUid(), p.GetSeriesInstanceUid(), p.GetSopInstanceUid()); | 634 |
635 std::string virtualSeriesId = target.Add(seriesInstanceUid, p.GetSopInstanceUid(), p.GetNumberOfFrames()); | |
636 virtualSeriesIds.insert(virtualSeriesId); | |
637 | |
638 FetchVirtualSeriesThumbnail(virtualSeriesId, p.GetStudyInstanceUid(), p.GetSeriesInstanceUid(), p.GetSopInstanceUid()); | |
556 } | 639 } |
557 } | 640 |
558 | 641 return true; |
559 return isMultiframe; | 642 } |
643 else | |
644 { | |
645 return false; | |
646 } | |
560 } | 647 } |
561 else | 648 else |
562 { | 649 { |
563 return false; | 650 return false; |
564 } | 651 } |
3185 pdf.empty() ? 0 : reinterpret_cast<intptr_t>(pdf.c_str()), // Explicit conversion to an integer | 3272 pdf.empty() ? 0 : reinterpret_cast<intptr_t>(pdf.c_str()), // Explicit conversion to an integer |
3186 pdf.size()); | 3273 pdf.size()); |
3187 } | 3274 } |
3188 | 3275 |
3189 | 3276 |
3190 virtual void SignalMultiframeInstanceThumbnailLoaded(const std::string& sopInstanceUid, | 3277 virtual void SignalVirtualSeriesThumbnailLoaded(const std::string& virtualSeriesId, |
3191 const std::string& jpeg) ORTHANC_OVERRIDE | 3278 const std::string& jpeg) ORTHANC_OVERRIDE |
3192 { | 3279 { |
3193 std::string dataUriScheme; | 3280 std::string dataUriScheme; |
3194 Orthanc::Toolbox::EncodeDataUriScheme(dataUriScheme, "image/jpeg", jpeg); | 3281 Orthanc::Toolbox::EncodeDataUriScheme(dataUriScheme, "image/jpeg", jpeg); |
3195 | 3282 |
3196 EM_ASM({ | 3283 EM_ASM({ |
3197 const customEvent = document.createEvent("CustomEvent"); | 3284 const customEvent = document.createEvent("CustomEvent"); |
3198 customEvent.initCustomEvent("MultiframeInstanceThumbnailLoaded", false, false, | 3285 customEvent.initCustomEvent("VirtualSeriesThumbnailLoaded", false, false, |
3199 { "sopInstanceUid" : UTF8ToString($0), | 3286 { "virtualSeriesId" : UTF8ToString($0), |
3200 "thumbnail" : UTF8ToString($1) }); | 3287 "thumbnail" : UTF8ToString($1) }); |
3201 window.dispatchEvent(customEvent); | 3288 window.dispatchEvent(customEvent); |
3202 }, | 3289 }, |
3203 sopInstanceUid.c_str(), | 3290 virtualSeriesId.c_str(), |
3204 dataUriScheme.c_str()); | 3291 dataUriScheme.c_str()); |
3205 } | 3292 } |
3206 | 3293 |
3207 virtual void SignalWindowingUpdated(const ViewerViewport& viewport, | 3294 virtual void SignalWindowingUpdated(const ViewerViewport& viewport, |
3208 double windowingCenter, | 3295 double windowingCenter, |
3269 static std::string stringBuffer_; | 3356 static std::string stringBuffer_; |
3270 static bool softwareRendering_ = false; | 3357 static bool softwareRendering_ = false; |
3271 static WebViewerAction leftButtonAction_ = WebViewerAction_Windowing; | 3358 static WebViewerAction leftButtonAction_ = WebViewerAction_Windowing; |
3272 static WebViewerAction middleButtonAction_ = WebViewerAction_Pan; | 3359 static WebViewerAction middleButtonAction_ = WebViewerAction_Pan; |
3273 static WebViewerAction rightButtonAction_ = WebViewerAction_Zoom; | 3360 static WebViewerAction rightButtonAction_ = WebViewerAction_Zoom; |
3361 static VirtualSeries virtualSeries_; | |
3274 | 3362 |
3275 | 3363 |
3276 static void FormatTags(std::string& target, | 3364 static void FormatTags(std::string& target, |
3277 const Orthanc::DicomMap& tags) | 3365 const Orthanc::DicomMap& tags) |
3278 { | 3366 { |
3615 return 0; | 3703 return 0; |
3616 } | 3704 } |
3617 | 3705 |
3618 | 3706 |
3619 EMSCRIPTEN_KEEPALIVE | 3707 EMSCRIPTEN_KEEPALIVE |
3620 int LoadMultipartInstanceInViewport(const char* canvas, | 3708 int LoadVirtualSeriesInViewport(const char* canvas, |
3621 const char* seriesInstanceUid, | 3709 const char* virtualSeriesId) |
3622 const char* sopInstanceUid) | |
3623 { | 3710 { |
3624 try | 3711 try |
3625 { | 3712 { |
3626 std::unique_ptr<OrthancStone::SortedFrames> frames(new OrthancStone::SortedFrames); | 3713 std::unique_ptr<OrthancStone::SortedFrames> frames(new OrthancStone::SortedFrames); |
3714 | |
3715 const std::string sopInstanceUid = virtualSeriesId; // TODO | |
3627 | 3716 |
3628 if (GetResourcesLoader().SortMultipartInstanceFrames(*frames, seriesInstanceUid, sopInstanceUid)) | 3717 if (GetResourcesLoader().SortMultipartInstanceFrames( |
3718 *frames, virtualSeries_.GetSeriesInstanceUid(virtualSeriesId), sopInstanceUid)) | |
3629 { | 3719 { |
3630 GetViewport(canvas)->SetFrames(frames.release()); | 3720 GetViewport(canvas)->SetFrames(frames.release()); |
3631 return 1; | 3721 return 1; |
3632 } | 3722 } |
3633 else | 3723 else |
3955 EXTERN_CATCH_EXCEPTIONS; | 4045 EXTERN_CATCH_EXCEPTIONS; |
3956 } | 4046 } |
3957 | 4047 |
3958 | 4048 |
3959 EMSCRIPTEN_KEEPALIVE | 4049 EMSCRIPTEN_KEEPALIVE |
3960 int LoadMultiframeInstancesFromSeries(const char* seriesInstanceUid) | 4050 int LookupVirtualSeries(const char* seriesInstanceUid) |
3961 { | 4051 { |
3962 try | 4052 try |
3963 { | 4053 { |
3964 std::map<std::string, unsigned int> numberOfFramesPerInstance; | 4054 std::set<std::string> virtualSeriesIds; |
3965 if (GetResourcesLoader().LookupMultiframeSeries(numberOfFramesPerInstance, seriesInstanceUid)) | 4055 if (GetResourcesLoader().LookupVirtualSeries(virtualSeries_, virtualSeriesIds, seriesInstanceUid)) |
3966 { | 4056 { |
3967 Json::Value json = Json::objectValue; | 4057 Json::Value json = Json::arrayValue; |
3968 for (std::map<std::string, unsigned int>::const_iterator it = | 4058 for (std::set<std::string>::const_iterator it = virtualSeriesIds.begin(); |
3969 numberOfFramesPerInstance.begin(); it != numberOfFramesPerInstance.end(); ++it) | 4059 it != virtualSeriesIds.end(); ++it) |
3970 { | 4060 { |
3971 json[it->first] = it->second; | 4061 Json::Value item = Json::objectValue; |
4062 item["ID"] = *it; | |
4063 item["NumberOfFrames"] = virtualSeries_.GetNumberOfFrames(*it); | |
4064 json.append(item); | |
3972 } | 4065 } |
3973 | 4066 |
3974 stringBuffer_ = json.toStyledString(); | 4067 stringBuffer_ = json.toStyledString(); |
3975 return true; | 4068 return true; |
3976 } | 4069 } |