Mercurial > hg > orthanc-stone
comparison Applications/StoneWebViewer/WebAssembly/StoneWebViewer.cpp @ 1672:570398585b5f
start support of cine sequences
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Mon, 23 Nov 2020 15:39:27 +0100 |
parents | 2c2512918a0f |
children | 0621e523b670 |
comparison
equal
deleted
inserted
replaced
1671:2c2512918a0f | 1672:570398585b5f |
---|---|
165 static const int PRIORITY_NORMAL = 0; | 165 static const int PRIORITY_NORMAL = 0; |
166 | 166 |
167 static const unsigned int QUALITY_JPEG = 0; | 167 static const unsigned int QUALITY_JPEG = 0; |
168 static const unsigned int QUALITY_FULL = 1; | 168 static const unsigned int QUALITY_FULL = 1; |
169 | 169 |
170 static const unsigned int DEFAULT_CINE_RATE = 30; | |
171 | |
172 | |
170 class ResourcesLoader : public OrthancStone::ObserverBase<ResourcesLoader> | 173 class ResourcesLoader : public OrthancStone::ObserverBase<ResourcesLoader> |
171 { | 174 { |
172 public: | 175 public: |
173 class IObserver : public boost::noncopyable | 176 class IObserver : public boost::noncopyable |
174 { | 177 { |
685 | 688 |
686 private: | 689 private: |
687 std::vector<size_t> prefetch_; | 690 std::vector<size_t> prefetch_; |
688 int framesCount_; | 691 int framesCount_; |
689 int currentFrame_; | 692 int currentFrame_; |
690 bool isCircular_; | 693 bool isCircularPrefetch_; |
691 int fastDelta_; | 694 int fastDelta_; |
692 Action lastAction_; | 695 Action lastAction_; |
693 | 696 |
694 int ComputeNextFrame(int currentFrame, | 697 int ComputeNextFrame(int currentFrame, |
695 Action action) const | 698 Action action, |
699 bool isCircular) const | |
696 { | 700 { |
697 if (framesCount_ == 0) | 701 if (framesCount_ == 0) |
698 { | 702 { |
699 assert(currentFrame == 0); | 703 assert(currentFrame == 0); |
700 return 0; | 704 return 0; |
725 | 729 |
726 default: | 730 default: |
727 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); | 731 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); |
728 } | 732 } |
729 | 733 |
730 if (isCircular_) | 734 if (isCircular) |
731 { | 735 { |
732 while (nextFrame < 0) | 736 while (nextFrame < 0) |
733 { | 737 { |
734 nextFrame += framesCount_; | 738 nextFrame += framesCount_; |
735 } | 739 } |
795 | 799 |
796 switch (previousAction) | 800 switch (previousAction) |
797 { | 801 { |
798 case Action_None: | 802 case Action_None: |
799 case Action_Plus: | 803 case Action_Plus: |
800 queue.push_back(std::make_pair(ComputeNextFrame(frame, Action_Plus), Action_Plus)); | 804 queue.push_back(std::make_pair(ComputeNextFrame(frame, Action_Plus, isCircularPrefetch_), Action_Plus)); |
801 queue.push_back(std::make_pair(ComputeNextFrame(frame, Action_Minus), Action_Minus)); | 805 queue.push_back(std::make_pair(ComputeNextFrame(frame, Action_Minus, isCircularPrefetch_), Action_Minus)); |
802 queue.push_back(std::make_pair(ComputeNextFrame(frame, Action_FastPlus), Action_FastPlus)); | 806 queue.push_back(std::make_pair(ComputeNextFrame(frame, Action_FastPlus, isCircularPrefetch_), Action_FastPlus)); |
803 queue.push_back(std::make_pair(ComputeNextFrame(frame, Action_FastMinus), Action_FastMinus)); | 807 queue.push_back(std::make_pair(ComputeNextFrame(frame, Action_FastMinus, isCircularPrefetch_), Action_FastMinus)); |
804 break; | 808 break; |
805 | 809 |
806 case Action_Minus: | 810 case Action_Minus: |
807 queue.push_back(std::make_pair(ComputeNextFrame(frame, Action_Minus), Action_Minus)); | 811 queue.push_back(std::make_pair(ComputeNextFrame(frame, Action_Minus, isCircularPrefetch_), Action_Minus)); |
808 queue.push_back(std::make_pair(ComputeNextFrame(frame, Action_Plus), Action_Plus)); | 812 queue.push_back(std::make_pair(ComputeNextFrame(frame, Action_Plus, isCircularPrefetch_), Action_Plus)); |
809 queue.push_back(std::make_pair(ComputeNextFrame(frame, Action_FastMinus), Action_FastMinus)); | 813 queue.push_back(std::make_pair(ComputeNextFrame(frame, Action_FastMinus, isCircularPrefetch_), Action_FastMinus)); |
810 queue.push_back(std::make_pair(ComputeNextFrame(frame, Action_FastPlus), Action_FastPlus)); | 814 queue.push_back(std::make_pair(ComputeNextFrame(frame, Action_FastPlus, isCircularPrefetch_), Action_FastPlus)); |
811 break; | 815 break; |
812 | 816 |
813 case Action_FastPlus: | 817 case Action_FastPlus: |
814 queue.push_back(std::make_pair(ComputeNextFrame(frame, Action_FastPlus), Action_FastPlus)); | 818 queue.push_back(std::make_pair(ComputeNextFrame(frame, Action_FastPlus, isCircularPrefetch_), Action_FastPlus)); |
815 queue.push_back(std::make_pair(ComputeNextFrame(frame, Action_FastMinus), Action_FastMinus)); | 819 queue.push_back(std::make_pair(ComputeNextFrame(frame, Action_FastMinus, isCircularPrefetch_), Action_FastMinus)); |
816 queue.push_back(std::make_pair(ComputeNextFrame(frame, Action_Plus), Action_Plus)); | 820 queue.push_back(std::make_pair(ComputeNextFrame(frame, Action_Plus, isCircularPrefetch_), Action_Plus)); |
817 queue.push_back(std::make_pair(ComputeNextFrame(frame, Action_Minus), Action_Minus)); | 821 queue.push_back(std::make_pair(ComputeNextFrame(frame, Action_Minus, isCircularPrefetch_), Action_Minus)); |
818 break; | 822 break; |
819 | 823 |
820 case Action_FastMinus: | 824 case Action_FastMinus: |
821 queue.push_back(std::make_pair(ComputeNextFrame(frame, Action_FastMinus), Action_FastMinus)); | 825 queue.push_back(std::make_pair(ComputeNextFrame(frame, Action_FastMinus, isCircularPrefetch_), Action_FastMinus)); |
822 queue.push_back(std::make_pair(ComputeNextFrame(frame, Action_FastPlus), Action_FastPlus)); | 826 queue.push_back(std::make_pair(ComputeNextFrame(frame, Action_FastPlus, isCircularPrefetch_), Action_FastPlus)); |
823 queue.push_back(std::make_pair(ComputeNextFrame(frame, Action_Minus), Action_Minus)); | 827 queue.push_back(std::make_pair(ComputeNextFrame(frame, Action_Minus, isCircularPrefetch_), Action_Minus)); |
824 queue.push_back(std::make_pair(ComputeNextFrame(frame, Action_Plus), Action_Plus)); | 828 queue.push_back(std::make_pair(ComputeNextFrame(frame, Action_Plus, isCircularPrefetch_), Action_Plus)); |
825 break; | 829 break; |
826 | 830 |
827 default: | 831 default: |
828 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); | 832 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); |
829 } | 833 } |
841 | 845 |
842 public: | 846 public: |
843 explicit SeriesCursor(size_t framesCount) : | 847 explicit SeriesCursor(size_t framesCount) : |
844 framesCount_(framesCount), | 848 framesCount_(framesCount), |
845 currentFrame_(framesCount / 2), // Start at the middle frame | 849 currentFrame_(framesCount / 2), // Start at the middle frame |
846 isCircular_(false), | 850 isCircularPrefetch_(false), |
847 lastAction_(Action_None) | 851 lastAction_(Action_None) |
848 { | 852 { |
849 SetFastDelta(framesCount / 20); | 853 SetFastDelta(framesCount / 20); |
850 UpdatePrefetch(); | 854 UpdatePrefetch(); |
851 } | 855 } |
852 | 856 |
853 void SetCircular(bool isCircular) | 857 void SetCircularPrefetch(bool isCircularPrefetch) |
854 { | 858 { |
855 isCircular_ = isCircular; | 859 isCircularPrefetch_ = isCircularPrefetch; |
856 UpdatePrefetch(); | 860 UpdatePrefetch(); |
857 } | 861 } |
858 | 862 |
859 void SetFastDelta(int delta) | 863 void SetFastDelta(int delta) |
860 { | 864 { |
884 { | 888 { |
885 assert(CheckFrameIndex(currentFrame_)); | 889 assert(CheckFrameIndex(currentFrame_)); |
886 return static_cast<size_t>(currentFrame_); | 890 return static_cast<size_t>(currentFrame_); |
887 } | 891 } |
888 | 892 |
889 void Apply(Action action) | 893 void Apply(Action action, |
890 { | 894 bool isCircular) |
891 currentFrame_ = ComputeNextFrame(currentFrame_, action); | 895 { |
896 currentFrame_ = ComputeNextFrame(currentFrame_, action, isCircular); | |
892 lastAction_ = action; | 897 lastAction_ = action; |
893 UpdatePrefetch(); | 898 UpdatePrefetch(); |
894 } | 899 } |
895 | 900 |
896 size_t GetPrefetchSize() const | 901 size_t GetPrefetchSize() const |
988 public: | 993 public: |
989 virtual ~IObserver() | 994 virtual ~IObserver() |
990 { | 995 { |
991 } | 996 } |
992 | 997 |
998 virtual void SignalSeriesDetailsReady(const ViewerViewport& viewport) = 0; | |
999 | |
993 virtual void SignalFrameUpdated(const ViewerViewport& viewport, | 1000 virtual void SignalFrameUpdated(const ViewerViewport& viewport, |
994 size_t currentFrame, | 1001 size_t currentFrame, |
995 size_t countFrames, | 1002 size_t countFrames, |
996 DisplayedFrameQuality quality) = 0; | 1003 DisplayedFrameQuality quality) = 0; |
997 | 1004 |
1043 { | 1050 { |
1044 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); | 1051 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); |
1045 } | 1052 } |
1046 }; | 1053 }; |
1047 | 1054 |
1048 class SetDefaultWindowingCommand : public ICommand | 1055 class LoadSeriesDetailsFromInstance : public ICommand |
1049 { | 1056 { |
1050 public: | 1057 public: |
1051 explicit SetDefaultWindowingCommand(boost::shared_ptr<ViewerViewport> viewport) : | 1058 explicit LoadSeriesDetailsFromInstance(boost::shared_ptr<ViewerViewport> viewport) : |
1052 ICommand(viewport) | 1059 ICommand(viewport) |
1053 { | 1060 { |
1054 } | 1061 } |
1055 | 1062 |
1056 virtual void Handle(const OrthancStone::DicomResourcesLoader::SuccessMessage& message) const ORTHANC_OVERRIDE | 1063 virtual void Handle(const OrthancStone::DicomResourcesLoader::SuccessMessage& message) const ORTHANC_OVERRIDE |
1080 LOG(INFO) << "No default windowing"; | 1087 LOG(INFO) << "No default windowing"; |
1081 GetViewport().ResetDefaultWindowing(); | 1088 GetViewport().ResetDefaultWindowing(); |
1082 } | 1089 } |
1083 } | 1090 } |
1084 | 1091 |
1092 uint32_t cineRate; | |
1093 if (dicom.ParseUnsignedInteger32(cineRate, Orthanc::DICOM_TAG_CINE_RATE) && | |
1094 cineRate > 0) | |
1095 { | |
1096 /** | |
1097 * If we detect a cine sequence, start on the first frame | |
1098 * instead of on the middle frame. | |
1099 **/ | |
1100 GetViewport().cursor_->SetCurrentIndex(0); | |
1101 GetViewport().cineRate_ = cineRate; | |
1102 } | |
1103 else | |
1104 { | |
1105 GetViewport().cineRate_ = DEFAULT_CINE_RATE; | |
1106 } | |
1107 | |
1085 GetViewport().Redraw(); | 1108 GetViewport().Redraw(); |
1109 | |
1110 if (GetViewport().observer_.get() != NULL) | |
1111 { | |
1112 GetViewport().observer_->SignalSeriesDetailsReady(GetViewport()); | |
1113 } | |
1086 } | 1114 } |
1087 }; | 1115 }; |
1088 | 1116 |
1089 | 1117 |
1090 class SetLowQualityFrame : public ICommand | 1118 class SetLowQualityFrame : public ICommand |
1295 std::unique_ptr<SeriesCursor> cursor_; | 1323 std::unique_ptr<SeriesCursor> cursor_; |
1296 float windowingCenter_; | 1324 float windowingCenter_; |
1297 float windowingWidth_; | 1325 float windowingWidth_; |
1298 float defaultWindowingCenter_; | 1326 float defaultWindowingCenter_; |
1299 float defaultWindowingWidth_; | 1327 float defaultWindowingWidth_; |
1328 unsigned int cineRate_; | |
1300 bool inverted_; | 1329 bool inverted_; |
1301 bool flipX_; | 1330 bool flipX_; |
1302 bool flipY_; | 1331 bool flipY_; |
1303 bool fitNextContent_; | 1332 bool fitNextContent_; |
1304 bool isCtrlDown_; | 1333 bool isCtrlDown_; |
1717 | 1746 |
1718 if (that.cursor_.get() != NULL) | 1747 if (that.cursor_.get() != NULL) |
1719 { | 1748 { |
1720 if (wheelEvent->deltaY < 0) | 1749 if (wheelEvent->deltaY < 0) |
1721 { | 1750 { |
1722 that.ChangeFrame(that.isCtrlDown_ ? SeriesCursor::Action_FastMinus : SeriesCursor::Action_Minus); | 1751 that.ChangeFrame(that.isCtrlDown_ ? SeriesCursor::Action_FastMinus : SeriesCursor::Action_Minus, false /* not circular */); |
1723 } | 1752 } |
1724 else if (wheelEvent->deltaY > 0) | 1753 else if (wheelEvent->deltaY > 0) |
1725 { | 1754 { |
1726 that.ChangeFrame(that.isCtrlDown_ ? SeriesCursor::Action_FastPlus : SeriesCursor::Action_Plus); | 1755 that.ChangeFrame(that.isCtrlDown_ ? SeriesCursor::Action_FastPlus : SeriesCursor::Action_Plus, false /* not circular */); |
1727 } | 1756 } |
1728 } | 1757 } |
1729 | 1758 |
1730 return true; | 1759 return true; |
1731 } | 1760 } |
1786 } | 1815 } |
1787 | 1816 |
1788 flipX_ = false; | 1817 flipX_ = false; |
1789 flipY_ = false; | 1818 flipY_ = false; |
1790 fitNextContent_ = true; | 1819 fitNextContent_ = true; |
1791 | 1820 cineRate_ = DEFAULT_CINE_RATE; |
1821 | |
1792 frames_.reset(frames); | 1822 frames_.reset(frames); |
1793 cursor_.reset(new SeriesCursor(frames_->GetFramesCount())); | 1823 cursor_.reset(new SeriesCursor(frames_->GetFramesCount())); |
1794 | 1824 |
1795 LOG(INFO) << "Number of frames in series: " << frames_->GetFramesCount(); | 1825 LOG(INFO) << "Number of frames in series: " << frames_->GetFramesCount(); |
1796 | 1826 |
1819 uid != OrthancStone::SopClassUid_RTDose && | 1849 uid != OrthancStone::SopClassUid_RTDose && |
1820 uid != OrthancStone::SopClassUid_RTPlan && | 1850 uid != OrthancStone::SopClassUid_RTPlan && |
1821 uid != OrthancStone::SopClassUid_RTStruct && | 1851 uid != OrthancStone::SopClassUid_RTStruct && |
1822 GetSeriesThumbnailType(uid) != OrthancStone::SeriesThumbnailType_Video) | 1852 GetSeriesThumbnailType(uid) != OrthancStone::SeriesThumbnailType_Video) |
1823 { | 1853 { |
1824 // Fetch the default windowing for the central instance | 1854 // Fetch the details of the series from the central instance |
1825 const std::string uri = ("studies/" + frames_->GetStudyInstanceUid() + | 1855 const std::string uri = ("studies/" + frames_->GetStudyInstanceUid() + |
1826 "/series/" + frames_->GetSeriesInstanceUid() + | 1856 "/series/" + frames_->GetSeriesInstanceUid() + |
1827 "/instances/" + centralInstance.GetSopInstanceUid() + "/metadata"); | 1857 "/instances/" + centralInstance.GetSopInstanceUid() + "/metadata"); |
1828 | 1858 |
1829 loader_->ScheduleGetDicomWeb( | 1859 loader_->ScheduleGetDicomWeb( |
1830 boost::make_shared<OrthancStone::LoadedDicomResources>(Orthanc::DICOM_TAG_SOP_INSTANCE_UID), | 1860 boost::make_shared<OrthancStone::LoadedDicomResources>(Orthanc::DICOM_TAG_SOP_INSTANCE_UID), |
1831 0, source_, uri, new SetDefaultWindowingCommand(GetSharedObserver())); | 1861 0, source_, uri, new LoadSeriesDetailsFromInstance(GetSharedObserver())); |
1832 } | 1862 } |
1833 } | 1863 } |
1834 | 1864 |
1835 ApplyScheduledFocus(); | 1865 ApplyScheduledFocus(); |
1836 } | 1866 } |
1909 } | 1939 } |
1910 } | 1940 } |
1911 } | 1941 } |
1912 | 1942 |
1913 | 1943 |
1914 void ChangeFrame(SeriesCursor::Action action) | 1944 // Returns "true" iff the frame has indeed changed |
1945 bool ChangeFrame(SeriesCursor::Action action, | |
1946 bool isCircular) | |
1915 { | 1947 { |
1916 if (cursor_.get() != NULL) | 1948 if (cursor_.get() != NULL) |
1917 { | 1949 { |
1918 size_t previous = cursor_->GetCurrentIndex(); | 1950 size_t previous = cursor_->GetCurrentIndex(); |
1919 | 1951 |
1920 cursor_->Apply(action); | 1952 cursor_->Apply(action, isCircular); |
1921 | 1953 |
1922 size_t current = cursor_->GetCurrentIndex(); | 1954 size_t current = cursor_->GetCurrentIndex(); |
1923 if (previous != current) | 1955 if (previous != current) |
1924 { | 1956 { |
1925 Redraw(); | 1957 Redraw(); |
1926 } | 1958 return true; |
1927 } | 1959 } |
1928 } | 1960 } |
1961 | |
1962 return false; | |
1963 } | |
1964 | |
1929 | 1965 |
1930 bool GetCurrentFrameOfReferenceUid(std::string& frameOfReferenceUid) const | 1966 bool GetCurrentFrameOfReferenceUid(std::string& frameOfReferenceUid) const |
1931 { | 1967 { |
1932 if (cursor_.get() != NULL && | 1968 if (cursor_.get() != NULL && |
1933 frames_.get() != NULL) | 1969 frames_.get() != NULL) |
2211 { | 2247 { |
2212 cursor_->SetCurrentIndex(cursorIndex); | 2248 cursor_->SetCurrentIndex(cursorIndex); |
2213 Redraw(); | 2249 Redraw(); |
2214 } | 2250 } |
2215 } | 2251 } |
2252 | |
2253 unsigned int GetCineRate() const | |
2254 { | |
2255 return cineRate_; | |
2256 } | |
2216 }; | 2257 }; |
2217 | 2258 |
2218 | 2259 |
2219 | 2260 |
2220 | 2261 |
2293 for (Viewports::const_iterator it = allViewports_.begin(); it != allViewports_.end(); ++it) | 2334 for (Viewports::const_iterator it = allViewports_.begin(); it != allViewports_.end(); ++it) |
2294 { | 2335 { |
2295 assert(it->second != NULL); | 2336 assert(it->second != NULL); |
2296 it->second->ApplyScheduledFocus(); | 2337 it->second->ApplyScheduledFocus(); |
2297 } | 2338 } |
2339 } | |
2340 | |
2341 virtual void SignalSeriesDetailsReady(const ViewerViewport& viewport) ORTHANC_OVERRIDE | |
2342 { | |
2343 EM_ASM({ | |
2344 const customEvent = document.createEvent("CustomEvent"); | |
2345 customEvent.initCustomEvent("SeriesDetailsReady", false, false, | |
2346 { "canvasId" : UTF8ToString($0) }); | |
2347 window.dispatchEvent(customEvent); | |
2348 }, | |
2349 viewport.GetCanvasId().c_str() | |
2350 ); | |
2298 } | 2351 } |
2299 | 2352 |
2300 virtual void SignalFrameUpdated(const ViewerViewport& viewport, | 2353 virtual void SignalFrameUpdated(const ViewerViewport& viewport, |
2301 size_t currentFrame, | 2354 size_t currentFrame, |
2302 size_t countFrames, | 2355 size_t countFrames, |
2652 EXTERN_CATCH_EXCEPTIONS; | 2705 EXTERN_CATCH_EXCEPTIONS; |
2653 } | 2706 } |
2654 | 2707 |
2655 | 2708 |
2656 EMSCRIPTEN_KEEPALIVE | 2709 EMSCRIPTEN_KEEPALIVE |
2657 void DecrementFrame(const char* canvas, | 2710 int DecrementFrame(const char* canvas, |
2658 int fitContent) | 2711 int isCircular) |
2659 { | 2712 { |
2660 try | 2713 try |
2661 { | 2714 { |
2662 GetViewport(canvas)->ChangeFrame(SeriesCursor::Action_Minus); | 2715 return GetViewport(canvas)->ChangeFrame(SeriesCursor::Action_Minus, isCircular) ? 1 : 0; |
2663 } | 2716 } |
2664 EXTERN_CATCH_EXCEPTIONS; | 2717 EXTERN_CATCH_EXCEPTIONS; |
2665 } | 2718 return 0; |
2666 | 2719 } |
2667 | 2720 |
2668 EMSCRIPTEN_KEEPALIVE | 2721 |
2669 void IncrementFrame(const char* canvas, | 2722 EMSCRIPTEN_KEEPALIVE |
2670 int fitContent) | 2723 int IncrementFrame(const char* canvas, |
2724 int isCircular) | |
2671 { | 2725 { |
2672 try | 2726 try |
2673 { | 2727 { |
2674 GetViewport(canvas)->ChangeFrame(SeriesCursor::Action_Plus); | 2728 return GetViewport(canvas)->ChangeFrame(SeriesCursor::Action_Plus, isCircular) ? 1 : 0; |
2675 } | 2729 } |
2676 EXTERN_CATCH_EXCEPTIONS; | 2730 EXTERN_CATCH_EXCEPTIONS; |
2731 return 0; | |
2677 } | 2732 } |
2678 | 2733 |
2679 | 2734 |
2680 EMSCRIPTEN_KEEPALIVE | 2735 EMSCRIPTEN_KEEPALIVE |
2681 void ShowReferenceLines(int show) | 2736 void ShowReferenceLines(int show) |
2864 LOG(INFO) << "Fetching PDF series: " << seriesInstanceUid; | 2919 LOG(INFO) << "Fetching PDF series: " << seriesInstanceUid; |
2865 GetResourcesLoader().FetchPdf(studyInstanceUid, seriesInstanceUid); | 2920 GetResourcesLoader().FetchPdf(studyInstanceUid, seriesInstanceUid); |
2866 } | 2921 } |
2867 EXTERN_CATCH_EXCEPTIONS; | 2922 EXTERN_CATCH_EXCEPTIONS; |
2868 } | 2923 } |
2924 | |
2925 | |
2926 EMSCRIPTEN_KEEPALIVE | |
2927 unsigned int GetCineRate(const char* canvas) | |
2928 { | |
2929 try | |
2930 { | |
2931 return GetViewport(canvas)->GetCineRate(); | |
2932 } | |
2933 EXTERN_CATCH_EXCEPTIONS; | |
2934 return 0; | |
2935 } | |
2869 } | 2936 } |