comparison Framework/Toolbox/DicomStructureSet.cpp @ 32:517c46f527cd

sync
author Sebastien Jodogne <s.jodogne@gmail.com>
date Mon, 19 Dec 2016 11:00:23 +0100
parents 9aace933cb64
children a865c7992a87
comparison
equal deleted inserted replaced
31:9aace933cb64 32:517c46f527cd
32 32
33 #include "DicomStructureSet.h" 33 #include "DicomStructureSet.h"
34 34
35 #include "../../Resources/Orthanc/Core/Logging.h" 35 #include "../../Resources/Orthanc/Core/Logging.h"
36 #include "../../Resources/Orthanc/Core/OrthancException.h" 36 #include "../../Resources/Orthanc/Core/OrthancException.h"
37 #include "../../Resources/Orthanc/Plugins/Samples/Common/FullOrthancDataset.h"
37 #include "../Messaging/MessagingToolbox.h" 38 #include "../Messaging/MessagingToolbox.h"
38 39
39 #include <stdio.h> 40 #include <stdio.h>
40 #include <boost/lexical_cast.hpp> 41 #include <boost/lexical_cast.hpp>
41 42
42 namespace OrthancStone 43 namespace OrthancStone
43 { 44 {
44 static const Json::Value& GetSequence(const Json::Value& instance, 45 static const OrthancPlugins::DicomTag DICOM_TAG_CONTOUR_GEOMETRIC_TYPE(0x3006, 0x0042);
45 uint16_t group, 46 static const OrthancPlugins::DicomTag DICOM_TAG_CONTOUR_IMAGE_SEQUENCE(0x3006, 0x0016);
46 uint16_t element) 47 static const OrthancPlugins::DicomTag DICOM_TAG_CONTOUR_SEQUENCE(0x3006, 0x0040);
47 { 48 static const OrthancPlugins::DicomTag DICOM_TAG_NUMBER_OF_CONTOUR_POINTS(0x3006, 0x0046);
48 char buf[16]; 49 static const OrthancPlugins::DicomTag DICOM_TAG_REFERENCED_SOP_INSTANCE_UID(0x0008, 0x1155);
49 sprintf(buf, "%04x,%04x", group, element); 50 static const OrthancPlugins::DicomTag DICOM_TAG_ROI_CONTOUR_SEQUENCE(0x3006, 0x0039);
50 51 static const OrthancPlugins::DicomTag DICOM_TAG_ROI_DISPLAY_COLOR(0x3006, 0x002a);
51 if (instance.type() != Json::objectValue || 52 static const OrthancPlugins::DicomTag DICOM_TAG_ROI_NAME(0x3006, 0x0026);
52 !instance.isMember(buf) || 53 static const OrthancPlugins::DicomTag DICOM_TAG_RT_ROI_INTERPRETED_TYPE(0x3006, 0x00a4);
53 instance[buf].type() != Json::objectValue || 54 static const OrthancPlugins::DicomTag DICOM_TAG_RT_ROI_OBSERVATIONS_SEQUENCE(0x3006, 0x0080);
54 !instance[buf].isMember("Type") || 55 static const OrthancPlugins::DicomTag DICOM_TAG_STRUCTURE_SET_ROI_SEQUENCE(0x3006, 0x0020);
55 !instance[buf].isMember("Value") ||
56 instance[buf]["Type"].type() != Json::stringValue ||
57 instance[buf]["Value"].type() != Json::arrayValue ||
58 instance[buf]["Type"].asString() != "Sequence")
59 {
60 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
61 }
62
63 return instance[buf]["Value"];
64 }
65 56
66 57
67 static uint8_t ConvertColor(double v) 58 static uint8_t ConvertColor(double v)
68 { 59 {
69 if (v < 0) 60 if (v < 0)
81 } 72 }
82 73
83 74
84 SliceGeometry DicomStructureSet::ExtractSliceGeometry(double& sliceThickness, 75 SliceGeometry DicomStructureSet::ExtractSliceGeometry(double& sliceThickness,
85 OrthancPlugins::IOrthancConnection& orthanc, 76 OrthancPlugins::IOrthancConnection& orthanc,
86 const Json::Value& contour) 77 const OrthancPlugins::IDicomDataset& tags,
87 { 78 size_t contourIndex,
88 const Json::Value& sequence = GetSequence(contour, 0x3006, 0x0016); 79 size_t sliceIndex)
89 80 {
90 if (sequence.size() != 1) 81 using namespace OrthancPlugins;
82
83 size_t size;
84 if (!tags.GetSequenceSize(size, DicomPath(DICOM_TAG_ROI_CONTOUR_SEQUENCE, contourIndex,
85 DICOM_TAG_CONTOUR_SEQUENCE, sliceIndex,
86 DICOM_TAG_CONTOUR_IMAGE_SEQUENCE)) ||
87 size != 1)
91 { 88 {
92 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); 89 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
93 } 90 }
94 91
95 DicomDataset contourImageSequence(sequence[0]); 92 DicomDatasetReader reader(tags);
96 std::string parentUid = contourImageSequence.GetStringValue(std::make_pair(0x0008, 0x1155)); 93 std::string parentUid = reader.GetMandatoryStringValue(DicomPath(DICOM_TAG_ROI_CONTOUR_SEQUENCE, contourIndex,
94 DICOM_TAG_CONTOUR_SEQUENCE, sliceIndex,
95 DICOM_TAG_CONTOUR_IMAGE_SEQUENCE, 0,
96 DICOM_TAG_REFERENCED_SOP_INSTANCE_UID));
97 97
98 std::string post; 98 std::string post;
99 orthanc.RestApiPost(post, "/tools/lookup", parentUid); 99 orthanc.RestApiPost(post, "/tools/lookup", parentUid);
100 100
101 Json::Value tmp; 101 Json::Value tmp;
133 { 133 {
134 LOG(ERROR) << "This RT-STRUCT refers to several different series"; 134 LOG(ERROR) << "This RT-STRUCT refers to several different series";
135 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); 135 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
136 } 136 }
137 137
138 Json::Value parentTags; 138 FullOrthancDataset parentTags(orthanc, "/instances/" + tmp[0]["ID"].asString() + "/tags");
139 MessagingToolbox::RestApiGet(parentTags, orthanc, "/instances/" + tmp[0]["ID"].asString() + "/tags?simplify"); 139 SliceGeometry slice(parentTags);
140 140
141 if (parentTags.type() != Json::objectValue || 141 Vector v;
142 !parentTags.isMember("ImageOrientationPatient") || 142 if (GeometryToolbox::ParseVector(v, parentTags, DICOM_TAG_SLICE_THICKNESS) &&
143 !parentTags.isMember("ImagePositionPatient") || 143 v.size() > 0)
144 parentTags["ImageOrientationPatient"].type() != Json::stringValue || 144 {
145 parentTags["ImagePositionPatient"].type() != Json::stringValue) 145 sliceThickness = v[0];
146 { 146 }
147 throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol); 147 else
148 } 148 {
149 149 sliceThickness = 1; // 1 mm by default
150 SliceGeometry slice(parentTags["ImagePositionPatient"].asString(),
151 parentTags["ImageOrientationPatient"].asString());
152
153 sliceThickness = 1; // 1 mm by default
154
155 if (parentTags.isMember("SliceThickness") &&
156 parentTags["SliceThickness"].type() == Json::stringValue)
157 {
158 Vector tmp;
159 GeometryToolbox::ParseVector(tmp, parentTags["SliceThickness"].asString());
160 if (tmp.size() > 0)
161 {
162 sliceThickness = tmp[0];
163 }
164 } 150 }
165 151
166 if (isFirst) 152 if (isFirst)
167 { 153 {
168 normal_ = slice.GetNormal(); 154 normal_ = slice.GetNormal();
199 185
200 186
201 DicomStructureSet::DicomStructureSet(OrthancPlugins::IOrthancConnection& orthanc, 187 DicomStructureSet::DicomStructureSet(OrthancPlugins::IOrthancConnection& orthanc,
202 const std::string& instanceId) 188 const std::string& instanceId)
203 { 189 {
204 Json::Value instance; 190 using namespace OrthancPlugins;
205 MessagingToolbox::RestApiGet(instance, orthanc, "/instances/" + instanceId + "/tags"); 191
206 192 FullOrthancDataset tags(orthanc, "/instances/" + instanceId + "/tags");
207 Json::Value rtRoiObservationSequence = GetSequence(instance, 0x3006, 0x0080); 193 DicomDatasetReader reader(tags);
208 Json::Value roiContourSequence = GetSequence(instance, 0x3006, 0x0039); 194
209 Json::Value structureSetRoiSequence = GetSequence(instance, 0x3006, 0x0020); 195 size_t count, tmp;
210 196 if (!tags.GetSequenceSize(count, DICOM_TAG_RT_ROI_OBSERVATIONS_SEQUENCE) ||
211 Json::Value::ArrayIndex count = rtRoiObservationSequence.size(); 197 !tags.GetSequenceSize(tmp, DICOM_TAG_ROI_CONTOUR_SEQUENCE) ||
212 if (count != roiContourSequence.size() || 198 tmp != count ||
213 count != structureSetRoiSequence.size()) 199 !tags.GetSequenceSize(tmp, DICOM_TAG_STRUCTURE_SET_ROI_SEQUENCE) ||
200 tmp != count)
214 { 201 {
215 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); 202 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
216 } 203 }
217 204
218 structures_.resize(count); 205 structures_.resize(count);
219 for (Json::Value::ArrayIndex i = 0; i < count; i++) 206 for (size_t i = 0; i < count; i++)
220 { 207 {
221 DicomDataset observation(rtRoiObservationSequence[i]); 208 structures_[i].interpretation_ = reader.GetStringValue(DicomPath(DICOM_TAG_RT_ROI_OBSERVATIONS_SEQUENCE, i,
222 DicomDataset roi(structureSetRoiSequence[i]); 209 DICOM_TAG_RT_ROI_INTERPRETED_TYPE),
223 DicomDataset content(roiContourSequence[i]); 210 "No interpretation");
224 211
225 structures_[i].interpretation_ = observation.GetStringValue(std::make_pair(0x3006, 0x00a4), "No interpretation"); 212 structures_[i].name_ = reader.GetStringValue(DicomPath(DICOM_TAG_STRUCTURE_SET_ROI_SEQUENCE, i,
226 structures_[i].name_ = roi.GetStringValue(std::make_pair(0x3006, 0x0026), "No name"); 213 DICOM_TAG_ROI_NAME),
227 structures_[i].red_ = 255; 214 "No interpretation");
228 structures_[i].green_ = 0;
229 structures_[i].blue_ = 0;
230
231 DicomDataset::Tag tag(0x3006, 0x002a);
232 215
233 Vector color; 216 Vector color;
234 if (content.HasTag(tag)) 217 if (GeometryToolbox::ParseVector(color, tags, DicomPath(DICOM_TAG_ROI_CONTOUR_SEQUENCE, i,
235 { 218 DICOM_TAG_ROI_DISPLAY_COLOR)) &&
236 content.GetVectorValue(color, tag); 219 color.size() == 3)
237 if (color.size() == 3) 220 {
238 { 221 structures_[i].red_ = ConvertColor(color[0]);
239 structures_[i].red_ = ConvertColor(color[0]); 222 structures_[i].green_ = ConvertColor(color[1]);
240 structures_[i].green_ = ConvertColor(color[1]); 223 structures_[i].blue_ = ConvertColor(color[2]);
241 structures_[i].blue_ = ConvertColor(color[2]); 224 }
242 } 225 else
243 } 226 {
244 227 structures_[i].red_ = 255;
245 const Json::Value& slices = GetSequence(roiContourSequence[i], 0x3006, 0x0040); 228 structures_[i].green_ = 0;
229 structures_[i].blue_ = 0;
230 }
231
232 size_t countSlices;
233 if (!tags.GetSequenceSize(countSlices, DicomPath(DICOM_TAG_ROI_CONTOUR_SEQUENCE, i,
234 DICOM_TAG_CONTOUR_SEQUENCE)))
235 {
236 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
237 }
246 238
247 LOG(WARNING) << "New RT structure: \"" << structures_[i].name_ 239 LOG(WARNING) << "New RT structure: \"" << structures_[i].name_
248 << "\" with interpretation \"" << structures_[i].interpretation_ 240 << "\" with interpretation \"" << structures_[i].interpretation_
249 << "\" containing " << slices.size() << " slices (color: " 241 << "\" containing " << countSlices << " slices (color: "
250 << static_cast<int>(structures_[i].red_) << "," 242 << static_cast<int>(structures_[i].red_) << ","
251 << static_cast<int>(structures_[i].green_) << "," 243 << static_cast<int>(structures_[i].green_) << ","
252 << static_cast<int>(structures_[i].blue_) << ")"; 244 << static_cast<int>(structures_[i].blue_) << ")";
253 245
254 for (Json::Value::ArrayIndex j = 0; j < slices.size(); j++) 246 for (size_t j = 0; j < countSlices; j++)
255 { 247 {
256 DicomDataset slice(slices[j]); 248 unsigned int countPoints;
257 249
258 unsigned int npoints = slice.GetUnsignedIntegerValue(std::make_pair(0x3006, 0x0046)); 250 if (!reader.GetUnsignedIntegerValue(countPoints, DicomPath(DICOM_TAG_ROI_CONTOUR_SEQUENCE, i,
259 LOG(INFO) << "Parsing slice containing " << npoints << " vertices"; 251 DICOM_TAG_CONTOUR_SEQUENCE, j,
260 252 DICOM_TAG_NUMBER_OF_CONTOUR_POINTS)))
261 if (slice.GetStringValue(std::make_pair(0x3006, 0x0042)) != "CLOSED_PLANAR")
262 { 253 {
254 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
255 }
256
257 LOG(INFO) << "Parsing slice containing " << countPoints << " vertices";
258
259 std::string type = reader.GetMandatoryStringValue(DicomPath(DICOM_TAG_ROI_CONTOUR_SEQUENCE, i,
260 DICOM_TAG_CONTOUR_SEQUENCE, j,
261 DICOM_TAG_CONTOUR_GEOMETRIC_TYPE));
262 if (type != "CLOSED_PLANAR")
263 {
264 LOG(ERROR) << "Cannot handle contour with geometry type: " << type;
263 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); 265 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
264 } 266 }
265 267
268 // The "CountourData" tag (3006,0050) is too large to be
269 // returned by the "/instances/{id}/tags" URI: Access it using
270 // the raw "/instances/{id}/content/{...}" endpoint
266 std::string slicesData; 271 std::string slicesData;
267 orthanc.RestApiGet(slicesData, "/instances/" + instanceId + "/content/3006-0039/" + 272 orthanc.RestApiGet(slicesData, "/instances/" + instanceId + "/content/3006-0039/" +
268 boost::lexical_cast<std::string>(i) + "/3006-0040/" + 273 boost::lexical_cast<std::string>(i) + "/3006-0040/" +
269 boost::lexical_cast<std::string>(j) + "/3006-0050"); 274 boost::lexical_cast<std::string>(j) + "/3006-0050");
270 275
271 Vector points; 276 Vector points;
272 if (!GeometryToolbox::ParseVector(points, slicesData) || 277 if (!GeometryToolbox::ParseVector(points, slicesData) ||
273 points.size() != 3 * npoints) 278 points.size() != 3 * countPoints)
274 { 279 {
275 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); 280 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
276 } 281 }
277 282
278 Polygon polygon; 283 Polygon polygon;
279 SliceGeometry geometry = ExtractSliceGeometry(polygon.sliceThickness_, orthanc, slices[j]); 284 SliceGeometry geometry = ExtractSliceGeometry(polygon.sliceThickness_, orthanc, tags, i, j);
280 polygon.projectionAlongNormal_ = geometry.ProjectAlongNormal(geometry.GetOrigin()); 285 polygon.projectionAlongNormal_ = geometry.ProjectAlongNormal(geometry.GetOrigin());
281 286
282 for (size_t k = 0; k < npoints; k++) 287 for (size_t k = 0; k < countPoints; k++)
283 { 288 {
284 Vector v(3); 289 Vector v(3);
285 v[0] = points[3 * k]; 290 v[0] = points[3 * k];
286 v[1] = points[3 * k + 1]; 291 v[1] = points[3 * k + 1];
287 v[2] = points[3 * k + 2]; 292 v[2] = points[3 * k + 2];