comparison Applications/StoneWebViewer/WebAssembly/StoneWebViewer.cpp @ 1813:53aa3f72b539

synchronization of annotations across viewports
author Sebastien Jodogne <s.jodogne@gmail.com>
date Tue, 25 May 2021 15:27:35 +0200
parents db341679dc9f
children 53f3411bf94b
comparison
equal deleted inserted replaced
1812:db341679dc9f 1813:53aa3f72b539
1170 } 1170 }
1171 } 1171 }
1172 1172
1173 1173
1174 1174
1175 class StoneAnnotationsRegistry : public boost::noncopyable
1176 {
1177 private:
1178 class Index
1179 {
1180 private:
1181 std::string sopInstanceUid_;
1182 size_t frame_;
1183
1184 public:
1185 Index(const std::string& sopInstanceUid,
1186 size_t frame) :
1187 sopInstanceUid_(sopInstanceUid),
1188 frame_(frame)
1189 {
1190 }
1191
1192 const std::string& GetSopInstanceUid() const
1193 {
1194 return sopInstanceUid_;
1195 }
1196
1197 size_t GetFrame() const
1198 {
1199 return frame_;
1200 }
1201
1202 bool operator< (const Index& other) const
1203 {
1204 if (sopInstanceUid_ < other.sopInstanceUid_)
1205 {
1206 return true;
1207 }
1208 else if (sopInstanceUid_ > other.sopInstanceUid_)
1209 {
1210 return false;
1211 }
1212 else
1213 {
1214 return frame_ < other.frame_;
1215 }
1216 }
1217 };
1218
1219 typedef std::map<Index, Json::Value*> Content;
1220
1221 Content content_;
1222
1223 void Clear()
1224 {
1225 for (Content::iterator it = content_.begin(); it != content_.end(); ++it)
1226 {
1227 assert(it->second != NULL);
1228 delete it->second;
1229 }
1230
1231 content_.clear();
1232 }
1233
1234 StoneAnnotationsRegistry()
1235 {
1236 }
1237
1238 public:
1239 ~StoneAnnotationsRegistry()
1240 {
1241 Clear();
1242 }
1243
1244 static StoneAnnotationsRegistry& GetInstance()
1245 {
1246 static StoneAnnotationsRegistry singleton;
1247 return singleton;
1248 }
1249
1250 void Save(const std::string& sopInstanceUid,
1251 size_t frame,
1252 const OrthancStone::AnnotationsSceneLayer& layer)
1253 {
1254 std::unique_ptr<Json::Value> serialized;
1255 layer.Serialize(*serialized);
1256
1257 const Index index(sopInstanceUid, frame);
1258
1259 Content::iterator found = content_.find(index);
1260 if (found == content_.end())
1261 {
1262 content_[index] = serialized.release();
1263 }
1264 else
1265 {
1266 assert(found->second != NULL);
1267 delete found->second;
1268 found->second = serialized.release();
1269 }
1270 }
1271
1272 void Load(OrthancStone::AnnotationsSceneLayer& layer,
1273 const std::string& sopInstanceUid,
1274 size_t frame) const
1275 {
1276 const Index index(sopInstanceUid, frame);
1277
1278 Content::const_iterator found = content_.find(index);
1279 if (found == content_.end())
1280 {
1281 layer.Clear();
1282 }
1283 else
1284 {
1285 assert(found->second != NULL);
1286 layer.Unserialize(*found->second);
1287 }
1288 }
1289 };
1290
1291
1292
1175 class ViewerViewport : public OrthancStone::ObserverBase<ViewerViewport> 1293 class ViewerViewport : public OrthancStone::ObserverBase<ViewerViewport>
1176 { 1294 {
1177 public: 1295 public:
1178 typedef std::map<std::string, boost::shared_ptr<Json::Value> > StoneAnnotationsRegistry;
1179
1180 class IObserver : public boost::noncopyable 1296 class IObserver : public boost::noncopyable
1181 { 1297 {
1182 public: 1298 public:
1183 virtual ~IObserver() 1299 virtual ~IObserver()
1184 { 1300 {
1201 const OrthancStone::Vector& normal) = 0; 1317 const OrthancStone::Vector& normal) = 0;
1202 1318
1203 virtual void SignalWindowingUpdated(const ViewerViewport& viewport, 1319 virtual void SignalWindowingUpdated(const ViewerViewport& viewport,
1204 double windowingCenter, 1320 double windowingCenter,
1205 double windowingWidth) = 0; 1321 double windowingWidth) = 0;
1322
1323 virtual void SignalStoneAnnotationsChanged(const ViewerViewport& viewport,
1324 const std::string& sopInstanceUid,
1325 size_t frame) = 0;
1206 }; 1326 };
1207 1327
1208 private: 1328 private:
1209 static const int LAYER_TEXTURE = 0; 1329 static const int LAYER_TEXTURE = 0;
1210 static const int LAYER_REFERENCE_LINES = 1; 1330 static const int LAYER_REFERENCE_LINES = 1;
1586 1706
1587 // The coordinates of Stone annotations are expressed in 2D 1707 // The coordinates of Stone annotations are expressed in 2D
1588 // coordinates of the current texture, with (0,0) corresponding to 1708 // coordinates of the current texture, with (0,0) corresponding to
1589 // the center of the top-left pixel 1709 // the center of the top-left pixel
1590 boost::shared_ptr<OrthancStone::AnnotationsSceneLayer> stoneAnnotations_; 1710 boost::shared_ptr<OrthancStone::AnnotationsSceneLayer> stoneAnnotations_;
1591 boost::shared_ptr<StoneAnnotationsRegistry> stoneAnnotationsRegistry_;
1592 1711
1593 1712
1594 void ScheduleNextPrefetch() 1713 void ScheduleNextPrefetch()
1595 { 1714 {
1596 while (!prefetchQueue_.empty()) 1715 while (!prefetchQueue_.empty())
1682 } 1801 }
1683 1802
1684 1803
1685 void RenderCurrentScene(const Orthanc::ImageAccessor& frame, 1804 void RenderCurrentScene(const Orthanc::ImageAccessor& frame,
1686 const OrthancStone::DicomInstanceParameters& instance, 1805 const OrthancStone::DicomInstanceParameters& instance,
1806 size_t frameIndex,
1687 const OrthancStone::CoordinateSystem3D& plane) 1807 const OrthancStone::CoordinateSystem3D& plane)
1688 { 1808 {
1689 /** 1809 /**
1690 * IMPORTANT - DO NOT use "instance.GetWidth()" and 1810 * IMPORTANT - DO NOT use "instance.GetWidth()" and
1691 * "instance.GetHeight()" in this method. Use the information from 1811 * "instance.GetHeight()" in this method. Use the information from
1784 annotationsOsiriX->AddLayer(factory.Create(annotation, plane)); 1904 annotationsOsiriX->AddLayer(factory.Create(annotation, plane));
1785 } 1905 }
1786 } 1906 }
1787 } 1907 }
1788 1908
1789 if (stoneAnnotationsRegistry_.get() == NULL) 1909 StoneAnnotationsRegistry::GetInstance().Load(*stoneAnnotations_, instance.GetSopInstanceUid(), frameIndex);
1790 { 1910
1791 stoneAnnotations_->Clear(); 1911 {
1792 } 1912 std::unique_ptr<OrthancStone::IViewport::ILock> lock(viewport_->Lock());
1793 else 1913
1794 { 1914 OrthancStone::Scene2D& scene = lock->GetController().GetScene();
1795 StoneAnnotationsRegistry::const_iterator found = stoneAnnotationsRegistry_->find(instance.GetSopInstanceUid()); 1915
1796 if (found == stoneAnnotationsRegistry_->end()) 1916 scene.SetLayer(LAYER_TEXTURE, layer.release());
1797 { 1917
1798 stoneAnnotations_->Clear(); 1918 if (annotationsOsiriX.get() != NULL)
1919 {
1920 scene.SetLayer(LAYER_ANNOTATIONS_OSIRIX, annotationsOsiriX.release());
1799 } 1921 }
1800 else 1922 else
1801 { 1923 {
1802 stoneAnnotations_->Unserialize(*found->second);
1803 }
1804 }
1805
1806 {
1807 std::unique_ptr<OrthancStone::IViewport::ILock> lock(viewport_->Lock());
1808
1809 OrthancStone::Scene2D& scene = lock->GetController().GetScene();
1810
1811 scene.SetLayer(LAYER_TEXTURE, layer.release());
1812
1813 if (annotationsOsiriX.get() != NULL)
1814 {
1815 scene.SetLayer(LAYER_ANNOTATIONS_OSIRIX, annotationsOsiriX.release());
1816 }
1817 else
1818 {
1819 scene.DeleteLayer(LAYER_ANNOTATIONS_OSIRIX); 1924 scene.DeleteLayer(LAYER_ANNOTATIONS_OSIRIX);
1820 } 1925 }
1926
1927 stoneAnnotations_->Render(scene); // Necessary for "FitContent()" to work
1821 1928
1822 if (fitNextContent_) 1929 if (fitNextContent_)
1823 { 1930 {
1824 lock->RefreshCanvasSize(); 1931 lock->RefreshCanvasSize();
1825 lock->GetCompositor().FitContent(scene); 1932 lock->GetCompositor().FitContent(scene);
1933 stoneAnnotations_->Render(scene);
1826 fitNextContent_ = false; 1934 fitNextContent_ = false;
1827 } 1935 }
1828
1829 stoneAnnotations_->Render(scene);
1830 1936
1831 //lock->GetCompositor().Refresh(scene); 1937 //lock->GetCompositor().Refresh(scene);
1832 lock->Invalidate(); 1938 lock->Invalidate();
1833 } 1939 }
1834 } 1940 }
1857 FramesCache::Accessor accessor(*framesCache_, instance.GetSopInstanceUid(), frameNumber); 1963 FramesCache::Accessor accessor(*framesCache_, instance.GetSopInstanceUid(), frameNumber);
1858 if (accessor.IsValid() && 1964 if (accessor.IsValid() &&
1859 accessor.GetQuality() == QUALITY_FULL) 1965 accessor.GetQuality() == QUALITY_FULL)
1860 { 1966 {
1861 // A high-res image was downloaded in between: Use this cached image instead of the low-res 1967 // A high-res image was downloaded in between: Use this cached image instead of the low-res
1862 RenderCurrentScene(accessor.GetImage(), instance, plane); 1968 RenderCurrentScene(accessor.GetImage(), instance, frameNumber, plane);
1863 SetupPrefetchAfterRendering(frame, DisplayedFrameQuality_High); 1969 SetupPrefetchAfterRendering(frame, DisplayedFrameQuality_High);
1864 } 1970 }
1865 else 1971 else
1866 { 1972 {
1867 // This frame is only available in low-res: Download the full DICOM 1973 // This frame is only available in low-res: Download the full DICOM
1868 RenderCurrentScene(frame, instance, plane); 1974 RenderCurrentScene(frame, instance, frameNumber, plane);
1869 SetupPrefetchAfterRendering(frame, quality); 1975 SetupPrefetchAfterRendering(frame, quality);
1870 1976
1871 /** 1977 /**
1872 * The command "SetupPrefetchAfterRendering()" must be 1978 * The command "SetupPrefetchAfterRendering()" must be
1873 * after "SetupPrefetchAfterRendering(quality)", as the 1979 * after "SetupPrefetchAfterRendering(quality)", as the
1881 } 1987 }
1882 else 1988 else
1883 { 1989 {
1884 assert(quality == DisplayedFrameQuality_High); 1990 assert(quality == DisplayedFrameQuality_High);
1885 SetupPrefetchAfterRendering(frame, quality); 1991 SetupPrefetchAfterRendering(frame, quality);
1886 RenderCurrentScene(frame, instance, plane); 1992 RenderCurrentScene(frame, instance, frameNumber, plane);
1887 } 1993 }
1888 } 1994 }
1889 } 1995 }
1890 } 1996 }
1891 1997
2029 hasFocusOnInstance_(false), 2135 hasFocusOnInstance_(false),
2030 focusFrameNumber_(0), 2136 focusFrameNumber_(0),
2031 synchronizationOffset_(OrthancStone::LinearAlgebra::CreateVector(0, 0, 0)), 2137 synchronizationOffset_(OrthancStone::LinearAlgebra::CreateVector(0, 0, 0)),
2032 synchronizationEnabled_(false), 2138 synchronizationEnabled_(false),
2033 centralPhysicalWidth_(1), 2139 centralPhysicalWidth_(1),
2034 centralPhysicalHeight_(1), 2140 centralPhysicalHeight_(1)
2035 stoneAnnotationsRegistry_(NULL)
2036 { 2141 {
2037 if (!framesCache_) 2142 if (!framesCache_)
2038 { 2143 {
2039 throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); 2144 throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
2040 } 2145 }
2155 if (cursor_.get() != NULL && 2260 if (cursor_.get() != NULL &&
2156 frames_.get() != NULL) 2261 frames_.get() != NULL)
2157 { 2262 {
2158 const size_t cursorIndex = cursor_->GetCurrentIndex(); 2263 const size_t cursorIndex = cursor_->GetCurrentIndex();
2159 const OrthancStone::DicomInstanceParameters& instance = frames_->GetInstanceOfFrame(cursorIndex); 2264 const OrthancStone::DicomInstanceParameters& instance = frames_->GetInstanceOfFrame(cursorIndex);
2160 2265 const size_t frameNumber = frames_->GetFrameNumberInInstance(cursorIndex);
2161 boost::shared_ptr<Json::Value> v(new Json::Value); 2266
2162 stoneAnnotations_->Serialize(*v); 2267 StoneAnnotationsRegistry::GetInstance().Save(instance.GetSopInstanceUid(), frameNumber, *stoneAnnotations_);
2163 (*stoneAnnotationsRegistry_) [instance.GetSopInstanceUid()] = v; 2268
2269 if (observer_.get() != NULL)
2270 {
2271 observer_->SignalStoneAnnotationsChanged(*this, instance.GetSopInstanceUid(), frameNumber);
2272 }
2164 } 2273 }
2165 } 2274 }
2166 } 2275 }
2167 2276
2168 void Handle(const OrthancStone::ViewportController::SceneTransformChanged& message) 2277 void Handle(const OrthancStone::ViewportController::SceneTransformChanged& message)
2352 const size_t frameNumber = frames_->GetFrameNumberInInstance(cursorIndex); 2461 const size_t frameNumber = frames_->GetFrameNumberInInstance(cursorIndex);
2353 2462
2354 FramesCache::Accessor accessor(*framesCache_, instance.GetSopInstanceUid(), frameNumber); 2463 FramesCache::Accessor accessor(*framesCache_, instance.GetSopInstanceUid(), frameNumber);
2355 if (accessor.IsValid()) 2464 if (accessor.IsValid())
2356 { 2465 {
2357 RenderCurrentScene(accessor.GetImage(), instance, frames_->GetFrameGeometry(cursorIndex)); 2466 RenderCurrentScene(accessor.GetImage(), instance, frameNumber, frames_->GetFrameGeometry(cursorIndex));
2358 2467
2359 DisplayedFrameQuality quality; 2468 DisplayedFrameQuality quality;
2360 2469
2361 if (accessor.GetQuality() < QUALITY_FULL) 2470 if (accessor.GetQuality() < QUALITY_FULL)
2362 { 2471 {
2802 OrthancStone::LinearAlgebra::AssignVector(synchronizationOffset_, 0, 0, 0); 2911 OrthancStone::LinearAlgebra::AssignVector(synchronizationOffset_, 0, 0, 0);
2803 synchronizationEnabled_ = enabled; 2912 synchronizationEnabled_ = enabled;
2804 } 2913 }
2805 2914
2806 2915
2807 void SetStoneAnnotationsRegistry(boost::shared_ptr<ViewerViewport::StoneAnnotationsRegistry>& registry) 2916 void SignalStoneAnnotationsChanged(const std::string& sopInstanceUid,
2808 { 2917 size_t frame)
2809 stoneAnnotationsRegistry_ = registry; 2918 {
2919 if (cursor_.get() != NULL &&
2920 frames_.get() != NULL)
2921 {
2922 const size_t cursorIndex = cursor_->GetCurrentIndex();
2923 const OrthancStone::DicomInstanceParameters& instance = frames_->GetInstanceOfFrame(cursorIndex);
2924 const size_t frameNumber = frames_->GetFrameNumberInInstance(cursorIndex);
2925
2926 if (instance.GetSopInstanceUid() == sopInstanceUid &&
2927 frameNumber == frame)
2928 {
2929 StoneAnnotationsRegistry::GetInstance().Load(*stoneAnnotations_, instance.GetSopInstanceUid(), frame);
2930
2931 {
2932 std::unique_ptr<OrthancStone::IViewport::ILock> lock(viewport_->Lock());
2933 stoneAnnotations_->Render(lock->GetController().GetScene());
2934 lock->Invalidate();
2935 }
2936 }
2937 }
2810 } 2938 }
2811 }; 2939 };
2812 2940
2813 2941
2814 2942
2817 typedef std::map<std::string, boost::shared_ptr<ViewerViewport> > Viewports; 2945 typedef std::map<std::string, boost::shared_ptr<ViewerViewport> > Viewports;
2818 2946
2819 static Viewports allViewports_; 2947 static Viewports allViewports_;
2820 static bool showReferenceLines_ = true; 2948 static bool showReferenceLines_ = true;
2821 static boost::shared_ptr<OrthancStone::OsiriX::CollectionOfAnnotations> osiriXAnnotations_; 2949 static boost::shared_ptr<OrthancStone::OsiriX::CollectionOfAnnotations> osiriXAnnotations_;
2822 static boost::shared_ptr<ViewerViewport::StoneAnnotationsRegistry> stoneAnnotationsRegistry_;
2823 2950
2824 2951
2825 static void UpdateReferenceLines() 2952 static void UpdateReferenceLines()
2826 { 2953 {
2827 if (showReferenceLines_) 2954 if (showReferenceLines_)
3022 static_cast<int>(boost::math::iround<double>(windowingCenter)), 3149 static_cast<int>(boost::math::iround<double>(windowingCenter)),
3023 static_cast<int>(boost::math::iround<double>(windowingWidth))); 3150 static_cast<int>(boost::math::iround<double>(windowingWidth)));
3024 3151
3025 UpdateReferenceLines(); 3152 UpdateReferenceLines();
3026 } 3153 }
3154
3155 virtual void SignalStoneAnnotationsChanged(const ViewerViewport& viewport,
3156 const std::string& sopInstanceUid,
3157 size_t frame) ORTHANC_OVERRIDE
3158 {
3159 for (Viewports::const_iterator it = allViewports_.begin(); it != allViewports_.end(); ++it)
3160 {
3161 assert(it->second.get() != NULL);
3162
3163 if (it->second.get() != &viewport)
3164 {
3165 it->second->SignalStoneAnnotationsChanged(sopInstanceUid, frame);
3166 }
3167 }
3168 }
3027 }; 3169 };
3028 3170
3029 3171
3030 3172
3031 static OrthancStone::DicomSource source_; 3173 static OrthancStone::DicomSource source_;
3081 boost::shared_ptr<ViewerViewport> viewport( 3223 boost::shared_ptr<ViewerViewport> viewport(
3082 ViewerViewport::Create(*context_, source_, canvas, framesCache_, softwareRendering_)); 3224 ViewerViewport::Create(*context_, source_, canvas, framesCache_, softwareRendering_));
3083 viewport->SetMouseButtonActions(leftButtonAction_, middleButtonAction_, rightButtonAction_); 3225 viewport->SetMouseButtonActions(leftButtonAction_, middleButtonAction_, rightButtonAction_);
3084 viewport->AcquireObserver(new WebAssemblyObserver); 3226 viewport->AcquireObserver(new WebAssemblyObserver);
3085 viewport->SetOsiriXAnnotations(osiriXAnnotations_); 3227 viewport->SetOsiriXAnnotations(osiriXAnnotations_);
3086 viewport->SetStoneAnnotationsRegistry(stoneAnnotationsRegistry_);
3087 allViewports_[canvas] = viewport; 3228 allViewports_[canvas] = viewport;
3088 return viewport; 3229 return viewport;
3089 } 3230 }
3090 else 3231 else
3091 { 3232 {
3106 context_.reset(new OrthancStone::WebAssemblyLoadersContext(1, 4, 1)); 3247 context_.reset(new OrthancStone::WebAssemblyLoadersContext(1, 4, 1));
3107 context_->SetDicomCacheSize(128 * 1024 * 1024); // 128MB 3248 context_->SetDicomCacheSize(128 * 1024 * 1024); // 128MB
3108 3249
3109 framesCache_.reset(new FramesCache); 3250 framesCache_.reset(new FramesCache);
3110 osiriXAnnotations_.reset(new OrthancStone::OsiriX::CollectionOfAnnotations); 3251 osiriXAnnotations_.reset(new OrthancStone::OsiriX::CollectionOfAnnotations);
3111 stoneAnnotationsRegistry_.reset(new ViewerViewport::StoneAnnotationsRegistry);
3112 3252
3113 { 3253 {
3114 // TODO - TEST 3254 // TODO - TEST
3115 OrthancStone::AnnotationsSceneLayer l(0); 3255 OrthancStone::AnnotationsSceneLayer l(0);
3116 l.AddSegmentAnnotation(OrthancStone::ScenePoint2D(0, 0), 3256 l.AddSegmentAnnotation(OrthancStone::ScenePoint2D(0, 0),
3119 OrthancStone::ScenePoint2D(150, 40), 3259 OrthancStone::ScenePoint2D(150, 40),
3120 OrthancStone::ScenePoint2D(200, 50)); 3260 OrthancStone::ScenePoint2D(200, 50));
3121 l.AddCircleAnnotation(OrthancStone::ScenePoint2D(50, 200), 3261 l.AddCircleAnnotation(OrthancStone::ScenePoint2D(50, 200),
3122 OrthancStone::ScenePoint2D(100, 250)); 3262 OrthancStone::ScenePoint2D(100, 250));
3123 l.SetActiveTool(OrthancStone::AnnotationsSceneLayer::Tool_Edit); 3263 l.SetActiveTool(OrthancStone::AnnotationsSceneLayer::Tool_Edit);
3124 boost::shared_ptr<Json::Value> s(new Json::Value); 3264 StoneAnnotationsRegistry::GetInstance().Save("1.2.840.113543.6.6.4.7.64234348190163144631511103849051737563212", 0, l);
3125 l.Serialize(*s);
3126 (*stoneAnnotationsRegistry_) ["1.2.840.113543.6.6.4.7.64234348190163144631511103849051737563212"] = s;
3127 } 3265 }
3128 3266
3129 3267
3130 DISPATCH_JAVASCRIPT_EVENT("StoneInitialized"); 3268 DISPATCH_JAVASCRIPT_EVENT("StoneInitialized");
3131 } 3269 }