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 }