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