Mercurial > hg > orthanc-stone
changeset 2255:ee2b76f07bad
support of US series with varying SequenceOfUltrasoundRegions
| author | Sebastien Jodogne <s.jodogne@gmail.com> |
|---|---|
| date | Wed, 03 Dec 2025 15:30:50 +0100 |
| parents | 14cd7e87def4 |
| children | 0633b2841e44 |
| files | Applications/StoneWebViewer/WebAssembly/StoneWebViewer.cpp OrthancStone/Sources/Scene2D/Scene2D.cpp OrthancStone/Sources/Scene2D/Scene2D.h OrthancStone/Sources/Toolbox/DicomInstanceParameters.h OrthancStone/Sources/Toolbox/ParsedDicomDataset.cpp OrthancStone/Sources/Toolbox/SortedFrames.cpp OrthancStone/Sources/Toolbox/SortedFrames.h |
| diffstat | 7 files changed, 123 insertions(+), 22 deletions(-) [+] |
line wrap: on
line diff
--- a/Applications/StoneWebViewer/WebAssembly/StoneWebViewer.cpp Wed Dec 03 11:17:37 2025 +0100 +++ b/Applications/StoneWebViewer/WebAssembly/StoneWebViewer.cpp Wed Dec 03 15:30:50 2025 +0100 @@ -56,6 +56,7 @@ #include "../../../OrthancStone/Sources/Toolbox/DicomStructuredReport.h" #include "../../../OrthancStone/Sources/Toolbox/GeometryToolbox.h" #include "../../../OrthancStone/Sources/Toolbox/OsiriX/CollectionOfAnnotations.h" +#include "../../../OrthancStone/Sources/Toolbox/ParsedDicomDataset.h" #include "../../../OrthancStone/Sources/Toolbox/SortedFrames.h" #include "../../../OrthancStone/Sources/Viewport/DefaultViewportInteractor.h" @@ -275,6 +276,9 @@ const OrthancStone::Vector& point, double maximumDistance) const = 0; + virtual void EnrichInstance(const std::string& sopInstanceUid, + Orthanc::ParsedDicomFile& dicom) = 0; + static OrthancStone::CoordinateSystem3D GetFrameGeometry(const IFramesCollection& frames, size_t frameIndex) { @@ -334,6 +338,13 @@ { return frames_->FindClosestFrame(frameIndex, point, maximumDistance); } + + virtual void EnrichInstance(const std::string& sopInstanceUid, + Orthanc::ParsedDicomFile& dicom) ORTHANC_OVERRIDE + { + OrthancStone::ParsedDicomDataset dataset(dicom); + frames_->EnrichInstance(sopInstanceUid, dataset); + } }; @@ -605,6 +616,12 @@ { GetColorInternal() = color; } + + virtual void EnrichInstance(const std::string& sopInstanceUid, + Orthanc::ParsedDicomFile& dicom) ORTHANC_OVERRIDE + { + // Not useful in this case + } }; @@ -2559,6 +2576,8 @@ virtual void Handle(const OrthancStone::ParseDicomSuccessMessage& message) const ORTHANC_OVERRIDE { + GetViewport().frames_->EnrichInstance(sopInstanceUid_, message.GetDicom()); + std::unique_ptr<Orthanc::ImageAccessor> frame; try @@ -2956,7 +2975,9 @@ pixelSpacingY = 1000; } - if (FIX_LSD_479) + layer->SetPixelSpacing(pixelSpacingX, pixelSpacingY); + + if (0 /* FIX_LSD_479 */) { /** * Some series contain a first instance (secondary capture) that @@ -2968,13 +2989,9 @@ double physicalWidth = pixelSpacingX * static_cast<double>(frame.GetWidth()); double physicalHeight = pixelSpacingY * static_cast<double>(frame.GetHeight()); - if (OrthancStone::LinearAlgebra::IsCloseToZero(physicalWidth) || - OrthancStone::LinearAlgebra::IsCloseToZero(physicalHeight)) - { - // Numerical instability, don't try further processing - layer->SetPixelSpacing(pixelSpacingX, pixelSpacingY); - } - else + // On numerical instability, don't try further processing + if (!OrthancStone::LinearAlgebra::IsCloseToZero(physicalWidth) && + !OrthancStone::LinearAlgebra::IsCloseToZero(physicalHeight)) { double scale = std::max(centralPhysicalWidth_ / physicalWidth, centralPhysicalHeight_ / physicalHeight); @@ -2983,10 +3000,6 @@ (centralPhysicalHeight_ - physicalHeight * scale) / 2.0); } } - else - { - layer->SetPixelSpacing(pixelSpacingX, pixelSpacingY); - } StoneAnnotationsRegistry::GetInstance().Load(*stoneAnnotations_, instance.GetSopInstanceUid(), frameIndex); @@ -3014,6 +3027,8 @@ holder.AddLayer(LAYER_STRUCTURED_REPORT, NULL); } + const unsigned int currentWidth = layer->GetTexture().GetWidth(); + const unsigned int currentHeight = layer->GetTexture().GetHeight(); holder.AddLayer(LAYER_TEXTURE, layer.release()); { @@ -3021,6 +3036,25 @@ OrthancStone::Scene2D& scene = lock->GetController().GetScene(); + bool hasPreviousSize = false; + unsigned int previousWidth, previousHeight; + OrthancStone::Extent2D previousExtent; + if (scene.HasLayer(LAYER_TEXTURE)) + { + const OrthancStone::ISceneLayer& previousLayer = scene.GetLayer(LAYER_TEXTURE); + previousLayer.GetBoundingBox(previousExtent); + if (!previousExtent.IsEmpty() && + (previousLayer.GetType() == OrthancStone::ISceneLayer::Type_ColorTexture || + previousLayer.GetType() == OrthancStone::ISceneLayer::Type_FloatTexture || + previousLayer.GetType() == OrthancStone::ISceneLayer::Type_LookupTableTexture)) + { + const OrthancStone::TextureBaseSceneLayer& texture = dynamic_cast<const OrthancStone::TextureBaseSceneLayer&>(previousLayer); + hasPreviousSize = true; + previousWidth = texture.GetTexture().GetWidth(); + previousHeight = texture.GetTexture().GetHeight(); + } + } + holder.Commit(scene); stoneAnnotations_->Render(scene); // Necessary for "FitContent()" to work @@ -3039,9 +3073,24 @@ lock->GetCompositor().FitContent(scene); } - stoneAnnotations_->Render(scene); + //stoneAnnotations_->Render(scene); fitNextContent_ = false; } + else if (hasPreviousSize) + { + if (currentWidth == previousWidth && + currentHeight == previousHeight) + { + // This is notably useful for US images, where the width/height is constant + // across the series, while the zoom level varies (cf. Michael Vitale) + scene.PreserveExtent(LAYER_TEXTURE, previousExtent); + } + else + { + // This supersedes the (incorrect) FIX_LSD_479 that broke pixel spacing in annotations + lock->GetCompositor().FitContent(scene); + } + } //lock->GetCompositor().Refresh(scene); lock->Invalidate();
--- a/OrthancStone/Sources/Scene2D/Scene2D.cpp Wed Dec 03 11:17:37 2025 +0100 +++ b/OrthancStone/Sources/Scene2D/Scene2D.cpp Wed Dec 03 15:30:50 2025 +0100 @@ -25,6 +25,8 @@ #include <OrthancException.h> +#include "ScenePoint2D.h" + namespace OrthancStone { @@ -354,4 +356,27 @@ GetSceneToCanvasTransform()); FitContent(transform, canvasWidth, canvasHeight); } + + + void Scene2D::PreserveExtent(int depth, + const Extent2D& previousExtent) + { + if (!previousExtent.IsEmpty() && + HasLayer(depth)) + { + Extent2D currentExtent; + GetLayer(depth).GetBoundingBox(currentExtent); + + if (!currentExtent.IsEmpty()) + { + AffineTransform2D t1 = GetSceneToCanvasTransform(); + AffineTransform2D t2 = AffineTransform2D::CreateOffset(-previousExtent.GetCenterX(), -previousExtent.GetCenterY()); + AffineTransform2D t3 = AffineTransform2D::CreateScaling(previousExtent.GetWidth() / currentExtent.GetWidth(), + previousExtent.GetHeight() / currentExtent.GetHeight()); + AffineTransform2D t4 = AffineTransform2D::CreateOffset(currentExtent.GetCenterX(), currentExtent.GetCenterY()); + + SetSceneToCanvasTransform(AffineTransform2D::Combine(t1, t2, t3, t4)); + } + } + } }
--- a/OrthancStone/Sources/Scene2D/Scene2D.h Wed Dec 03 11:17:37 2025 +0100 +++ b/OrthancStone/Sources/Scene2D/Scene2D.h Wed Dec 03 15:30:50 2025 +0100 @@ -136,5 +136,8 @@ void FlipViewportY(unsigned int canvasWidth, unsigned int canvasHeight); + + void PreserveExtent(int depth, + const Extent2D& previousExtent); }; }
--- a/OrthancStone/Sources/Toolbox/DicomInstanceParameters.h Wed Dec 03 11:17:37 2025 +0100 +++ b/OrthancStone/Sources/Toolbox/DicomInstanceParameters.h Wed Dec 03 15:30:50 2025 +0100 @@ -77,8 +77,6 @@ std::unique_ptr<Orthanc::DicomMap> tags_; std::unique_ptr<Orthanc::DicomImageInformation> imageInformation_; // Lazy evaluation - void InjectSequenceTags(const IDicomDataset& dataset); - public: explicit DicomInstanceParameters(const DicomInstanceParameters& other); @@ -264,5 +262,7 @@ bool LookupPerFrameWindowing(Windowing& windowing, unsigned int frame) const; + + void InjectSequenceTags(const IDicomDataset& dataset); }; }
--- a/OrthancStone/Sources/Toolbox/ParsedDicomDataset.cpp Wed Dec 03 11:17:37 2025 +0100 +++ b/OrthancStone/Sources/Toolbox/ParsedDicomDataset.cpp Wed Dec 03 15:30:50 2025 +0100 @@ -23,7 +23,9 @@ #include "ParsedDicomDataset.h" -#include <dcmtk/dcmdata/dcfilefo.h> +#include <Logging.h> +#include <DicomParsing/FromDcmtkBridge.h> + namespace OrthancStone { @@ -31,7 +33,7 @@ const Orthanc::DicomPath& path) { DcmItem* node = dicom.GetDcmtkObject().getDataset(); - + for (size_t i = 0; i < path.GetPrefixLength(); i++) { const Orthanc::DicomTag& tmp = path.GetPrefixTag(i); @@ -70,12 +72,17 @@ { DcmTagKey tag(path.GetFinalTag().GetGroup(), path.GetFinalTag().GetElement()); - const char* s = NULL; - if (node->findAndGetString(tag, s).good() && - s != NULL) + DcmElement* element = NULL; + if (node->findAndGetElement(tag, element).good() && + element != NULL) { - result.assign(s); - return true; + // Leverage the Orthanc framework to convert any VR as a string + const Orthanc::ValueRepresentation vr = Orthanc::FromDcmtkBridge::LookupValueRepresentation(path.GetFinalTag()); + const std::set<Orthanc::DicomTag> ignoreTagLength; + std::unique_ptr<Orthanc::DicomValue> value( + Orthanc::FromDcmtkBridge::ConvertLeafElement( + *element, Orthanc::DicomToJsonFlags_None, 0, Orthanc::Encoding_Ascii, false, ignoreTagLength, vr)); + return value->CopyToString(result, false /* no binary */); } }
--- a/OrthancStone/Sources/Toolbox/SortedFrames.cpp Wed Dec 03 11:17:37 2025 +0100 +++ b/OrthancStone/Sources/Toolbox/SortedFrames.cpp Wed Dec 03 15:30:50 2025 +0100 @@ -443,4 +443,18 @@ "Sort() has not been called"); } } + + + void SortedFrames::EnrichInstance(const std::string& sopInstanceUid, + const IDicomDataset& dicom) + { + size_t index; + if (LookupSopInstanceUid(index, sopInstanceUid)) + { + DicomInstanceParameters* instance = instances_[index]; + assert(instance != NULL); + + instance->InjectSequenceTags(dicom); + } + } }
--- a/OrthancStone/Sources/Toolbox/SortedFrames.h Wed Dec 03 11:17:37 2025 +0100 +++ b/OrthancStone/Sources/Toolbox/SortedFrames.h Wed Dec 03 15:30:50 2025 +0100 @@ -154,5 +154,8 @@ bool FindClosestFrame(size_t& frameIndex, const Vector& point, double maximumDistance) const; + + void EnrichInstance(const std::string& sopInstanceUid, + const IDicomDataset& dicom); }; }
