# HG changeset patch # User Sebastien Jodogne # Date 1712249411 -7200 # Node ID 2460b376d3f7c7302664b33e24e227fc82e1c31c # Parent 976da547681033a43d2c3fd36fe56143af670403 reorganization diff -r 976da5476810 -r 2460b376d3f7 CMakeLists.txt --- a/CMakeLists.txt Thu Apr 04 18:35:54 2024 +0200 +++ b/CMakeLists.txt Thu Apr 04 18:50:11 2024 +0200 @@ -205,7 +205,8 @@ add_library(OrthancSTL SHARED Sources/Extent2D.cpp Sources/Plugin.cpp - Sources/Toolbox.cpp + Sources/STLToolbox.cpp + Sources/StructurePolygon.cpp Sources/VTKToolbox.cpp Sources/Vector3D.cpp diff -r 976da5476810 -r 2460b376d3f7 Sources/Plugin.cpp --- a/Sources/Plugin.cpp Thu Apr 04 18:35:54 2024 +0200 +++ b/Sources/Plugin.cpp Thu Apr 04 18:50:11 2024 +0200 @@ -22,6 +22,7 @@ **/ +#include "StructurePolygon.h" #include "VTKToolbox.h" #include "Vector3D.h" #include "Toolbox.h" @@ -40,6 +41,8 @@ #include #include +#include + #include #define ORTHANC_PLUGIN_NAME "stl" @@ -147,235 +150,11 @@ -#include #include -#include #include #include -static std::string GetStringValue(DcmItem& item, - const DcmTagKey& key) -{ - const char* s = NULL; - if (!item.findAndGetString(key, s).good() || - s == NULL) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); - } - else - { - return Orthanc::Toolbox::StripSpaces(s); - } -} - - -static void ListStructuresNames(std::set& target, - Orthanc::ParsedDicomFile& source) -{ - target.clear(); - - DcmSequenceOfItems* sequence = NULL; - if (!source.GetDcmtkObject().getDataset()->findAndGetSequence(DCM_StructureSetROISequence, sequence).good() || - sequence == NULL) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); - } - - for (unsigned long i = 0; i < sequence->card(); i++) - { - DcmItem* item = sequence->getItem(i); - if (item == NULL) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); - } - else - { - target.insert(GetStringValue(*item, DCM_ROIName)); - } - } -} - - -class StructurePolygon : public boost::noncopyable -{ -private: - std::string roiName_; - std::string referencedSopInstanceUid_; - uint8_t red_; - uint8_t green_; - uint8_t blue_; - std::vector points_; - -public: - StructurePolygon(Orthanc::ParsedDicomFile& dicom, - unsigned long roiIndex, - unsigned long contourIndex) - { - DcmDataset& dataset = *dicom.GetDcmtkObject().getDataset(); - - DcmItem* structure = NULL; - DcmItem* roi = NULL; - DcmItem* contour = NULL; - DcmSequenceOfItems* referenced = NULL; - - if (!dataset.findAndGetSequenceItem(DCM_StructureSetROISequence, structure, roiIndex).good() || - structure == NULL || - !dataset.findAndGetSequenceItem(DCM_ROIContourSequence, roi, roiIndex).good() || - roi == NULL || - !roi->findAndGetSequenceItem(DCM_ContourSequence, contour, contourIndex).good() || - contour == NULL || - !contour->findAndGetSequence(DCM_ContourImageSequence, referenced).good() || - referenced == NULL || - referenced->card() != 1) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); - } - - roiName_ = GetStringValue(*structure, DCM_ROIName); - referencedSopInstanceUid_ = GetStringValue(*referenced->getItem(0), DCM_ReferencedSOPInstanceUID); - - if (GetStringValue(*contour, DCM_ContourGeometricType) != "CLOSED_PLANAR") - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); - } - - { - std::vector tokens; - Orthanc::Toolbox::TokenizeString(tokens, GetStringValue(*roi, DCM_ROIDisplayColor), '\\'); - - uint32_t r, g, b; - if (tokens.size() != 3 || - !Orthanc::SerializationToolbox::ParseFirstUnsignedInteger32(r, tokens[0]) || - !Orthanc::SerializationToolbox::ParseFirstUnsignedInteger32(g, tokens[1]) || - !Orthanc::SerializationToolbox::ParseFirstUnsignedInteger32(b, tokens[2]) || - r > 255 || - g > 255 || - b > 255) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); - } - - red_ = r; - green_ = g; - blue_ = b; - } - - { - std::vector tokens; - Orthanc::Toolbox::TokenizeString(tokens, GetStringValue(*contour, DCM_ContourData), '\\'); - - const std::string s = GetStringValue(*contour, DCM_NumberOfContourPoints); - - uint32_t countPoints; - if (!Orthanc::SerializationToolbox::ParseUnsignedInteger32(countPoints, s) || - tokens.size() != 3 * countPoints) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); - } - - points_.reserve(countPoints); - - for (size_t i = 0; i < tokens.size(); i += 3) - { - double x, y, z; - if (!Toolbox::MyParseDouble(x, tokens[i]) || - !Toolbox::MyParseDouble(y, tokens[i + 1]) || - !Toolbox::MyParseDouble(z, tokens[i + 2])) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); - } - - points_.push_back(Vector3D(x, y, z)); - } - - assert(points_.size() == countPoints); - } - } - - const std::string& GetRoiName() const - { - return roiName_; - } - - const std::string& GetReferencedSopInstanceUid() const - { - return referencedSopInstanceUid_; - } - - size_t GetPointsCount() const - { - return points_.size(); - } - - const Vector3D& GetPoint(size_t i) const - { - if (i >= points_.size()) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); - } - else - { - return points_[i]; - } - } - - bool IsCoplanar(Vector3D& normal) const - { - if (points_.size() < 3) - { - return false; - } - - bool hasNormal = false; - - for (size_t i = 0; i < points_.size(); i++) - { - normal = Vector3D::CrossProduct(Vector3D(points_[1], points_[0]), - Vector3D(points_[2], points_[0])); - if (!Toolbox::IsNear(normal.ComputeNorm(), 0)) - { - normal.Normalize(); - hasNormal = true; - } - } - - if (!hasNormal) - { - return false; - } - - double a = Vector3D::DotProduct(points_[0], normal); - - for (size_t i = 1; i < points_.size(); i++) - { - double b = Vector3D::DotProduct(points_[i], normal); - if (!Toolbox::IsNear(a, b)) - { - return false; - } - } - - return true; - } - - void Add(Extent2D& extent, - const Vector3D& axisX, - const Vector3D& axisY) const - { - assert(Toolbox::IsNear(1, axisX.ComputeNorm())); - assert(Toolbox::IsNear(1, axisY.ComputeNorm())); - - for (size_t i = 0; i < points_.size(); i++) - { - extent.Add(Vector3D::DotProduct(axisX, points_[i]), - Vector3D::DotProduct(axisY, points_[i])); - } - } -}; - - - class StructureSet : public boost::noncopyable { private: @@ -392,10 +171,10 @@ hasFrameOfReferenceUid_(false) { DcmDataset& dataset = *dicom.GetDcmtkObject().getDataset(); - patientId_ = GetStringValue(dataset, DCM_PatientID); - studyInstanceUid_ = GetStringValue(dataset, DCM_StudyInstanceUID); - seriesInstanceUid_ = GetStringValue(dataset, DCM_SeriesInstanceUID); - sopInstanceUid_ = GetStringValue(dataset, DCM_SOPInstanceUID); + patientId_ = STLToolbox::GetStringValue(dataset, DCM_PatientID); + studyInstanceUid_ = STLToolbox::GetStringValue(dataset, DCM_StudyInstanceUID); + seriesInstanceUid_ = STLToolbox::GetStringValue(dataset, DCM_SeriesInstanceUID); + sopInstanceUid_ = STLToolbox::GetStringValue(dataset, DCM_SOPInstanceUID); DcmSequenceOfItems* frame = NULL; if (!dataset.findAndGetSequence(DCM_ReferencedFrameOfReferenceSequence, frame).good() || @@ -523,6 +302,33 @@ throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); } } + + // This static method is faster than constructing the full "StructureSet" object + static void ListStructuresNames(std::set& target, + Orthanc::ParsedDicomFile& source) + { + target.clear(); + + DcmSequenceOfItems* sequence = NULL; + if (!source.GetDcmtkObject().getDataset()->findAndGetSequence(DCM_StructureSetROISequence, sequence).good() || + sequence == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } + + for (unsigned long i = 0; i < sequence->card(); i++) + { + DcmItem* item = sequence->getItem(i); + if (item == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } + else + { + target.insert(STLToolbox::GetStringValue(*item, DCM_ROIName)); + } + } + } }; @@ -555,7 +361,7 @@ double d = (z - minProjectionAlongNormal_) / slicesSpacing_; - if (Toolbox::IsNear(d, round(d))) + if (STLToolbox::IsNear(d, round(d))) { if (d < 0.0 || d > static_cast(slicesCount_) - 1.0) @@ -623,7 +429,7 @@ // Only keep unique projections std::sort(projections.begin(), projections.end()); - Toolbox::RemoveDuplicateValues(projections); + STLToolbox::RemoveDuplicateValues(projections); assert(!projections.empty()); if (projections.size() == 1) @@ -650,7 +456,7 @@ } std::sort(spacings.begin(), spacings.end()); - Toolbox::RemoveDuplicateValues(spacings); + STLToolbox::RemoveDuplicateValues(spacings); if (spacings.empty()) { @@ -687,7 +493,7 @@ while (it != candidates.end()) { double d = (projections[*it] - projections[reference]) / slicesSpacing_; - if (Toolbox::IsNear(d, round(d))) + if (STLToolbox::IsNear(d, round(d))) { countSupport ++; } @@ -726,7 +532,7 @@ for (size_t i = 0; i < projections.size(); i++) { double d = (projections[i] - bestProjection) / slicesSpacing_; - if (Toolbox::IsNear(d, round(d))) + if (STLToolbox::IsNear(d, round(d))) { minProjectionAlongNormal_ = std::min(minProjectionAlongNormal_, projections[i]); maxProjectionAlongNormal_ = std::max(maxProjectionAlongNormal_, projections[i]); @@ -734,7 +540,7 @@ } double d = (maxProjectionAlongNormal_ - minProjectionAlongNormal_) / slicesSpacing_; - if (Toolbox::IsNear(d, round(d))) + if (STLToolbox::IsNear(d, round(d))) { slicesCount_ = static_cast(round(d)) + 1; } @@ -902,12 +708,12 @@ double x1, x2, x3, y1, y2, y3; if (items.size() == 6 && - Toolbox::MyParseDouble(x1, items[0]) && - Toolbox::MyParseDouble(x2, items[1]) && - Toolbox::MyParseDouble(x3, items[2]) && - Toolbox::MyParseDouble(y1, items[3]) && - Toolbox::MyParseDouble(y2, items[4]) && - Toolbox::MyParseDouble(y3, items[5])) + STLToolbox::MyParseDouble(x1, items[0]) && + STLToolbox::MyParseDouble(x2, items[1]) && + STLToolbox::MyParseDouble(x3, items[2]) && + STLToolbox::MyParseDouble(y1, items[3]) && + STLToolbox::MyParseDouble(y2, items[4]) && + STLToolbox::MyParseDouble(y3, items[5])) { axisX = Vector3D(x1, x2, x3); axisY = Vector3D(y1, y2, y3); @@ -938,7 +744,7 @@ throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); } - if (!Toolbox::IsNear(1, geometry.GetSlicesNormal().ComputeNorm())) + if (!STLToolbox::IsNear(1, geometry.GetSlicesNormal().ComputeNorm())) { throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); } @@ -948,8 +754,8 @@ Vector3D axisZ = Vector3D::CrossProduct(axisX, axisY); - if (!Toolbox::IsNear(1, axisX.ComputeNorm()) || - !Toolbox::IsNear(1, axisY.ComputeNorm()) || + if (!STLToolbox::IsNear(1, axisX.ComputeNorm()) || + !STLToolbox::IsNear(1, axisY.ComputeNorm()) || !Vector3D::AreParallel(axisZ, geometry.GetSlicesNormal())) { throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); @@ -1037,7 +843,7 @@ std::unique_ptr dicom(LoadInstance(instanceId)); std::set names; - ListStructuresNames(names, *dicom); + StructureSet::ListStructuresNames(names, *dicom); Json::Value answer = Json::arrayValue; @@ -1260,8 +1066,8 @@ DcmDataset& dataset = *dicom->GetDcmtkObject().getDataset(); std::string stl; - if (GetStringValue(dataset, DCM_MIMETypeOfEncapsulatedDocument) != Orthanc::MIME_STL || - GetStringValue(dataset, DCM_SOPClassUID) != UID_EncapsulatedSTLStorage || + if (STLToolbox::GetStringValue(dataset, DCM_MIMETypeOfEncapsulatedDocument) != Orthanc::MIME_STL || + STLToolbox::GetStringValue(dataset, DCM_SOPClassUID) != UID_EncapsulatedSTLStorage || !dicom->GetTagValue(stl, Orthanc::DICOM_TAG_ENCAPSULATED_DOCUMENT)) { throw Orthanc::OrthancException(Orthanc::ErrorCode_BadRequest, "DICOM instance not encapsulating a STL model: " + instanceId); diff -r 976da5476810 -r 2460b376d3f7 Sources/STLToolbox.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Sources/STLToolbox.cpp Thu Apr 04 18:50:11 2024 +0200 @@ -0,0 +1,91 @@ +/** + * SPDX-FileCopyrightText: 2023-2024 Sebastien Jodogne, UCLouvain, Belgium + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +/** + * STL plugin for Orthanc + * Copyright (C) 2023-2024 Sebastien Jodogne, UCLouvain, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + **/ + + +#include "STLToolbox.h" + +#include +#include + +#include +#include + + +namespace STLToolbox +{ + bool IsNear(double a, + double b) + { + return std::abs(a - b) < 10.0 * std::numeric_limits::epsilon(); + } + + + bool MyParseDouble(double& value, + const std::string& s) + { +#if 1 + char* end = NULL; + value = strtod(s.c_str(), &end); + return (end == s.c_str() + s.size()); +#else + return Orthanc::SerializationToolbox::ParseDouble(value, s); +#endif + } + + + namespace + { + struct IsNearPredicate + { + bool operator() (const double& a, + const double& b) + { + return IsNear(a, b); + } + }; + } + + + void RemoveDuplicateValues(std::vector& v) + { + IsNearPredicate predicate; + std::vector::iterator last = std::unique(v.begin(), v.end(), predicate); + v.erase(last, v.end()); + } + + + std::string GetStringValue(DcmItem& item, + const DcmTagKey& key) + { + const char* s = NULL; + if (!item.findAndGetString(key, s).good() || + s == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } + else + { + return Orthanc::Toolbox::StripSpaces(s); + } + } +} diff -r 976da5476810 -r 2460b376d3f7 Sources/STLToolbox.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Sources/STLToolbox.h Thu Apr 04 18:50:11 2024 +0200 @@ -0,0 +1,48 @@ +/** + * SPDX-FileCopyrightText: 2023-2024 Sebastien Jodogne, UCLouvain, Belgium + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +/** + * STL plugin for Orthanc + * Copyright (C) 2023-2024 Sebastien Jodogne, UCLouvain, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#include +#include + +#include +#include + + +namespace STLToolbox +{ + bool IsNear(double a, + double b); + + // This version is much faster, as "ParseDouble()" internally uses + // "boost::lexical_cast()" + bool MyParseDouble(double& value, + const std::string& s); + + void RemoveDuplicateValues(std::vector& v); + + std::string GetStringValue(DcmItem& item, + const DcmTagKey& key); +} diff -r 976da5476810 -r 2460b376d3f7 Sources/StructurePolygon.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Sources/StructurePolygon.cpp Thu Apr 04 18:50:11 2024 +0200 @@ -0,0 +1,187 @@ +/** + * SPDX-FileCopyrightText: 2023-2024 Sebastien Jodogne, UCLouvain, Belgium + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +/** + * STL plugin for Orthanc + * Copyright (C) 2023-2024 Sebastien Jodogne, UCLouvain, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + **/ + + +#include "StructurePolygon.h" + +#include "STLToolbox.h" + +#include +#include + +#include +#include + + +StructurePolygon::StructurePolygon(Orthanc::ParsedDicomFile& dicom, + unsigned long roiIndex, + unsigned long contourIndex) +{ + DcmDataset& dataset = *dicom.GetDcmtkObject().getDataset(); + + DcmItem* structure = NULL; + DcmItem* roi = NULL; + DcmItem* contour = NULL; + DcmSequenceOfItems* referenced = NULL; + + if (!dataset.findAndGetSequenceItem(DCM_StructureSetROISequence, structure, roiIndex).good() || + structure == NULL || + !dataset.findAndGetSequenceItem(DCM_ROIContourSequence, roi, roiIndex).good() || + roi == NULL || + !roi->findAndGetSequenceItem(DCM_ContourSequence, contour, contourIndex).good() || + contour == NULL || + !contour->findAndGetSequence(DCM_ContourImageSequence, referenced).good() || + referenced == NULL || + referenced->card() != 1) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } + + roiName_ = STLToolbox::GetStringValue(*structure, DCM_ROIName); + referencedSopInstanceUid_ = STLToolbox::GetStringValue(*referenced->getItem(0), DCM_ReferencedSOPInstanceUID); + + if (STLToolbox::GetStringValue(*contour, DCM_ContourGeometricType) != "CLOSED_PLANAR") + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } + + { + std::vector tokens; + Orthanc::Toolbox::TokenizeString(tokens, STLToolbox::GetStringValue(*roi, DCM_ROIDisplayColor), '\\'); + + uint32_t r, g, b; + if (tokens.size() != 3 || + !Orthanc::SerializationToolbox::ParseFirstUnsignedInteger32(r, tokens[0]) || + !Orthanc::SerializationToolbox::ParseFirstUnsignedInteger32(g, tokens[1]) || + !Orthanc::SerializationToolbox::ParseFirstUnsignedInteger32(b, tokens[2]) || + r > 255 || + g > 255 || + b > 255) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } + + red_ = r; + green_ = g; + blue_ = b; + } + + { + std::vector tokens; + Orthanc::Toolbox::TokenizeString(tokens, STLToolbox::GetStringValue(*contour, DCM_ContourData), '\\'); + + const std::string s = STLToolbox::GetStringValue(*contour, DCM_NumberOfContourPoints); + + uint32_t countPoints; + if (!Orthanc::SerializationToolbox::ParseUnsignedInteger32(countPoints, s) || + tokens.size() != 3 * countPoints) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } + + points_.reserve(countPoints); + + for (size_t i = 0; i < tokens.size(); i += 3) + { + double x, y, z; + if (!STLToolbox::MyParseDouble(x, tokens[i]) || + !STLToolbox::MyParseDouble(y, tokens[i + 1]) || + !STLToolbox::MyParseDouble(z, tokens[i + 2])) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } + + points_.push_back(Vector3D(x, y, z)); + } + + assert(points_.size() == countPoints); + } +} + + +const Vector3D& StructurePolygon::GetPoint(size_t i) const +{ + if (i >= points_.size()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + else + { + return points_[i]; + } +} + + +bool StructurePolygon::IsCoplanar(Vector3D& normal) const +{ + if (points_.size() < 3) + { + return false; + } + + bool hasNormal = false; + + for (size_t i = 0; i < points_.size(); i++) + { + normal = Vector3D::CrossProduct(Vector3D(points_[1], points_[0]), + Vector3D(points_[2], points_[0])); + if (!STLToolbox::IsNear(normal.ComputeNorm(), 0)) + { + normal.Normalize(); + hasNormal = true; + } + } + + if (!hasNormal) + { + return false; + } + + double a = Vector3D::DotProduct(points_[0], normal); + + for (size_t i = 1; i < points_.size(); i++) + { + double b = Vector3D::DotProduct(points_[i], normal); + if (!STLToolbox::IsNear(a, b)) + { + return false; + } + } + + return true; +} + + +void StructurePolygon::Add(Extent2D& extent, + const Vector3D& axisX, + const Vector3D& axisY) const +{ + assert(STLToolbox::IsNear(1, axisX.ComputeNorm())); + assert(STLToolbox::IsNear(1, axisY.ComputeNorm())); + + for (size_t i = 0; i < points_.size(); i++) + { + extent.Add(Vector3D::DotProduct(axisX, points_[i]), + Vector3D::DotProduct(axisY, points_[i])); + } +} diff -r 976da5476810 -r 2460b376d3f7 Sources/StructurePolygon.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Sources/StructurePolygon.h Thu Apr 04 18:50:11 2024 +0200 @@ -0,0 +1,70 @@ +/** + * SPDX-FileCopyrightText: 2023-2024 Sebastien Jodogne, UCLouvain, Belgium + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +/** + * STL plugin for Orthanc + * Copyright (C) 2023-2024 Sebastien Jodogne, UCLouvain, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + **/ + + +#pragma once + +#include "Extent2D.h" +#include "Vector3D.h" + +#include + + +class StructurePolygon : public boost::noncopyable +{ +private: + std::string roiName_; + std::string referencedSopInstanceUid_; + uint8_t red_; + uint8_t green_; + uint8_t blue_; + std::vector points_; + +public: + StructurePolygon(Orthanc::ParsedDicomFile& dicom, + unsigned long roiIndex, + unsigned long contourIndex); + + const std::string& GetRoiName() const + { + return roiName_; + } + + const std::string& GetReferencedSopInstanceUid() const + { + return referencedSopInstanceUid_; + } + + size_t GetPointsCount() const + { + return points_.size(); + } + + const Vector3D& GetPoint(size_t i) const; + + bool IsCoplanar(Vector3D& normal) const; + + void Add(Extent2D& extent, + const Vector3D& axisX, + const Vector3D& axisY) const; +}; diff -r 976da5476810 -r 2460b376d3f7 Sources/Toolbox.cpp --- a/Sources/Toolbox.cpp Thu Apr 04 18:35:54 2024 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,72 +0,0 @@ -/** - * SPDX-FileCopyrightText: 2023-2024 Sebastien Jodogne, UCLouvain, Belgium - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -/** - * STL plugin for Orthanc - * Copyright (C) 2023-2024 Sebastien Jodogne, UCLouvain, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#include "Toolbox.h" - -#include -#include - - -namespace Toolbox -{ - bool IsNear(double a, - double b) - { - return std::abs(a - b) < 10.0 * std::numeric_limits::epsilon(); - } - - - bool MyParseDouble(double& value, - const std::string& s) - { -#if 1 - char* end = NULL; - value = strtod(s.c_str(), &end); - return (end == s.c_str() + s.size()); -#else - return Orthanc::SerializationToolbox::ParseDouble(value, s); -#endif - } - - - namespace - { - struct IsNearPredicate - { - bool operator() (const double& a, - const double& b) - { - return Toolbox::IsNear(a, b); - } - }; - } - - - void RemoveDuplicateValues(std::vector& v) - { - IsNearPredicate predicate; - std::vector::iterator last = std::unique(v.begin(), v.end(), predicate); - v.erase(last, v.end()); - } -} diff -r 976da5476810 -r 2460b376d3f7 Sources/Toolbox.h --- a/Sources/Toolbox.h Thu Apr 04 18:35:54 2024 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,42 +0,0 @@ -/** - * SPDX-FileCopyrightText: 2023-2024 Sebastien Jodogne, UCLouvain, Belgium - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -/** - * STL plugin for Orthanc - * Copyright (C) 2023-2024 Sebastien Jodogne, UCLouvain, Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include -#include - - -namespace Toolbox -{ - bool IsNear(double a, - double b); - - // This version is much faster, as "ParseDouble()" internally uses - // "boost::lexical_cast()" - bool MyParseDouble(double& value, - const std::string& s); - - void RemoveDuplicateValues(std::vector& v); -} diff -r 976da5476810 -r 2460b376d3f7 Sources/VTKToolbox.cpp --- a/Sources/VTKToolbox.cpp Thu Apr 04 18:35:54 2024 +0200 +++ b/Sources/VTKToolbox.cpp Thu Apr 04 18:50:11 2024 +0200 @@ -30,12 +30,13 @@ #include #include -#include +#include #include -#include #include +#include +#include #include -#include +#include #include diff -r 976da5476810 -r 2460b376d3f7 Sources/Vector3D.cpp --- a/Sources/Vector3D.cpp Thu Apr 04 18:35:54 2024 +0200 +++ b/Sources/Vector3D.cpp Thu Apr 04 18:50:11 2024 +0200 @@ -73,7 +73,7 @@ void Vector3D::Normalize() { double norm = ComputeNorm(); - if (!Toolbox::IsNear(norm, 0)) + if (!STLToolbox::IsNear(norm, 0)) { x_ /= norm; y_ /= norm; @@ -101,10 +101,10 @@ bool Vector3D::AreParallel(const Vector3D& a, const Vector3D& b) { - if (Toolbox::IsNear(a.ComputeSquaredNorm(), 1) && - Toolbox::IsNear(b.ComputeSquaredNorm(), 1)) + if (STLToolbox::IsNear(a.ComputeSquaredNorm(), 1) && + STLToolbox::IsNear(b.ComputeSquaredNorm(), 1)) { - return Toolbox::IsNear(std::abs(Vector3D::DotProduct(a, b)), 1); + return STLToolbox::IsNear(std::abs(Vector3D::DotProduct(a, b)), 1); } else {