Mercurial > hg > orthanc-stone
comparison Framework/Deprecated/Toolbox/Slice.cpp @ 754:92c400a09f1b
Merge from default
author | Benjamin Golinvaux <bgo@osimis.io> |
---|---|
date | Wed, 22 May 2019 16:13:46 +0200 |
parents | c35e98d22764 |
children | 1f74bc3459ba |
comparison
equal
deleted
inserted
replaced
753:a386bbc955dc | 754:92c400a09f1b |
---|---|
1 /** | |
2 * Stone of Orthanc | |
3 * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics | |
4 * Department, University Hospital of Liege, Belgium | |
5 * Copyright (C) 2017-2019 Osimis S.A., Belgium | |
6 * | |
7 * This program is free software: you can redistribute it and/or | |
8 * modify it under the terms of the GNU Affero General Public License | |
9 * as published by the Free Software Foundation, either version 3 of | |
10 * the License, or (at your option) any later version. | |
11 * | |
12 * This program is distributed in the hope that it will be useful, but | |
13 * WITHOUT ANY WARRANTY; without even the implied warranty of | |
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
15 * Affero General Public License for more details. | |
16 * | |
17 * You should have received a copy of the GNU Affero General Public License | |
18 * along with this program. If not, see <http://www.gnu.org/licenses/>. | |
19 **/ | |
20 | |
21 | |
22 #include "Slice.h" | |
23 | |
24 #include "../../StoneEnumerations.h" | |
25 #include "../../Toolbox/GeometryToolbox.h" | |
26 | |
27 #include <Core/Logging.h> | |
28 #include <Core/OrthancException.h> | |
29 #include <Core/Toolbox.h> | |
30 | |
31 #include <boost/lexical_cast.hpp> | |
32 | |
33 namespace Deprecated | |
34 { | |
35 static bool ParseDouble(double& target, | |
36 const std::string& source) | |
37 { | |
38 try | |
39 { | |
40 target = boost::lexical_cast<double>(source); | |
41 return true; | |
42 } | |
43 catch (boost::bad_lexical_cast&) | |
44 { | |
45 return false; | |
46 } | |
47 } | |
48 | |
49 Slice* Slice::Clone() const | |
50 { | |
51 std::auto_ptr<Slice> target(new Slice()); | |
52 | |
53 target->type_ = type_; | |
54 target->orthancInstanceId_ = orthancInstanceId_; | |
55 target->sopClassUid_ = sopClassUid_; | |
56 target->frame_ = frame_; | |
57 target->frameCount_ = frameCount_; | |
58 target->geometry_ = geometry_; | |
59 target->pixelSpacingX_ = pixelSpacingX_; | |
60 target->pixelSpacingY_ = pixelSpacingY_; | |
61 target->thickness_ = thickness_; | |
62 target->width_ = width_; | |
63 target->height_ = height_; | |
64 target->converter_ = converter_; | |
65 if (imageInformation_.get() != NULL) | |
66 target->imageInformation_.reset(imageInformation_->Clone()); | |
67 | |
68 return target.release(); | |
69 } | |
70 | |
71 bool Slice::ComputeRTDoseGeometry(const Orthanc::DicomMap& dataset, | |
72 unsigned int frame) | |
73 { | |
74 // http://dicom.nema.org/medical/Dicom/2016a/output/chtml/part03/sect_C.8.8.3.2.html | |
75 | |
76 { | |
77 std::string increment; | |
78 | |
79 if (dataset.CopyToString(increment, Orthanc::DICOM_TAG_FRAME_INCREMENT_POINTER, false)) | |
80 { | |
81 Orthanc::Toolbox::ToUpperCase(increment); | |
82 if (increment != "3004,000C") // This is the "Grid Frame Offset Vector" tag | |
83 { | |
84 LOG(ERROR) << "Bad value for the \"FrameIncrementPointer\" tag"; | |
85 return false; | |
86 } | |
87 } | |
88 } | |
89 | |
90 std::string offsetTag; | |
91 | |
92 if (!dataset.CopyToString(offsetTag, Orthanc::DICOM_TAG_GRID_FRAME_OFFSET_VECTOR, false) || | |
93 offsetTag.empty()) | |
94 { | |
95 LOG(ERROR) << "Cannot read the \"GridFrameOffsetVector\" tag, check you are using Orthanc >= 1.3.1"; | |
96 return false; | |
97 } | |
98 | |
99 std::vector<std::string> offsets; | |
100 Orthanc::Toolbox::TokenizeString(offsets, offsetTag, '\\'); | |
101 | |
102 if (frameCount_ <= 1 || | |
103 offsets.size() < frameCount_ || | |
104 offsets.size() < 2 || | |
105 frame >= frameCount_) | |
106 { | |
107 LOG(ERROR) << "No information about the 3D location of some slice(s) in a RT DOSE"; | |
108 return false; | |
109 } | |
110 | |
111 double offset0, offset1, z; | |
112 | |
113 if (!ParseDouble(offset0, offsets[0]) || | |
114 !ParseDouble(offset1, offsets[1]) || | |
115 !ParseDouble(z, offsets[frame])) | |
116 { | |
117 LOG(ERROR) << "Invalid syntax"; | |
118 return false; | |
119 } | |
120 | |
121 if (!OrthancStone::LinearAlgebra::IsCloseToZero(offset0)) | |
122 { | |
123 LOG(ERROR) << "Invalid syntax"; | |
124 return false; | |
125 } | |
126 | |
127 geometry_ = OrthancStone::CoordinateSystem3D(geometry_.GetOrigin() + z * geometry_.GetNormal(), | |
128 //+ 650 * geometry_.GetAxisX(), | |
129 geometry_.GetAxisX(), | |
130 geometry_.GetAxisY()); | |
131 | |
132 thickness_ = offset1 - offset0; | |
133 if (thickness_ < 0) | |
134 { | |
135 thickness_ = -thickness_; | |
136 } | |
137 | |
138 return true; | |
139 } | |
140 | |
141 | |
142 bool Slice::ParseOrthancFrame(const Orthanc::DicomMap& dataset, | |
143 const std::string& instanceId, | |
144 unsigned int frame) | |
145 { | |
146 orthancInstanceId_ = instanceId; | |
147 frame_ = frame; | |
148 type_ = Type_OrthancDecodableFrame; | |
149 imageInformation_.reset(new Orthanc::DicomImageInformation(dataset)); | |
150 | |
151 if (!dataset.CopyToString(sopClassUid_, Orthanc::DICOM_TAG_SOP_CLASS_UID, false) || | |
152 sopClassUid_.empty()) | |
153 { | |
154 LOG(ERROR) << "Instance without a SOP class UID"; | |
155 return false; | |
156 } | |
157 | |
158 if (!dataset.ParseUnsignedInteger32(frameCount_, Orthanc::DICOM_TAG_NUMBER_OF_FRAMES)) | |
159 { | |
160 frameCount_ = 1; // Assume instance with one frame | |
161 } | |
162 | |
163 if (frame >= frameCount_) | |
164 { | |
165 return false; | |
166 } | |
167 | |
168 if (!dataset.ParseUnsignedInteger32(width_, Orthanc::DICOM_TAG_COLUMNS) || | |
169 !dataset.ParseUnsignedInteger32(height_, Orthanc::DICOM_TAG_ROWS)) | |
170 { | |
171 return false; | |
172 } | |
173 | |
174 thickness_ = 100.0 * std::numeric_limits<double>::epsilon(); | |
175 | |
176 std::string tmp; | |
177 if (dataset.CopyToString(tmp, Orthanc::DICOM_TAG_SLICE_THICKNESS, false)) | |
178 { | |
179 if (!tmp.empty() && | |
180 !ParseDouble(thickness_, tmp)) | |
181 { | |
182 return false; // Syntax error | |
183 } | |
184 } | |
185 | |
186 converter_.ReadParameters(dataset); | |
187 | |
188 OrthancStone::GeometryToolbox::GetPixelSpacing(pixelSpacingX_, pixelSpacingY_, dataset); | |
189 | |
190 std::string position, orientation; | |
191 if (dataset.CopyToString(position, Orthanc::DICOM_TAG_IMAGE_POSITION_PATIENT, false) && | |
192 dataset.CopyToString(orientation, Orthanc::DICOM_TAG_IMAGE_ORIENTATION_PATIENT, false)) | |
193 { | |
194 geometry_ = OrthancStone::CoordinateSystem3D(position, orientation); | |
195 | |
196 bool ok = true; | |
197 | |
198 switch (OrthancStone::StringToSopClassUid(sopClassUid_)) | |
199 { | |
200 case OrthancStone::SopClassUid_RTDose: | |
201 type_ = Type_OrthancRawFrame; | |
202 ok = ComputeRTDoseGeometry(dataset, frame); | |
203 break; | |
204 | |
205 default: | |
206 break; | |
207 } | |
208 | |
209 if (!ok) | |
210 { | |
211 LOG(ERROR) << "Cannot deduce the 3D location of frame " << frame | |
212 << " in instance " << instanceId << ", whose SOP class UID is: " << sopClassUid_; | |
213 return false; | |
214 } | |
215 } | |
216 | |
217 return true; | |
218 } | |
219 | |
220 | |
221 const std::string Slice::GetOrthancInstanceId() const | |
222 { | |
223 if (type_ == Type_OrthancDecodableFrame || | |
224 type_ == Type_OrthancRawFrame) | |
225 { | |
226 return orthancInstanceId_; | |
227 } | |
228 else | |
229 { | |
230 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); | |
231 } | |
232 } | |
233 | |
234 | |
235 unsigned int Slice::GetFrame() const | |
236 { | |
237 if (type_ == Type_Invalid) | |
238 { | |
239 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); | |
240 } | |
241 | |
242 return frame_; | |
243 } | |
244 | |
245 | |
246 const OrthancStone::CoordinateSystem3D& Slice::GetGeometry() const | |
247 { | |
248 if (type_ == Type_Invalid) | |
249 { | |
250 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); | |
251 } | |
252 | |
253 return geometry_; | |
254 } | |
255 | |
256 | |
257 double Slice::GetThickness() const | |
258 { | |
259 if (type_ == Type_Invalid) | |
260 { | |
261 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); | |
262 } | |
263 | |
264 return thickness_; | |
265 } | |
266 | |
267 | |
268 double Slice::GetPixelSpacingX() const | |
269 { | |
270 if (type_ == Type_Invalid) | |
271 { | |
272 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); | |
273 } | |
274 | |
275 return pixelSpacingX_; | |
276 } | |
277 | |
278 | |
279 double Slice::GetPixelSpacingY() const | |
280 { | |
281 if (type_ == Type_Invalid) | |
282 { | |
283 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); | |
284 } | |
285 | |
286 return pixelSpacingY_; | |
287 } | |
288 | |
289 | |
290 unsigned int Slice::GetWidth() const | |
291 { | |
292 if (type_ == Type_Invalid) | |
293 { | |
294 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); | |
295 } | |
296 | |
297 return width_; | |
298 } | |
299 | |
300 | |
301 unsigned int Slice::GetHeight() const | |
302 { | |
303 if (type_ == Type_Invalid) | |
304 { | |
305 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); | |
306 } | |
307 | |
308 return height_; | |
309 } | |
310 | |
311 | |
312 const DicomFrameConverter& Slice::GetConverter() const | |
313 { | |
314 if (type_ == Type_Invalid) | |
315 { | |
316 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); | |
317 } | |
318 | |
319 return converter_; | |
320 } | |
321 | |
322 | |
323 bool Slice::ContainsPlane(const OrthancStone::CoordinateSystem3D& plane) const | |
324 { | |
325 if (type_ == Type_Invalid) | |
326 { | |
327 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); | |
328 } | |
329 | |
330 bool opposite; | |
331 return (OrthancStone::GeometryToolbox::IsParallelOrOpposite(opposite, | |
332 GetGeometry().GetNormal(), | |
333 plane.GetNormal()) && | |
334 OrthancStone::LinearAlgebra::IsNear(GetGeometry().ProjectAlongNormal(GetGeometry().GetOrigin()), | |
335 GetGeometry().ProjectAlongNormal(plane.GetOrigin()), | |
336 thickness_ / 2.0)); | |
337 } | |
338 | |
339 | |
340 void Slice::GetExtent(std::vector<OrthancStone::Vector>& points) const | |
341 { | |
342 double sx = GetPixelSpacingX(); | |
343 double sy = GetPixelSpacingY(); | |
344 double w = static_cast<double>(GetWidth()); | |
345 double h = static_cast<double>(GetHeight()); | |
346 | |
347 points.clear(); | |
348 points.push_back(GetGeometry().MapSliceToWorldCoordinates(-0.5 * sx, -0.5 * sy)); | |
349 points.push_back(GetGeometry().MapSliceToWorldCoordinates((w - 0.5) * sx, -0.5 * sy)); | |
350 points.push_back(GetGeometry().MapSliceToWorldCoordinates(-0.5 * sx, (h - 0.5) * sy)); | |
351 points.push_back(GetGeometry().MapSliceToWorldCoordinates((w - 0.5) * sx, (h - 0.5) * sy)); | |
352 } | |
353 | |
354 | |
355 const Orthanc::DicomImageInformation& Slice::GetImageInformation() const | |
356 { | |
357 if (imageInformation_.get() == NULL) | |
358 { | |
359 // Only available if constructing the "Slice" object with a DICOM map | |
360 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); | |
361 } | |
362 else | |
363 { | |
364 return *imageInformation_; | |
365 } | |
366 } | |
367 } |