33
|
1 /**
|
|
2 * SPDX-FileCopyrightText: 2023-2024 Sebastien Jodogne, UCLouvain, Belgium
|
|
3 * SPDX-License-Identifier: GPL-3.0-or-later
|
|
4 */
|
|
5
|
|
6 /**
|
|
7 * STL plugin for Orthanc
|
|
8 * Copyright (C) 2023-2024 Sebastien Jodogne, UCLouvain, Belgium
|
|
9 *
|
|
10 * This program is free software: you can redistribute it and/or
|
|
11 * modify it under the terms of the GNU General Public License as
|
|
12 * published by the Free Software Foundation, either version 3 of the
|
|
13 * License, or (at your option) any later version.
|
|
14 *
|
|
15 * This program is distributed in the hope that it will be useful, but
|
|
16 * WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
18 * General Public License for more details.
|
|
19 *
|
|
20 * You should have received a copy of the GNU General Public License
|
|
21 * along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
22 **/
|
|
23
|
|
24
|
|
25 #include "StructurePolygon.h"
|
|
26
|
|
27 #include "STLToolbox.h"
|
|
28
|
|
29 #include <OrthancException.h>
|
|
30 #include <SerializationToolbox.h>
|
|
31
|
|
32 #include <dcmtk/dcmdata/dcfilefo.h>
|
|
33 #include <dcmtk/dcmdata/dcdeftag.h>
|
|
34
|
|
35
|
|
36 StructurePolygon::StructurePolygon(Orthanc::ParsedDicomFile& dicom,
|
|
37 unsigned long roiIndex,
|
|
38 unsigned long contourIndex)
|
|
39 {
|
|
40 DcmDataset& dataset = *dicom.GetDcmtkObject().getDataset();
|
|
41
|
|
42 DcmItem* structure = NULL;
|
|
43 DcmItem* roi = NULL;
|
|
44 DcmItem* contour = NULL;
|
|
45 DcmSequenceOfItems* referenced = NULL;
|
|
46
|
|
47 if (!dataset.findAndGetSequenceItem(DCM_StructureSetROISequence, structure, roiIndex).good() ||
|
|
48 structure == NULL ||
|
|
49 !dataset.findAndGetSequenceItem(DCM_ROIContourSequence, roi, roiIndex).good() ||
|
|
50 roi == NULL ||
|
|
51 !roi->findAndGetSequenceItem(DCM_ContourSequence, contour, contourIndex).good() ||
|
|
52 contour == NULL ||
|
|
53 !contour->findAndGetSequence(DCM_ContourImageSequence, referenced).good() ||
|
|
54 referenced == NULL ||
|
|
55 referenced->card() != 1)
|
|
56 {
|
|
57 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
|
|
58 }
|
|
59
|
|
60 roiName_ = STLToolbox::GetStringValue(*structure, DCM_ROIName);
|
|
61 referencedSopInstanceUid_ = STLToolbox::GetStringValue(*referenced->getItem(0), DCM_ReferencedSOPInstanceUID);
|
|
62
|
|
63 if (STLToolbox::GetStringValue(*contour, DCM_ContourGeometricType) != "CLOSED_PLANAR")
|
|
64 {
|
|
65 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
|
|
66 }
|
|
67
|
|
68 {
|
|
69 std::vector<std::string> tokens;
|
|
70 Orthanc::Toolbox::TokenizeString(tokens, STLToolbox::GetStringValue(*roi, DCM_ROIDisplayColor), '\\');
|
|
71
|
|
72 uint32_t r, g, b;
|
|
73 if (tokens.size() != 3 ||
|
|
74 !Orthanc::SerializationToolbox::ParseFirstUnsignedInteger32(r, tokens[0]) ||
|
|
75 !Orthanc::SerializationToolbox::ParseFirstUnsignedInteger32(g, tokens[1]) ||
|
|
76 !Orthanc::SerializationToolbox::ParseFirstUnsignedInteger32(b, tokens[2]) ||
|
|
77 r > 255 ||
|
|
78 g > 255 ||
|
|
79 b > 255)
|
|
80 {
|
|
81 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
|
|
82 }
|
|
83
|
|
84 red_ = r;
|
|
85 green_ = g;
|
|
86 blue_ = b;
|
|
87 }
|
|
88
|
|
89 {
|
|
90 std::vector<std::string> tokens;
|
|
91 Orthanc::Toolbox::TokenizeString(tokens, STLToolbox::GetStringValue(*contour, DCM_ContourData), '\\');
|
|
92
|
|
93 const std::string s = STLToolbox::GetStringValue(*contour, DCM_NumberOfContourPoints);
|
|
94
|
|
95 uint32_t countPoints;
|
|
96 if (!Orthanc::SerializationToolbox::ParseUnsignedInteger32(countPoints, s) ||
|
|
97 tokens.size() != 3 * countPoints)
|
|
98 {
|
|
99 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
|
|
100 }
|
|
101
|
|
102 points_.reserve(countPoints);
|
|
103
|
|
104 for (size_t i = 0; i < tokens.size(); i += 3)
|
|
105 {
|
|
106 double x, y, z;
|
|
107 if (!STLToolbox::MyParseDouble(x, tokens[i]) ||
|
|
108 !STLToolbox::MyParseDouble(y, tokens[i + 1]) ||
|
|
109 !STLToolbox::MyParseDouble(z, tokens[i + 2]))
|
|
110 {
|
|
111 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
|
|
112 }
|
|
113
|
|
114 points_.push_back(Vector3D(x, y, z));
|
|
115 }
|
|
116
|
|
117 assert(points_.size() == countPoints);
|
|
118 }
|
|
119 }
|
|
120
|
|
121
|
|
122 const Vector3D& StructurePolygon::GetPoint(size_t i) const
|
|
123 {
|
|
124 if (i >= points_.size())
|
|
125 {
|
|
126 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
|
|
127 }
|
|
128 else
|
|
129 {
|
|
130 return points_[i];
|
|
131 }
|
|
132 }
|
|
133
|
|
134
|
|
135 bool StructurePolygon::IsCoplanar(Vector3D& normal) const
|
|
136 {
|
|
137 if (points_.size() < 3)
|
|
138 {
|
|
139 return false;
|
|
140 }
|
|
141
|
|
142 bool hasNormal = false;
|
|
143
|
|
144 for (size_t i = 0; i < points_.size(); i++)
|
|
145 {
|
|
146 normal = Vector3D::CrossProduct(Vector3D(points_[1], points_[0]),
|
|
147 Vector3D(points_[2], points_[0]));
|
|
148 if (!STLToolbox::IsNear(normal.ComputeNorm(), 0))
|
|
149 {
|
|
150 normal.Normalize();
|
|
151 hasNormal = true;
|
|
152 }
|
|
153 }
|
|
154
|
|
155 if (!hasNormal)
|
|
156 {
|
|
157 return false;
|
|
158 }
|
|
159
|
|
160 double a = Vector3D::DotProduct(points_[0], normal);
|
|
161
|
|
162 for (size_t i = 1; i < points_.size(); i++)
|
|
163 {
|
|
164 double b = Vector3D::DotProduct(points_[i], normal);
|
|
165 if (!STLToolbox::IsNear(a, b))
|
|
166 {
|
|
167 return false;
|
|
168 }
|
|
169 }
|
|
170
|
|
171 return true;
|
|
172 }
|
|
173
|
|
174
|
|
175 void StructurePolygon::Add(Extent2D& extent,
|
|
176 const Vector3D& axisX,
|
|
177 const Vector3D& axisY) const
|
|
178 {
|
|
179 assert(STLToolbox::IsNear(1, axisX.ComputeNorm()));
|
|
180 assert(STLToolbox::IsNear(1, axisY.ComputeNorm()));
|
|
181
|
|
182 for (size_t i = 0; i < points_.size(); i++)
|
|
183 {
|
|
184 extent.Add(Vector3D::DotProduct(axisX, points_[i]),
|
|
185 Vector3D::DotProduct(axisY, points_[i]));
|
|
186 }
|
|
187 }
|