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 }