# HG changeset patch # User Sebastien Jodogne # Date 1621002654 -7200 # Node ID f053c80ea41129aa9daa5488d543307ba1b7de8c # Parent bf4b15b059eadb9e2586fb6703e60c18e2329f2c ImageBuffer3D::CommitSagittalSlice() diff -r bf4b15b059ea -r f053c80ea411 OrthancStone/Sources/Volumes/ImageBuffer3D.cpp --- a/OrthancStone/Sources/Volumes/ImageBuffer3D.cpp Fri May 14 15:35:24 2021 +0200 +++ b/OrthancStone/Sources/Volumes/ImageBuffer3D.cpp Fri May 14 16:30:54 2021 +0200 @@ -76,29 +76,29 @@ 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::unique_ptr 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_); + const unsigned int bytesPerPixel = Orthanc::GetBytesPerPixel(format_); for (unsigned int z = 0; z < depth_; z++) { - //uint8_t* target = reinterpret_cast(result->GetRow(depth_ - 1 - z)); - uint8_t* target = reinterpret_cast(result->GetRow(z)); + uint8_t* q = reinterpret_cast(result->GetRow(z)); for (unsigned int y = 0; y < height_; y++) { - const void* source = (reinterpret_cast(image_.GetConstRow(y + z * height_)) + bytesPerPixel * slice); - const uint8_t* byteSrc = reinterpret_cast(source); - for (size_t byte = 0; byte < bytesPerPixel; ++byte) - target[byte] = byteSrc[byte]; - target += bytesPerPixel; + const uint8_t* p = reinterpret_cast(image_.GetConstRow(y + z * height_)) + bytesPerPixel * slice; + + for (size_t x = 0; x < bytesPerPixel; ++x) + { + q[x] = p[x]; + } + + q += bytesPerPixel; } } @@ -106,6 +106,35 @@ } + void ImageBuffer3D::CommitSagittalSlice(unsigned int slice, + const Orthanc::ImageAccessor& source) + { + if (slice >= width_) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + const unsigned int bytesPerPixel = Orthanc::GetBytesPerPixel(format_); + + for (unsigned int z = 0; z < depth_; z++) + { + const uint8_t* p = reinterpret_cast(source.GetConstRow(z)); + + for (unsigned int y = 0; y < height_; y++) + { + uint8_t* q = reinterpret_cast(image_.GetRow(y + z * height_)) + bytesPerPixel * slice; + + for (size_t x = 0; x < bytesPerPixel; ++x) + { + q[x] = p[x]; + } + + p += bytesPerPixel; + } + } + } + + ImageBuffer3D::ImageBuffer3D(Orthanc::PixelFormat format, unsigned int width, unsigned int height, @@ -237,8 +266,11 @@ { if (sagittal_.get() != NULL) { - // TODO - throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + assert(sagittal_->GetWidth() == that_.height_ && + sagittal_->GetHeight() == that_.depth_ && + sagittal_->GetFormat() == that_.format_); + + that_.CommitSagittalSlice(slice_, *sagittal_); } // Update the dynamic range of the underlying image, if @@ -252,7 +284,8 @@ VolumeProjection projection, unsigned int slice) : that_(that), - modified_(false) + modified_(false), + slice_(slice) { switch (projection) { diff -r bf4b15b059ea -r f053c80ea411 OrthancStone/Sources/Volumes/ImageBuffer3D.h --- a/OrthancStone/Sources/Volumes/ImageBuffer3D.h Fri May 14 15:35:24 2021 +0200 +++ b/OrthancStone/Sources/Volumes/ImageBuffer3D.h Fri May 14 16:30:54 2021 +0200 @@ -95,6 +95,9 @@ Orthanc::Image* ExtractSagittalSlice(unsigned int slice) const; + void CommitSagittalSlice(unsigned int slice, + const Orthanc::ImageAccessor& source); + template T GetPixelUnchecked(unsigned int x, unsigned int y, @@ -204,6 +207,7 @@ bool modified_; Orthanc::ImageAccessor accessor_; std::unique_ptr sagittal_; // Unused for axial and coronal + unsigned int slice_; void Flush(); diff -r bf4b15b059ea -r f053c80ea411 UnitTestsSources/VolumeRenderingTests.cpp --- a/UnitTestsSources/VolumeRenderingTests.cpp Fri May 14 15:35:24 2021 +0200 +++ b/UnitTestsSources/VolumeRenderingTests.cpp Fri May 14 16:30:54 2021 +0200 @@ -188,10 +188,17 @@ // Render the scene using the identity viewpoint (default) static Orthanc::ImageAccessor* Render(OrthancStone::ISceneLayer* layer, unsigned int width, - unsigned int height) + unsigned int height, + bool fitScene) { OrthancStone::Scene2D scene; scene.SetLayer(0, layer); + + if (fitScene) + { + scene.FitContent(width, height); + } + return Render(scene, width, height); } @@ -244,19 +251,38 @@ } -static OrthancStone::TextureBaseSceneLayer* Slice3x3x1Pattern(const OrthancStone::CoordinateSystem3D& volumeCoordinates, +static OrthancStone::TextureBaseSceneLayer* Slice3x3x1Pattern(OrthancStone::VolumeProjection projection, + const OrthancStone::CoordinateSystem3D& volumeCoordinates, const OrthancStone::CoordinateSystem3D& cuttingPlane, SlicerType type) { OrthancStone::VolumeImageGeometry geometry; - geometry.SetSizeInVoxels(3, 3, 1); + + switch (projection) + { + case OrthancStone::VolumeProjection_Axial: + geometry.SetSizeInVoxels(3, 3, 1); + break; + + case OrthancStone::VolumeProjection_Sagittal: + geometry.SetSizeInVoxels(1, 3, 3); + break; + + case OrthancStone::VolumeProjection_Coronal: + geometry.SetSizeInVoxels(3, 1, 3); + break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + geometry.SetAxialGeometry(volumeCoordinates); boost::shared_ptr volume(new OrthancStone::DicomVolumeImage); volume->Initialize(geometry, Orthanc::PixelFormat_Grayscale8, false); { - OrthancStone::ImageBuffer3D::SliceWriter writer(volume->GetPixelData(), OrthancStone::VolumeProjection_Axial, 0); + OrthancStone::ImageBuffer3D::SliceWriter writer(volume->GetPixelData(), projection, 0); Assign3x3Pattern(writer.GetAccessor()); } @@ -282,7 +308,8 @@ { OrthancStone::CoordinateSystem3D cuttingPlane; - std::unique_ptr layer(Slice3x3x1Pattern(axial, cuttingPlane, static_cast(mode))); + std::unique_ptr layer( + Slice3x3x1Pattern(OrthancStone::VolumeProjection_Axial, axial, cuttingPlane, static_cast(mode))); ASSERT_TRUE(layer.get() != NULL); ASSERT_EQ(OrthancStone::ISceneLayer::Type_FloatTexture, layer->GetType()); @@ -309,7 +336,7 @@ ASSERT_FLOAT_EQ(200, GetPixelValue(texture, 2, 2)); } - std::unique_ptr rendered(Render(layer.release(), 5, 5)); + std::unique_ptr rendered(Render(layer.release(), 5, 5, false)); ASSERT_EQ(5u, rendered->GetWidth()); ASSERT_EQ(5u, rendered->GetHeight()); ASSERT_FLOAT_EQ(0, GetPixelValue(*rendered, 0, 0)); @@ -354,7 +381,7 @@ std::unique_ptr layer(new OrthancStone::ColorTextureSceneLayer(pixel)); layer->SetOrigin(0, 0); - std::unique_ptr rendered(Render(layer.release(), 2, 2)); + std::unique_ptr rendered(Render(layer.release(), 2, 2, false)); ASSERT_EQ(2u, rendered->GetWidth()); ASSERT_EQ(2u, rendered->GetHeight()); ASSERT_FLOAT_EQ(0, GetPixelValue(*rendered, 0, 0)); @@ -367,7 +394,7 @@ std::unique_ptr layer(new OrthancStone::ColorTextureSceneLayer(pixel)); layer->SetOrigin(-0.01, 0); - std::unique_ptr rendered(Render(layer.release(), 2, 2)); + std::unique_ptr rendered(Render(layer.release(), 2, 2, false)); ASSERT_FLOAT_EQ(0, GetPixelValue(*rendered, 0, 0)); ASSERT_FLOAT_EQ(0, GetPixelValue(*rendered, 1, 0)); ASSERT_FLOAT_EQ(255, GetPixelValue(*rendered, 0, 1)); @@ -378,7 +405,7 @@ std::unique_ptr layer(new OrthancStone::ColorTextureSceneLayer(pixel)); layer->SetOrigin(-0.01, -0.01); - std::unique_ptr rendered(Render(layer.release(), 2, 2)); + std::unique_ptr rendered(Render(layer.release(), 2, 2, false)); ASSERT_FLOAT_EQ(255, GetPixelValue(*rendered, 0, 0)); ASSERT_FLOAT_EQ(0, GetPixelValue(*rendered, 1, 0)); ASSERT_FLOAT_EQ(0, GetPixelValue(*rendered, 0, 1)); @@ -389,7 +416,7 @@ std::unique_ptr layer(new OrthancStone::ColorTextureSceneLayer(pixel)); layer->SetOrigin(0, -0.01); - std::unique_ptr rendered(Render(layer.release(), 2, 2)); + std::unique_ptr rendered(Render(layer.release(), 2, 2, false)); ASSERT_FLOAT_EQ(0, GetPixelValue(*rendered, 0, 0)); ASSERT_FLOAT_EQ(255, GetPixelValue(*rendered, 1, 0)); ASSERT_FLOAT_EQ(0, GetPixelValue(*rendered, 0, 1)); @@ -482,6 +509,7 @@ } + TEST(VolumeRendering, FlipAxial) { double x = 2; @@ -513,7 +541,8 @@ OrthancStone::LinearAlgebra::CreateVector(1, 0, 0), OrthancStone::LinearAlgebra::CreateVector(0, 1, 0)); - std::unique_ptr layer(Slice3x3x1Pattern(axial, cuttingPlane, static_cast(mode))); + std::unique_ptr layer( + Slice3x3x1Pattern(OrthancStone::VolumeProjection_Axial, axial, cuttingPlane, static_cast(mode))); ASSERT_TRUE(AreSameImages(layer->GetTexture(), pattern)); OrthancStone::Extent2D extent; @@ -523,7 +552,7 @@ ASSERT_FLOAT_EQ(x + 2.5, extent.GetX2()); ASSERT_FLOAT_EQ(y + 2.5, extent.GetY2()); - std::unique_ptr rendered(Render(layer.release(), 15, 15)); + std::unique_ptr rendered(Render(layer.release(), 15, 15, false)); ASSERT_TRUE(IsConstImageWithExclusion(0.0f, *rendered, 9, 8, 3, 3)); Orthanc::ImageAccessor p; @@ -536,7 +565,8 @@ OrthancStone::LinearAlgebra::CreateVector(-1, 0, 0), OrthancStone::LinearAlgebra::CreateVector(0, 1, 0)); - std::unique_ptr layer(Slice3x3x1Pattern(axial, cuttingPlane, static_cast(mode))); + std::unique_ptr layer( + Slice3x3x1Pattern(OrthancStone::VolumeProjection_Axial, axial, cuttingPlane, static_cast(mode))); if (mode == 1) { // Reslicer directly flips the pixels of the texture @@ -555,7 +585,7 @@ ASSERT_FLOAT_EQ(-(x - 0.5), extent.GetX2()); ASSERT_FLOAT_EQ(y + 2.5, extent.GetY2()); - std::unique_ptr rendered(Render(layer.release(), 15, 15)); + std::unique_ptr rendered(Render(layer.release(), 15, 15, false)); ASSERT_TRUE(IsConstImageWithExclusion(0.0f, *rendered, 3, 8, 3, 3)); Orthanc::ImageAccessor p; @@ -568,7 +598,8 @@ OrthancStone::LinearAlgebra::CreateVector(1, 0, 0), OrthancStone::LinearAlgebra::CreateVector(0, -1, 0)); - std::unique_ptr layer(Slice3x3x1Pattern(axial, cuttingPlane, static_cast(mode))); + std::unique_ptr layer( + Slice3x3x1Pattern(OrthancStone::VolumeProjection_Axial, axial, cuttingPlane, static_cast(mode))); if (mode == 1) { ASSERT_TRUE(AreSameImages(layer->GetTexture(), patternY)); @@ -585,7 +616,7 @@ ASSERT_FLOAT_EQ(x + 2.5, extent.GetX2()); ASSERT_FLOAT_EQ(-(y - 0.5), extent.GetY2()); - std::unique_ptr rendered(Render(layer.release(), 15, 15)); + std::unique_ptr rendered(Render(layer.release(), 15, 15, false)); ASSERT_TRUE(IsConstImageWithExclusion(0.0f, *rendered, 9, 4, 3, 3)); Orthanc::ImageAccessor p; @@ -598,7 +629,8 @@ OrthancStone::LinearAlgebra::CreateVector(-1, 0, 0), OrthancStone::LinearAlgebra::CreateVector(0, -1, 0)); - std::unique_ptr layer(Slice3x3x1Pattern(axial, cuttingPlane, static_cast(mode))); + std::unique_ptr layer( + Slice3x3x1Pattern(OrthancStone::VolumeProjection_Axial, axial, cuttingPlane, static_cast(mode))); if (mode == 1) { ASSERT_TRUE(AreSameImages(layer->GetTexture(), patternXY)); @@ -615,7 +647,7 @@ ASSERT_FLOAT_EQ(-(x - 0.5), extent.GetX2()); ASSERT_FLOAT_EQ(-(y - 0.5), extent.GetY2()); - std::unique_ptr rendered(Render(layer.release(), 15, 15)); + std::unique_ptr rendered(Render(layer.release(), 15, 15, false)); ASSERT_TRUE(IsConstImageWithExclusion(0.0f, *rendered, 3, 4, 3, 3)); Orthanc::ImageAccessor p;