# HG changeset patch # User Sebastien Jodogne # Date 1606491403 -3600 # Node ID 76c590a6275581ed690224f538e0c535ada8b727 # Parent bc40b64502612ff53082345431b085523249910e start work on series with multiple multiframe instances diff -r bc40b6450261 -r 76c590a62755 Applications/StoneWebViewer/WebApplication/app.js --- a/Applications/StoneWebViewer/WebApplication/app.js Fri Nov 27 13:57:28 2020 +0100 +++ b/Applications/StoneWebViewer/WebApplication/app.js Fri Nov 27 16:36:43 2020 +0100 @@ -224,10 +224,7 @@ }); }, methods: { - SeriesDragAccept: function(event) { - event.preventDefault(); - }, - SeriesDragDrop: function(event) { + DragDrop: function(event) { event.preventDefault(); // The "parseInt()" is because of Microsoft Edge Legacy (*) @@ -368,7 +365,8 @@ selectedStudies: [], series: [], studies: [], - seriesIndex: {} // Maps "SeriesInstanceUID" to "index in this.series" + seriesIndex: {}, // Maps "SeriesInstanceUID" to "index in this.series" + multiframeInstanceThumbnails: {} } }, computed: { @@ -502,7 +500,8 @@ 'complete' : false, 'type' : stone.ThumbnailType.LOADING, 'color': study.color, - 'tags': sourceSeries[i] + 'tags': sourceSeries[i], + 'multiframeInstances': null }); } } @@ -665,6 +664,10 @@ stone.FetchPdf(studyInstanceUid, seriesInstanceUid); delete pendingSeriesPdf_[seriesInstanceUid]; } + + if (stone.LoadMultiframeInstancesFromSeries(seriesInstanceUid)) { + series.multiframeInstances = JSON.parse(stone.GetStringBuffer()); + } } // https://fr.vuejs.org/2016/02/06/common-gotchas/#Why-isn%E2%80%99t-the-DOM-updating @@ -828,6 +831,24 @@ } this.modalNotDiagnostic = this.settingNotDiagnostic; + + var that = this; + + window.addEventListener('MultiframeInstanceThumbnailLoaded', function(args) { + that.$set(that.multiframeInstanceThumbnails, args.detail.sopInstanceUid, args.detail.thumbnail); + }); + + window.addEventListener('ThumbnailLoaded', function(args) { + //var studyInstanceUid = args.detail.studyInstanceUid; + var seriesInstanceUid = args.detail.seriesInstanceUid; + that.UpdateSeriesThumbnail(seriesInstanceUid); + }); + + window.addEventListener('MetadataLoaded', function(args) { + var studyInstanceUid = args.detail.studyInstanceUid; + var seriesInstanceUid = args.detail.seriesInstanceUid; + that.UpdateIsSeriesComplete(studyInstanceUid, seriesInstanceUid); + }); } }); @@ -916,20 +937,6 @@ }); -window.addEventListener('ThumbnailLoaded', function(args) { - //var studyInstanceUid = args.detail.studyInstanceUid; - var seriesInstanceUid = args.detail.seriesInstanceUid; - app.UpdateSeriesThumbnail(seriesInstanceUid); -}); - - -window.addEventListener('MetadataLoaded', function(args) { - var studyInstanceUid = args.detail.studyInstanceUid; - var seriesInstanceUid = args.detail.seriesInstanceUid; - app.UpdateIsSeriesComplete(studyInstanceUid, seriesInstanceUid); -}); - - window.addEventListener('StoneException', function() { console.error('Exception catched in Stone'); }); diff -r bc40b6450261 -r 76c590a62755 Applications/StoneWebViewer/WebApplication/index.html --- a/Applications/StoneWebViewer/WebApplication/index.html Fri Nov 27 13:57:28 2020 +0100 +++ b/Applications/StoneWebViewer/WebApplication/index.html Fri Nov 27 16:36:43 2020 +0100 @@ -187,57 +187,93 @@
@@ -550,8 +586,8 @@ 'wvSplitpane__cellBorder--yellow' : series.color == 'yellow', 'wvSplitpane__cellBorder--violet' : series.color == 'violet' }" - v-on:dragover="SeriesDragAccept($event)" - v-on:drop="SeriesDragDrop($event)" + ondragover="event.preventDefault()" + v-on:drop="DragDrop($event)" style="width:100%;height:100%">
diff -r bc40b6450261 -r 76c590a62755 Applications/StoneWebViewer/WebAssembly/StoneWebViewer.cpp --- a/Applications/StoneWebViewer/WebAssembly/StoneWebViewer.cpp Fri Nov 27 13:57:28 2020 +0100 +++ b/Applications/StoneWebViewer/WebAssembly/StoneWebViewer.cpp Fri Nov 27 16:36:43 2020 +0100 @@ -195,6 +195,9 @@ virtual void SignalSeriesPdfLoaded(const std::string& studyInstanceUid, const std::string& seriesInstanceUid, const std::string& pdf) = 0; + + virtual void SignalMultiframeInstanceThumbnailLoaded(const std::string& sopInstanceUid, + const std::string& jpeg) = 0; }; private: @@ -207,6 +210,7 @@ boost::shared_ptr resourcesLoader_; boost::shared_ptr thumbnailsLoader_; boost::shared_ptr metadataLoader_; + std::set scheduledMultiframeInstances_; explicit ResourcesLoader(OrthancStone::ILoadersContext& context, const OrthancStone::DicomSource& source) : @@ -361,6 +365,44 @@ } } + void FetchInstanceThumbnail(const std::string& studyInstanceUid, + const std::string& seriesInstanceUid, + const std::string& sopInstanceUid) + { + if (scheduledMultiframeInstances_.find(sopInstanceUid) == scheduledMultiframeInstances_.end()) + { + scheduledMultiframeInstances_.insert(sopInstanceUid); + + std::map arguments; + std::map headers; + arguments["viewport"] = ( + boost::lexical_cast(thumbnailsLoader_->GetThumbnailWidth()) + "," + + boost::lexical_cast(thumbnailsLoader_->GetThumbnailHeight())); + headers["Accept"] = Orthanc::MIME_JPEG; + + const std::string uri = ("studies/" + studyInstanceUid + "/series/" + seriesInstanceUid + + "/instances/" + sopInstanceUid + "/frames/1/rendered"); + + { + std::unique_ptr lock(context_.Lock()); + lock->Schedule( + GetSharedObserver(), PRIORITY_LOW + 2, source_.CreateDicomWebCommand( + uri, arguments, headers, new Orthanc::SingleValueObject(sopInstanceUid))); + } + } + } + + void HandleInstanceThumbnail(const OrthancStone::HttpCommand::SuccessMessage& message) + { + if (observer_.get() != NULL) + { + const std::string& sopInstanceUid = + dynamic_cast&>( + message.GetOrigin().GetPayload()).GetValue(); + observer_->SignalMultiframeInstanceThumbnailLoaded(sopInstanceUid, message.GetAnswer()); + } + } + public: static boost::shared_ptr Create(OrthancStone::ILoadersContext::ILock& lock, const OrthancStone::DicomSource& source) @@ -382,7 +424,10 @@ loader->Register( lock.GetOracleObservable(), &ResourcesLoader::Handle); - + + loader->Register( + lock.GetOracleObservable(), &ResourcesLoader::HandleInstanceThumbnail); + return loader; } @@ -428,13 +473,13 @@ } void GetStudy(Orthanc::DicomMap& target, - size_t i) + size_t i) const { target.Assign(studies_->GetResource(i)); } void GetSeries(Orthanc::DicomMap& target, - size_t i) + size_t i) const { target.Assign(series_->GetResource(i)); @@ -449,26 +494,65 @@ OrthancStone::SeriesThumbnailType GetSeriesThumbnail(std::string& image, std::string& mime, - const std::string& seriesInstanceUid) + const std::string& seriesInstanceUid) const { return thumbnailsLoader_->GetSeriesThumbnail(image, mime, seriesInstanceUid); } void FetchSeriesMetadata(int priority, const std::string& studyInstanceUid, - const std::string& seriesInstanceUid) + const std::string& seriesInstanceUid) const { metadataLoader_->ScheduleLoadSeries(priority, source_, studyInstanceUid, seriesInstanceUid); } - bool IsSeriesComplete(const std::string& seriesInstanceUid) + bool IsSeriesComplete(const std::string& seriesInstanceUid) const { OrthancStone::SeriesMetadataLoader::Accessor accessor(*metadataLoader_, seriesInstanceUid); return accessor.IsComplete(); } + bool LookupMultiframeSeries(std::map& numberOfFramesPerInstance, + const std::string& seriesInstanceUid) + { + numberOfFramesPerInstance.clear(); + + OrthancStone::SeriesMetadataLoader::Accessor accessor(*metadataLoader_, seriesInstanceUid); + if (accessor.IsComplete() && + accessor.GetInstancesCount() >= 2) + { + bool isMultiframe = false; + + for (size_t i = 0; i < accessor.GetInstancesCount(); i++) + { + OrthancStone::DicomInstanceParameters p(accessor.GetInstance(i)); + numberOfFramesPerInstance[p.GetSopInstanceUid()] = p.GetNumberOfFrames(); + + if (p.GetNumberOfFrames() > 1) + { + isMultiframe = true; + } + } + + if (isMultiframe) + { + for (size_t i = 0; i < accessor.GetInstancesCount(); i++) + { + OrthancStone::DicomInstanceParameters p(accessor.GetInstance(i)); + FetchInstanceThumbnail(p.GetStudyInstanceUid(), p.GetSeriesInstanceUid(), p.GetSopInstanceUid()); + } + } + + return isMultiframe; + } + else + { + return false; + } + } + bool SortSeriesFrames(OrthancStone::SortedFrames& target, - const std::string& seriesInstanceUid) + const std::string& seriesInstanceUid) const { OrthancStone::SeriesMetadataLoader::Accessor accessor(*metadataLoader_, seriesInstanceUid); @@ -2674,6 +2758,24 @@ pdf.empty() ? 0 : reinterpret_cast(pdf.c_str()), // Explicit conversion to an integer pdf.size()); } + + + virtual void SignalMultiframeInstanceThumbnailLoaded(const std::string& sopInstanceUid, + const std::string& jpeg) ORTHANC_OVERRIDE + { + std::string dataUriScheme; + Orthanc::Toolbox::EncodeDataUriScheme(dataUriScheme, "image/jpeg", jpeg); + + EM_ASM({ + const customEvent = document.createEvent("CustomEvent"); + customEvent.initCustomEvent("MultiframeInstanceThumbnailLoaded", false, false, + { "sopInstanceUid" : UTF8ToString($0), + "thumbnail" : UTF8ToString($1) }); + window.dispatchEvent(customEvent); + }, + sopInstanceUid.c_str(), + dataUriScheme.c_str()); + } }; @@ -3246,4 +3348,32 @@ } EXTERN_CATCH_EXCEPTIONS; } + + + EMSCRIPTEN_KEEPALIVE + int LoadMultiframeInstancesFromSeries(const char* seriesInstanceUid) + { + try + { + std::map numberOfFramesPerInstance; + if (GetResourcesLoader().LookupMultiframeSeries(numberOfFramesPerInstance, seriesInstanceUid)) + { + Json::Value json = Json::objectValue; + for (std::map::const_iterator it = + numberOfFramesPerInstance.begin(); it != numberOfFramesPerInstance.end(); ++it) + { + json[it->first] = it->second; + } + + stringBuffer_ = json.toStyledString(); + return true; + } + else + { + return false; + } + } + EXTERN_CATCH_EXCEPTIONS; + return false; + } } diff -r bc40b6450261 -r 76c590a62755 OrthancStone/Sources/Loaders/SeriesThumbnailsLoader.h --- a/OrthancStone/Sources/Loaders/SeriesThumbnailsLoader.h Fri Nov 27 13:57:28 2020 +0100 +++ b/OrthancStone/Sources/Loaders/SeriesThumbnailsLoader.h Fri Nov 27 16:36:43 2020 +0100 @@ -213,6 +213,16 @@ unsigned int height); void Clear(); + + unsigned int GetThumbnailWidth() const + { + return width_; + } + + unsigned int GetThumbnailHeight() const + { + return height_; + } SeriesThumbnailType GetSeriesThumbnail(std::string& image, std::string& mime,