comparison Framework/Toolbox/DicomInstanceParameters.cpp @ 746:d716bfb3e07c

reorganization
author Sebastien Jodogne <s.jodogne@gmail.com>
date Wed, 22 May 2019 12:48:57 +0200
parents
children f7c236894c1a
comparison
equal deleted inserted replaced
745:c44c1d2d3598 746:d716bfb3e07c
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 "DicomInstanceParameters.h"
23
24 #include "../Scene2D/ColorTextureSceneLayer.h"
25 #include "../Scene2D/FloatTextureSceneLayer.h"
26 #include "../Toolbox/GeometryToolbox.h"
27
28 #include <Core/Images/Image.h>
29 #include <Core/Images/ImageProcessing.h>
30 #include <Core/Logging.h>
31 #include <Core/OrthancException.h>
32 #include <Core/Toolbox.h>
33
34
35 namespace OrthancStone
36 {
37 void DicomInstanceParameters::Data::ComputeDoseOffsets(const Orthanc::DicomMap& dicom)
38 {
39 // http://dicom.nema.org/medical/Dicom/2016a/output/chtml/part03/sect_C.8.8.3.2.html
40
41 {
42 std::string increment;
43
44 if (dicom.CopyToString(increment, Orthanc::DICOM_TAG_FRAME_INCREMENT_POINTER, false))
45 {
46 Orthanc::Toolbox::ToUpperCase(increment);
47 if (increment != "3004,000C") // This is the "Grid Frame Offset Vector" tag
48 {
49 LOG(ERROR) << "RT-DOSE: Bad value for the \"FrameIncrementPointer\" tag";
50 return;
51 }
52 }
53 }
54
55 if (!LinearAlgebra::ParseVector(frameOffsets_, dicom, Orthanc::DICOM_TAG_GRID_FRAME_OFFSET_VECTOR) ||
56 frameOffsets_.size() < imageInformation_.GetNumberOfFrames())
57 {
58 LOG(ERROR) << "RT-DOSE: No information about the 3D location of some slice(s)";
59 frameOffsets_.clear();
60 }
61 else
62 {
63 if (frameOffsets_.size() >= 2)
64 {
65 thickness_ = frameOffsets_[1] - frameOffsets_[0];
66
67 if (thickness_ < 0)
68 {
69 thickness_ = -thickness_;
70 }
71 }
72 }
73 }
74
75
76 DicomInstanceParameters::Data::Data(const Orthanc::DicomMap& dicom) :
77 imageInformation_(dicom)
78 {
79 if (imageInformation_.GetNumberOfFrames() <= 0)
80 {
81 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
82 }
83
84 if (!dicom.CopyToString(studyInstanceUid_, Orthanc::DICOM_TAG_STUDY_INSTANCE_UID, false) ||
85 !dicom.CopyToString(seriesInstanceUid_, Orthanc::DICOM_TAG_SERIES_INSTANCE_UID, false) ||
86 !dicom.CopyToString(sopInstanceUid_, Orthanc::DICOM_TAG_SOP_INSTANCE_UID, false))
87 {
88 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
89 }
90
91 std::string s;
92 if (!dicom.CopyToString(s, Orthanc::DICOM_TAG_SOP_CLASS_UID, false))
93 {
94 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
95 }
96 else
97 {
98 sopClassUid_ = StringToSopClassUid(s);
99 }
100
101 if (!dicom.ParseDouble(thickness_, Orthanc::DICOM_TAG_SLICE_THICKNESS))
102 {
103 thickness_ = 100.0 * std::numeric_limits<double>::epsilon();
104 }
105
106 GeometryToolbox::GetPixelSpacing(pixelSpacingX_, pixelSpacingY_, dicom);
107
108 std::string position, orientation;
109 if (dicom.CopyToString(position, Orthanc::DICOM_TAG_IMAGE_POSITION_PATIENT, false) &&
110 dicom.CopyToString(orientation, Orthanc::DICOM_TAG_IMAGE_ORIENTATION_PATIENT, false))
111 {
112 geometry_ = CoordinateSystem3D(position, orientation);
113 }
114
115 if (sopClassUid_ == SopClassUid_RTDose)
116 {
117 ComputeDoseOffsets(dicom);
118 }
119
120 isColor_ = (imageInformation_.GetPhotometricInterpretation() != Orthanc::PhotometricInterpretation_Monochrome1 &&
121 imageInformation_.GetPhotometricInterpretation() != Orthanc::PhotometricInterpretation_Monochrome2);
122
123 double doseGridScaling;
124
125 if (dicom.ParseDouble(rescaleIntercept_, Orthanc::DICOM_TAG_RESCALE_INTERCEPT) &&
126 dicom.ParseDouble(rescaleSlope_, Orthanc::DICOM_TAG_RESCALE_SLOPE))
127 {
128 hasRescale_ = true;
129 }
130 else if (dicom.ParseDouble(doseGridScaling, Orthanc::DICOM_TAG_DOSE_GRID_SCALING))
131 {
132 hasRescale_ = true;
133 rescaleIntercept_ = 0;
134 rescaleSlope_ = doseGridScaling;
135 }
136 else
137 {
138 hasRescale_ = false;
139 }
140
141 Vector c, w;
142 if (LinearAlgebra::ParseVector(c, dicom, Orthanc::DICOM_TAG_WINDOW_CENTER) &&
143 LinearAlgebra::ParseVector(w, dicom, Orthanc::DICOM_TAG_WINDOW_WIDTH) &&
144 c.size() > 0 &&
145 w.size() > 0)
146 {
147 hasDefaultWindowing_ = true;
148 defaultWindowingCenter_ = static_cast<float>(c[0]);
149 defaultWindowingWidth_ = static_cast<float>(w[0]);
150 }
151 else
152 {
153 hasDefaultWindowing_ = false;
154 }
155
156 if (sopClassUid_ == SopClassUid_RTDose)
157 {
158 switch (imageInformation_.GetBitsStored())
159 {
160 case 16:
161 expectedPixelFormat_ = Orthanc::PixelFormat_Grayscale16;
162 break;
163
164 case 32:
165 expectedPixelFormat_ = Orthanc::PixelFormat_Grayscale32;
166 break;
167
168 default:
169 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
170 }
171 }
172 else if (isColor_)
173 {
174 expectedPixelFormat_ = Orthanc::PixelFormat_RGB24;
175 }
176 else if (imageInformation_.IsSigned())
177 {
178 expectedPixelFormat_ = Orthanc::PixelFormat_SignedGrayscale16;
179 }
180 else
181 {
182 expectedPixelFormat_ = Orthanc::PixelFormat_Grayscale16;
183 }
184 }
185
186
187 CoordinateSystem3D DicomInstanceParameters::Data::GetFrameGeometry(unsigned int frame) const
188 {
189 if (frame == 0)
190 {
191 return geometry_;
192 }
193 else if (frame >= imageInformation_.GetNumberOfFrames())
194 {
195 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
196 }
197 else if (sopClassUid_ == SopClassUid_RTDose)
198 {
199 if (frame >= frameOffsets_.size())
200 {
201 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
202 }
203
204 return CoordinateSystem3D(
205 geometry_.GetOrigin() + frameOffsets_[frame] * geometry_.GetNormal(),
206 geometry_.GetAxisX(),
207 geometry_.GetAxisY());
208 }
209 else
210 {
211 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
212 }
213 }
214
215
216 bool DicomInstanceParameters::Data::IsPlaneWithinSlice(unsigned int frame,
217 const CoordinateSystem3D& plane) const
218 {
219 if (frame >= imageInformation_.GetNumberOfFrames())
220 {
221 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
222 }
223
224 CoordinateSystem3D tmp = geometry_;
225
226 if (frame != 0)
227 {
228 tmp = GetFrameGeometry(frame);
229 }
230
231 double distance;
232
233 return (CoordinateSystem3D::GetDistance(distance, tmp, plane) &&
234 distance <= thickness_ / 2.0);
235 }
236
237
238 void DicomInstanceParameters::Data::ApplyRescale(Orthanc::ImageAccessor& image,
239 bool useDouble) const
240 {
241 if (image.GetFormat() != Orthanc::PixelFormat_Float32)
242 {
243 throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat);
244 }
245
246 if (hasRescale_)
247 {
248 const unsigned int width = image.GetWidth();
249 const unsigned int height = image.GetHeight();
250
251 for (unsigned int y = 0; y < height; y++)
252 {
253 float* p = reinterpret_cast<float*>(image.GetRow(y));
254
255 if (useDouble)
256 {
257 // Slower, accurate implementation using double
258 for (unsigned int x = 0; x < width; x++, p++)
259 {
260 double value = static_cast<double>(*p);
261 *p = static_cast<float>(value * rescaleSlope_ + rescaleIntercept_);
262 }
263 }
264 else
265 {
266 // Fast, approximate implementation using float
267 for (unsigned int x = 0; x < width; x++, p++)
268 {
269 *p = (*p) * static_cast<float>(rescaleSlope_) + static_cast<float>(rescaleIntercept_);
270 }
271 }
272 }
273 }
274 }
275
276 double DicomInstanceParameters::GetRescaleIntercept() const
277 {
278 if (data_.hasRescale_)
279 {
280 return data_.rescaleIntercept_;
281 }
282 else
283 {
284 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
285 }
286 }
287
288
289 double DicomInstanceParameters::GetRescaleSlope() const
290 {
291 if (data_.hasRescale_)
292 {
293 return data_.rescaleSlope_;
294 }
295 else
296 {
297 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
298 }
299 }
300
301
302 float DicomInstanceParameters::GetDefaultWindowingCenter() const
303 {
304 if (data_.hasDefaultWindowing_)
305 {
306 return data_.defaultWindowingCenter_;
307 }
308 else
309 {
310 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
311 }
312 }
313
314
315 float DicomInstanceParameters::GetDefaultWindowingWidth() const
316 {
317 if (data_.hasDefaultWindowing_)
318 {
319 return data_.defaultWindowingWidth_;
320 }
321 else
322 {
323 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
324 }
325 }
326
327
328 TextureBaseSceneLayer* DicomInstanceParameters::CreateTexture(const Orthanc::ImageAccessor& pixelData) const
329 {
330 assert(sizeof(float) == 4);
331
332 Orthanc::PixelFormat sourceFormat = pixelData.GetFormat();
333
334 if (sourceFormat != GetExpectedPixelFormat())
335 {
336 throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat);
337 }
338
339 if (sourceFormat == Orthanc::PixelFormat_RGB24)
340 {
341 // This is the case of a color image. No conversion has to be done.
342 return new ColorTextureSceneLayer(pixelData);
343 }
344 else
345 {
346 if (sourceFormat != Orthanc::PixelFormat_Grayscale16 &&
347 sourceFormat != Orthanc::PixelFormat_Grayscale32 &&
348 sourceFormat != Orthanc::PixelFormat_SignedGrayscale16)
349 {
350 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
351 }
352
353 std::auto_ptr<FloatTextureSceneLayer> texture;
354
355 {
356 // This is the case of a grayscale frame. Convert it to Float32.
357 std::auto_ptr<Orthanc::Image> converted(new Orthanc::Image(Orthanc::PixelFormat_Float32,
358 pixelData.GetWidth(),
359 pixelData.GetHeight(),
360 false));
361 Orthanc::ImageProcessing::Convert(*converted, pixelData);
362
363 // Correct rescale slope/intercept if need be
364 data_.ApplyRescale(*converted, (sourceFormat == Orthanc::PixelFormat_Grayscale32));
365
366 texture.reset(new FloatTextureSceneLayer(*converted));
367 }
368
369 if (data_.hasDefaultWindowing_)
370 {
371 texture->SetCustomWindowing(data_.defaultWindowingCenter_,
372 data_.defaultWindowingWidth_);
373 }
374
375 return texture.release();
376 }
377 }
378 }