Mercurial > hg > orthanc-stone
changeset 949:32eaf4929b08 toa2019081301
OrthancMultiframeVolumeLoader and OrthancSeriesVolumeProgressiveLoader now implement IGeometryProvider so that the geometry reference can be switched (CT or DOSE, for instance) + VolumeImageGeometry::SetSize renamed to VolumeImageGeometry::SetSizeInVoxels + prevent text layer update if text or properties do not change + a few stream operator<< for debug (Vector, Matrix,...) + fixed memory access aligment issues in ImageBuffer3D::ExtractSagittalSlice + fix for wrong screen Y offset of mpr slices in DicomVolumeImageMPRSlicer.
line wrap: on
line diff
--- a/Framework/Loaders/OrthancMultiframeVolumeLoader.cpp Sat Aug 10 13:13:11 2019 +0200 +++ b/Framework/Loaders/OrthancMultiframeVolumeLoader.cpp Tue Aug 13 16:01:05 2019 +0200 @@ -224,7 +224,7 @@ { VolumeImageGeometry geometry; - geometry.SetSize(width, height, depth); + geometry.SetSizeInVoxels(width, height, depth); geometry.SetAxialGeometry(parameters.GetGeometry()); geometry.SetVoxelDimensions(parameters.GetPixelSpacingX(), parameters.GetPixelSpacingY(), spacingZ); @@ -235,6 +235,8 @@ ScheduleFrameDownloads(); + + BroadcastMessage(DicomVolumeImage::GeometryReadyMessage(*volume_)); } @@ -319,7 +321,16 @@ pixelDataLoaded_ = true; BroadcastMessage(DicomVolumeImage::ContentUpdatedMessage(*volume_)); } + + bool OrthancMultiframeVolumeLoader::HasGeometry() const + { + return volume_->HasGeometry(); + } + const OrthancStone::VolumeImageGeometry& OrthancMultiframeVolumeLoader::GetImageGeometry() const + { + return volume_->GetGeometry(); + } OrthancMultiframeVolumeLoader::OrthancMultiframeVolumeLoader(boost::shared_ptr<DicomVolumeImage> volume, IOracle& oracle,
--- a/Framework/Loaders/OrthancMultiframeVolumeLoader.h Sat Aug 10 13:13:11 2019 +0200 +++ b/Framework/Loaders/OrthancMultiframeVolumeLoader.h Tue Aug 13 16:01:05 2019 +0200 @@ -30,7 +30,8 @@ { class OrthancMultiframeVolumeLoader : public LoaderStateMachine, - public IObservable + public IObservable, + public IGeometryProvider { private: class LoadRTDoseGeometry; @@ -56,6 +57,9 @@ void SetUncompressedPixelData(const std::string& pixelData); + virtual bool HasGeometry() const ORTHANC_OVERRIDE; + virtual const VolumeImageGeometry& GetImageGeometry() const ORTHANC_OVERRIDE; + public: OrthancMultiframeVolumeLoader(boost::shared_ptr<DicomVolumeImage> volume, IOracle& oracle,
--- a/Framework/Loaders/OrthancSeriesVolumeProgressiveLoader.cpp Sat Aug 10 13:13:11 2019 +0200 +++ b/Framework/Loaders/OrthancSeriesVolumeProgressiveLoader.cpp Tue Aug 13 16:01:05 2019 +0200 @@ -190,7 +190,7 @@ const DicomInstanceParameters& parameters = *slices_[0]; geometry_.reset(new VolumeImageGeometry); - geometry_->SetSize(parameters.GetImageInformation().GetWidth(), + geometry_->SetSizeInVoxels(parameters.GetImageInformation().GetWidth(), parameters.GetImageInformation().GetHeight(), static_cast<unsigned int>(slices.GetSlicesCount())); geometry_->SetAxialGeometry(slices.GetSliceGeometry(0));
--- a/Framework/Loaders/OrthancSeriesVolumeProgressiveLoader.h Sat Aug 10 13:13:11 2019 +0200 +++ b/Framework/Loaders/OrthancSeriesVolumeProgressiveLoader.h Tue Aug 13 16:01:05 2019 +0200 @@ -44,7 +44,8 @@ class OrthancSeriesVolumeProgressiveLoader : public IObserver, public IObservable, - public IVolumeSlicer + public IVolumeSlicer, + public IGeometryProvider { private: static const unsigned int LOW_QUALITY = 0; @@ -78,12 +79,12 @@ void ComputeGeometry(SlicesSorter& slices); - bool HasGeometry() const + virtual bool HasGeometry() const { return geometry_.get() != NULL; } - const VolumeImageGeometry& GetImageGeometry() const; + virtual const VolumeImageGeometry& GetImageGeometry() const; const DicomInstanceParameters& GetSliceParameters(size_t index) const; @@ -139,7 +140,7 @@ subscribing, for instance if they are created or listening only AFTER the "geometry loaded" message is broadcast */ - bool HasGeometry() const + bool HasGeometry() const ORTHANC_OVERRIDE { return seriesGeometry_.HasGeometry(); } @@ -147,7 +148,7 @@ /** Same remark as HasGeometry */ - const VolumeImageGeometry& GetImageGeometry() const + const VolumeImageGeometry& GetImageGeometry() const ORTHANC_OVERRIDE { return seriesGeometry_.GetImageGeometry(); }
--- a/Framework/Scene2D/OpenGLCompositor.cpp Sat Aug 10 13:13:11 2019 +0200 +++ b/Framework/Scene2D/OpenGLCompositor.cpp Tue Aug 13 16:01:05 2019 +0200 @@ -107,6 +107,7 @@ const Font* font = GetFont(l.GetFontIndex()); if (font == NULL) { + LOG(WARNING) << "There is no font at index " << l.GetFontIndex(); return NULL; } else
--- a/Framework/Scene2D/TextSceneLayer.cpp Sat Aug 10 13:13:11 2019 +0200 +++ b/Framework/Scene2D/TextSceneLayer.cpp Tue Aug 13 16:01:05 2019 +0200 @@ -50,32 +50,47 @@ void TextSceneLayer::SetPosition(double x, double y) { - x_ = x; - y_ = y; - revision_ ++; + if (x != x_ || y != y_) + { + x_ = x; + y_ = y; + revision_++; + } } void TextSceneLayer::SetText(const std::string& utf8) { - utf8_ = utf8; - revision_ ++; + if (utf8 != utf8_) + { + utf8_ = utf8; + revision_++; + } } void TextSceneLayer::SetFontIndex(size_t fontIndex) { - fontIndex_ = fontIndex; - revision_ ++; + if (fontIndex != fontIndex_) + { + fontIndex_ = fontIndex; + revision_++; + } } void TextSceneLayer::SetAnchor(BitmapAnchor anchor) { - anchor_ = anchor; - revision_ ++; + if (anchor != anchor_) + { + anchor_ = anchor; + revision_++; + } } void TextSceneLayer::SetBorder(unsigned int border) { - border_ = border; - revision_ ++; + if (border != border_) + { + border_ = border; + revision_++; + } } }
--- a/Framework/Scene2DViewport/LayerHolder.cpp Sat Aug 10 13:13:11 2019 +0200 +++ b/Framework/Scene2DViewport/LayerHolder.cpp Tue Aug 13 16:01:05 2019 +0200 @@ -81,6 +81,12 @@ return controller->GetScene(); } + void LayerHolder::DeleteLayersIfNeeded() + { + if (baseLayerIndex_ != -1) + DeleteLayers(); + } + void LayerHolder::DeleteLayers() { for (int i = 0; i < textLayerCount_ + polylineLayerCount_; ++i)
--- a/Framework/Scene2DViewport/LayerHolder.h Sat Aug 10 13:13:11 2019 +0200 +++ b/Framework/Scene2DViewport/LayerHolder.h Tue Aug 13 16:01:05 2019 +0200 @@ -69,6 +69,11 @@ void DeleteLayers(); /** + This removes the layers from the scene if they are already created + */ + void DeleteLayersIfNeeded(); + + /** Please note that the returned pointer belongs to the scene.Don't you dare storing or deleting it, you fool!
--- a/Framework/StoneException.h Sat Aug 10 13:13:11 2019 +0200 +++ b/Framework/StoneException.h Tue Aug 13 16:01:05 2019 +0200 @@ -22,6 +22,9 @@ #pragma once #include "Core/OrthancException.h" +#include "Toolbox/LinearAlgebra.h" + +#include <iostream> #include <boost/lexical_cast.hpp> namespace OrthancStone @@ -126,7 +129,6 @@ return boost::lexical_cast<std::string>(applicationErrorCode_).c_str(); } }; - } // See https://isocpp.org/wiki/faq/misc-technical-issues#macros-with-multi-stmts @@ -151,6 +153,18 @@ # define GET_ORTHANC_ASSERT(_1,_2,NAME,...) NAME # define ORTHANC_ASSERT(...) ORTHANC_EXPAND(GET_ORTHANC_ASSERT(__VA_ARGS__, ORTHANC_ASSERT2, ORTHANC_ASSERT1, UNUSED)(__VA_ARGS__)) + + + + + + + + + + + + /* Explanation:
--- a/Framework/Toolbox/CoordinateSystem3D.cpp Sat Aug 10 13:13:11 2019 +0200 +++ b/Framework/Toolbox/CoordinateSystem3D.cpp Tue Aug 13 16:01:05 2019 +0200 @@ -220,4 +220,13 @@ return false; } } + + std::ostream& operator<< (std::ostream& s, const CoordinateSystem3D& that) + { + s << "origin: " << that.origin_ << " normal: " << that.normal_ + << " axisX: " << that.axisX_ << " axisY: " << that.axisY_ + << " D: " << that.d_; + return s; + } + }
--- a/Framework/Toolbox/CoordinateSystem3D.h Sat Aug 10 13:13:11 2019 +0200 +++ b/Framework/Toolbox/CoordinateSystem3D.h Tue Aug 13 16:01:05 2019 +0200 @@ -25,6 +25,8 @@ #include <Plugins/Samples/Common/IDicomDataset.h> +#include <iosfwd> + namespace OrthancStone { // Geometry of a 3D plane @@ -52,6 +54,8 @@ SetupCanonical(); } + friend std::ostream& operator<< (std::ostream& s, const CoordinateSystem3D& that); + CoordinateSystem3D(const Vector& origin, const Vector& axisX, const Vector& axisY);
--- a/Framework/Toolbox/DicomInstanceParameters.cpp Sat Aug 10 13:13:11 2019 +0200 +++ b/Framework/Toolbox/DicomInstanceParameters.cpp Tue Aug 13 16:01:05 2019 +0200 @@ -44,7 +44,7 @@ if (dicom.CopyToString(increment, Orthanc::DICOM_TAG_FRAME_INCREMENT_POINTER, false)) { Orthanc::Toolbox::ToUpperCase(increment); - if (increment != "3004,000C") // This is the "Grid Frame Offset Vector" tag + if (increment != "3004,000C") // This is the "Grid Frame Offset Vector" tag (DICOM_TAG_GRID_FRAME_OFFSET_VECTOR) { LOG(ERROR) << "RT-DOSE: Bad value for the \"FrameIncrementPointer\" tag"; return;
--- a/Framework/Toolbox/DicomStructureSet.cpp Sat Aug 10 13:13:11 2019 +0200 +++ b/Framework/Toolbox/DicomStructureSet.cpp Tue Aug 13 16:01:05 2019 +0200 @@ -362,7 +362,6 @@ return structures_[index]; } - DicomStructureSet::DicomStructureSet(const OrthancPlugins::FullOrthancDataset& tags) { OrthancPlugins::DicomDatasetReader reader(tags); @@ -388,7 +387,7 @@ structures_[i].name_ = reader.GetStringValue (OrthancPlugins::DicomPath(DICOM_TAG_STRUCTURE_SET_ROI_SEQUENCE, i, DICOM_TAG_ROI_NAME), - "No interpretation"); + "No name"); Vector color; if (ParseVector(color, tags, OrthancPlugins::DicomPath(DICOM_TAG_ROI_CONTOUR_SEQUENCE, i,
--- a/Framework/Toolbox/LinearAlgebra.cpp Sat Aug 10 13:13:11 2019 +0200 +++ b/Framework/Toolbox/LinearAlgebra.cpp Tue Aug 13 16:01:05 2019 +0200 @@ -21,14 +21,18 @@ #include "LinearAlgebra.h" +#include "../StoneException.h" + #include <Core/Logging.h> #include <Core/OrthancException.h> #include <Core/Toolbox.h> -#include <stdio.h> #include <boost/lexical_cast.hpp> #include <boost/numeric/ublas/lu.hpp> +#include <stdio.h> +#include <iostream> + namespace OrthancStone { namespace LinearAlgebra @@ -652,4 +656,38 @@ return m; } } + + std::ostream& operator<<(std::ostream& s, const Vector& vec) + { + s << "("; + for (size_t i = 0; i < vec.size(); ++i) + { + s << vec(i); + if (i < (vec.size() - 1)) + s << ", "; + } + s << ")"; + return s; + } + + std::ostream& operator<<(std::ostream& s, const Matrix& m) + { + ORTHANC_ASSERT(m.size1() == m.size2()); + s << "("; + for (size_t i = 0; i < m.size1(); ++i) + { + s << "("; + for (size_t j = 0; j < m.size2(); ++j) + { + s << m(i,j); + if (j < (m.size2() - 1)) + s << ", "; + } + s << ")"; + if (i < (m.size1() - 1)) + s << ", "; + } + s << ")"; + return s; + } }
--- a/Framework/Toolbox/LinearAlgebra.h Sat Aug 10 13:13:11 2019 +0200 +++ b/Framework/Toolbox/LinearAlgebra.h Tue Aug 13 16:01:05 2019 +0200 @@ -38,6 +38,10 @@ typedef boost::numeric::ublas::matrix<double> Matrix; typedef boost::numeric::ublas::vector<double> Vector; + // logs, debugging... + std::ostream& operator<<(std::ostream& s, const Vector& vec); + std::ostream& operator<<(std::ostream& s, const Matrix& m); + namespace LinearAlgebra { void Print(const Vector& v);
--- a/Framework/Volumes/DicomVolumeImage.cpp Sat Aug 10 13:13:11 2019 +0200 +++ b/Framework/Volumes/DicomVolumeImage.cpp Tue Aug 13 16:01:05 2019 +0200 @@ -21,8 +21,11 @@ #include "DicomVolumeImage.h" +#include "../StoneException.h" + #include <Core/OrthancException.h> + namespace OrthancStone { void DicomVolumeImage::CheckHasGeometry() const
--- a/Framework/Volumes/DicomVolumeImage.h Sat Aug 10 13:13:11 2019 +0200 +++ b/Framework/Volumes/DicomVolumeImage.h Tue Aug 13 16:01:05 2019 +0200 @@ -28,6 +28,14 @@ namespace OrthancStone { + class IGeometryProvider + { + public: + virtual ~IGeometryProvider() {} + virtual bool HasGeometry() const = 0; + virtual const VolumeImageGeometry& GetImageGeometry() const = 0; + }; + /** This class combines a 3D image buffer, a 3D volume geometry and information about the DICOM parameters of the series.
--- a/Framework/Volumes/DicomVolumeImageMPRSlicer.cpp Sat Aug 10 13:13:11 2019 +0200 +++ b/Framework/Volumes/DicomVolumeImageMPRSlicer.cpp Tue Aug 13 16:01:05 2019 +0200 @@ -21,7 +21,11 @@ #include "DicomVolumeImageMPRSlicer.h" +#include "../StoneException.h" + #include <Core/OrthancException.h> +//#include <Core/Images/PngWriter.h> +#include <Core/Images/JpegWriter.h> namespace OrthancStone { @@ -76,14 +80,76 @@ ImageBuffer3D::SliceReader reader(volume_.GetPixelData(), projection_, sliceIndex_); texture.reset(dynamic_cast<TextureBaseSceneLayer*> (configurator->CreateTextureFromDicom(reader.GetAccessor(), parameters))); + + // <DEBUG-BLOCK> +#if 0 + Orthanc::JpegWriter writer; + writer.SetQuality(60); + static int index = 0; + std::string filePath = "C:\\temp\\sliceReader_P"; + filePath += boost::lexical_cast<std::string>(projection_); + filePath += "_I"; + filePath += boost::lexical_cast<std::string>(index); + filePath += ".jpg"; + index++; + writer.WriteToFile(filePath, reader.GetAccessor()); +#endif + // <END-OF-DEBUG-BLOCK> } - + const CoordinateSystem3D& system = volume_.GetGeometry().GetProjectionGeometry(projection_); double x0, y0, x1, y1; cuttingPlane.ProjectPoint(x0, y0, system.GetOrigin()); cuttingPlane.ProjectPoint(x1, y1, system.GetOrigin() + system.GetAxisX()); + + // <DEBUG-BLOCK> +#if 0 + { + LOG(ERROR) << "+----------------------------------------------------+"; + LOG(ERROR) << "| DicomVolumeImageMPRSlicer::Slice::CreateSceneLayer |"; + LOG(ERROR) << "+----------------------------------------------------+"; + std::string projectionString; + switch (projection_) + { + case VolumeProjection_Coronal: + projectionString = "CORONAL"; + break; + case VolumeProjection_Axial: + projectionString = "CORONAL"; + break; + case VolumeProjection_Sagittal: + projectionString = "SAGITTAL"; + break; + default: + ORTHANC_ASSERT(false); + } + if(volume_.GetGeometry().GetDepth() == 200) + LOG(ERROR) << "| CT IMAGE 512x512 with projection " << projectionString; + else + LOG(ERROR) << "| RTDOSE IMAGE NNNxNNN with projection " << projectionString; + LOG(ERROR) << "+----------------------------------------------------+"; + LOG(ERROR) << "| cuttingPlane = " << cuttingPlane; + LOG(ERROR) << "| point to project = " << system.GetOrigin(); + LOG(ERROR) << "| result = x0: " << x0 << " y0: " << y0; + LOG(ERROR) << "+----------------------- END ------------------------+"; + } +#endif + // <END-OF-DEBUG-BLOCK> + +#if 1 // BGO 2019-08-13 + // The sagittal coordinate system has a Y vector going down. The displayed + // image (scene coords) has a Y vector pointing upwards (towards the patient + // coord Z index) + // we need to flip the Y local coordinates to get the scene-coord offset. + // TODO: this is quite ugly. Isn't there a better way? + if(projection_ == VolumeProjection_Sagittal) + texture->SetOrigin(x0, -y0); + else + texture->SetOrigin(x0, y0); +#else texture->SetOrigin(x0, y0); +#endif double dx = x1 - x0; double dy = y1 - y0; @@ -96,6 +162,19 @@ Vector tmp = volume_.GetGeometry().GetVoxelDimensions(projection_); texture->SetPixelSpacing(tmp[0], tmp[1]); + // <DEBUG-BLOCK> + { + //using std::endl; + //std::stringstream ss; + //ss << "DicomVolumeImageMPRSlicer::Slice::CreateSceneLayer | cuttingPlane = " << cuttingPlane << " | projection_ = " << projection_ << endl; + //ss << "volume_.GetGeometry().GetProjectionGeometry(projection_) = " << system << endl; + //ss << "cuttingPlane.ProjectPoint(x0, y0, system.GetOrigin()); --> | x0 = " << x0 << " | y0 = " << y0 << "| x1 = " << x1 << " | y1 = " << y1 << endl; + //ss << "volume_.GetGeometry() = " << volume_.GetGeometry() << endl; + //ss << "volume_.GetGeometry() = " << volume_.GetGeometry() << endl; + //LOG(ERROR) << ss.str(); + } + // <END-OF-DEBUG-BLOCK> + return texture.release(); }
--- a/Framework/Volumes/ImageBuffer3D.cpp Sat Aug 10 13:13:11 2019 +0200 +++ b/Framework/Volumes/ImageBuffer3D.cpp Tue Aug 13 16:01:05 2019 +0200 @@ -75,12 +75,14 @@ Orthanc::Image* ImageBuffer3D::ExtractSagittalSlice(unsigned int slice) const { + //LOG(TRACE) << "ImageBuffer3D::ExtractSagittalSlice this= " << std::hex << this << std::dec << " width_ = " << width_ << " height_ = " << height_ << " depth_ = " << depth_ << " slice = " << slice; if (slice >= width_) { throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); } std::auto_ptr<Orthanc::Image> result(new Orthanc::Image(format_, height_, depth_, false)); + //LOG(TRACE) << "ImageBuffer3D::ExtractSagittalSlice result will be an image of WIDTH = " << height_ << " and HEIGHT = " << depth_; unsigned int bytesPerPixel = Orthanc::GetBytesPerPixel(format_); @@ -91,10 +93,10 @@ for (unsigned int y = 0; y < height_; y++) { - const void* source = (reinterpret_cast<const uint8_t*>(image_.GetConstRow(y + z * height_)) + - bytesPerPixel * slice); - - memcpy(target, source, bytesPerPixel); + const void* source = (reinterpret_cast<const uint8_t*>(image_.GetConstRow(y + z * height_)) + bytesPerPixel * slice); + const uint8_t* byteSrc = reinterpret_cast<const uint8_t*>(source); + for (size_t byte = 0; byte < bytesPerPixel; ++byte) + target[byte] = byteSrc[byte]; target += bytesPerPixel; } }
--- a/Framework/Volumes/VolumeImageGeometry.cpp Sat Aug 10 13:13:11 2019 +0200 +++ b/Framework/Volumes/VolumeImageGeometry.cpp Tue Aug 13 16:01:05 2019 +0200 @@ -46,6 +46,8 @@ -0.5 * voxelDimensions_[1]) - 0.5 * voxelDimensions_[2] * axialGeometry_.GetNormal()); + LOG(TRACE) << "VolumeImageGeometry::Invalidate() origin = " << origin(0) << "," << origin(1) << "," << origin(2) << " | width_ = " << width_ << " | height_ = " << height_ << " | depth_ = " << depth_; + Vector scaling; if (width_ == 0 || @@ -80,7 +82,7 @@ } - void VolumeImageGeometry::SetSize(unsigned int width, + void VolumeImageGeometry::SetSizeInVoxels(unsigned int width, unsigned int height, unsigned int depth) { @@ -323,4 +325,19 @@ return plane; } + + std::ostream& operator<<(std::ostream& s, const VolumeImageGeometry& v) + { + s << "width: " << v.width_ << " height: " << v.height_ + << " depth: " << v.depth_ + << " axialGeometry: " << v.axialGeometry_ + << " coronalGeometry: " << v.coronalGeometry_ + << " sagittalGeometry: " << v.sagittalGeometry_ + << " voxelDimensions_: " << v.voxelDimensions_ + << " height: " << v.height_ + << " transform: " << v.transform_ + << " transformInverse: " << v.transformInverse_; + return s; + } + }
--- a/Framework/Volumes/VolumeImageGeometry.h Sat Aug 10 13:13:11 2019 +0200 +++ b/Framework/Volumes/VolumeImageGeometry.h Tue Aug 13 16:01:05 2019 +0200 @@ -24,6 +24,8 @@ #include "../StoneEnumerations.h" #include "../Toolbox/CoordinateSystem3D.h" +#include <iosfwd> + namespace OrthancStone { class VolumeImageGeometry @@ -41,6 +43,8 @@ void Invalidate(); + friend std::ostream& operator<<(std::ostream& s, const VolumeImageGeometry& v); + public: VolumeImageGeometry(); @@ -86,7 +90,7 @@ return transformInverse_; } - void SetSize(unsigned int width, + void SetSizeInVoxels(unsigned int width, unsigned int height, unsigned int depth); @@ -120,7 +124,7 @@ axial, sagittal or coronal cut and returns the slice number corresponding to this cut. - If the cutting plane is not parallel to the tree x = 0, y = 0 or z = 0 + If the cutting plane is not parallel to the three x = 0, y = 0 or z = 0 planes, it is considered as arbitrary and the method returns false. Otherwise, it returns true. */