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 }