# HG changeset patch # User Sebastien Jodogne # Date 1648034675 -3600 # Node ID f4050908c6bc5534f7f188c98968806a69a15f7b # Parent f4cdcba8c32a7a34e0d89314c2d67e1fe1fee8f0 display of overlays diff -r f4cdcba8c32a -r f4050908c6bc Applications/StoneWebViewer/NEWS --- a/Applications/StoneWebViewer/NEWS Tue Mar 22 17:39:19 2022 +0100 +++ b/Applications/StoneWebViewer/NEWS Wed Mar 23 12:24:35 2022 +0100 @@ -1,6 +1,7 @@ Pending changes in the mainline =============================== +* Display of overlays * SeriesList: - display the SeriesNumber tag in front of image count. - order series by SeriesNumber diff -r f4cdcba8c32a -r f4050908c6bc Applications/StoneWebViewer/WebAssembly/StoneWebViewer.cpp --- a/Applications/StoneWebViewer/WebAssembly/StoneWebViewer.cpp Tue Mar 22 17:39:19 2022 +0100 +++ b/Applications/StoneWebViewer/WebAssembly/StoneWebViewer.cpp Wed Mar 23 12:24:35 2022 +0100 @@ -1462,6 +1462,86 @@ +class OverlaysRegistry : public boost::noncopyable +{ +private: + typedef std::map Content; + + Content content_; + +public: + ~OverlaysRegistry() + { + for (Content::iterator it = content_.begin(); it != content_.end(); ++it) + { + assert(it->second != NULL); + delete it->second; + } + } + + static OverlaysRegistry& GetInstance() + { + static OverlaysRegistry singleton; + return singleton; + } + + void Register(const std::string& sopInstanceUid, + const OrthancStone::DicomInstanceParameters& parameters, + int overlayX, + int overlayY, + const Orthanc::ImageAccessor& overlay) + { + // Don't register twice the same overlay + Content::iterator found = content_.find(sopInstanceUid); + if (found == content_.end()) + { + content_[sopInstanceUid] = parameters.CreateOverlayTexture(overlayX, overlayY, overlay); + } + } + + class Accessor : public boost::noncopyable + { + private: + const OrthancStone::LookupTableTextureSceneLayer* texture_; + + public: + Accessor(const OverlaysRegistry& registry, + const std::string& sopInstanceUid) + { + Content::const_iterator found = registry.content_.find(sopInstanceUid); + if (found == registry.content_.end()) + { + texture_ = NULL; + } + else + { + assert(found->second != NULL); + texture_ = found->second; + } + } + + bool IsValid() const + { + return texture_ != NULL; + } + + OrthancStone::LookupTableTextureSceneLayer* CreateTexture() const + { + if (texture_ == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + else + { + return dynamic_cast(texture_->Clone()); + } + } + }; +}; + + + + class ViewerViewport : public OrthancStone::ObserverBase { public: @@ -1505,9 +1585,10 @@ private: static const int LAYER_TEXTURE = 0; - static const int LAYER_REFERENCE_LINES = 1; - static const int LAYER_ANNOTATIONS_OSIRIX = 2; - static const int LAYER_ANNOTATIONS_STONE = 3; + static const int LAYER_OVERLAY = 1; + static const int LAYER_REFERENCE_LINES = 2; + static const int LAYER_ANNOTATIONS_OSIRIX = 3; + static const int LAYER_ANNOTATIONS_STONE = 4; class ICommand : public Orthanc::IDynamicObject @@ -1823,6 +1904,8 @@ Orthanc::DicomMap tags; dicom.ExtractDicomSummary(tags, ORTHANC_STONE_MAX_TAG_LENGTH); + OrthancStone::DicomInstanceParameters parameters(tags); + std::unique_ptr converted; if (frameProtection->GetFormat() == Orthanc::PixelFormat_RGB24) @@ -1831,25 +1914,28 @@ } else { - double a = 1; - double b = 0; - - double doseScaling; - if (tags.ParseDouble(doseScaling, Orthanc::DICOM_TAG_DOSE_GRID_SCALING)) - { - a = doseScaling; - } - - double rescaleIntercept, rescaleSlope; - dicom.GetRescale(rescaleIntercept, rescaleSlope, frameNumber); - a *= rescaleSlope; - b = rescaleIntercept; - converted.reset(new Orthanc::Image(Orthanc::PixelFormat_Float32, frameProtection->GetWidth(), frameProtection->GetHeight(), false)); Orthanc::ImageProcessing::Convert(*converted, *frameProtection); - Orthanc::ImageProcessing::ShiftScale2(*converted, b, a, false); + parameters.ApplyRescaleAndDoseScaling(*converted, false /* don't use double */); } + try + { + int x, y; + std::unique_ptr overlay(dicom.DecodeAllOverlays(x, y)); + + if (overlay.get() != NULL && + overlay->GetWidth() > 0 && + overlay->GetHeight() > 0) + { + OverlaysRegistry::GetInstance().Register(sopInstanceUid, parameters, x, y, *overlay); + } + } + catch (Orthanc::OrthancException& e) + { + LOG(ERROR) << "Cannot decode overlays from instance " << sopInstanceUid; + } + assert(converted.get() != NULL); viewport.RenderCurrentSceneFromCommand(*converted, sopInstanceUid, frameNumber, DisplayedFrameQuality_High); viewport.framesCache_->Acquire(sopInstanceUid, frameNumber, converted.release(), QUALITY_FULL); @@ -2107,6 +2193,18 @@ layer->SetPixelSpacing(pixelSpacingX, pixelSpacingY); } + std::unique_ptr overlay; + + { + OverlaysRegistry::Accessor accessor(OverlaysRegistry::GetInstance(), instance.GetSopInstanceUid()); + if (accessor.IsValid()) + { + overlay.reset(accessor.CreateTexture()); + overlay->SetFlipX(flipX_); + overlay->SetFlipY(flipY_); + } + } + std::unique_ptr annotationsOsiriX; if (osiriXAnnotations_) @@ -2139,6 +2237,15 @@ scene.SetLayer(LAYER_TEXTURE, layer.release()); + if (overlay.get() != NULL) + { + scene.SetLayer(LAYER_OVERLAY, overlay.release()); + } + else + { + scene.DeleteLayer(LAYER_OVERLAY); + } + if (annotationsOsiriX.get() != NULL) { scene.SetLayer(LAYER_ANNOTATIONS_OSIRIX, annotationsOsiriX.release()); @@ -2339,6 +2446,15 @@ layer.SetFlipX(flipX_); layer.SetFlipY(flipY_); } + + { + OrthancStone::TextureBaseSceneLayer& layer = + dynamic_cast( + lock->GetController().GetScene().GetLayer(LAYER_OVERLAY)); + + layer.SetFlipX(flipX_); + layer.SetFlipY(flipY_); + } lock->Invalidate(); } diff -r f4cdcba8c32a -r f4050908c6bc OrthancStone/Sources/Toolbox/DicomInstanceParameters.cpp --- a/OrthancStone/Sources/Toolbox/DicomInstanceParameters.cpp Tue Mar 22 17:39:19 2022 +0100 +++ b/OrthancStone/Sources/Toolbox/DicomInstanceParameters.cpp Wed Mar 23 12:24:35 2022 +0100 @@ -355,45 +355,18 @@ throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat); } - double factor = data_.doseGridScaling_; + double scaling = data_.doseGridScaling_; double offset = 0.0; if (data_.hasRescale_) { - factor *= data_.rescaleSlope_; + scaling *= data_.rescaleSlope_; offset = data_.rescaleIntercept_; } - if (!LinearAlgebra::IsNear(factor, 1) || - !LinearAlgebra::IsNear(offset, 0)) - { - const unsigned int width = image.GetWidth(); - const unsigned int height = image.GetHeight(); - - for (unsigned int y = 0; y < height; y++) - { - float* p = reinterpret_cast(image.GetRow(y)); - - if (useDouble) - { - // Slower, accurate implementation using double - for (unsigned int x = 0; x < width; x++, p++) - { - double value = static_cast(*p); - *p = static_cast(value * factor + offset); - } - } - else - { - // Fast, approximate implementation using float - for (unsigned int x = 0; x < width; x++, p++) - { - *p = (*p) * static_cast(factor) + static_cast(offset); - } - } - } - } + Orthanc::ImageProcessing::ShiftScale2(image, offset, scaling, false); } + double DicomInstanceParameters::GetRescaleIntercept() const { @@ -686,6 +659,29 @@ texture->SetOrigin(static_cast(originX - 1) * texture->GetPixelSpacingX(), static_cast(originY - 1) * texture->GetPixelSpacingY()); + std::vector lut(4 * 256); + for (size_t i = 0; i < 256; i++) + { + if (i < 127) + { + // Black pixels are converted to transparent pixels + lut[4 * i] = 0; + lut[4 * i + 1] = 0; + lut[4 * i + 2] = 0; + lut[4 * i + 3] = 0; // alpha + } + else + { + // White pixels are converted to opaque white + lut[4 * i] = 255; + lut[4 * i + 1] = 255; + lut[4 * i + 2] = 255; + lut[4 * i + 3] = 255; // alpha + } + } + + texture->SetLookupTable(lut); + return texture.release(); } @@ -706,16 +702,16 @@ double DicomInstanceParameters::ApplyRescale(double value) const { - double factor = data_.doseGridScaling_; + double scaling = data_.doseGridScaling_; double offset = 0.0; if (data_.hasRescale_) { - factor *= data_.rescaleSlope_; + scaling *= data_.rescaleSlope_; offset = data_.rescaleIntercept_; } - return (value * factor + offset); + return (value * scaling + offset); } diff -r f4cdcba8c32a -r f4050908c6bc OrthancStone/Sources/Toolbox/DicomInstanceParameters.h --- a/OrthancStone/Sources/Toolbox/DicomInstanceParameters.h Tue Mar 22 17:39:19 2022 +0100 +++ b/OrthancStone/Sources/Toolbox/DicomInstanceParameters.h Wed Mar 23 12:24:35 2022 +0100 @@ -75,9 +75,6 @@ std::unique_ptr tags_; std::unique_ptr imageInformation_; // Lazy evaluation - void ApplyRescaleAndDoseScaling(Orthanc::ImageAccessor& image, - bool useDouble) const; - public: explicit DicomInstanceParameters(const DicomInstanceParameters& other) : data_(other.data_), @@ -231,6 +228,9 @@ return data_.doseGridScaling_; } + void ApplyRescaleAndDoseScaling(Orthanc::ImageAccessor& image, + bool useDouble) const; + double ApplyRescale(double value) const; // Required for RT-DOSE