Mercurial > hg > orthanc
comparison OrthancServer/RadiotherapyRestApi.cpp @ 535:da8e064d0d49 dicom-rt
rth api refactored
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Tue, 03 Sep 2013 17:19:02 +0200 |
parents | b22312081388 |
children | 505d6deb9947 |
comparison
equal
deleted
inserted
replaced
532:b22312081388 | 535:da8e064d0d49 |
---|---|
56 #define CONTOUR_SEQUENCE "3006,0040" | 56 #define CONTOUR_SEQUENCE "3006,0040" |
57 #define CONTOUR_IMAGE_SEQUENCE "3006,0016" | 57 #define CONTOUR_IMAGE_SEQUENCE "3006,0016" |
58 #define CONTOUR_GEOMETRIC_TYPE "3006,0042" | 58 #define CONTOUR_GEOMETRIC_TYPE "3006,0042" |
59 #define NUMBER_OF_CONTOUR_POINTS "3006,0046" | 59 #define NUMBER_OF_CONTOUR_POINTS "3006,0046" |
60 #define CONTOUR_DATA "3006,0050" | 60 #define CONTOUR_DATA "3006,0050" |
61 #define CONTOUR_SLAB_THICKNESS "3006,0044" | |
61 | 62 |
62 | 63 |
63 namespace Orthanc | 64 namespace Orthanc |
64 { | 65 { |
65 static bool CheckSeriesModality(Json::Value& study, | 66 static bool CheckSeriesModality(Json::Value& study, |
97 | 98 |
98 return true; | 99 return true; |
99 } | 100 } |
100 | 101 |
101 | 102 |
103 static bool ContourToPoints(Json::Value& result, | |
104 const Json::Value& source) | |
105 { | |
106 std::vector<std::string> points; | |
107 Toolbox::Split(points, source.asString(), '\\'); | |
108 | |
109 if (points.size() % 3 != 0) | |
110 { | |
111 return false; | |
112 } | |
113 | |
114 result = Json::arrayValue; | |
115 | |
116 for (size_t k = 0; k < points.size(); k += 3) | |
117 { | |
118 Json::Value p = Json::arrayValue; | |
119 | |
120 try | |
121 { | |
122 p.append(boost::lexical_cast<float>(points[k])); | |
123 p.append(boost::lexical_cast<float>(points[k + 1])); | |
124 p.append(boost::lexical_cast<float>(points[k + 2])); | |
125 } | |
126 catch (boost::bad_lexical_cast) | |
127 { | |
128 return false; | |
129 } | |
130 | |
131 result.append(p); | |
132 } | |
133 | |
134 return true; | |
135 } | |
136 | |
137 | |
102 static bool GetRtStructuresInfo(Json::Value& study, | 138 static bool GetRtStructuresInfo(Json::Value& study, |
103 Json::Value& series, | 139 Json::Value& series, |
104 Json::Value& content, | 140 Json::Value& content, |
105 std::string& frameOfReference, | 141 std::string& frameOfReference, |
106 ServerContext& context, | 142 ServerContext& context, |
134 | 170 |
135 return true; | 171 return true; |
136 } | 172 } |
137 | 173 |
138 | 174 |
175 static bool GetRtStructuresRoi(Json::Value& result, | |
176 Json::Value& contourSequence, | |
177 std::string& instanceId, | |
178 ServerContext& context, | |
179 const std::string& seriesId, | |
180 const std::string& roiNumber) | |
181 { | |
182 Json::Value study, series, content; | |
183 std::string frameOfReference; | |
184 | |
185 if (!GetRtStructuresInfo(study, series, content, frameOfReference, context, seriesId)) | |
186 { | |
187 return false; | |
188 } | |
189 | |
190 if (!content.isMember(STRUCTURE_SET_ROI_SEQUENCE) || | |
191 !content.isMember(ROI_CONTOUR_SEQUENCE)) | |
192 { | |
193 return false; | |
194 } | |
195 | |
196 instanceId = series["Instances"][0].asString(); | |
197 | |
198 bool found = false; | |
199 | |
200 for (Json::Value::ArrayIndex i = 0; i < content[STRUCTURE_SET_ROI_SEQUENCE]["Value"].size(); i++) | |
201 { | |
202 const Json::Value& roi = content[STRUCTURE_SET_ROI_SEQUENCE]["Value"][i]; | |
203 | |
204 if (roi.isMember(ROI_NUMBER) && | |
205 roi.isMember(ROI_NAME) && | |
206 roi[ROI_NUMBER]["Value"].asString() == roiNumber) | |
207 { | |
208 result["InternalIndex"] = i; | |
209 result["Number"] = boost::lexical_cast<unsigned int>(roiNumber); | |
210 result["Name"] = roi[ROI_NAME]["Value"].asString(); | |
211 result["GenerationAlgorithm"] = roi[ROI_GENERATION_ALGORITHM]["Value"].asString(); | |
212 found = true; | |
213 } | |
214 } | |
215 | |
216 if (!found) | |
217 { | |
218 return false; | |
219 } | |
220 | |
221 for (Json::Value::ArrayIndex i = 0; i < content[ROI_CONTOUR_SEQUENCE]["Value"].size(); i++) | |
222 { | |
223 const Json::Value& contour = content[ROI_CONTOUR_SEQUENCE]["Value"][i]; | |
224 | |
225 if (contour.isMember(REFERENCED_ROI_NUMBER) && | |
226 contour.isMember(ROI_DISPLAY_COLOR) && | |
227 contour.isMember(CONTOUR_SEQUENCE) && | |
228 contour[REFERENCED_ROI_NUMBER]["Value"].asString() == roiNumber) | |
229 { | |
230 std::vector<std::string> color; | |
231 Toolbox::Split(color, contour[ROI_DISPLAY_COLOR]["Value"].asString(), '\\'); | |
232 | |
233 result["DisplayColor"] = Json::arrayValue; | |
234 if (color.size() != 3) | |
235 { | |
236 return false; | |
237 } | |
238 | |
239 for (size_t k = 0; k < color.size(); k++) | |
240 { | |
241 try | |
242 { | |
243 result["DisplayColor"].append(boost::lexical_cast<int>(color[k])); | |
244 } | |
245 catch (boost::bad_lexical_cast) | |
246 { | |
247 return false; | |
248 } | |
249 } | |
250 | |
251 contourSequence = contour[CONTOUR_SEQUENCE]["Value"]; | |
252 | |
253 return true; | |
254 } | |
255 } | |
256 | |
257 return false; | |
258 } | |
259 | |
260 | |
139 static void GetRtStructuresInfo(RestApi::GetCall& call) | 261 static void GetRtStructuresInfo(RestApi::GetCall& call) |
140 { | 262 { |
141 RETRIEVE_CONTEXT(call); | 263 RETRIEVE_CONTEXT(call); |
142 | 264 |
143 Json::Value study, series, content; | 265 Json::Value study, series, content; |
188 { | 310 { |
189 for (Json::Value::ArrayIndex i = 0; i < content[STRUCTURE_SET_ROI_SEQUENCE]["Value"].size(); i++) | 311 for (Json::Value::ArrayIndex i = 0; i < content[STRUCTURE_SET_ROI_SEQUENCE]["Value"].size(); i++) |
190 { | 312 { |
191 if (content[STRUCTURE_SET_ROI_SEQUENCE]["Value"][i].isMember(ROI_NUMBER)) | 313 if (content[STRUCTURE_SET_ROI_SEQUENCE]["Value"][i].isMember(ROI_NUMBER)) |
192 { | 314 { |
193 result.append(content[STRUCTURE_SET_ROI_SEQUENCE]["Value"][i][ROI_NUMBER]["Value"].asString()); | 315 result.append(boost::lexical_cast<int>(content[STRUCTURE_SET_ROI_SEQUENCE]["Value"][i][ROI_NUMBER]["Value"].asString())); |
194 } | 316 } |
195 } | 317 } |
196 } | 318 } |
197 | 319 |
198 call.GetOutput().AnswerJson(result); | 320 call.GetOutput().AnswerJson(result); |
200 } | 322 } |
201 | 323 |
202 | 324 |
203 static void GetRtStructuresROI(RestApi::GetCall& call) | 325 static void GetRtStructuresROI(RestApi::GetCall& call) |
204 { | 326 { |
327 RETRIEVE_CONTEXT(call); | |
328 | |
329 Json::Value roi, contour; | |
330 std::string instanceId; | |
331 | |
332 if (GetRtStructuresRoi(roi, contour, instanceId, context, | |
333 call.GetUriComponent("id", ""), | |
334 call.GetUriComponent("roi", ""))) | |
335 { | |
336 roi.removeMember("InternalIndex"); | |
337 call.GetOutput().AnswerJson(roi); | |
338 } | |
339 } | |
340 | |
341 | |
342 static void GetRtStructuresROIPoints(RestApi::GetCall& call) | |
343 { | |
344 RETRIEVE_CONTEXT(call); | |
345 | |
346 Json::Value roi, contour; | |
347 std::string instanceId; | |
348 | |
349 if (GetRtStructuresRoi(roi, contour, instanceId, context, | |
350 call.GetUriComponent("id", ""), | |
351 call.GetUriComponent("roi", ""))) | |
352 { | |
353 Json::Value result = Json::arrayValue; | |
354 | |
355 for (Json::Value::ArrayIndex i = 0; i < contour.size(); i++) | |
356 { | |
357 if (contour[i][CONTOUR_GEOMETRIC_TYPE]["Value"].asString() == "POINT") | |
358 { | |
359 Json::Value p; | |
360 if (ContourToPoints(p, contour[i][CONTOUR_DATA]["Value"].asString()) && | |
361 p.size() == 1) | |
362 { | |
363 result.append(p[0]); | |
364 } | |
365 } | |
366 } | |
367 | |
368 call.GetOutput().AnswerJson(result); | |
369 } | |
370 } | |
371 | |
372 | |
373 static void GetRtStructuresListOfClosedPlanars(RestApi::GetCall& call) | |
374 { | |
375 RETRIEVE_CONTEXT(call); | |
376 | |
377 Json::Value roi, contour; | |
378 std::string instanceId; | |
379 | |
380 if (GetRtStructuresRoi(roi, contour, instanceId, context, | |
381 call.GetUriComponent("id", ""), | |
382 call.GetUriComponent("roi", ""))) | |
383 { | |
384 Json::Value result = Json::arrayValue; | |
385 | |
386 for (Json::Value::ArrayIndex i = 0; i < contour.size(); i++) | |
387 { | |
388 if (contour[i].isMember(CONTOUR_IMAGE_SEQUENCE) && | |
389 contour[i].isMember(CONTOUR_GEOMETRIC_TYPE) && | |
390 contour[i].isMember(NUMBER_OF_CONTOUR_POINTS) && | |
391 contour[i].isMember(CONTOUR_DATA) && | |
392 contour[i].isMember(CONTOUR_SLAB_THICKNESS) && | |
393 contour[i][CONTOUR_IMAGE_SEQUENCE]["Value"].size() == 1 && | |
394 contour[i][CONTOUR_IMAGE_SEQUENCE]["Value"][0].isMember(REFERENCED_SOP_INSTANCE_UID) && | |
395 contour[i][CONTOUR_GEOMETRIC_TYPE]["Value"].asString() == "CLOSED_PLANAR") | |
396 { | |
397 result.append(i); | |
398 } | |
399 } | |
400 | |
401 call.GetOutput().AnswerJson(result); | |
402 } | |
403 } | |
404 | |
405 | |
406 static void GetRtStructuresSingleClosedPlanar(RestApi::GetCall& call) | |
407 { | |
408 RETRIEVE_CONTEXT(call); | |
409 | |
410 Json::Value roi, contour; | |
411 std::string instanceId; | |
412 | |
413 if (GetRtStructuresRoi(roi, contour, instanceId, context, | |
414 call.GetUriComponent("id", ""), | |
415 call.GetUriComponent("roi", ""))) | |
416 { | |
417 unsigned int index = boost::lexical_cast<unsigned int>(call.GetUriComponent("polygon", "")); | |
418 | |
419 boost::mutex::scoped_lock lock(context.GetDicomFileMutex()); | |
420 ParsedDicomFile& dicom = context.GetDicomFile(instanceId); | |
421 | |
422 ParsedDicomFile::SequencePath path; | |
423 path.push_back(std::make_pair(DicomTag(0x3006, 0x0039 /* ROIContourSequence */), roi["InternalIndex"].asInt())); | |
424 path.push_back(std::make_pair(DicomTag(0x3006, 0x0040 /* ContourSequence */), index)); | |
425 | |
426 Json::Value result; | |
427 std::string contourData; | |
428 std::string numberOfPoints; | |
429 | |
430 if (dicom.GetTagValue(contourData, path, DicomTag(0x3006, 0x0050 /* ContourData */)) && | |
431 dicom.GetTagValue(numberOfPoints, path, DicomTag(0x3006, 0x0046 /* NumberOfContourPoints */)) && | |
432 ContourToPoints(result, contourData) && | |
433 result.size() == boost::lexical_cast<unsigned int>(numberOfPoints)) | |
434 { | |
435 call.GetOutput().AnswerJson(result); | |
436 } | |
437 } | |
438 } | |
439 | |
440 | |
441 static void GetRtStructuresClosedPlanarThickness(RestApi::GetCall& call) | |
442 { | |
443 RETRIEVE_CONTEXT(call); | |
444 | |
445 Json::Value roi, contour; | |
446 std::string instanceId; | |
447 | |
448 if (GetRtStructuresRoi(roi, contour, instanceId, context, | |
449 call.GetUriComponent("id", ""), | |
450 call.GetUriComponent("roi", ""))) | |
451 { | |
452 unsigned int index = boost::lexical_cast<unsigned int>(call.GetUriComponent("polygon", "")); | |
453 | |
454 if (contour[index].isMember(CONTOUR_SLAB_THICKNESS)) | |
455 { | |
456 std::cout <<contour[index][CONTOUR_SLAB_THICKNESS] << std::endl; | |
457 call.GetOutput().AnswerBuffer(contour[index][CONTOUR_SLAB_THICKNESS]["Value"].asString(), "text/plain"); | |
458 } | |
459 else | |
460 { | |
461 // TODO - No slab thickness is explicitely specified: Should we | |
462 // fallback to the thickness of the instance? | |
463 } | |
464 } | |
465 } | |
466 | |
467 | |
468 static void GetRtStructuresClosedPlanarInstance(RestApi::GetCall& call) | |
469 { | |
205 RETRIEVE_CONTEXT(call); | 470 RETRIEVE_CONTEXT(call); |
206 | 471 |
207 Json::Value study, series, content; | 472 Json::Value roi, contour; |
208 std::string frameOfReference; | 473 std::string instanceId; |
209 if (GetRtStructuresInfo(study, series, content, frameOfReference, context, call.GetUriComponent("id", ""))) | 474 |
210 { | 475 if (GetRtStructuresRoi(roi, contour, instanceId, context, |
211 if (content.isMember(STRUCTURE_SET_ROI_SEQUENCE) && | 476 call.GetUriComponent("id", ""), |
212 content.isMember(ROI_CONTOUR_SEQUENCE)) | 477 call.GetUriComponent("roi", ""))) |
213 { | 478 { |
479 unsigned int index = boost::lexical_cast<unsigned int>(call.GetUriComponent("polygon", "")); | |
480 | |
481 if (contour[index].isMember(CONTOUR_IMAGE_SEQUENCE) && | |
482 contour[index][CONTOUR_IMAGE_SEQUENCE]["Value"].size() == 1 && | |
483 contour[index][CONTOUR_IMAGE_SEQUENCE]["Value"][0].isMember(REFERENCED_SOP_INSTANCE_UID)) | |
484 { | |
485 std::string uid = contour[index][CONTOUR_IMAGE_SEQUENCE]["Value"][0][REFERENCED_SOP_INSTANCE_UID]["Value"].asString(); | |
486 | |
487 std::list<std::string> instance; | |
488 context.GetIndex().LookupTagValue(instance, DICOM_TAG_SOP_INSTANCE_UID, uid); | |
489 | |
214 Json::Value result; | 490 Json::Value result; |
215 | 491 if (instance.size() == 1 && |
216 bool found = false; | 492 context.GetIndex().LookupResource(result, instance.front(), ResourceType_Instance)) |
217 for (Json::Value::ArrayIndex i = 0; i < content[STRUCTURE_SET_ROI_SEQUENCE]["Value"].size(); i++) | |
218 { | |
219 const Json::Value& roi = content[STRUCTURE_SET_ROI_SEQUENCE]["Value"][i]; | |
220 | |
221 if (roi.isMember(ROI_NUMBER) && | |
222 roi.isMember(ROI_NAME) && | |
223 roi[ROI_NUMBER]["Value"].asString() == call.GetUriComponent("roi", "")) | |
224 { | |
225 result["Number"] = call.GetUriComponent("roi", ""); | |
226 result["Name"] = roi[ROI_NAME]["Value"].asString(); | |
227 result["GenerationAlgorithm"] = roi[ROI_GENERATION_ALGORITHM]["Value"].asString(); | |
228 found = true; | |
229 } | |
230 } | |
231 | |
232 if (!found) | |
233 { | |
234 return; | |
235 } | |
236 | |
237 found = false; | |
238 | |
239 boost::mutex::scoped_lock lock(context.GetDicomFileMutex()); | |
240 ParsedDicomFile& dicom = context.GetDicomFile(series["Instances"][0].asString()); | |
241 | |
242 for (Json::Value::ArrayIndex i = 0; i < content[ROI_CONTOUR_SEQUENCE]["Value"].size(); i++) | |
243 { | |
244 const Json::Value& contour = content[ROI_CONTOUR_SEQUENCE]["Value"][i]; | |
245 | |
246 if (contour.isMember(REFERENCED_ROI_NUMBER) && | |
247 contour.isMember(ROI_DISPLAY_COLOR) && | |
248 contour.isMember(CONTOUR_SEQUENCE) && | |
249 contour[REFERENCED_ROI_NUMBER]["Value"].asString() == call.GetUriComponent("roi", "")) | |
250 { | |
251 std::vector<std::string> color; | |
252 Toolbox::Split(color, contour[ROI_DISPLAY_COLOR]["Value"].asString(), '\\'); | |
253 | |
254 result["Points"] = Json::objectValue; | |
255 result["ClosedPlanar"] = Json::objectValue; | |
256 result["DisplayColor"] = Json::arrayValue; | |
257 for (size_t k = 0; k < color.size(); k++) | |
258 { | |
259 result["DisplayColor"].append(boost::lexical_cast<int>(color[k])); | |
260 } | |
261 | |
262 for (Json::Value::ArrayIndex j = 0; j < contour[CONTOUR_SEQUENCE]["Value"].size(); j++) | |
263 { | |
264 const Json::Value& contourSequence = contour[CONTOUR_SEQUENCE]["Value"][j]; | |
265 | |
266 if (contourSequence.isMember(CONTOUR_IMAGE_SEQUENCE) && | |
267 contourSequence.isMember(CONTOUR_GEOMETRIC_TYPE) && | |
268 contourSequence.isMember(NUMBER_OF_CONTOUR_POINTS) && | |
269 contourSequence.isMember(CONTOUR_DATA) && | |
270 contourSequence[CONTOUR_IMAGE_SEQUENCE]["Value"].size() == 1 && | |
271 contourSequence[CONTOUR_IMAGE_SEQUENCE]["Value"][0].isMember(REFERENCED_SOP_INSTANCE_UID)) | |
272 { | |
273 const std::string type = contourSequence[CONTOUR_GEOMETRIC_TYPE]["Value"].asString(); | |
274 if (type != "POINT" && type != "CLOSED_PLANAR") | |
275 { | |
276 continue; | |
277 } | |
278 | |
279 const std::string uid = (contourSequence[CONTOUR_IMAGE_SEQUENCE]["Value"][0] | |
280 [REFERENCED_SOP_INSTANCE_UID]["Value"].asString()); | |
281 | |
282 std::list<std::string> instance; | |
283 context.GetIndex().LookupTagValue(instance, DICOM_TAG_SOP_INSTANCE_UID, uid); | |
284 if (instance.size() != 1) | |
285 { | |
286 continue; | |
287 } | |
288 | |
289 unsigned int countPoints = boost::lexical_cast<unsigned int> | |
290 (contourSequence[NUMBER_OF_CONTOUR_POINTS]["Value"].asString()); | |
291 if (countPoints <= 0) | |
292 { | |
293 continue; | |
294 } | |
295 | |
296 ParsedDicomFile::SequencePath path; | |
297 path.push_back(std::make_pair(DicomTag(0x3006, 0x0039 /* ROIContourSequence */), i)); | |
298 path.push_back(std::make_pair(DicomTag(0x3006, 0x0040 /* ContourSequence */), j)); | |
299 | |
300 std::string contourData; | |
301 dicom.GetTagValue(contourData, path, DicomTag(0x3006, 0x0050 /* ContourData */)); | |
302 | |
303 std::vector<std::string> points; | |
304 Toolbox::Split(points, contourData, '\\'); | |
305 | |
306 Json::Value* target; | |
307 Json::Value item = Json::arrayValue; | |
308 | |
309 if (type == "POINT" && | |
310 countPoints == 1 && | |
311 points.size() == 3) | |
312 { | |
313 target = &result["Points"]; | |
314 item.append(boost::lexical_cast<float>(points[0])); | |
315 item.append(boost::lexical_cast<float>(points[1])); | |
316 item.append(boost::lexical_cast<float>(points[2])); | |
317 } | |
318 else if (type == "CLOSED_PLANAR" && | |
319 points.size() == 3 * countPoints) | |
320 { | |
321 target = &result["ClosedPlanar"]; | |
322 for (size_t k = 0; k < countPoints; k++) | |
323 { | |
324 Json::Value p = Json::arrayValue; | |
325 p.append(boost::lexical_cast<float>(points[3 * k])); | |
326 p.append(boost::lexical_cast<float>(points[3 * k + 1])); | |
327 p.append(boost::lexical_cast<float>(points[3 * k + 2])); | |
328 item.append(p); | |
329 } | |
330 } | |
331 else | |
332 { | |
333 continue; | |
334 } | |
335 | |
336 if (!target->isMember(instance.front())) | |
337 { | |
338 (*target) [instance.front()] = Json::arrayValue; | |
339 } | |
340 | |
341 (*target) [instance.front()].append(item); | |
342 } | |
343 } | |
344 | |
345 found = true; | |
346 } | |
347 } | |
348 | |
349 if (found) | |
350 { | 493 { |
351 call.GetOutput().AnswerJson(result); | 494 call.GetOutput().AnswerJson(result); |
352 } | 495 } |
353 } | 496 } |
354 } | 497 } |
357 | 500 |
358 RadiotherapyRestApi::RadiotherapyRestApi(ServerContext& context) : OrthancRestApi(context) | 501 RadiotherapyRestApi::RadiotherapyRestApi(ServerContext& context) : OrthancRestApi(context) |
359 { | 502 { |
360 Register("/series/{id}/rt-structures", GetRtStructuresInfo); | 503 Register("/series/{id}/rt-structures", GetRtStructuresInfo); |
361 Register("/series/{id}/rt-structures/roi", GetRtStructuresListOfROIs); | 504 Register("/series/{id}/rt-structures/roi", GetRtStructuresListOfROIs); |
362 Register("/series/{id}/rt-structures/roi/{roi}", GetRtStructuresROI); | 505 Register("/series/{id}/rt-structures/roi/{roi}/info", GetRtStructuresROI); |
506 Register("/series/{id}/rt-structures/roi/{roi}/points", GetRtStructuresROIPoints); | |
507 Register("/series/{id}/rt-structures/roi/{roi}/closed-planar", GetRtStructuresListOfClosedPlanars); | |
508 Register("/series/{id}/rt-structures/roi/{roi}/closed-planar/{polygon}/points", GetRtStructuresSingleClosedPlanar); | |
509 Register("/series/{id}/rt-structures/roi/{roi}/closed-planar/{polygon}/thickness", GetRtStructuresClosedPlanarThickness); | |
510 Register("/series/{id}/rt-structures/roi/{roi}/closed-planar/{polygon}/instance", GetRtStructuresClosedPlanarInstance); | |
363 } | 511 } |
364 | 512 |
365 } | 513 } |
366 | 514 |
367 | 515 |