Mercurial > hg > orthanc-stone
comparison Applications/StoneWebViewer/WebAssembly/StoneWebViewer.cpp @ 1850:932dc2265baa
Group together in a single "virtual series" all the instances without the tag "NumberOfFrames"
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Tue, 29 Jun 2021 14:09:54 +0200 |
parents | 023cce3d7844 |
children | 73a4bee08bb6 |
comparison
equal
deleted
inserted
replaced
1849:023cce3d7844 | 1850:932dc2265baa |
---|---|
189 | 189 |
190 | 190 |
191 class VirtualSeries : public boost::noncopyable | 191 class VirtualSeries : public boost::noncopyable |
192 { | 192 { |
193 private: | 193 private: |
194 class Item | 194 class Item : public boost::noncopyable |
195 { | 195 { |
196 private: | 196 private: |
197 std::string seriesInstanceUid_; | 197 std::string seriesInstanceUid_; |
198 unsigned int numberOfFrames_; | 198 std::list<std::string> sopInstanceUids_; |
199 | 199 |
200 public: | 200 public: |
201 Item(const std::string& seriesInstanceUid, | 201 Item(const std::string& seriesInstanceUid, |
202 unsigned int numberOfFrames) : | 202 const std::list<std::string>& sopInstanceUids) : |
203 seriesInstanceUid_(seriesInstanceUid), | 203 seriesInstanceUid_(seriesInstanceUid), |
204 numberOfFrames_(numberOfFrames) | 204 sopInstanceUids_(sopInstanceUids) |
205 { | 205 { |
206 if (numberOfFrames == 0) | |
207 { | |
208 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); | |
209 } | |
210 } | 206 } |
211 | 207 |
212 const std::string& GetSeriesInstanceUid() const | 208 const std::string& GetSeriesInstanceUid() const |
213 { | 209 { |
214 return seriesInstanceUid_; | 210 return seriesInstanceUid_; |
215 } | 211 } |
216 | 212 |
217 unsigned int GetNumberOfFrames() const | 213 const std::list<std::string>& GetSopInstanceUids() const |
218 { | 214 { |
219 return numberOfFrames_; | 215 return sopInstanceUids_; |
220 } | 216 } |
221 }; | 217 }; |
222 | 218 |
223 typedef std::map<std::string, Item> Content; | 219 typedef std::map<std::string, Item*> Content; |
224 | 220 |
225 Content content_; | 221 Content content_; |
226 | 222 |
227 const Item& GetItem(const std::string& id) const | 223 const Item& GetItem(const std::string& id) const |
228 { | 224 { |
232 { | 228 { |
233 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); | 229 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); |
234 } | 230 } |
235 else | 231 else |
236 { | 232 { |
237 return found->second; | 233 assert(found->second != NULL); |
238 } | 234 return *found->second; |
235 } | |
239 } | 236 } |
240 | 237 |
241 public: | 238 public: |
242 std::string Add(const std::string& seriesInstanceUid, | 239 ~VirtualSeries() |
243 const std::string& sopInstanceUid, | 240 { |
244 unsigned int numberOfFrames) | 241 for (Content::iterator it = content_.begin(); it != content_.end(); ++it) |
245 { | 242 { |
246 if (content_.find(sopInstanceUid) != content_.end()) | 243 assert(it->second != NULL); |
244 delete it->second; | |
245 } | |
246 } | |
247 | |
248 std::string AddSingleInstance(const std::string& seriesInstanceUid, | |
249 const std::string& sopInstanceUid) | |
250 { | |
251 std::list<std::string> sopInstanceUids; | |
252 sopInstanceUids.push_back(sopInstanceUid); | |
253 return AddMultipleInstances(seriesInstanceUid, sopInstanceUids); | |
254 } | |
255 | |
256 std::string AddMultipleInstances(const std::string& seriesInstanceUid, | |
257 const std::list<std::string>& sopInstanceUids) | |
258 { | |
259 // Generate a unique identifier for this virtual series | |
260 const std::string virtualSeriesId = "virtual-" + boost::lexical_cast<std::string>(content_.size()); | |
261 | |
262 if (content_.find(virtualSeriesId) != content_.end()) | |
247 { | 263 { |
248 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); | 264 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); |
249 } | 265 } |
250 else | 266 else |
251 { | 267 { |
252 content_.insert(std::make_pair(sopInstanceUid, Item(seriesInstanceUid, numberOfFrames))); | 268 content_.insert(std::make_pair(virtualSeriesId, new Item(seriesInstanceUid, sopInstanceUids))); |
253 return sopInstanceUid; | 269 return virtualSeriesId; |
254 } | 270 } |
255 } | 271 } |
256 | 272 |
257 const std::string& GetSeriesInstanceUid(const std::string& id) const | 273 const std::string& GetSeriesInstanceUid(const std::string& id) const |
258 { | 274 { |
259 return GetItem(id).GetSeriesInstanceUid(); | 275 return GetItem(id).GetSeriesInstanceUid(); |
260 } | 276 } |
261 | 277 |
262 unsigned int GetNumberOfFrames(const std::string& id) const | 278 const std::list<std::string>& GetSopInstanceUids(const std::string& id) const |
263 { | 279 { |
264 return GetItem(id).GetNumberOfFrames(); | 280 return GetItem(id).GetSopInstanceUids(); |
265 } | 281 } |
266 }; | 282 }; |
267 | 283 |
268 | 284 |
269 | 285 |
302 boost::shared_ptr<OrthancStone::LoadedDicomResources> series_; | 318 boost::shared_ptr<OrthancStone::LoadedDicomResources> series_; |
303 boost::shared_ptr<OrthancStone::DicomResourcesLoader> resourcesLoader_; | 319 boost::shared_ptr<OrthancStone::DicomResourcesLoader> resourcesLoader_; |
304 boost::shared_ptr<OrthancStone::SeriesThumbnailsLoader> thumbnailsLoader_; | 320 boost::shared_ptr<OrthancStone::SeriesThumbnailsLoader> thumbnailsLoader_; |
305 boost::shared_ptr<OrthancStone::SeriesMetadataLoader> metadataLoader_; | 321 boost::shared_ptr<OrthancStone::SeriesMetadataLoader> metadataLoader_; |
306 std::set<std::string> scheduledVirtualSeriesThumbnails_; | 322 std::set<std::string> scheduledVirtualSeriesThumbnails_; |
323 VirtualSeries virtualSeries_; | |
307 | 324 |
308 explicit ResourcesLoader(OrthancStone::ILoadersContext& context, | 325 explicit ResourcesLoader(OrthancStone::ILoadersContext& context, |
309 const OrthancStone::DicomSource& source) : | 326 const OrthancStone::DicomSource& source) : |
310 context_(context), | 327 context_(context), |
311 source_(source), | 328 source_(source), |
604 { | 621 { |
605 OrthancStone::SeriesMetadataLoader::Accessor accessor(*metadataLoader_, seriesInstanceUid); | 622 OrthancStone::SeriesMetadataLoader::Accessor accessor(*metadataLoader_, seriesInstanceUid); |
606 return accessor.IsComplete(); | 623 return accessor.IsComplete(); |
607 } | 624 } |
608 | 625 |
609 bool LookupVirtualSeries(VirtualSeries& target /* out */, | 626 bool LookupVirtualSeries(std::map<std::string, unsigned int>& virtualSeries /* out */, |
610 std::set<std::string>& virtualSeriesIds /* out */, | |
611 const std::string& seriesInstanceUid) | 627 const std::string& seriesInstanceUid) |
612 { | 628 { |
613 OrthancStone::SeriesMetadataLoader::Accessor accessor(*metadataLoader_, seriesInstanceUid); | 629 OrthancStone::SeriesMetadataLoader::Accessor accessor(*metadataLoader_, seriesInstanceUid); |
614 if (accessor.IsComplete() && | 630 if (accessor.IsComplete() && |
615 accessor.GetInstancesCount() >= 2) | 631 accessor.GetInstancesCount() >= 2) |
626 } | 642 } |
627 } | 643 } |
628 | 644 |
629 if (hasMultiframe) | 645 if (hasMultiframe) |
630 { | 646 { |
647 std::string studyInstanceUid; | |
648 std::list<std::string> instancesWithoutFrameNumber; | |
649 | |
631 for (size_t i = 0; i < accessor.GetInstancesCount(); i++) | 650 for (size_t i = 0; i < accessor.GetInstancesCount(); i++) |
632 { | 651 { |
633 OrthancStone::DicomInstanceParameters p(accessor.GetInstance(i)); | 652 OrthancStone::DicomInstanceParameters p(accessor.GetInstance(i)); |
634 | 653 |
635 std::string virtualSeriesId = target.Add(seriesInstanceUid, p.GetSopInstanceUid(), p.GetNumberOfFrames()); | 654 if (p.HasNumberOfFrames()) |
636 virtualSeriesIds.insert(virtualSeriesId); | 655 { |
637 | 656 const std::string virtualSeriesId = virtualSeries_.AddSingleInstance(seriesInstanceUid, p.GetSopInstanceUid()); |
638 FetchVirtualSeriesThumbnail(virtualSeriesId, p.GetStudyInstanceUid(), p.GetSeriesInstanceUid(), p.GetSopInstanceUid()); | 657 if (virtualSeries.find(virtualSeriesId) != virtualSeries.end()) |
658 { | |
659 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); | |
660 } | |
661 else | |
662 { | |
663 virtualSeries[virtualSeriesId] = p.GetNumberOfFrames(); | |
664 FetchVirtualSeriesThumbnail(virtualSeriesId, p.GetStudyInstanceUid(), seriesInstanceUid, p.GetSopInstanceUid()); | |
665 } | |
666 } | |
667 else | |
668 { | |
669 studyInstanceUid = p.GetStudyInstanceUid(); | |
670 instancesWithoutFrameNumber.push_back(p.GetSopInstanceUid()); | |
671 } | |
672 } | |
673 | |
674 if (!instancesWithoutFrameNumber.empty()) | |
675 { | |
676 /** | |
677 * Group together in a single "virtual series" all the DICOM | |
678 * instances that have no value for the tag "NumberOfFrames" | |
679 * (0028,0008). This can happen in US CINE series. New in | |
680 * Stone Web viewer 2.1. | |
681 * https://groups.google.com/g/orthanc-users/c/V-vOnlwj06A/m/2sPNwteYAAAJ | |
682 **/ | |
683 const std::string virtualSeriesId = virtualSeries_.AddMultipleInstances(seriesInstanceUid, instancesWithoutFrameNumber); | |
684 virtualSeries[virtualSeriesId] = instancesWithoutFrameNumber.size(); | |
685 FetchVirtualSeriesThumbnail(virtualSeriesId, studyInstanceUid, seriesInstanceUid, instancesWithoutFrameNumber.front()); | |
639 } | 686 } |
640 | 687 |
641 return true; | 688 return true; |
642 } | 689 } |
643 else | 690 else |
673 { | 720 { |
674 return false; | 721 return false; |
675 } | 722 } |
676 } | 723 } |
677 | 724 |
678 bool SortMultipartInstanceFrames(OrthancStone::SortedFrames& target, | 725 bool SortVirtualSeriesFrames(OrthancStone::SortedFrames& target, |
679 const std::string& seriesInstanceUid, | 726 const std::string& virtualSeriesId) const |
680 const std::string& sopInstanceUid) const | 727 { |
681 { | 728 const std::string& seriesInstanceUid = virtualSeries_.GetSeriesInstanceUid(virtualSeriesId); |
729 | |
682 OrthancStone::SeriesMetadataLoader::Accessor accessor(*metadataLoader_, seriesInstanceUid); | 730 OrthancStone::SeriesMetadataLoader::Accessor accessor(*metadataLoader_, seriesInstanceUid); |
683 | 731 |
684 if (accessor.IsComplete()) | 732 if (accessor.IsComplete()) |
685 { | 733 { |
686 for (size_t i = 0; i < accessor.GetInstancesCount(); i++) | 734 const std::list<std::string>& sopInstanceUids = virtualSeries_.GetSopInstanceUids(virtualSeriesId); |
687 { | 735 |
688 std::string s; | 736 target.Clear(); |
689 if (accessor.GetInstance(i).LookupStringValue(s, Orthanc::DICOM_TAG_SOP_INSTANCE_UID, false) && | 737 |
690 s == sopInstanceUid) | 738 for (std::list<std::string>::const_iterator |
739 it = sopInstanceUids.begin(); it != sopInstanceUids.end(); ++it) | |
740 { | |
741 Orthanc::DicomMap instance; | |
742 if (accessor.LookupInstance(instance, *it)) | |
691 { | 743 { |
692 target.Clear(); | 744 target.AddInstance(instance); |
693 target.AddInstance(accessor.GetInstance(i)); | |
694 target.Sort(); | |
695 return true; | |
696 } | 745 } |
746 else | |
747 { | |
748 LOG(ERROR) << "Missing instance: " << *it; | |
749 } | |
697 } | 750 } |
698 | 751 |
752 target.Sort(); | |
699 return true; | 753 return true; |
700 } | 754 } |
701 else | 755 else |
702 { | 756 { |
703 return false; | 757 return false; |
3356 static std::string stringBuffer_; | 3410 static std::string stringBuffer_; |
3357 static bool softwareRendering_ = false; | 3411 static bool softwareRendering_ = false; |
3358 static WebViewerAction leftButtonAction_ = WebViewerAction_Windowing; | 3412 static WebViewerAction leftButtonAction_ = WebViewerAction_Windowing; |
3359 static WebViewerAction middleButtonAction_ = WebViewerAction_Pan; | 3413 static WebViewerAction middleButtonAction_ = WebViewerAction_Pan; |
3360 static WebViewerAction rightButtonAction_ = WebViewerAction_Zoom; | 3414 static WebViewerAction rightButtonAction_ = WebViewerAction_Zoom; |
3361 static VirtualSeries virtualSeries_; | |
3362 | 3415 |
3363 | 3416 |
3364 static void FormatTags(std::string& target, | 3417 static void FormatTags(std::string& target, |
3365 const Orthanc::DicomMap& tags) | 3418 const Orthanc::DicomMap& tags) |
3366 { | 3419 { |
3710 { | 3763 { |
3711 try | 3764 try |
3712 { | 3765 { |
3713 std::unique_ptr<OrthancStone::SortedFrames> frames(new OrthancStone::SortedFrames); | 3766 std::unique_ptr<OrthancStone::SortedFrames> frames(new OrthancStone::SortedFrames); |
3714 | 3767 |
3715 const std::string sopInstanceUid = virtualSeriesId; // TODO | 3768 if (GetResourcesLoader().SortVirtualSeriesFrames(*frames, virtualSeriesId)) |
3716 | |
3717 if (GetResourcesLoader().SortMultipartInstanceFrames( | |
3718 *frames, virtualSeries_.GetSeriesInstanceUid(virtualSeriesId), sopInstanceUid)) | |
3719 { | 3769 { |
3720 GetViewport(canvas)->SetFrames(frames.release()); | 3770 GetViewport(canvas)->SetFrames(frames.release()); |
3721 return 1; | 3771 return 1; |
3722 } | 3772 } |
3723 else | 3773 else |
4049 EMSCRIPTEN_KEEPALIVE | 4099 EMSCRIPTEN_KEEPALIVE |
4050 int LookupVirtualSeries(const char* seriesInstanceUid) | 4100 int LookupVirtualSeries(const char* seriesInstanceUid) |
4051 { | 4101 { |
4052 try | 4102 try |
4053 { | 4103 { |
4054 std::set<std::string> virtualSeriesIds; | 4104 typedef std::map<std::string, unsigned int> VirtualSeries; |
4055 if (GetResourcesLoader().LookupVirtualSeries(virtualSeries_, virtualSeriesIds, seriesInstanceUid)) | 4105 |
4106 VirtualSeries virtualSeries; | |
4107 if (GetResourcesLoader().LookupVirtualSeries(virtualSeries, seriesInstanceUid)) | |
4056 { | 4108 { |
4057 Json::Value json = Json::arrayValue; | 4109 Json::Value json = Json::arrayValue; |
4058 for (std::set<std::string>::const_iterator it = virtualSeriesIds.begin(); | 4110 for (VirtualSeries::const_iterator it = virtualSeries.begin(); it != virtualSeries.end(); ++it) |
4059 it != virtualSeriesIds.end(); ++it) | |
4060 { | 4111 { |
4061 Json::Value item = Json::objectValue; | 4112 Json::Value item = Json::objectValue; |
4062 item["ID"] = *it; | 4113 item["ID"] = it->first; |
4063 item["NumberOfFrames"] = virtualSeries_.GetNumberOfFrames(*it); | 4114 item["NumberOfFrames"] = it->second; |
4064 json.append(item); | 4115 json.append(item); |
4065 } | 4116 } |
4066 | 4117 |
4067 stringBuffer_ = json.toStyledString(); | 4118 stringBuffer_ = json.toStyledString(); |
4068 return true; | 4119 return true; |