Mercurial > hg > orthanc-stone
changeset 1890:6ce81914f7e4
added classes BucketAccumulator1D/2D
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Tue, 18 Jan 2022 22:08:55 +0100 |
parents | fe4befc9c2b0 |
children | 3716d72161d2 |
files | OrthancStone/Sources/Toolbox/DicomStructureSet.cpp OrthancStone/Sources/Toolbox/DicomStructureSet.h OrthancStone/Sources/Toolbox/LinearAlgebra.cpp OrthancStone/Sources/Toolbox/LinearAlgebra.h OrthancStone/UnitTestsSources/ComputationalGeometryTests.cpp RenderingPlugin/Sources/Plugin.cpp |
diffstat | 6 files changed, 512 insertions(+), 4 deletions(-) [+] |
line wrap: on
line diff
--- a/OrthancStone/Sources/Toolbox/DicomStructureSet.cpp Tue Jan 18 17:52:43 2022 +0100 +++ b/OrthancStone/Sources/Toolbox/DicomStructureSet.cpp Tue Jan 18 22:08:55 2022 +0100 @@ -1109,4 +1109,41 @@ } } } + + + void DicomStructureSet::Test() + { + printf("OK\n"); + + for (size_t i = 0; i < structures_.size(); i++) + { + const Polygons& polygons = structures_[i].polygons_; + + for (Polygons::const_iterator it = polygons.begin(); it != polygons.end(); ++it) + { + const Points& points = it->GetPoints(); + + if (points.size() >= 3) + { + const Vector& a = points[0]; + const Vector& b = points[1]; + const Vector& c = points[2]; + Vector n; + LinearAlgebra::CrossProduct(n, b - a, c - a); + LinearAlgebra::NormalizeVector(n); + if (n[2] < 0) + n = -n; + //LinearAlgebra::Print(n); + + // https://en.wikipedia.org/wiki/Vector_fields_in_cylindrical_and_spherical_coordinates#Vector_fields_2 + double r = 1.0; + double theta = acos(n[2]); + double phi = atan(n[1]); + printf("%.02f %.02f %.02f => ", n[0], n[1], n[2]); + printf("%.02f %.02f =>", theta, phi); + printf("%.02f %.02f %.02f\n", r * sin(theta) * cos(phi), r * sin(theta) * sin(phi), r * cos(theta)); + } + } + } + } }
--- a/OrthancStone/Sources/Toolbox/DicomStructureSet.h Tue Jan 18 17:52:43 2022 +0100 +++ b/OrthancStone/Sources/Toolbox/DicomStructureSet.h Tue Jan 18 22:08:55 2022 +0100 @@ -241,5 +241,7 @@ void GetStructurePoints(std::list< std::vector<Vector> >& target, size_t structureIndex, const std::string& sopInstanceUid) const; + + void Test(); }; }
--- a/OrthancStone/Sources/Toolbox/LinearAlgebra.cpp Tue Jan 18 17:52:43 2022 +0100 +++ b/OrthancStone/Sources/Toolbox/LinearAlgebra.cpp Tue Jan 18 22:08:55 2022 +0100 @@ -717,6 +717,49 @@ return m; } + + + template <typename T> + static T ComputeMedianInternal(std::vector<T>& v) + { + if (v.size() == 0) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange, "Empty vector"); + } + + const size_t middle = v.size() / 2; + nth_element(v.begin(), v.begin() + middle, v.end()); + + T median = v[middle]; + + if (v.size() % 2 == 1) + { + return median; + } + else + { + /** + * Side-effect of "nth_element()": "All of the elements before + * this new nth element are less than or equal to the elements + * after the new nth element." + * https://en.cppreference.com/w/cpp/algorithm/nth_element + **/ + + typename std::vector<T>::const_iterator m = std::max_element(v.begin(), v.begin() + middle); + + return (*m + median) / static_cast<T>(2); + } + } + + double ComputeMedian(std::vector<double>& v) + { + return ComputeMedianInternal<double>(v); + } + + float ComputeMedian(std::vector<float>& v) + { + return ComputeMedianInternal<float>(v); + } } std::ostream& operator<<(std::ostream& s, const Vector& vec)
--- a/OrthancStone/Sources/Toolbox/LinearAlgebra.h Tue Jan 18 17:52:43 2022 +0100 +++ b/OrthancStone/Sources/Toolbox/LinearAlgebra.h Tue Jan 18 22:08:55 2022 +0100 @@ -297,5 +297,9 @@ bool IsShearMatrix(const Matrix& shear); Matrix InvertShearMatrix(const Matrix& shear); + + double ComputeMedian(std::vector<double>& v); + + float ComputeMedian(std::vector<float>& v); }; }
--- a/OrthancStone/UnitTestsSources/ComputationalGeometryTests.cpp Tue Jan 18 17:52:43 2022 +0100 +++ b/OrthancStone/UnitTestsSources/ComputationalGeometryTests.cpp Tue Jan 18 22:08:55 2022 +0100 @@ -25,6 +25,7 @@ #include "../Sources/Toolbox/Internals/OrientedIntegerLine2D.h" #include "../Sources/Toolbox/Internals/RectanglesIntegerProjection.h" +#include "../Sources/Toolbox/LinearAlgebra.h" #include "../Sources/Toolbox/SegmentTree.h" #include "../Sources/Toolbox/UnionOfRectangles.h" @@ -973,3 +974,416 @@ ASSERT_TRUE(contours.front()[12].IsEqual(OrthancStone::ScenePoint2D(2, 5))); } } + + +TEST(LinearAlgebra, ComputeMedian) +{ + { + std::vector<double> v; + ASSERT_THROW(OrthancStone::LinearAlgebra::ComputeMedian(v), Orthanc::OrthancException); + + v.push_back(1); + v.push_back(3); + v.push_back(3); + v.push_back(6); + v.push_back(7); + v.push_back(8); + v.push_back(9); + ASSERT_DOUBLE_EQ(6.0, OrthancStone::LinearAlgebra::ComputeMedian(v)); + } + + { + std::vector<double> v; + v.push_back(1); + v.push_back(2); + v.push_back(3); + v.push_back(4); + v.push_back(5); + v.push_back(6); + v.push_back(8); + v.push_back(9); + ASSERT_DOUBLE_EQ(4.5, OrthancStone::LinearAlgebra::ComputeMedian(v)); + } +} + + + + + + +namespace OrthancStone +{ + namespace Internals + { + class BucketMapper : public boost::noncopyable + { + private: + double minValue_; + double maxValue_; + size_t bucketsCount_; + + public: + BucketMapper(double minValue, + double maxValue, + size_t bucketsCount) : + minValue_(minValue), + maxValue_(maxValue), + bucketsCount_(bucketsCount) + { + if (minValue >= maxValue || + bucketsCount <= 0) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + } + + size_t GetSize() const + { + return bucketsCount_; + } + + void CheckIndex(size_t i) const + { + if (i >= bucketsCount_) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + } + + double GetBucketLow(size_t i) const + { + CheckIndex(i); + double alpha = static_cast<double>(i) / static_cast<double>(bucketsCount_); + return (1.0 - alpha) * minValue_ + alpha * maxValue_; + } + + double GetBucketHigh(size_t i) const + { + CheckIndex(i); + double alpha = static_cast<double>(i + 1) / static_cast<double>(bucketsCount_); + return (1.0 - alpha) * minValue_ + alpha * maxValue_; + } + + double GetBucketCenter(size_t i) const + { + CheckIndex(i); + double alpha = (static_cast<double>(i) + 0.5) / static_cast<double>(bucketsCount_); + return (1.0 - alpha) * minValue_ + alpha * maxValue_; + } + + size_t GetBucketIndex(double value) const + { + if (value < minValue_ || + value > maxValue_) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + else + { + double tmp = (value - minValue_) / (maxValue_ - minValue_) * static_cast<double>(bucketsCount_); + assert(tmp >= 0 && tmp <= static_cast<double>(bucketsCount_)); + + size_t bucket = static_cast<unsigned int>(std::floor(tmp)); + if (bucket == bucketsCount_) // This is the case if "value == maxValue_" + { + return bucketsCount_ - 1; + } + else + { + return bucket; + } + } + } + }; + } + + + class BucketAccumulator1D : public boost::noncopyable + { + private: + struct Bucket + { + size_t count_; + std::list<double> values_; + }; + + Internals::BucketMapper mapper_; + std::vector<Bucket> buckets_; + bool storeValues_; + + public: + BucketAccumulator1D(double minValue, + double maxValue, + size_t countBuckets, + bool storeValues) : + mapper_(minValue, maxValue, countBuckets), + buckets_(countBuckets), + storeValues_(storeValues) + { + } + + size_t GetSize() const + { + return mapper_.GetSize(); + } + + double GetBucketLow(size_t i) const + { + return mapper_.GetBucketLow(i); + } + + double GetBucketHigh(size_t i) const + { + return mapper_.GetBucketHigh(i); + } + + double GetBucketCenter(size_t i) const + { + return mapper_.GetBucketCenter(i); + } + + size_t GetBucketContentSize(size_t i) const + { + mapper_.CheckIndex(i); + return buckets_[i].count_; + } + + void AddValue(double value) + { + Bucket& bucket = buckets_[mapper_.GetBucketIndex(value)]; + + bucket.count_++; + + if (storeValues_) + { + bucket.values_.push_back(value); + } + } + + size_t FindBestBucket() const + { + size_t best = 0; + + for (size_t i = 0; i < buckets_.size(); i++) + { + if (buckets_[i].count_ > buckets_[best].count_) + { + best = i; + } + } + + return best; + } + + double ComputeBestCenter() const + { + return GetBucketCenter(FindBestBucket()); + } + + double ComputeBestMedian() const + { + if (!storeValues_) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + + const std::list<double>& values = buckets_[FindBestBucket()].values_; + + std::vector<double> v; + v.reserve(values.size()); + for (std::list<double>::const_iterator it = values.begin(); it != values.end(); ++it) + { + v.push_back(*it); + } + + return LinearAlgebra::ComputeMedian(v); + } + }; + + + class BucketAccumulator2D : public boost::noncopyable + { + private: + struct Bucket + { + size_t count_; + std::list<double> valuesX_; + std::list<double> valuesY_; + }; + + Internals::BucketMapper mapperX_; + Internals::BucketMapper mapperY_; + std::vector<Bucket> buckets_; + bool storeValues_; + + size_t FindBestInternal() const + { + size_t best = 0; + + for (size_t i = 0; i < buckets_.size(); i++) + { + if (buckets_[i].count_ > buckets_[best].count_) + { + best = i; + } + } + + return best; + } + + public: + BucketAccumulator2D(double minValueX, + double maxValueX, + size_t countBucketsX, + double minValueY, + double maxValueY, + size_t countBucketsY, + bool storeValues) : + mapperX_(minValueX, maxValueX, countBucketsX), + mapperY_(minValueY, maxValueY, countBucketsY), + buckets_(countBucketsX * countBucketsY), + storeValues_(storeValues) + { + } + + void AddValue(double valueX, + double valueY) + { + size_t x = mapperX_.GetBucketIndex(valueX); + size_t y = mapperY_.GetBucketIndex(valueY); + + Bucket& bucket = buckets_[x + y * mapperX_.GetSize()]; + + bucket.count_++; + + if (storeValues_) + { + bucket.valuesX_.push_back(valueX); + bucket.valuesY_.push_back(valueY); + } + } + + void FindBestBucket(size_t& bucketX, + size_t& bucketY) const + { + size_t best = FindBestInternal(); + bucketX = best % mapperX_.GetSize(); + bucketY = best / mapperX_.GetSize(); + } + + void ComputeBestCenter(double& x, + double& y) const + { + size_t bucketX, bucketY; + FindBestBucket(bucketX, bucketY); + x = mapperX_.GetBucketCenter(bucketX); + y = mapperY_.GetBucketCenter(bucketY); + } + + void ComputeBestMedian(double& x, + double& y) const + { + if (!storeValues_) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + + const std::list<double>& valuesX = buckets_[FindBestInternal()].valuesX_; + const std::list<double>& valuesY = buckets_[FindBestInternal()].valuesY_; + + std::vector<double> v; + v.reserve(valuesX.size()); + for (std::list<double>::const_iterator it = valuesX.begin(); it != valuesX.end(); ++it) + { + v.push_back(*it); + } + + x = LinearAlgebra::ComputeMedian(v); + + v.clear(); + v.reserve(valuesY.size()); + for (std::list<double>::const_iterator it = valuesY.begin(); it != valuesY.end(); ++it) + { + v.push_back(*it); + } + + y = LinearAlgebra::ComputeMedian(v); + } + }; +} + + +TEST(BucketAccumulator1D, Basic) +{ + for (int storeValues = 0; storeValues <= 1; storeValues++) + { + OrthancStone::BucketAccumulator1D b(-10, 30, 4, storeValues != 0); + ASSERT_EQ(4u, b.GetSize()); + + ASSERT_DOUBLE_EQ(-10.0, b.GetBucketLow(0)); + ASSERT_DOUBLE_EQ(0.0, b.GetBucketLow(1)); + ASSERT_DOUBLE_EQ(10.0, b.GetBucketLow(2)); + ASSERT_DOUBLE_EQ(20.0, b.GetBucketLow(3)); + + ASSERT_DOUBLE_EQ(0.0, b.GetBucketHigh(0)); + ASSERT_DOUBLE_EQ(10.0, b.GetBucketHigh(1)); + ASSERT_DOUBLE_EQ(20.0, b.GetBucketHigh(2)); + ASSERT_DOUBLE_EQ(30.0, b.GetBucketHigh(3)); + + ASSERT_DOUBLE_EQ(-5.0, b.GetBucketCenter(0)); + ASSERT_DOUBLE_EQ(5.0, b.GetBucketCenter(1)); + ASSERT_DOUBLE_EQ(15.0, b.GetBucketCenter(2)); + ASSERT_DOUBLE_EQ(25.0, b.GetBucketCenter(3)); + + ASSERT_EQ(0u, b.GetBucketContentSize(0)); + ASSERT_EQ(0u, b.GetBucketContentSize(1)); + ASSERT_EQ(0u, b.GetBucketContentSize(2)); + ASSERT_EQ(0u, b.GetBucketContentSize(3)); + + ASSERT_THROW(b.ComputeBestMedian(), Orthanc::OrthancException); // No data point + + b.AddValue(-10.0); + b.AddValue(0.0); + b.AddValue(9.9999); + b.AddValue(10.0); + b.AddValue(20.0); + b.AddValue(29.9999); + b.AddValue(30.0); + ASSERT_THROW(b.AddValue(30.00001), Orthanc::OrthancException); + + ASSERT_EQ(3u, b.FindBestBucket()); + ASSERT_EQ(25.0, b.ComputeBestCenter()); + + ASSERT_EQ(1u, b.GetBucketContentSize(0)); + ASSERT_EQ(2u, b.GetBucketContentSize(1)); + ASSERT_EQ(1u, b.GetBucketContentSize(2)); + ASSERT_EQ(3u, b.GetBucketContentSize(3)); + + if (storeValues == 0) + { + ASSERT_THROW(b.ComputeBestMedian(), Orthanc::OrthancException); + } + else + { + ASSERT_EQ(29.9999, b.ComputeBestMedian()); + } + } +} + +TEST(BucketAccumulator2D, Basic) +{ + for (int storeValues = 0; storeValues <= 1; storeValues++) + { + OrthancStone::BucketAccumulator2D b(-10, 30, 4, + 0, 3, 3, storeValues != 0); + + if (storeValues == 0) + { + //ASSERT_THROW(b.ComputeBestMedian(), Orthanc::OrthancException); + } + else + { + //ASSERT_EQ(29.9999, b.ComputeBestMedian()); + } + } +}
--- a/RenderingPlugin/Sources/Plugin.cpp Tue Jan 18 17:52:43 2022 +0100 +++ b/RenderingPlugin/Sources/Plugin.cpp Tue Jan 18 22:08:55 2022 +0100 @@ -71,9 +71,9 @@ class Accessor : public boost::noncopyable { private: - boost::mutex::scoped_lock lock_; - std::string instanceId_; - const OrthancStone::DicomStructureSet* rtstruct_; + boost::mutex::scoped_lock lock_; + std::string instanceId_; + OrthancStone::DicomStructureSet* rtstruct_; public: Accessor(DicomStructureCache& that, @@ -113,7 +113,7 @@ return rtstruct_ != NULL; } - const OrthancStone::DicomStructureSet& GetRtStruct() const + OrthancStone::DicomStructureSet& GetRtStruct() const { if (IsValid()) { @@ -765,6 +765,7 @@ } + OrthancPluginErrorCode OnChangeCallback(OrthancPluginChangeType changeType, OrthancPluginResourceType resourceType, const char* resourceId) @@ -779,6 +780,13 @@ break; + case OrthancPluginChangeType_OrthancStarted: + { + DicomStructureCache::Accessor accessor(DicomStructureCache::GetSingleton(), "54460695-ba3885ee-ddf61ac0-f028e31d-a6e474d9"); + accessor.GetRtStruct().Test(); + break; + } + default: break; }