Mercurial > hg > orthanc-stone
comparison OrthancStone/Sources/Toolbox/DicomInstanceParameters.cpp @ 1634:a4418a489e86
improving robustness of DicomInstanceParameters
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Tue, 10 Nov 2020 11:11:28 +0100 |
parents | 8563ea5d8ae4 |
children | 1a714e21ea7c |
comparison
equal
deleted
inserted
replaced
1633:53d378ef7277 | 1634:a4418a489e86 |
---|---|
34 #include <Toolbox.h> | 34 #include <Toolbox.h> |
35 | 35 |
36 | 36 |
37 namespace OrthancStone | 37 namespace OrthancStone |
38 { | 38 { |
39 void DicomInstanceParameters::Data::ComputeDoseOffsets(const Orthanc::DicomMap& dicom) | 39 void DicomInstanceParameters::Data::ExtractFrameOffsets(const Orthanc::DicomMap& dicom) |
40 { | 40 { |
41 // http://dicom.nema.org/medical/Dicom/2016a/output/chtml/part03/sect_C.8.8.3.2.html | 41 // http://dicom.nema.org/medical/Dicom/2016a/output/chtml/part03/sect_C.8.8.3.2.html |
42 | 42 |
43 { | 43 std::string increment; |
44 std::string increment; | 44 |
45 | 45 if (dicom.LookupStringValue(increment, Orthanc::DICOM_TAG_FRAME_INCREMENT_POINTER, false)) |
46 if (dicom.LookupStringValue(increment, Orthanc::DICOM_TAG_FRAME_INCREMENT_POINTER, false)) | 46 { |
47 { | 47 Orthanc::Toolbox::ToUpperCase(increment); |
48 Orthanc::Toolbox::ToUpperCase(increment); | 48 if (increment != "3004,000C") // This is the "Grid Frame Offset Vector" tag (DICOM_TAG_GRID_FRAME_OFFSET_VECTOR) |
49 if (increment != "3004,000C") // This is the "Grid Frame Offset Vector" tag (DICOM_TAG_GRID_FRAME_OFFSET_VECTOR) | 49 { |
50 { | 50 LOG(WARNING) << "Bad value for the FrameIncrementPointer tags in a multiframe image"; |
51 LOG(ERROR) << "RT-DOSE: Bad value for the \"FrameIncrementPointer\" tag"; | 51 return; |
52 return; | |
53 } | |
54 } | 52 } |
55 } | 53 } |
56 | 54 |
57 if (!LinearAlgebra::ParseVector(frameOffsets_, dicom, Orthanc::DICOM_TAG_GRID_FRAME_OFFSET_VECTOR) || | 55 if (!LinearAlgebra::ParseVector(frameOffsets_, dicom, Orthanc::DICOM_TAG_GRID_FRAME_OFFSET_VECTOR) || |
58 frameOffsets_.size() < imageInformation_.GetNumberOfFrames()) | 56 frameOffsets_.size() != imageInformation_.GetNumberOfFrames()) |
59 { | 57 { |
60 LOG(ERROR) << "RT-DOSE: No information about the 3D location of some slice(s)"; | 58 LOG(INFO) << "The frame offset information is missing in a multiframe image"; |
61 frameOffsets_.clear(); | 59 frameOffsets_.clear(); |
62 } | |
63 else | |
64 { | |
65 if (frameOffsets_.size() >= 2) | |
66 { | |
67 thickness_ = std::abs(frameOffsets_[1] - frameOffsets_[0]); | |
68 } | |
69 } | 60 } |
70 } | 61 } |
71 | 62 |
72 | 63 |
73 DicomInstanceParameters::Data::Data(const Orthanc::DicomMap& dicom) : | 64 DicomInstanceParameters::Data::Data(const Orthanc::DicomMap& dicom) : |
93 else | 84 else |
94 { | 85 { |
95 sopClassUid_ = StringToSopClassUid(s); | 86 sopClassUid_ = StringToSopClassUid(s); |
96 } | 87 } |
97 | 88 |
98 if (!dicom.ParseDouble(thickness_, Orthanc::DICOM_TAG_SLICE_THICKNESS)) | 89 if (!dicom.ParseDouble(sliceThickness_, Orthanc::DICOM_TAG_SLICE_THICKNESS)) |
99 { | 90 { |
100 thickness_ = 100.0 * std::numeric_limits<double>::epsilon(); | 91 sliceThickness_ = 100.0 * std::numeric_limits<double>::epsilon(); |
101 } | 92 } |
102 | 93 |
103 GeometryToolbox::GetPixelSpacing(pixelSpacingX_, pixelSpacingY_, dicom); | 94 GeometryToolbox::GetPixelSpacing(pixelSpacingX_, pixelSpacingY_, dicom); |
104 | 95 |
105 std::string position, orientation; | 96 std::string position, orientation; |
107 dicom.LookupStringValue(orientation, Orthanc::DICOM_TAG_IMAGE_ORIENTATION_PATIENT, false)) | 98 dicom.LookupStringValue(orientation, Orthanc::DICOM_TAG_IMAGE_ORIENTATION_PATIENT, false)) |
108 { | 99 { |
109 geometry_ = CoordinateSystem3D(position, orientation); | 100 geometry_ = CoordinateSystem3D(position, orientation); |
110 } | 101 } |
111 | 102 |
103 ExtractFrameOffsets(dicom); | |
104 | |
112 if (sopClassUid_ == SopClassUid_RTDose) | 105 if (sopClassUid_ == SopClassUid_RTDose) |
113 { | 106 { |
114 ComputeDoseOffsets(dicom); | |
115 | |
116 static const Orthanc::DicomTag DICOM_TAG_DOSE_UNITS(0x3004, 0x0002); | 107 static const Orthanc::DicomTag DICOM_TAG_DOSE_UNITS(0x3004, 0x0002); |
117 | 108 |
118 if (!dicom.LookupStringValue(doseUnits_, DICOM_TAG_DOSE_UNITS, false)) | 109 if (!dicom.LookupStringValue(doseUnits_, DICOM_TAG_DOSE_UNITS, false)) |
119 { | 110 { |
120 LOG(ERROR) << "Tag DoseUnits (0x3004, 0x0002) is missing in " << sopInstanceUid_; | 111 LOG(ERROR) << "Tag DoseUnits (0x3004, 0x0002) is missing in " << sopInstanceUid_; |
121 doseUnits_ = ""; | 112 doseUnits_.clear(); |
122 } | 113 } |
123 } | 114 } |
124 | 115 |
125 isColor_ = (imageInformation_.GetPhotometricInterpretation() != Orthanc::PhotometricInterpretation_Monochrome1 && | 116 isColor_ = (imageInformation_.GetPhotometricInterpretation() != Orthanc::PhotometricInterpretation_Monochrome1 && |
126 imageInformation_.GetPhotometricInterpretation() != Orthanc::PhotometricInterpretation_Monochrome2); | 117 imageInformation_.GetPhotometricInterpretation() != Orthanc::PhotometricInterpretation_Monochrome2); |
176 hasDefaultWindowing_ = false; | 167 hasDefaultWindowing_ = false; |
177 defaultWindowingCenter_ = 0; | 168 defaultWindowingCenter_ = 0; |
178 defaultWindowingWidth_ = 0; | 169 defaultWindowingWidth_ = 0; |
179 } | 170 } |
180 | 171 |
172 expectedPixelFormat_ = Orthanc::PixelFormat_Grayscale16; // Rough guess | |
173 | |
181 if (sopClassUid_ == SopClassUid_RTDose) | 174 if (sopClassUid_ == SopClassUid_RTDose) |
182 { | 175 { |
183 switch (imageInformation_.GetBitsStored()) | 176 switch (imageInformation_.GetBitsStored()) |
184 { | 177 { |
185 case 16: | 178 case 16: |
189 case 32: | 182 case 32: |
190 expectedPixelFormat_ = Orthanc::PixelFormat_Grayscale32; | 183 expectedPixelFormat_ = Orthanc::PixelFormat_Grayscale32; |
191 break; | 184 break; |
192 | 185 |
193 default: | 186 default: |
194 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); | 187 break; |
195 } | 188 } |
196 } | 189 } |
197 else if (isColor_) | 190 else if (isColor_) |
198 { | 191 { |
199 expectedPixelFormat_ = Orthanc::PixelFormat_RGB24; | 192 expectedPixelFormat_ = Orthanc::PixelFormat_RGB24; |
200 } | 193 } |
201 else if (imageInformation_.IsSigned()) | 194 else if (imageInformation_.IsSigned()) |
202 { | 195 { |
203 expectedPixelFormat_ = Orthanc::PixelFormat_SignedGrayscale16; | 196 expectedPixelFormat_ = Orthanc::PixelFormat_SignedGrayscale16; |
204 } | |
205 else | |
206 { | |
207 expectedPixelFormat_ = Orthanc::PixelFormat_Grayscale16; | |
208 } | 197 } |
209 | 198 |
210 // This computes the "IndexInSeries" metadata from Orthanc (check | 199 // This computes the "IndexInSeries" metadata from Orthanc (check |
211 // out "Orthanc::ServerIndex::Store()") | 200 // out "Orthanc::ServerIndex::Store()") |
212 hasIndexInSeries_ = ( | 201 hasIndexInSeries_ = ( |
223 } | 212 } |
224 else if (frame >= imageInformation_.GetNumberOfFrames()) | 213 else if (frame >= imageInformation_.GetNumberOfFrames()) |
225 { | 214 { |
226 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); | 215 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); |
227 } | 216 } |
228 else if (sopClassUid_ == SopClassUid_RTDose) | 217 else if (frameOffsets_.empty()) |
229 { | 218 { |
230 if (frame >= frameOffsets_.size()) | 219 return geometry_; |
231 { | 220 } |
232 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); | 221 else |
233 } | 222 { |
223 assert(frameOffsets_.size() == imageInformation_.GetNumberOfFrames()); | |
234 | 224 |
235 return CoordinateSystem3D( | 225 return CoordinateSystem3D( |
236 geometry_.GetOrigin() + frameOffsets_[frame] * geometry_.GetNormal(), | 226 geometry_.GetOrigin() + frameOffsets_[frame] * geometry_.GetNormal(), |
237 geometry_.GetAxisX(), | 227 geometry_.GetAxisX(), |
238 geometry_.GetAxisY()); | 228 geometry_.GetAxisY()); |
239 } | 229 } |
240 else | |
241 { | |
242 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); | |
243 } | |
244 } | 230 } |
245 | 231 |
246 | 232 |
247 bool DicomInstanceParameters::Data::IsPlaneWithinSlice(unsigned int frame, | 233 bool DicomInstanceParameters::Data::IsPlaneWithinSlice(unsigned int frame, |
248 const CoordinateSystem3D& plane) const | 234 const CoordinateSystem3D& plane) const |
260 } | 246 } |
261 | 247 |
262 double distance; | 248 double distance; |
263 | 249 |
264 return (CoordinateSystem3D::ComputeDistance(distance, tmp, plane) && | 250 return (CoordinateSystem3D::ComputeDistance(distance, tmp, plane) && |
265 distance <= thickness_ / 2.0); | 251 distance <= sliceThickness_ / 2.0); |
266 } | 252 } |
267 | 253 |
268 void DicomInstanceParameters::Data::ApplyRescaleAndDoseScaling(Orthanc::ImageAccessor& image, | 254 void DicomInstanceParameters::Data::ApplyRescaleAndDoseScaling(Orthanc::ImageAccessor& image, |
269 bool useDouble) const | 255 bool useDouble) const |
270 { | 256 { |
280 { | 266 { |
281 factor *= rescaleSlope_; | 267 factor *= rescaleSlope_; |
282 offset = rescaleIntercept_; | 268 offset = rescaleIntercept_; |
283 } | 269 } |
284 | 270 |
285 if ( (factor != 1.0) || (offset != 0.0) ) | 271 if (!LinearAlgebra::IsNear(factor, 1) || |
272 !LinearAlgebra::IsNear(offset, 0)) | |
286 { | 273 { |
287 const unsigned int width = image.GetWidth(); | 274 const unsigned int width = image.GetWidth(); |
288 const unsigned int height = image.GetHeight(); | 275 const unsigned int height = image.GetHeight(); |
289 | 276 |
290 for (unsigned int y = 0; y < height; y++) | 277 for (unsigned int y = 0; y < height; y++) |
369 | 356 |
370 | 357 |
371 Orthanc::ImageAccessor* DicomInstanceParameters::ConvertToFloat(const Orthanc::ImageAccessor& pixelData) const | 358 Orthanc::ImageAccessor* DicomInstanceParameters::ConvertToFloat(const Orthanc::ImageAccessor& pixelData) const |
372 { | 359 { |
373 std::unique_ptr<Orthanc::Image> converted(new Orthanc::Image(Orthanc::PixelFormat_Float32, | 360 std::unique_ptr<Orthanc::Image> converted(new Orthanc::Image(Orthanc::PixelFormat_Float32, |
374 pixelData.GetWidth(), | 361 pixelData.GetWidth(), |
375 pixelData.GetHeight(), | 362 pixelData.GetHeight(), |
376 false)); | 363 false)); |
377 Orthanc::ImageProcessing::Convert(*converted, pixelData); | 364 Orthanc::ImageProcessing::Convert(*converted, pixelData); |
378 | 365 |
379 | 366 |
380 // Correct rescale slope/intercept if need be | 367 // Correct rescale slope/intercept if need be |
381 //data_.ApplyRescaleAndDoseScaling(*converted, (pixelData.GetFormat() == Orthanc::PixelFormat_Grayscale32)); | 368 //data_.ApplyRescaleAndDoseScaling(*converted, (pixelData.GetFormat() == Orthanc::PixelFormat_Grayscale32)); |
436 { | 423 { |
437 texture->SetCustomWindowing(data_.defaultWindowingCenter_, | 424 texture->SetCustomWindowing(data_.defaultWindowingCenter_, |
438 data_.defaultWindowingWidth_); | 425 data_.defaultWindowingWidth_); |
439 } | 426 } |
440 | 427 |
441 | 428 switch (data_.imageInformation_.GetPhotometricInterpretation()) |
442 if (data_.imageInformation_.GetPhotometricInterpretation() | 429 { |
443 == Orthanc::PhotometricInterpretation_Monochrome1) | 430 case Orthanc::PhotometricInterpretation_Monochrome1: |
444 { | 431 texture->SetInverted(true); |
445 texture->SetInverted(true); | 432 break; |
446 } | 433 |
447 else if (data_.imageInformation_.GetPhotometricInterpretation() | 434 case Orthanc::PhotometricInterpretation_Monochrome2: |
448 == Orthanc::PhotometricInterpretation_Monochrome2) | 435 texture->SetInverted(false); |
449 { | 436 break; |
450 texture->SetInverted(false); | 437 |
438 default: | |
439 break; | |
451 } | 440 } |
452 | 441 |
453 return texture.release(); | 442 return texture.release(); |
454 } | 443 } |
455 } | 444 } |