# HG changeset patch # User Sebastien Jodogne # Date 1622045615 -7200 # Node ID 0489fe25ce48a987d24e0e0d7da33fa94c8bc188 # Parent 36430d73e36c842fef8918861ee3f5fa6bea17b9 support of pixel spacing in ultrasound images from tag SequenceOfUltrasoundRegions diff -r 36430d73e36c -r 0489fe25ce48 Applications/Samples/Sdl/SingleFrameViewer/SdlSimpleViewerApplication.h --- a/Applications/Samples/Sdl/SingleFrameViewer/SdlSimpleViewerApplication.h Wed May 26 14:02:12 2021 +0200 +++ b/Applications/Samples/Sdl/SingleFrameViewer/SdlSimpleViewerApplication.h Wed May 26 18:13:35 2021 +0200 @@ -156,9 +156,11 @@ * "PixelSpacing" in the "SequenceOfUltrasoundRegions" * (0018,6011) sequence, cf. tags "PhysicalDeltaX" (0018,602c) * and "PhysicalDeltaY" (0018,602e) => This would require - * storing the full JSON into the "LoadedDicomResources" class - * or to use DCMTK + * parsing "message.GetResources()->GetSourceJson(0)" + * => cf. "DicomInstanceParameters::EnrichUsingDicomWeb()" **/ + + // std::cout << message.GetResources()->GetSourceJson(0).toStyledString(); LOG(INFO) << "Using millimeters units, as the DICOM instance contains the PixelSpacing tag"; units_ = OrthancStone::Units_Millimeters; diff -r 36430d73e36c -r 0489fe25ce48 Applications/StoneWebViewer/WebAssembly/StoneWebViewer.cpp --- a/Applications/StoneWebViewer/WebAssembly/StoneWebViewer.cpp Wed May 26 14:02:12 2021 +0200 +++ b/Applications/StoneWebViewer/WebAssembly/StoneWebViewer.cpp Wed May 26 18:13:35 2021 +0200 @@ -1399,6 +1399,30 @@ { OrthancStone::DicomInstanceParameters params(dicom); + + params.EnrichUsingDicomWeb(message.GetResources()->GetSourceJson(0)); + GetViewport().centralPixelSpacingX_ = params.GetPixelSpacingX(); + GetViewport().centralPixelSpacingY_ = params.GetPixelSpacingY(); + + if (params.HasPixelSpacing()) + { + GetViewport().stoneAnnotations_->SetUnits(OrthancStone::Units_Millimeters); + } + else + { + GetViewport().stoneAnnotations_->SetUnits(OrthancStone::Units_Pixels); + } + + if (params.GetPixelSpacingX() != 0 && + params.GetPixelSpacingY() != 0 && + params.GetWidth() != 0 && + params.GetHeight()) + { + GetViewport().centralPhysicalWidth_ = (params.GetPixelSpacingX() * + static_cast(params.GetWidth())); + GetViewport().centralPhysicalHeight_ = (params.GetPixelSpacingY() * + static_cast(params.GetHeight())); + } GetViewport().windowingPresetCenters_.resize(params.GetWindowingPresetsCount()); GetViewport().windowingPresetWidths_.resize(params.GetWindowingPresetsCount()); @@ -1703,6 +1727,8 @@ bool synchronizationEnabled_; double centralPhysicalWidth_; // LSD-479 double centralPhysicalHeight_; + double centralPixelSpacingX_; + double centralPixelSpacingY_; bool hasFocusOnInstance_; std::string focusSopInstanceUid_; @@ -1856,7 +1882,17 @@ layer->SetFlipY(flipY_); double pixelSpacingX, pixelSpacingY; - OrthancStone::GeometryToolbox::GetPixelSpacing(pixelSpacingX, pixelSpacingY, instance.GetTags()); + + if (instance.HasPixelSpacing()) + { + pixelSpacingX = instance.GetPixelSpacingX(); + pixelSpacingY = instance.GetPixelSpacingY(); + } + else + { + pixelSpacingX = centralPixelSpacingX_; + pixelSpacingY = centralPixelSpacingY_; + } if (FIX_LSD_479) { @@ -2144,7 +2180,9 @@ synchronizationOffset_(OrthancStone::LinearAlgebra::CreateVector(0, 0, 0)), synchronizationEnabled_(false), centralPhysicalWidth_(1), - centralPhysicalHeight_(1) + centralPhysicalHeight_(1), + centralPixelSpacingX_(1), + centralPixelSpacingY_(1) { if (!framesCache_) { @@ -2420,17 +2458,6 @@ boost::make_shared(Orthanc::DICOM_TAG_SOP_INSTANCE_UID), 0, source_, uri, new LoadSeriesDetailsFromInstance(GetSharedObserver())); } - - if (centralInstance.GetPixelSpacingX() != 0 && - centralInstance.GetPixelSpacingY() != 0 && - centralInstance.GetWidth() != 0 && - centralInstance.GetHeight()) - { - centralPhysicalWidth_ = (centralInstance.GetPixelSpacingX() * - static_cast(centralInstance.GetWidth())); - centralPhysicalHeight_ = (centralInstance.GetPixelSpacingY() * - static_cast(centralInstance.GetHeight())); - } } ApplyScheduledFocus(); diff -r 36430d73e36c -r 0489fe25ce48 OrthancStone/Sources/Loaders/LoadedDicomResources.cpp --- a/OrthancStone/Sources/Loaders/LoadedDicomResources.cpp Wed May 26 14:02:12 2021 +0200 +++ b/OrthancStone/Sources/Loaders/LoadedDicomResources.cpp Wed May 26 18:13:35 2021 +0200 @@ -29,7 +29,58 @@ namespace OrthancStone { - void LoadedDicomResources::Flatten() + LoadedDicomResources::Resource::Resource(const Orthanc::DicomMap& dicom) : + dicom_(dicom.Clone()) + { + if (dicom_.get() == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + } + + + LoadedDicomResources::Resource* LoadedDicomResources::Resource::Clone() const + { + assert(dicom_.get() != NULL); + return new Resource(*dicom_); + } + + + const Json::Value& LoadedDicomResources::Resource::GetSourceJson() const + { + if (sourceJson_.get() == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + else + { + return *sourceJson_; + } + } + + + void LoadedDicomResources::Resource::SetSourceJson(const Json::Value& json) + { + sourceJson_.reset(new Json::Value(json)); + } + + + void LoadedDicomResources::AddResourceInternal(Resource* resource) + { + std::unique_ptr protection(resource); + + std::string id; + + if (protection->GetDicom().LookupStringValue(id, indexedTag_, false /* no binary value */) && + resources_.find(id) == resources_.end() /* Don't index twice the same resource */) + { + resources_[id] = protection.release(); + flattened_.clear(); // Invalidate the flattened version + } + } + + + const LoadedDicomResources::Resource& LoadedDicomResources::GetResourceInternal(size_t index) { // Lazy generation of a "std::vector" from the "std::map" if (flattened_.empty()) @@ -48,6 +99,16 @@ // No need to flatten assert(flattened_.size() == resources_.size()); } + + if (index >= flattened_.size()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + else + { + assert(flattened_[index] != NULL); + return *flattened_[index]; + } } @@ -56,7 +117,10 @@ assert(dicomweb.type() == Json::objectValue); Orthanc::DicomMap dicom; dicom.FromDicomWeb(dicomweb); - AddResource(dicom); + + std::unique_ptr resource(new Resource(dicom)); + resource->SetSourceJson(dicomweb); + AddResourceInternal(resource.release()); } @@ -68,7 +132,7 @@ it != other.resources_.end(); ++it) { assert(it->second != NULL); - AddResource(*it->second); + AddResourceInternal(it->second->Clone()); } } @@ -85,22 +149,6 @@ } - Orthanc::DicomMap& LoadedDicomResources::GetResource(size_t index) - { - Flatten(); - - if (index >= flattened_.size()) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); - } - else - { - assert(flattened_[index] != NULL); - return *flattened_[index]; - } - } - - void LoadedDicomResources::MergeResource(Orthanc::DicomMap& target, const std::string& id) const { @@ -113,7 +161,7 @@ else { assert(it->second != NULL); - target.Merge(*it->second); + target.Merge(it->second->GetDicom()); } } @@ -131,21 +179,14 @@ else { assert(found->second != NULL); - return found->second->LookupStringValue(target, tag, false); + return found->second->GetDicom().LookupStringValue(target, tag, false); } } void LoadedDicomResources::AddResource(const Orthanc::DicomMap& dicom) { - std::string id; - - if (dicom.LookupStringValue(id, indexedTag_, false /* no binary value */) && - resources_.find(id) == resources_.end() /* Don't index twice the same resource */) - { - resources_[id] = dicom.Clone(); - flattened_.clear(); // Invalidate the flattened version - } + AddResourceInternal(new Resource(dicom)); } @@ -153,7 +194,10 @@ { Orthanc::DicomMap dicom; dicom.FromDicomAsJson(tags); - AddResource(dicom); + + std::unique_ptr resource(new Resource(dicom)); + resource->SetSourceJson(tags); + AddResourceInternal(resource.release()); } @@ -196,7 +240,7 @@ assert(it->second != NULL); std::string value; - if (it->second->LookupStringValue(value, tag, false)) + if (it->second->GetDicom().LookupStringValue(value, tag, false)) { Counter::iterator found = counter.find(value); if (found == counter.end()) diff -r 36430d73e36c -r 0489fe25ce48 OrthancStone/Sources/Loaders/LoadedDicomResources.h --- a/OrthancStone/Sources/Loaders/LoadedDicomResources.h Wed May 26 14:02:12 2021 +0200 +++ b/OrthancStone/Sources/Loaders/LoadedDicomResources.h Wed May 26 18:13:35 2021 +0200 @@ -34,13 +34,41 @@ class LoadedDicomResources : public boost::noncopyable { private: - typedef std::map Resources; + class Resource : public boost::noncopyable + { + private: + std::unique_ptr dicom_; + std::unique_ptr sourceJson_; + + public: + Resource(const Orthanc::DicomMap& dicom); + + Resource* Clone() const; + + const Orthanc::DicomMap& GetDicom() const + { + return *dicom_; + } - Orthanc::DicomTag indexedTag_; - Resources resources_; - std::vector flattened_; + bool HasSourceJson() const + { + return sourceJson_.get() != NULL; + } + + const Json::Value& GetSourceJson() const; - void Flatten(); + void SetSourceJson(const Json::Value& json); + }; + + typedef std::map Resources; + + Orthanc::DicomTag indexedTag_; + Resources resources_; + std::vector flattened_; + + void AddResourceInternal(Resource* resource); + + const Resource& GetResourceInternal(size_t index); void AddFromDicomWebInternal(const Json::Value& dicomweb); @@ -71,7 +99,10 @@ return resources_.size(); } - Orthanc::DicomMap& GetResource(size_t index); + const Orthanc::DicomMap& GetResource(size_t index) + { + return GetResourceInternal(index).GetDicom(); + } bool HasResource(const std::string& id) const { @@ -93,5 +124,15 @@ bool LookupTagValueConsensus(std::string& target, const Orthanc::DicomTag& tag) const; + + bool HasSourceJson(size_t index) + { + return GetResourceInternal(index).HasSourceJson(); + } + + const Json::Value& GetSourceJson(size_t index) + { + return GetResourceInternal(index).GetSourceJson(); + } }; } diff -r 36430d73e36c -r 0489fe25ce48 OrthancStone/Sources/Toolbox/DicomInstanceParameters.cpp --- a/OrthancStone/Sources/Toolbox/DicomInstanceParameters.cpp Wed May 26 14:02:12 2021 +0200 +++ b/OrthancStone/Sources/Toolbox/DicomInstanceParameters.cpp Wed May 26 18:13:35 2021 +0200 @@ -706,4 +706,76 @@ return true; } } + + + void DicomInstanceParameters::SetPixelSpacing(double pixelSpacingX, + double pixelSpacingY) + { + data_.hasPixelSpacing_ = true; + data_.pixelSpacingX_ = pixelSpacingX; + data_.pixelSpacingY_ = pixelSpacingY; + } + + + static const Json::Value* LookupDicomWebSingleValue(const Json::Value& dicomweb, + const std::string& tag, + const std::string& vr) + { + static const char* const VALUE = "Value"; + static const char* const VR = "vr"; + + if (dicomweb.type() == Json::objectValue && + dicomweb.isMember(tag) && + dicomweb[tag].type() == Json::objectValue && + dicomweb[tag].isMember(VALUE) && + dicomweb[tag].isMember(VR) && + dicomweb[tag][VR].type() == Json::stringValue && + dicomweb[tag][VR].asString() == vr && + dicomweb[tag][VALUE].type() == Json::arrayValue && + dicomweb[tag][VALUE].size() == 1u) + { + return &dicomweb[tag][VALUE][0]; + } + else + { + return NULL; + } + } + + + void DicomInstanceParameters::EnrichUsingDicomWeb(const Json::Value& dicomweb) + { + /** + * Use DICOM tag "SequenceOfUltrasoundRegions" (0018,6011) in + * order to derive the pixel spacing on ultrasound (US) images + **/ + + if (!data_.hasPixelSpacing_) + { + const Json::Value* region = LookupDicomWebSingleValue(dicomweb, "00186011", "SQ"); + if (region != NULL) + { + const Json::Value* physicalUnitsXDirection = LookupDicomWebSingleValue(*region, "00186024", "US"); + const Json::Value* physicalUnitsYDirection = LookupDicomWebSingleValue(*region, "00186026", "US"); + const Json::Value* physicalDeltaX = LookupDicomWebSingleValue(*region, "0018602C", "FD"); + const Json::Value* physicalDeltaY = LookupDicomWebSingleValue(*region, "0018602E", "FD"); + + if (physicalUnitsXDirection != NULL && + physicalUnitsYDirection != NULL && + physicalDeltaX != NULL && + physicalDeltaY != NULL && + physicalUnitsXDirection->type() == Json::intValue && + physicalUnitsYDirection->type() == Json::intValue && + physicalUnitsXDirection->asInt() == 0x0003 && // Centimeters + physicalUnitsYDirection->asInt() == 0x0003 && // Centimeters + physicalDeltaX->isNumeric() && + physicalDeltaY->isNumeric()) + { + // Scene coordinates are expressed in millimeters => multiplication by 10 + SetPixelSpacing(10.0 * physicalDeltaX->asDouble(), + 10.0 * physicalDeltaY->asDouble()); + } + } + } + } } diff -r 36430d73e36c -r 0489fe25ce48 OrthancStone/Sources/Toolbox/DicomInstanceParameters.h --- a/OrthancStone/Sources/Toolbox/DicomInstanceParameters.h Wed May 26 14:02:12 2021 +0200 +++ b/OrthancStone/Sources/Toolbox/DicomInstanceParameters.h Wed May 26 18:13:35 2021 +0200 @@ -235,5 +235,10 @@ { return data_.hasPixelSpacing_; } + + void SetPixelSpacing(double pixelSpacingX, + double pixelSpacingY); + + void EnrichUsingDicomWeb(const Json::Value& dicomweb); }; }