comparison Framework/Toolbox/DicomStructureSet.cpp @ 981:c20dbaab360c

Ability to cope with empty "Referenced SOP Instance UID" (dicom path (3006,0039)[i] / (0x3006, 0x0040)[0] / (0x3006, 0x0016)[0] / (0x0008, 0x1155)) + better logs + code formating
author Benjamin Golinvaux <bgo@osimis.io>
date Fri, 06 Sep 2019 09:38:18 +0200
parents 13e078adfb94
children 4c9b4c4de814
comparison
equal deleted inserted replaced
978:0e21ecafcc23 981:c20dbaab360c
23 23
24 #include "../Toolbox/GeometryToolbox.h" 24 #include "../Toolbox/GeometryToolbox.h"
25 25
26 #include <Core/Logging.h> 26 #include <Core/Logging.h>
27 #include <Core/OrthancException.h> 27 #include <Core/OrthancException.h>
28 #include <Core/Toolbox.h>
28 #include <Plugins/Samples/Common/FullOrthancDataset.h> 29 #include <Plugins/Samples/Common/FullOrthancDataset.h>
29 #include <Plugins/Samples/Common/DicomDatasetReader.h> 30 #include <Plugins/Samples/Common/DicomDatasetReader.h>
30 31
31 #include <limits> 32 #include <limits>
32 #include <stdio.h> 33 #include <stdio.h>
124 std::string value; 125 std::string value;
125 return (dataset.GetStringValue(value, tag) && 126 return (dataset.GetStringValue(value, tag) &&
126 LinearAlgebra::ParseVector(target, value)); 127 LinearAlgebra::ParseVector(target, value));
127 } 128 }
128 129
129 130 void DicomStructureSet::Polygon::CheckPointIsOnSlice(const Vector& v) const
130 void DicomStructureSet::Polygon::CheckPoint(const Vector& v)
131 { 131 {
132 if (hasSlice_) 132 if (hasSlice_)
133 { 133 {
134 if (!LinearAlgebra::IsNear(GeometryToolbox::ProjectAlongNormal(v, geometry_.GetNormal()), 134 double magnitude =
135 projectionAlongNormal_, 135 GeometryToolbox::ProjectAlongNormal(v, geometry_.GetNormal());
136 sliceThickness_ / 2.0 /* in mm */)) 136 if(!LinearAlgebra::IsNear(
137 { 137 magnitude,
138 LOG(ERROR) << "This RT-STRUCT contains a point that is off the slice of its instance"; 138 projectionAlongNormal_,
139 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); 139 sliceThickness_ / 2.0 /* in mm */ ))
140 } 140 {
141 } 141 LOG(ERROR) << "This RT-STRUCT contains a point that is off the "
142 } 142 << "slice of its instance | "
143 143 << "magnitude = " << magnitude << " | "
144 << "projectionAlongNormal_ = " << projectionAlongNormal_ << " | "
145 << "tolerance (sliceThickness_ / 2.0) = " << (sliceThickness_ / 2.0);
146
147 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
148 }
149 }
150 }
151
152 bool DicomStructureSet::Polygon::IsPointOnSlice(const Vector& v) const
153 {
154 if (hasSlice_)
155 {
156 double magnitude =
157 GeometryToolbox::ProjectAlongNormal(v, geometry_.GetNormal());
158 bool onSlice = LinearAlgebra::IsNear(
159 magnitude,
160 projectionAlongNormal_,
161 sliceThickness_ / 2.0 /* in mm */);
162 if (!onSlice)
163 {
164 LOG(WARNING) << "This RT-STRUCT contains a point that is off the "
165 << "slice of its instance | "
166 << "magnitude = " << magnitude << " | "
167 << "projectionAlongNormal_ = " << projectionAlongNormal_ << " | "
168 << "tolerance (sliceThickness_ / 2.0) = " << (sliceThickness_ / 2.0);
169 }
170 return onSlice;
171 }
172 else
173 {
174 return false;
175 }
176 }
144 177
145 void DicomStructureSet::Polygon::AddPoint(const Vector& v) 178 void DicomStructureSet::Polygon::AddPoint(const Vector& v)
146 { 179 {
180 #if 1
181 // BGO 2019-09-03
182 if (IsPointOnSlice(v))
183 {
184 points_.push_back(v);
185 }
186 #else
147 CheckPoint(v); 187 CheckPoint(v);
148 points_.push_back(v); 188 points_.push_back(v);
189 #endif
149 } 190 }
150 191
151 192
152 bool DicomStructureSet::Polygon::UpdateReferencedSlice(const ReferencedSlices& slices) 193 bool DicomStructureSet::Polygon::UpdateReferencedSlice(const ReferencedSlices& slices)
153 { 194 {
174 215
175 extent_.Reset(); 216 extent_.Reset();
176 217
177 for (Points::const_iterator it = points_.begin(); it != points_.end(); ++it) 218 for (Points::const_iterator it = points_.begin(); it != points_.end(); ++it)
178 { 219 {
179 CheckPoint(*it); 220 if (IsPointOnSlice(*it))
180 221 {
181 double x, y; 222 double x, y;
182 geometry.ProjectPoint(x, y, *it); 223 geometry.ProjectPoint(x, y, *it);
183 extent_.AddPoint(x, y); 224 extent_.AddPoint(x, y);
184 } 225 }
185 226 }
186 return true; 227 return true;
187 } 228 }
188 } 229 }
189 } 230 }
190 231
191
192 bool DicomStructureSet::Polygon::IsOnSlice(const CoordinateSystem3D& slice) const 232 bool DicomStructureSet::Polygon::IsOnSlice(const CoordinateSystem3D& slice) const
193 { 233 {
194 bool isOpposite; 234 bool isOpposite = false;
195 235
196 if (points_.empty() || 236 if (points_.empty() ||
197 !hasSlice_ || 237 !hasSlice_ ||
198 !GeometryToolbox::IsParallelOrOpposite(isOpposite, slice.GetNormal(), geometry_.GetNormal())) 238 !GeometryToolbox::IsParallelOrOpposite(isOpposite, slice.GetNormal(), geometry_.GetNormal()))
199 { 239 {
203 double d = GeometryToolbox::ProjectAlongNormal(slice.GetOrigin(), geometry_.GetNormal()); 243 double d = GeometryToolbox::ProjectAlongNormal(slice.GetOrigin(), geometry_.GetNormal());
204 244
205 return (LinearAlgebra::IsNear(d, projectionAlongNormal_, 245 return (LinearAlgebra::IsNear(d, projectionAlongNormal_,
206 sliceThickness_ / 2.0)); 246 sliceThickness_ / 2.0));
207 } 247 }
208 248
209
210 bool DicomStructureSet::Polygon::Project(double& x1, 249 bool DicomStructureSet::Polygon::Project(double& x1,
211 double& y1, 250 double& y1,
212 double& x2, 251 double& x2,
213 double& y2, 252 double& y2,
214 const CoordinateSystem3D& slice) const 253 const CoordinateSystem3D& slice) const
215 { 254 {
216 // TODO Optimize this method using a sweep-line algorithm for polygons 255 // TODO: optimize this method using a sweep-line algorithm for polygons
217 256
218 if (!hasSlice_ || 257 if (!hasSlice_ ||
219 points_.size() <= 1) 258 points_.size() <= 1)
220 { 259 {
221 return false; 260 return false;
430 469
431 OrthancPlugins::DicomPath imageSequencePath(DICOM_TAG_ROI_CONTOUR_SEQUENCE, i, 470 OrthancPlugins::DicomPath imageSequencePath(DICOM_TAG_ROI_CONTOUR_SEQUENCE, i,
432 DICOM_TAG_CONTOUR_SEQUENCE, 0, 471 DICOM_TAG_CONTOUR_SEQUENCE, 0,
433 DICOM_TAG_CONTOUR_IMAGE_SEQUENCE); 472 DICOM_TAG_CONTOUR_IMAGE_SEQUENCE);
434 473
474 // (3006,0039)[i] / (0x3006, 0x0040)[0] / (0x3006, 0x0016)[0] / (0x0008, 0x1155)
435 OrthancPlugins::DicomPath referencedInstancePath(DICOM_TAG_ROI_CONTOUR_SEQUENCE, i, 475 OrthancPlugins::DicomPath referencedInstancePath(DICOM_TAG_ROI_CONTOUR_SEQUENCE, i,
436 DICOM_TAG_CONTOUR_SEQUENCE, 0, 476 DICOM_TAG_CONTOUR_SEQUENCE, 0,
437 DICOM_TAG_CONTOUR_IMAGE_SEQUENCE, 0, 477 DICOM_TAG_CONTOUR_IMAGE_SEQUENCE, 0,
438 DICOM_TAG_REFERENCED_SOP_INSTANCE_UID); 478 DICOM_TAG_REFERENCED_SOP_INSTANCE_UID);
439 479
462 } 502 }
463 503
464 size_t size; 504 size_t size;
465 505
466 imageSequencePath.SetPrefixIndex(1, j); 506 imageSequencePath.SetPrefixIndex(1, j);
467 if (!tags.GetSequenceSize(size, imageSequencePath) || 507 if (!tags.GetSequenceSize(size, imageSequencePath) || size != 1)
468 size != 1) 508 {
469 { 509 LOG(ERROR) << "The ContourImageSequence sequence (tag 3006,0016) must be present and contain one entry.";
470 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); 510 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
471 } 511 }
472 512
473 referencedInstancePath.SetPrefixIndex(1, j); 513 referencedInstancePath.SetPrefixIndex(1, j);
474 std::string sopInstanceUid = reader.GetMandatoryStringValue(referencedInstancePath); 514 std::string sopInstanceUid = reader.GetMandatoryStringValue(referencedInstancePath);
479 Vector points; 519 Vector points;
480 if (!LinearAlgebra::ParseVector(points, slicesData) || 520 if (!LinearAlgebra::ParseVector(points, slicesData) ||
481 points.size() != 3 * countPoints) 521 points.size() != 3 * countPoints)
482 { 522 {
483 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); 523 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
524 }
525
526 // seen in real world
527 if(Orthanc::Toolbox::StripSpaces(sopInstanceUid) == "")
528 {
529 LOG(ERROR) << "WARNING. The following Dicom tag (Referenced SOP Instance UID) contains an empty value : // (3006,0039)[" << i << "] / (0x3006, 0x0040)[0] / (0x3006, 0x0016)[0] / (0x0008, 0x1155)";
484 } 530 }
485 531
486 Polygon polygon(sopInstanceUid); 532 Polygon polygon(sopInstanceUid);
487 polygon.Reserve(countPoints); 533 polygon.Reserve(countPoints);
488 534
579 { 625 {
580 if (referencedSlices_.find(sopInstanceUid) != referencedSlices_.end()) 626 if (referencedSlices_.find(sopInstanceUid) != referencedSlices_.end())
581 { 627 {
582 // This geometry is already known 628 // This geometry is already known
583 LOG(ERROR) << "DicomStructureSet::AddReferencedSlice(): (referencedSlices_.find(sopInstanceUid) != referencedSlices_.end()). sopInstanceUid = " << sopInstanceUid; 629 LOG(ERROR) << "DicomStructureSet::AddReferencedSlice(): (referencedSlices_.find(sopInstanceUid) != referencedSlices_.end()). sopInstanceUid = " << sopInstanceUid;
584 630
585 // TODO: the following assertion has been disabled on 20190822 by BGO 631 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
586 // because it occurred from time to time. Since it wrecked havoc on the
587
588 //throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
589 } 632 }
590 else 633 else
591 { 634 {
592 if (thickness < 0) 635 if (thickness < 0)
593 { 636 {
662 for (Polygons::iterator polygon = structure->polygons_.begin(); 705 for (Polygons::iterator polygon = structure->polygons_.begin();
663 polygon != structure->polygons_.end(); ++polygon) 706 polygon != structure->polygons_.end(); ++polygon)
664 { 707 {
665 if (!polygon->UpdateReferencedSlice(referencedSlices_)) 708 if (!polygon->UpdateReferencedSlice(referencedSlices_))
666 { 709 {
667 LOG(ERROR) << "DicomStructureSet::CheckReferencedSlices(): missing information about referenced instance: " 710 std::string sopInstanceUid = polygon->GetSopInstanceUid();
668 << polygon->GetSopInstanceUid(); 711 if (Orthanc::Toolbox::StripSpaces(sopInstanceUid) == "")
669 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); 712 {
713 LOG(ERROR) << "DicomStructureSet::CheckReferencedSlices(): "
714 << " missing information about referenced instance "
715 << "(sopInstanceUid is empty!)";
716 }
717 else
718 {
719 LOG(ERROR) << "DicomStructureSet::CheckReferencedSlices(): "
720 << " missing information about referenced instance "
721 << "(sopInstanceUid = " << sopInstanceUid << ")";
722 }
723 //throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
670 } 724 }
671 } 725 }
672 } 726 }
673 } 727 }
674 728
686 return referencedSlices_.begin()->second.geometry_.GetNormal(); 740 return referencedSlices_.begin()->second.geometry_.GetNormal();
687 } 741 }
688 } 742 }
689 743
690 744
691 bool DicomStructureSet::ProjectStructure(std::vector< std::vector<PolygonPoint> >& polygons, 745 bool DicomStructureSet::ProjectStructure(std::vector< std::vector<PolygonPoint2D> >& polygons,
692 const Structure& structure, 746 const Structure& structure,
693 const CoordinateSystem3D& slice) const 747 const CoordinateSystem3D& slice) const
694 { 748 {
695 polygons.clear(); 749 polygons.clear();
696 750
704 for (Polygons::const_iterator polygon = structure.polygons_.begin(); 758 for (Polygons::const_iterator polygon = structure.polygons_.begin();
705 polygon != structure.polygons_.end(); ++polygon) 759 polygon != structure.polygons_.end(); ++polygon)
706 { 760 {
707 if (polygon->IsOnSlice(slice)) 761 if (polygon->IsOnSlice(slice))
708 { 762 {
709 polygons.push_back(std::vector<PolygonPoint>()); 763 polygons.push_back(std::vector<PolygonPoint2D>());
710 764
711 for (Points::const_iterator p = polygon->GetPoints().begin(); 765 for (Points::const_iterator p = polygon->GetPoints().begin();
712 p != polygon->GetPoints().end(); ++p) 766 p != polygon->GetPoints().end(); ++p)
713 { 767 {
714 double x, y; 768 double x, y;
760 polygon != structure.polygons_.end(); ++polygon) 814 polygon != structure.polygons_.end(); ++polygon)
761 { 815 {
762 double x1, y1, x2, y2; 816 double x1, y1, x2, y2;
763 if (polygon->Project(x1, y1, x2, y2, slice)) 817 if (polygon->Project(x1, y1, x2, y2, slice))
764 { 818 {
765 std::vector<PolygonPoint> p(4); 819 std::vector<PolygonPoint2D> p(4);
766 p[0] = std::make_pair(x1, y1); 820 p[0] = std::make_pair(x1, y1);
767 p[1] = std::make_pair(x2, y1); 821 p[1] = std::make_pair(x2, y1);
768 p[2] = std::make_pair(x2, y2); 822 p[2] = std::make_pair(x2, y2);
769 p[3] = std::make_pair(x1, y2); 823 p[3] = std::make_pair(x1, y2);
770 polygons.push_back(p); 824 polygons.push_back(p);