Mercurial > hg > orthanc-stone
comparison Framework/Toolbox/DicomStructureSet.cpp @ 122:e3433dabfb8d wasm
refactoring DicomStructureSet
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Fri, 06 Oct 2017 17:25:08 +0200 |
parents | e66b2c757790 |
children | 44fc253d4876 |
comparison
equal
deleted
inserted
replaced
121:e66b2c757790 | 122:e3433dabfb8d |
---|---|
34 namespace OrthancStone | 34 namespace OrthancStone |
35 { | 35 { |
36 static const OrthancPlugins::DicomTag DICOM_TAG_CONTOUR_GEOMETRIC_TYPE(0x3006, 0x0042); | 36 static const OrthancPlugins::DicomTag DICOM_TAG_CONTOUR_GEOMETRIC_TYPE(0x3006, 0x0042); |
37 static const OrthancPlugins::DicomTag DICOM_TAG_CONTOUR_IMAGE_SEQUENCE(0x3006, 0x0016); | 37 static const OrthancPlugins::DicomTag DICOM_TAG_CONTOUR_IMAGE_SEQUENCE(0x3006, 0x0016); |
38 static const OrthancPlugins::DicomTag DICOM_TAG_CONTOUR_SEQUENCE(0x3006, 0x0040); | 38 static const OrthancPlugins::DicomTag DICOM_TAG_CONTOUR_SEQUENCE(0x3006, 0x0040); |
39 static const OrthancPlugins::DicomTag DICOM_TAG_CONTOUR_DATA(0x3006, 0x0050); | |
39 static const OrthancPlugins::DicomTag DICOM_TAG_NUMBER_OF_CONTOUR_POINTS(0x3006, 0x0046); | 40 static const OrthancPlugins::DicomTag DICOM_TAG_NUMBER_OF_CONTOUR_POINTS(0x3006, 0x0046); |
40 static const OrthancPlugins::DicomTag DICOM_TAG_REFERENCED_SOP_INSTANCE_UID(0x0008, 0x1155); | 41 static const OrthancPlugins::DicomTag DICOM_TAG_REFERENCED_SOP_INSTANCE_UID(0x0008, 0x1155); |
41 static const OrthancPlugins::DicomTag DICOM_TAG_ROI_CONTOUR_SEQUENCE(0x3006, 0x0039); | 42 static const OrthancPlugins::DicomTag DICOM_TAG_ROI_CONTOUR_SEQUENCE(0x3006, 0x0039); |
42 static const OrthancPlugins::DicomTag DICOM_TAG_ROI_DISPLAY_COLOR(0x3006, 0x002a); | 43 static const OrthancPlugins::DicomTag DICOM_TAG_ROI_DISPLAY_COLOR(0x3006, 0x002a); |
43 static const OrthancPlugins::DicomTag DICOM_TAG_ROI_NAME(0x3006, 0x0026); | 44 static const OrthancPlugins::DicomTag DICOM_TAG_ROI_NAME(0x3006, 0x0026); |
70 std::string value; | 71 std::string value; |
71 return (dataset.GetStringValue(value, tag) && | 72 return (dataset.GetStringValue(value, tag) && |
72 GeometryToolbox::ParseVector(target, value)); | 73 GeometryToolbox::ParseVector(target, value)); |
73 } | 74 } |
74 | 75 |
75 CoordinateSystem3D DicomStructureSet:: | 76 |
76 ExtractSliceGeometry(double& sliceThickness, | 77 void DicomStructureSet::Polygon::CheckPoint(const Vector& v) |
77 OrthancPlugins::IOrthancConnection& orthanc, | 78 { |
78 const OrthancPlugins::IDicomDataset& tags, | 79 if (hasSlice_) |
79 size_t contourIndex, | 80 { |
80 size_t sliceIndex) | 81 if (!GeometryToolbox::IsNear(GeometryToolbox::ProjectAlongNormal(v, normal_), |
82 projectionAlongNormal_, | |
83 sliceThickness_ / 2.0 /* in mm */)) | |
84 { | |
85 LOG(ERROR) << "This RT-STRUCT contains a point that is off the slice of its instance"; | |
86 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); | |
87 } | |
88 } | |
89 } | |
90 | |
91 | |
92 void DicomStructureSet::Polygon::AddPoint(const Vector& v) | |
93 { | |
94 CheckPoint(v); | |
95 points_.push_back(v); | |
96 } | |
97 | |
98 | |
99 bool DicomStructureSet::Polygon::UpdateReferencedSlice(const ReferencedSlices& slices) | |
100 { | |
101 if (hasSlice_) | |
102 { | |
103 return true; | |
104 } | |
105 else | |
106 { | |
107 ReferencedSlices::const_iterator it = slices.find(sopInstanceUid_); | |
108 | |
109 if (it == slices.end()) | |
110 { | |
111 return false; | |
112 } | |
113 else | |
114 { | |
115 const CoordinateSystem3D& geometry = it->second.geometry_; | |
116 | |
117 hasSlice_ = true; | |
118 normal_ = geometry.GetNormal(); | |
119 projectionAlongNormal_ = GeometryToolbox::ProjectAlongNormal(geometry.GetOrigin(), normal_); | |
120 sliceThickness_ = it->second.thickness_; | |
121 | |
122 for (Points::const_iterator it = points_.begin(); it != points_.end(); ++it) | |
123 { | |
124 CheckPoint(*it); | |
125 } | |
126 | |
127 return true; | |
128 } | |
129 } | |
130 } | |
131 | |
132 | |
133 bool DicomStructureSet::Polygon::IsOnSlice(const CoordinateSystem3D& slice) const | |
134 { | |
135 bool isOpposite; | |
136 | |
137 if (points_.empty() || | |
138 !hasSlice_ || | |
139 !GeometryToolbox::IsParallelOrOpposite(isOpposite, slice.GetNormal(), normal_)) | |
140 { | |
141 return false; | |
142 } | |
143 | |
144 double d = GeometryToolbox::ProjectAlongNormal(slice.GetOrigin(), normal_); | |
145 | |
146 return (GeometryToolbox::IsNear(d, projectionAlongNormal_, | |
147 sliceThickness_ / 2.0)); | |
148 } | |
149 | |
150 | |
151 const DicomStructureSet::Structure& DicomStructureSet::GetStructure(size_t index) const | |
152 { | |
153 if (index >= structures_.size()) | |
154 { | |
155 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); | |
156 } | |
157 | |
158 return structures_[index]; | |
159 } | |
160 | |
161 | |
162 DicomStructureSet::DicomStructureSet(const OrthancPlugins::FullOrthancDataset& tags) | |
81 { | 163 { |
82 using namespace OrthancPlugins; | 164 using namespace OrthancPlugins; |
83 | 165 |
84 size_t size; | |
85 if (!tags.GetSequenceSize(size, DicomPath(DICOM_TAG_ROI_CONTOUR_SEQUENCE, contourIndex, | |
86 DICOM_TAG_CONTOUR_SEQUENCE, sliceIndex, | |
87 DICOM_TAG_CONTOUR_IMAGE_SEQUENCE)) || | |
88 size != 1) | |
89 { | |
90 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); | |
91 } | |
92 | |
93 DicomDatasetReader reader(tags); | |
94 std::string parentUid = reader.GetMandatoryStringValue | |
95 (DicomPath(DICOM_TAG_ROI_CONTOUR_SEQUENCE, contourIndex, | |
96 DICOM_TAG_CONTOUR_SEQUENCE, sliceIndex, | |
97 DICOM_TAG_CONTOUR_IMAGE_SEQUENCE, 0, | |
98 DICOM_TAG_REFERENCED_SOP_INSTANCE_UID)); | |
99 | |
100 Json::Value parentLookup; | |
101 MessagingToolbox::RestApiPost(parentLookup, orthanc, "/tools/lookup", parentUid); | |
102 | |
103 if (parentLookup.type() != Json::arrayValue || | |
104 parentLookup.size() != 1 || | |
105 !parentLookup[0].isMember("Type") || | |
106 !parentLookup[0].isMember("Path") || | |
107 parentLookup[0]["Type"].type() != Json::stringValue || | |
108 parentLookup[0]["ID"].type() != Json::stringValue || | |
109 parentLookup[0]["Type"].asString() != "Instance") | |
110 { | |
111 throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource); | |
112 } | |
113 | |
114 Json::Value parentInstance; | |
115 MessagingToolbox::RestApiGet(parentInstance, orthanc, "/instances/" + parentLookup[0]["ID"].asString()); | |
116 | |
117 if (parentInstance.type() != Json::objectValue || | |
118 !parentInstance.isMember("ParentSeries") || | |
119 parentInstance["ParentSeries"].type() != Json::stringValue) | |
120 { | |
121 throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol); | |
122 } | |
123 | |
124 std::string parentSeriesId = parentInstance["ParentSeries"].asString(); | |
125 bool isFirst = parentSeriesId_.empty(); | |
126 | |
127 if (isFirst) | |
128 { | |
129 parentSeriesId_ = parentSeriesId; | |
130 } | |
131 else if (parentSeriesId_ != parentSeriesId) | |
132 { | |
133 LOG(ERROR) << "This RT-STRUCT refers to several different series"; | |
134 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); | |
135 } | |
136 | |
137 FullOrthancDataset parentTags(orthanc, "/instances/" + parentLookup[0]["ID"].asString() + "/tags"); | |
138 CoordinateSystem3D slice(parentTags); | |
139 | |
140 Vector v; | |
141 if (ParseVector(v, parentTags, DICOM_TAG_SLICE_THICKNESS) && | |
142 v.size() > 0) | |
143 { | |
144 sliceThickness = v[0]; | |
145 } | |
146 else | |
147 { | |
148 sliceThickness = 1; // 1 mm by default | |
149 } | |
150 | |
151 if (isFirst) | |
152 { | |
153 normal_ = slice.GetNormal(); | |
154 } | |
155 else if (!GeometryToolbox::IsParallel(normal_, slice.GetNormal())) | |
156 { | |
157 LOG(ERROR) << "Incompatible orientation of slices in this RT-STRUCT"; | |
158 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); | |
159 } | |
160 | |
161 return slice; | |
162 } | |
163 | |
164 | |
165 const DicomStructureSet::Structure& DicomStructureSet::GetStructure(size_t index) const | |
166 { | |
167 if (index >= structures_.size()) | |
168 { | |
169 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); | |
170 } | |
171 | |
172 return structures_[index]; | |
173 } | |
174 | |
175 | |
176 bool DicomStructureSet::IsPolygonOnSlice(const Polygon& polygon, | |
177 const CoordinateSystem3D& geometry) const | |
178 { | |
179 double d = boost::numeric::ublas::inner_prod(geometry.GetOrigin(), normal_); | |
180 | |
181 return (GeometryToolbox::IsNear(d, polygon.projectionAlongNormal_, polygon.sliceThickness_ / 2.0) && | |
182 !polygon.points_.empty()); | |
183 } | |
184 | |
185 | |
186 DicomStructureSet::DicomStructureSet(OrthancPlugins::IOrthancConnection& orthanc, | |
187 const std::string& instanceId) | |
188 { | |
189 using namespace OrthancPlugins; | |
190 | |
191 FullOrthancDataset tags(orthanc, "/instances/" + instanceId + "/tags"); | |
192 DicomDatasetReader reader(tags); | 166 DicomDatasetReader reader(tags); |
193 | 167 |
194 size_t count, tmp; | 168 size_t count, tmp; |
195 if (!tags.GetSequenceSize(count, DICOM_TAG_RT_ROI_OBSERVATIONS_SEQUENCE) || | 169 if (!tags.GetSequenceSize(count, DICOM_TAG_RT_ROI_OBSERVATIONS_SEQUENCE) || |
196 !tags.GetSequenceSize(tmp, DICOM_TAG_ROI_CONTOUR_SEQUENCE) || | 170 !tags.GetSequenceSize(tmp, DICOM_TAG_ROI_CONTOUR_SEQUENCE) || |
202 } | 176 } |
203 | 177 |
204 structures_.resize(count); | 178 structures_.resize(count); |
205 for (size_t i = 0; i < count; i++) | 179 for (size_t i = 0; i < count; i++) |
206 { | 180 { |
207 structures_[i].interpretation_ = reader.GetStringValue(DicomPath(DICOM_TAG_RT_ROI_OBSERVATIONS_SEQUENCE, i, | 181 structures_[i].interpretation_ = reader.GetStringValue |
208 DICOM_TAG_RT_ROI_INTERPRETED_TYPE), | 182 (DicomPath(DICOM_TAG_RT_ROI_OBSERVATIONS_SEQUENCE, i, |
209 "No interpretation"); | 183 DICOM_TAG_RT_ROI_INTERPRETED_TYPE), |
210 | 184 "No interpretation"); |
211 structures_[i].name_ = reader.GetStringValue(DicomPath(DICOM_TAG_STRUCTURE_SET_ROI_SEQUENCE, i, | 185 |
212 DICOM_TAG_ROI_NAME), | 186 structures_[i].name_ = reader.GetStringValue |
213 "No interpretation"); | 187 (DicomPath(DICOM_TAG_STRUCTURE_SET_ROI_SEQUENCE, i, |
188 DICOM_TAG_ROI_NAME), | |
189 "No interpretation"); | |
214 | 190 |
215 Vector color; | 191 Vector color; |
216 if (ParseVector(color, tags, DicomPath(DICOM_TAG_ROI_CONTOUR_SEQUENCE, i, | 192 if (ParseVector(color, tags, DicomPath(DICOM_TAG_ROI_CONTOUR_SEQUENCE, i, |
217 DICOM_TAG_ROI_DISPLAY_COLOR)) && | 193 DICOM_TAG_ROI_DISPLAY_COLOR)) && |
218 color.size() == 3) | 194 color.size() == 3) |
244 | 220 |
245 for (size_t j = 0; j < countSlices; j++) | 221 for (size_t j = 0; j < countSlices; j++) |
246 { | 222 { |
247 unsigned int countPoints; | 223 unsigned int countPoints; |
248 | 224 |
249 if (!reader.GetUnsignedIntegerValue(countPoints, DicomPath(DICOM_TAG_ROI_CONTOUR_SEQUENCE, i, | 225 if (!reader.GetUnsignedIntegerValue |
250 DICOM_TAG_CONTOUR_SEQUENCE, j, | 226 (countPoints, DicomPath(DICOM_TAG_ROI_CONTOUR_SEQUENCE, i, |
251 DICOM_TAG_NUMBER_OF_CONTOUR_POINTS))) | 227 DICOM_TAG_CONTOUR_SEQUENCE, j, |
228 DICOM_TAG_NUMBER_OF_CONTOUR_POINTS))) | |
252 { | 229 { |
253 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); | 230 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); |
254 } | 231 } |
255 | 232 |
256 //LOG(INFO) << "Parsing slice containing " << countPoints << " vertices"; | 233 //LOG(INFO) << "Parsing slice containing " << countPoints << " vertices"; |
257 | 234 |
258 std::string type = reader.GetMandatoryStringValue(DicomPath(DICOM_TAG_ROI_CONTOUR_SEQUENCE, i, | 235 std::string type = reader.GetMandatoryStringValue |
259 DICOM_TAG_CONTOUR_SEQUENCE, j, | 236 (DicomPath(DICOM_TAG_ROI_CONTOUR_SEQUENCE, i, |
260 DICOM_TAG_CONTOUR_GEOMETRIC_TYPE)); | 237 DICOM_TAG_CONTOUR_SEQUENCE, j, |
238 DICOM_TAG_CONTOUR_GEOMETRIC_TYPE)); | |
261 if (type != "CLOSED_PLANAR") | 239 if (type != "CLOSED_PLANAR") |
262 { | 240 { |
263 LOG(ERROR) << "Cannot handle contour with geometry type: " << type; | 241 LOG(ERROR) << "Cannot handle contour with geometry type: " << type; |
264 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); | 242 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); |
265 } | 243 } |
266 | 244 |
267 // The "CountourData" tag (3006,0050) is too large to be | 245 size_t size; |
268 // returned by the "/instances/{id}/tags" URI: Access it using | 246 if (!tags.GetSequenceSize(size, DicomPath(DICOM_TAG_ROI_CONTOUR_SEQUENCE, i, |
269 // the raw "/instances/{id}/content/{...}" endpoint | 247 DICOM_TAG_CONTOUR_SEQUENCE, j, |
270 std::string slicesData; | 248 DICOM_TAG_CONTOUR_IMAGE_SEQUENCE)) || |
271 orthanc.RestApiGet(slicesData, "/instances/" + instanceId + "/content/3006-0039/" + | 249 size != 1) |
272 boost::lexical_cast<std::string>(i) + "/3006-0040/" + | 250 { |
273 boost::lexical_cast<std::string>(j) + "/3006-0050"); | 251 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); |
252 } | |
253 | |
254 std::string sopInstanceUid = reader.GetMandatoryStringValue | |
255 (DicomPath(DICOM_TAG_ROI_CONTOUR_SEQUENCE, i, | |
256 DICOM_TAG_CONTOUR_SEQUENCE, j, | |
257 DICOM_TAG_CONTOUR_IMAGE_SEQUENCE, 0, | |
258 DICOM_TAG_REFERENCED_SOP_INSTANCE_UID)); | |
259 | |
260 std::string slicesData = reader.GetMandatoryStringValue | |
261 (DicomPath(DICOM_TAG_ROI_CONTOUR_SEQUENCE, i, | |
262 DICOM_TAG_CONTOUR_SEQUENCE, j, | |
263 DICOM_TAG_CONTOUR_DATA)); | |
274 | 264 |
275 Vector points; | 265 Vector points; |
276 if (!GeometryToolbox::ParseVector(points, slicesData) || | 266 if (!GeometryToolbox::ParseVector(points, slicesData) || |
277 points.size() != 3 * countPoints) | 267 points.size() != 3 * countPoints) |
278 { | 268 { |
279 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); | 269 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); |
280 } | 270 } |
281 | 271 |
282 Polygon polygon; | 272 Polygon polygon(sopInstanceUid); |
283 CoordinateSystem3D geometry = ExtractSliceGeometry(polygon.sliceThickness_, orthanc, tags, i, j); | |
284 polygon.projectionAlongNormal_ = geometry.ProjectAlongNormal(geometry.GetOrigin()); | |
285 | 273 |
286 for (size_t k = 0; k < countPoints; k++) | 274 for (size_t k = 0; k < countPoints; k++) |
287 { | 275 { |
288 Vector v(3); | 276 Vector v(3); |
289 v[0] = points[3 * k]; | 277 v[0] = points[3 * k]; |
290 v[1] = points[3 * k + 1]; | 278 v[1] = points[3 * k + 1]; |
291 v[2] = points[3 * k + 2]; | 279 v[2] = points[3 * k + 2]; |
292 | 280 polygon.AddPoint(v); |
293 if (!GeometryToolbox::IsNear(geometry.ProjectAlongNormal(v), | |
294 polygon.projectionAlongNormal_, | |
295 polygon.sliceThickness_ / 2.0 /* in mm */)) | |
296 { | |
297 LOG(ERROR) << "This RT-STRUCT contains a point that is off the slice of its instance"; | |
298 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); | |
299 } | |
300 | |
301 polygon.points_.push_back(v); | |
302 } | 281 } |
303 | 282 |
304 structures_[i].polygons_.push_back(polygon); | 283 structures_[i].polygons_.push_back(polygon); |
305 } | 284 } |
306 } | |
307 | |
308 if (parentSeriesId_.empty() || | |
309 normal_.size() != 3) | |
310 { | |
311 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); | |
312 } | 285 } |
313 } | 286 } |
314 | 287 |
315 | 288 |
316 Vector DicomStructureSet::GetStructureCenter(size_t index) const | 289 Vector DicomStructureSet::GetStructureCenter(size_t index) const |
327 double n = static_cast<double>(structure.polygons_.size()); | 300 double n = static_cast<double>(structure.polygons_.size()); |
328 | 301 |
329 for (Polygons::const_iterator polygon = structure.polygons_.begin(); | 302 for (Polygons::const_iterator polygon = structure.polygons_.begin(); |
330 polygon != structure.polygons_.end(); ++polygon) | 303 polygon != structure.polygons_.end(); ++polygon) |
331 { | 304 { |
332 if (!polygon->points_.empty()) | 305 if (!polygon->GetPoints().empty()) |
333 { | 306 { |
334 center += polygon->points_.front() / n; | 307 center += polygon->GetPoints().front() / n; |
335 } | 308 } |
336 } | 309 } |
337 | 310 |
338 return center; | 311 return center; |
339 } | 312 } |
362 blue = s.blue_; | 335 blue = s.blue_; |
363 } | 336 } |
364 | 337 |
365 | 338 |
366 void DicomStructureSet::Render(CairoContext& context, | 339 void DicomStructureSet::Render(CairoContext& context, |
367 const CoordinateSystem3D& slice) const | 340 const CoordinateSystem3D& slice) |
368 { | 341 { |
369 cairo_t* cr = context.GetObject(); | 342 cairo_t* cr = context.GetObject(); |
370 | 343 |
371 for (Structures::const_iterator structure = structures_.begin(); | 344 for (Structures::iterator structure = structures_.begin(); |
372 structure != structures_.end(); ++structure) | 345 structure != structures_.end(); ++structure) |
373 { | 346 { |
374 if (structure->name_ != "SKIN" && | 347 for (Polygons::iterator polygon = structure->polygons_.begin(); |
375 structure->name_ != "HEART" && | |
376 //structure->name_ != "CORD" && | |
377 structure->name_ != "ESOPHAGUS" && | |
378 structure->name_ != "LUNG_LT" && | |
379 structure->name_ != "LUNG_RT" && | |
380 structure->name_ != "GTV_EXH_PRIMARY" && | |
381 structure->name_ != "GTV_INH_PRIMARY" && | |
382 structure->name_ != "GTV_PRIMARY") | |
383 { | |
384 continue; | |
385 } | |
386 | |
387 for (Polygons::const_iterator polygon = structure->polygons_.begin(); | |
388 polygon != structure->polygons_.end(); ++polygon) | 348 polygon != structure->polygons_.end(); ++polygon) |
389 { | 349 { |
390 if (IsPolygonOnSlice(*polygon, slice)) | 350 polygon->UpdateReferencedSlice(referencedSlices_); |
351 | |
352 if (polygon->IsOnSlice(slice)) | |
391 { | 353 { |
392 context.SetSourceColor(structure->red_, structure->green_, structure->blue_); | 354 context.SetSourceColor(structure->red_, structure->green_, structure->blue_); |
393 | 355 |
394 Points::const_iterator p = polygon->points_.begin(); | 356 Points::const_iterator p = polygon->GetPoints().begin(); |
395 | 357 |
396 double x, y; | 358 double x, y; |
397 slice.ProjectPoint(x, y, *p); | 359 slice.ProjectPoint(x, y, *p); |
398 cairo_move_to(cr, x, y); | 360 cairo_move_to(cr, x, y); |
399 ++p; | 361 ++p; |
400 | 362 |
401 while (p != polygon->points_.end()) | 363 while (p != polygon->GetPoints().end()) |
402 { | 364 { |
403 slice.ProjectPoint(x, y, *p); | 365 slice.ProjectPoint(x, y, *p); |
404 cairo_line_to(cr, x, y); | 366 cairo_line_to(cr, x, y); |
405 ++p; | 367 ++p; |
406 } | 368 } |
407 | 369 |
408 slice.ProjectPoint(x, y, *polygon->points_.begin()); | 370 slice.ProjectPoint(x, y, *polygon->GetPoints().begin()); |
409 cairo_line_to(cr, x, y); | 371 cairo_line_to(cr, x, y); |
410 | 372 |
411 cairo_stroke(cr); | 373 cairo_stroke(cr); |
412 } | 374 } |
413 } | 375 } |
414 } | 376 } |
415 } | 377 } |
378 | |
379 | |
380 void DicomStructureSet::GetReferencedInstances(std::set<std::string>& instances) | |
381 { | |
382 for (Structures::const_iterator structure = structures_.begin(); | |
383 structure != structures_.end(); ++structure) | |
384 { | |
385 for (Polygons::const_iterator polygon = structure->polygons_.begin(); | |
386 polygon != structure->polygons_.end(); ++polygon) | |
387 { | |
388 instances.insert(polygon->GetSopInstanceUid()); | |
389 } | |
390 } | |
391 } | |
392 | |
393 | |
394 void DicomStructureSet::AddReferencedSlice(const std::string& sopInstanceUid, | |
395 const std::string& seriesInstanceUid, | |
396 const CoordinateSystem3D& geometry, | |
397 double thickness) | |
398 { | |
399 if (referencedSlices_.find(sopInstanceUid) != referencedSlices_.end()) | |
400 { | |
401 // This geometry is already known | |
402 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); | |
403 } | |
404 else | |
405 { | |
406 if (thickness < 0) | |
407 { | |
408 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); | |
409 } | |
410 | |
411 if (!referencedSlices_.empty()) | |
412 { | |
413 const ReferencedSlice& reference = referencedSlices_.begin()->second; | |
414 | |
415 if (reference.seriesInstanceUid_ != seriesInstanceUid) | |
416 { | |
417 LOG(ERROR) << "This RT-STRUCT refers to several different series"; | |
418 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); | |
419 } | |
420 | |
421 if (!GeometryToolbox::IsParallel(reference.geometry_.GetNormal(), geometry.GetNormal())) | |
422 { | |
423 LOG(ERROR) << "The slices in this RT-STRUCT are not parallel"; | |
424 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); | |
425 } | |
426 } | |
427 | |
428 referencedSlices_[sopInstanceUid] = ReferencedSlice(seriesInstanceUid, geometry, thickness); | |
429 } | |
430 } | |
431 | |
432 | |
433 void DicomStructureSet::AddReferencedSlice(const Orthanc::DicomMap& dataset) | |
434 { | |
435 CoordinateSystem3D slice(dataset); | |
436 | |
437 double thickness = 1; // 1 mm by default | |
438 | |
439 std::string s; | |
440 Vector v; | |
441 if (dataset.CopyToString(s, Orthanc::DICOM_TAG_SLICE_THICKNESS, false) && | |
442 GeometryToolbox::ParseVector(v, s) && | |
443 v.size() > 0) | |
444 { | |
445 thickness = v[0]; | |
446 } | |
447 | |
448 std::string instance, series; | |
449 if (dataset.CopyToString(instance, Orthanc::DICOM_TAG_SOP_INSTANCE_UID, false) && | |
450 dataset.CopyToString(series, Orthanc::DICOM_TAG_SERIES_INSTANCE_UID, false)) | |
451 { | |
452 AddReferencedSlice(instance, series, slice, thickness); | |
453 } | |
454 else | |
455 { | |
456 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); | |
457 } | |
458 } | |
459 | |
460 | |
461 void DicomStructureSet::CheckReferencedSlices() | |
462 { | |
463 for (Structures::iterator structure = structures_.begin(); | |
464 structure != structures_.end(); ++structure) | |
465 { | |
466 for (Polygons::iterator polygon = structure->polygons_.begin(); | |
467 polygon != structure->polygons_.end(); ++polygon) | |
468 { | |
469 if (!polygon->UpdateReferencedSlice(referencedSlices_)) | |
470 { | |
471 LOG(ERROR) << "Missing information about referenced instance: " | |
472 << polygon->GetSopInstanceUid(); | |
473 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); | |
474 } | |
475 } | |
476 } | |
477 } | |
478 | |
479 | |
480 Vector DicomStructureSet::GetNormal() const | |
481 { | |
482 if (!referencedSlices_.empty()) | |
483 { | |
484 Vector v; | |
485 GeometryToolbox::AssignVector(v, 0, 0, 1); | |
486 return v; | |
487 } | |
488 else | |
489 { | |
490 return referencedSlices_.begin()->second.geometry_.GetNormal(); | |
491 } | |
492 } | |
493 | |
494 | |
495 DicomStructureSet* DicomStructureSet::SynchronousLoad(OrthancPlugins::IOrthancConnection& orthanc, | |
496 const std::string& instanceId) | |
497 { | |
498 const std::string uri = "/instances/" + instanceId + "/tags?ignore-length=3006-0050"; | |
499 OrthancPlugins::FullOrthancDataset dataset(orthanc, uri); | |
500 | |
501 std::auto_ptr<DicomStructureSet> result(new DicomStructureSet(dataset)); | |
502 | |
503 std::set<std::string> instances; | |
504 result->GetReferencedInstances(instances); | |
505 | |
506 for (std::set<std::string>::const_iterator it = instances.begin(); | |
507 it != instances.end(); ++it) | |
508 { | |
509 Json::Value lookup; | |
510 MessagingToolbox::RestApiPost(lookup, orthanc, "/tools/lookup", *it); | |
511 | |
512 if (lookup.type() != Json::arrayValue || | |
513 lookup.size() != 1 || | |
514 !lookup[0].isMember("Type") || | |
515 !lookup[0].isMember("Path") || | |
516 lookup[0]["Type"].type() != Json::stringValue || | |
517 lookup[0]["ID"].type() != Json::stringValue || | |
518 lookup[0]["Type"].asString() != "Instance") | |
519 { | |
520 throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource); | |
521 } | |
522 | |
523 OrthancPlugins::FullOrthancDataset slice | |
524 (orthanc, "/instances/" + lookup[0]["ID"].asString() + "/tags"); | |
525 Orthanc::DicomMap m; | |
526 MessagingToolbox::ConvertDataset(m, slice); | |
527 result->AddReferencedSlice(m); | |
528 } | |
529 | |
530 result->CheckReferencedSlices(); | |
531 | |
532 return result.release(); | |
533 } | |
534 | |
416 } | 535 } |