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 }