Mercurial > hg > orthanc-stone
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]; |