Mercurial > hg > orthanc
changeset 761:12a3f2eaa99a dicom-rt
move
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Wed, 16 Apr 2014 16:05:50 +0200 |
parents | 8cfc6119a5bd |
children | 2f7802e95dd0 |
files | CMakeLists.txt OrthancServer/OrthancRestApi/RadiotherapyRestApi.cpp OrthancServer/RadiotherapyRestApi.cpp OrthancServer/RadiotherapyRestApi.h |
diffstat | 4 files changed, 816 insertions(+), 860 deletions(-) [+] |
line wrap: on
line diff
--- a/CMakeLists.txt Wed Apr 16 16:04:55 2014 +0200 +++ b/CMakeLists.txt Wed Apr 16 16:05:50 2014 +0200 @@ -223,7 +223,7 @@ OrthancServer/OrthancFindRequestHandler.cpp OrthancServer/OrthancMoveRequestHandler.cpp - #OrthancServer/RadiotherapyRestApi.cpp + OrthancServer/OrthancRestApi/RadiotherapyRestApi.cpp ) # Ensure autogenerated code is built before building ServerLibrary
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/OrthancRestApi/RadiotherapyRestApi.cpp Wed Apr 16 16:05:50 2014 +0200 @@ -0,0 +1,815 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, + * Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#include "RadiotherapyRestApi.h" + +#include "ServerToolbox.h" + +#define RETRIEVE_CONTEXT(call) \ + OrthancRestApi& contextApi = \ + dynamic_cast<OrthancRestApi&>(call.GetContext()); \ + ServerContext& context = contextApi.GetContext() + + +// DICOM tags for RT-STRUCT + +/** + * REFERENCE: http://www.dabsoft.ch/dicom/3/C.8.8.6/ + * + * IMPORTANT: The points/vertices coordinates are reported in [mm]. + * + * TODO: Support "Contour Offset Vector" + **/ + + +#define REFERENCED_STUDY_SEQUENCE "0008,1110" +#define REFERENCED_SOP_INSTANCE_UID "0008,1155" +#define FRAME_OF_REFERENCE_UID "0020,0052" +#define REFERENCED_FRAME_OF_REFERENCE_SEQUENCE "3006,0010" +#define STRUCTURE_SET_ROI_SEQUENCE "3006,0020" +#define ROI_NUMBER "3006,0022" +#define ROI_NAME "3006,0026" +#define ROI_GENERATION_ALGORITHM "3006,0036" +#define ROI_CONTOUR_SEQUENCE "3006,0039" +#define REFERENCED_ROI_NUMBER "3006,0084" +#define ROI_DISPLAY_COLOR "3006,002a" +#define CONTOUR_SEQUENCE "3006,0040" +#define CONTOUR_IMAGE_SEQUENCE "3006,0016" +#define CONTOUR_GEOMETRIC_TYPE "3006,0042" +#define NUMBER_OF_CONTOUR_POINTS "3006,0046" +#define CONTOUR_DATA "3006,0050" +#define CONTOUR_SLAB_THICKNESS "3006,0044" +#define SLICE_THICKNESS "0018,0050" + + + +#include <boost/geometry.hpp> +#include <boost/geometry/geometries/point.hpp> +#include <boost/geometry/geometries/point_xy.hpp> +#include <boost/geometry/geometries/polygon.hpp> +#include <boost/geometry/geometries/linestring.hpp> + +namespace Orthanc +{ + static bool CheckSeriesModality(Json::Value& study, + Json::Value& series, + Json::Value& content, + ServerContext& context, + const std::string& seriesId, + const std::string& modality) + { + if (!context.GetIndex().LookupResource(series, seriesId, ResourceType_Series)) + { + return false; + } + + // Retrieve the parent study + std::string studyId = series["ParentStudy"].asString(); + if (!context.GetIndex().LookupResource(study, studyId, ResourceType_Study)) + { + return false; + } + + // Check the modality and that there is a single instance inside the series + if (!series["MainDicomTags"].isMember("Modality") || + series["MainDicomTags"]["Modality"].asString() != modality || + series["Instances"].size() != 1) + { + return false; + } + + // Retrieve the instance data + std::string instanceId = series["Instances"][0].asString(); + + context.ReadJson(content, instanceId); + + return true; + } + + + static bool ContourToPoints(Json::Value& result, + const Json::Value& source) + { + std::vector<std::string> points; + Toolbox::Split(points, source.asString(), '\\'); + + if (points.size() % 3 != 0) + { + return false; + } + + result = Json::arrayValue; + + for (size_t k = 0; k < points.size(); k += 3) + { + Json::Value p = Json::arrayValue; + + try + { + p.append(boost::lexical_cast<float>(points[k])); + p.append(boost::lexical_cast<float>(points[k + 1])); + p.append(boost::lexical_cast<float>(points[k + 2])); + } + catch (boost::bad_lexical_cast) + { + return false; + } + + result.append(p); + } + + return true; + } + + + static bool GetRtStructuresInfo(Json::Value& study, + Json::Value& series, + Json::Value& content, + std::string& frameOfReference, + ServerContext& context, + const std::string& seriesId) + { + if (!CheckSeriesModality(study, series, content, context, seriesId, "RTSTRUCT")) + { + return false; + } + + // Check that the "ReferencedStudySequence" (if any) is the same as the parent study. + if (content.isMember(REFERENCED_STUDY_SEQUENCE)) + { + if (content[REFERENCED_STUDY_SEQUENCE]["Value"].size() != 1 || + !content[REFERENCED_STUDY_SEQUENCE]["Value"][0].isMember(REFERENCED_SOP_INSTANCE_UID) || + content[REFERENCED_STUDY_SEQUENCE]["Value"][0][REFERENCED_SOP_INSTANCE_UID]["Value"].asString() != + study["MainDicomTags"]["StudyInstanceUID"].asString()) + { + return false; + } + } + + // Lookup for the frame of reference. Orthanc does not support + // RTSTRUCT with multiple frames of reference. + if (!content.isMember(REFERENCED_FRAME_OF_REFERENCE_SEQUENCE) || + content[REFERENCED_FRAME_OF_REFERENCE_SEQUENCE]["Value"].size() != 1 || + !content[REFERENCED_FRAME_OF_REFERENCE_SEQUENCE]["Value"][0].isMember(FRAME_OF_REFERENCE_UID)) + { + return false; + } + + frameOfReference = content[REFERENCED_FRAME_OF_REFERENCE_SEQUENCE]["Value"][0][FRAME_OF_REFERENCE_UID]["Value"].asString(); + + return true; + } + + + static bool GetRtStructuresRoi(Json::Value& result, + Json::Value& contourSequence, + std::string& instanceId, + ServerContext& context, + const std::string& seriesId, + const std::string& roiNumber) + { + Json::Value study, series, content; + std::string frameOfReference; + + if (!GetRtStructuresInfo(study, series, content, frameOfReference, context, seriesId)) + { + return false; + } + + if (!content.isMember(STRUCTURE_SET_ROI_SEQUENCE) || + !content.isMember(ROI_CONTOUR_SEQUENCE)) + { + return false; + } + + instanceId = series["Instances"][0].asString(); + + bool found = false; + + for (Json::Value::ArrayIndex i = 0; i < content[STRUCTURE_SET_ROI_SEQUENCE]["Value"].size(); i++) + { + const Json::Value& roi = content[STRUCTURE_SET_ROI_SEQUENCE]["Value"][i]; + + if (roi.isMember(ROI_NUMBER) && + roi.isMember(ROI_NAME) && + roi[ROI_NUMBER]["Value"].asString() == roiNumber) + { + result["InternalIndex"] = i; + result["Number"] = boost::lexical_cast<unsigned int>(roiNumber); + result["Name"] = roi[ROI_NAME]["Value"].asString(); + result["GenerationAlgorithm"] = roi[ROI_GENERATION_ALGORITHM]["Value"].asString(); + found = true; + } + } + + if (!found) + { + return false; + } + + for (Json::Value::ArrayIndex i = 0; i < content[ROI_CONTOUR_SEQUENCE]["Value"].size(); i++) + { + const Json::Value& contour = content[ROI_CONTOUR_SEQUENCE]["Value"][i]; + + if (contour.isMember(REFERENCED_ROI_NUMBER) && + contour.isMember(ROI_DISPLAY_COLOR) && + contour.isMember(CONTOUR_SEQUENCE) && + contour[REFERENCED_ROI_NUMBER]["Value"].asString() == roiNumber) + { + std::vector<std::string> color; + Toolbox::Split(color, contour[ROI_DISPLAY_COLOR]["Value"].asString(), '\\'); + + result["DisplayColor"] = Json::arrayValue; + if (color.size() != 3) + { + return false; + } + + for (size_t k = 0; k < color.size(); k++) + { + try + { + result["DisplayColor"].append(boost::lexical_cast<int>(color[k])); + } + catch (boost::bad_lexical_cast) + { + return false; + } + } + + contourSequence = contour[CONTOUR_SEQUENCE]["Value"]; + + return true; + } + } + + return false; + } + + + static bool GetClosedPlanarPoints(Json::Value& result, + ServerContext& context, + const std::string& instanceId, + const Json::Value& roi, + unsigned int index) + { + boost::mutex::scoped_lock lock(context.GetDicomFileMutex()); + ParsedDicomFile& dicom = context.GetDicomFile(instanceId); + + ParsedDicomFile::SequencePath path; + path.push_back(std::make_pair(DicomTag(0x3006, 0x0039 /* ROIContourSequence */), roi["InternalIndex"].asInt())); + path.push_back(std::make_pair(DicomTag(0x3006, 0x0040 /* ContourSequence */), index)); + + std::string contourData; + std::string numberOfPoints; + + if (dicom.GetTagValue(contourData, path, DicomTag(0x3006, 0x0050 /* ContourData */)) && + dicom.GetTagValue(numberOfPoints, path, DicomTag(0x3006, 0x0046 /* NumberOfContourPoints */)) && + ContourToPoints(result, contourData) && + result.size() == boost::lexical_cast<unsigned int>(numberOfPoints)) + { + return true; + } + else + { + return false; + } + } + + + + static bool LookupReferencedInstance(Json::Value& result, + ServerContext& context, + const Json::Value& contour) + { + if (contour.isMember(CONTOUR_IMAGE_SEQUENCE) && + contour[CONTOUR_IMAGE_SEQUENCE]["Value"].size() == 1 && + contour[CONTOUR_IMAGE_SEQUENCE]["Value"][0].isMember(REFERENCED_SOP_INSTANCE_UID)) + { + std::string uid = contour[CONTOUR_IMAGE_SEQUENCE]["Value"][0][REFERENCED_SOP_INSTANCE_UID]["Value"].asString(); + + std::list<std::string> instance; + context.GetIndex().LookupTagValue(instance, DICOM_TAG_SOP_INSTANCE_UID, uid); + + if (instance.size() == 1 && + context.GetIndex().LookupResource(result, instance.front(), ResourceType_Instance)) + { + return true; + } + } + + return false; + } + + + static bool GetRtStructuresClosedPlanarThickness(float& result, + ServerContext& context, + const Json::Value& contour) + { + if (contour.isMember(CONTOUR_SLAB_THICKNESS)) + { + result = boost::lexical_cast<float>(contour[CONTOUR_SLAB_THICKNESS]["Value"].asString()); + return true; + } + + // No slab thickness is explicitely specified: Fallback to + // the thickness of the referred instance + + Json::Value instance; + if (LookupReferencedInstance(instance, context, contour)) + { + Json::Value info; + context.ReadJson(info, instance["ID"].asString()); + + if (info.isMember(SLICE_THICKNESS)) + { + result = boost::lexical_cast<float>(info[SLICE_THICKNESS]["Value"].asString()); + return true; + } + } + + return false; + } + + + + static void GetRtStructuresInfo(RestApi::GetCall& call) + { + RETRIEVE_CONTEXT(call); + + Json::Value study, series, content; + std::string frameOfReference; + if (GetRtStructuresInfo(study, series, content, frameOfReference, context, call.GetUriComponent("id", ""))) + { + Json::Value result; + + result["Study"] = study["ID"]; + + + // Lookup the series with the same frame of reference inside this study + result["RelatedSeries"] = Json::arrayValue; + + for (Json::Value::ArrayIndex i = 0; i < study["Series"].size(); i++) + { + Json::Value otherSeries; + if (context.GetIndex().LookupResource(otherSeries, study["Series"][i].asString(), ResourceType_Series) && + otherSeries["Instances"].size() > 0) + { + Json::Value info; + context.ReadJson(info, otherSeries["Instances"][0].asString()); + + if (info.isMember(FRAME_OF_REFERENCE_UID)) + { + result["RelatedSeries"].append(study["Series"][i].asString()); + } + } + } + + + call.GetOutput().AnswerJson(result); + } + } + + + static void GetRtStructuresListOfROIs(RestApi::GetCall& call) + { + RETRIEVE_CONTEXT(call); + + Json::Value study, series, content; + std::string frameOfReference; + if (GetRtStructuresInfo(study, series, content, frameOfReference, context, call.GetUriComponent("id", ""))) + { + Json::Value result(Json::arrayValue); + + if (content.isMember(STRUCTURE_SET_ROI_SEQUENCE)) + { + for (Json::Value::ArrayIndex i = 0; i < content[STRUCTURE_SET_ROI_SEQUENCE]["Value"].size(); i++) + { + if (content[STRUCTURE_SET_ROI_SEQUENCE]["Value"][i].isMember(ROI_NUMBER)) + { + result.append(boost::lexical_cast<int>(content[STRUCTURE_SET_ROI_SEQUENCE]["Value"][i][ROI_NUMBER]["Value"].asString())); + } + } + } + + call.GetOutput().AnswerJson(result); + } + } + + + static void GetRtStructuresROI(RestApi::GetCall& call) + { + RETRIEVE_CONTEXT(call); + + Json::Value roi, contour; + std::string instanceId; + + if (GetRtStructuresRoi(roi, contour, instanceId, context, + call.GetUriComponent("id", ""), + call.GetUriComponent("roi", ""))) + { + roi.removeMember("InternalIndex"); + call.GetOutput().AnswerJson(roi); + } + } + + + static void GetRtStructuresROIPoints(RestApi::GetCall& call) + { + RETRIEVE_CONTEXT(call); + + Json::Value roi, contour; + std::string instanceId; + + if (GetRtStructuresRoi(roi, contour, instanceId, context, + call.GetUriComponent("id", ""), + call.GetUriComponent("roi", ""))) + { + Json::Value result = Json::arrayValue; + + for (Json::Value::ArrayIndex i = 0; i < contour.size(); i++) + { + if (contour[i][CONTOUR_GEOMETRIC_TYPE]["Value"].asString() == "POINT") + { + Json::Value p; + if (ContourToPoints(p, contour[i][CONTOUR_DATA]["Value"].asString()) && + p.size() == 1) + { + result.append(p[0]); + } + } + } + + call.GetOutput().AnswerJson(result); + } + } + + + static void GetRtStructuresListOfClosedPlanars(RestApi::GetCall& call) + { + RETRIEVE_CONTEXT(call); + + Json::Value roi, contour; + std::string instanceId; + + if (GetRtStructuresRoi(roi, contour, instanceId, context, + call.GetUriComponent("id", ""), + call.GetUriComponent("roi", ""))) + { + Json::Value result = Json::arrayValue; + + for (Json::Value::ArrayIndex i = 0; i < contour.size(); i++) + { + if (contour[i].isMember(CONTOUR_IMAGE_SEQUENCE) && + contour[i].isMember(CONTOUR_GEOMETRIC_TYPE) && + contour[i].isMember(NUMBER_OF_CONTOUR_POINTS) && + contour[i].isMember(CONTOUR_DATA) && + contour[i][CONTOUR_IMAGE_SEQUENCE]["Value"].size() == 1 && + contour[i][CONTOUR_IMAGE_SEQUENCE]["Value"][0].isMember(REFERENCED_SOP_INSTANCE_UID) && + contour[i][CONTOUR_GEOMETRIC_TYPE]["Value"].asString() == "CLOSED_PLANAR") + { + result.append(i); + } + } + + call.GetOutput().AnswerJson(result); + } + } + + + static void GetRtStructuresSingleClosedPlanar(RestApi::GetCall& call) + { + RETRIEVE_CONTEXT(call); + + Json::Value roi, contour, result; + std::string instanceId; + + if (GetRtStructuresRoi(roi, contour, instanceId, context, + call.GetUriComponent("id", ""), + call.GetUriComponent("roi", ""))) + { + unsigned int index = boost::lexical_cast<unsigned int>(call.GetUriComponent("polygon", "")); + + if (GetClosedPlanarPoints(result, context, instanceId, roi, index)) + { + call.GetOutput().AnswerJson(result); + } + } + } + + + static void GetRtStructuresClosedPlanarThickness(RestApi::GetCall& call) + { + RETRIEVE_CONTEXT(call); + + Json::Value roi, contour; + std::string instanceId; + + if (GetRtStructuresRoi(roi, contour, instanceId, context, + call.GetUriComponent("id", ""), + call.GetUriComponent("roi", ""))) + { + unsigned int index = boost::lexical_cast<unsigned int>(call.GetUriComponent("polygon", "")); + + float thickness; + if (GetRtStructuresClosedPlanarThickness(thickness, context, contour[index])) + { + call.GetOutput().AnswerBuffer(boost::lexical_cast<std::string>(thickness), "text/plain"); + } + } + } + + + static bool ComputeClosedPlanarArea(double& area, + const Json::Value& vertices) + { + if (vertices.size() <= 1) + { + area = 0; + return true; + } + + // Check that all the points share the same z coordinates + for (Json::Value::ArrayIndex i = 1; i < vertices.size(); i++) + { + static float THRESHOLD = 10.0f * std::numeric_limits<float>::epsilon(); + + assert(vertices[i].size() == 3); + if (fabs(vertices[i][2].asFloat() - vertices[0][2].asFloat()) > THRESHOLD) + { + // This point has not the same z coordinate + return false; + } + } + + // Calculate the area of a cartesian polygon + // TODO - What happens if self-crossing polygon? + typedef boost::geometry::model::d2::point_xy<float> point_type; + + boost::geometry::model::linestring<point_type> points; + + points.resize(vertices.size()); + for (Json::Value::ArrayIndex i = 0; i < vertices.size(); i++) + { + float x = vertices[i][0].asFloat(); + float y = vertices[i][1].asFloat(); + points[i] = boost::geometry::make<point_type>(x, y); + } + + boost::geometry::model::polygon<point_type> poly; + boost::geometry::append(poly, points); + + area = abs(boost::geometry::area(poly)); + return true; + } + + + static void GetRtStructuresClosedPlanarArea(RestApi::GetCall& call) + { + RETRIEVE_CONTEXT(call); + + Json::Value roi, contour, vertices; + std::string instanceId; + + if (GetRtStructuresRoi(roi, contour, instanceId, context, + call.GetUriComponent("id", ""), + call.GetUriComponent("roi", ""))) + { + unsigned int index = boost::lexical_cast<unsigned int>(call.GetUriComponent("polygon", "")); + + double area; + if (GetClosedPlanarPoints(vertices, context, instanceId, roi, index) && + ComputeClosedPlanarArea(area, vertices)) + { + call.GetOutput().AnswerBuffer(boost::lexical_cast<std::string>(area), "text/plain"); + } + } + } + + + static void GetRtStructuresInstanceOfClosedPlanar(RestApi::GetCall& call) + { + RETRIEVE_CONTEXT(call); + + Json::Value roi, contour; + std::string instanceId; + + if (GetRtStructuresRoi(roi, contour, instanceId, context, + call.GetUriComponent("id", ""), + call.GetUriComponent("roi", ""))) + { + unsigned int index = boost::lexical_cast<unsigned int>(call.GetUriComponent("polygon", "")); + + Json::Value result; + if (LookupReferencedInstance(result, context, contour[index])) + { + call.GetOutput().AnswerJson(result); + } + } + } + + + static void GetRtStructuresListOfInstances(RestApi::GetCall& call) + { + RETRIEVE_CONTEXT(call); + + Json::Value roi, contour; + std::string instanceId; + + if (GetRtStructuresRoi(roi, contour, instanceId, context, + call.GetUriComponent("id", ""), + call.GetUriComponent("roi", ""))) + { + Json::Value result = Json::arrayValue; + + for (Json::Value::ArrayIndex i = 0; i < contour.size(); i++) + { + if (contour[i].isMember(CONTOUR_IMAGE_SEQUENCE) && + contour[i][CONTOUR_IMAGE_SEQUENCE]["Value"].size() == 1 && + contour[i][CONTOUR_IMAGE_SEQUENCE]["Value"][0].isMember(REFERENCED_SOP_INSTANCE_UID)) + { + std::string uid = contour[i][CONTOUR_IMAGE_SEQUENCE]["Value"][0][REFERENCED_SOP_INSTANCE_UID]["Value"].asString(); + + std::list<std::string> instance; + context.GetIndex().LookupTagValue(instance, DICOM_TAG_SOP_INSTANCE_UID, uid); + + if (instance.size() == 1) + { + result.append(instance.front()); + } + } + } + + call.GetOutput().AnswerJson(result); + } + } + + + + static void GetRtStructuresClosedPlanarsOfInstance(RestApi::GetCall& call) + { + RETRIEVE_CONTEXT(call); + + Json::Value roi, contour, instance; + std::string instanceId; + + if (context.GetIndex().LookupResource(instance, call.GetUriComponent("instance", ""), ResourceType_Instance) && + GetRtStructuresRoi(roi, contour, instanceId, context, + call.GetUriComponent("id", ""), + call.GetUriComponent("roi", ""))) + { + Json::Value result = Json::arrayValue; + + for (Json::Value::ArrayIndex i = 0; i < contour.size(); i++) + { + if (contour[i].isMember(CONTOUR_DATA) && + contour[i].isMember(CONTOUR_IMAGE_SEQUENCE) && + contour[i].isMember(NUMBER_OF_CONTOUR_POINTS) && + contour[i].isMember(CONTOUR_GEOMETRIC_TYPE) && + contour[i][CONTOUR_IMAGE_SEQUENCE]["Value"].size() == 1 && + contour[i][CONTOUR_IMAGE_SEQUENCE]["Value"][0].isMember(REFERENCED_SOP_INSTANCE_UID) && + contour[i][CONTOUR_GEOMETRIC_TYPE]["Value"].asString() == "CLOSED_PLANAR") + { + std::string uid = contour[i][CONTOUR_IMAGE_SEQUENCE]["Value"][0][REFERENCED_SOP_INSTANCE_UID]["Value"].asString(); + + Json::Value points; + if (uid == instance["MainDicomTags"]["SOPInstanceUID"].asString() && + GetClosedPlanarPoints(points, context, instanceId, roi, i)) + { + result.append(points); + } + } + } + + call.GetOutput().AnswerJson(result); + } + } + + + + static void GetRtStructuresPointsOfInstance(RestApi::GetCall& call) + { + RETRIEVE_CONTEXT(call); + + Json::Value roi, contour, instance; + std::string instanceId; + + if (context.GetIndex().LookupResource(instance, call.GetUriComponent("instance", ""), ResourceType_Instance) && + GetRtStructuresRoi(roi, contour, instanceId, context, + call.GetUriComponent("id", ""), + call.GetUriComponent("roi", ""))) + { + Json::Value result = Json::arrayValue; + + for (Json::Value::ArrayIndex i = 0; i < contour.size(); i++) + { + if (contour[i].isMember(CONTOUR_DATA) && + contour[i].isMember(CONTOUR_IMAGE_SEQUENCE) && + contour[i].isMember(CONTOUR_GEOMETRIC_TYPE) && + contour[i][CONTOUR_IMAGE_SEQUENCE]["Value"].size() == 1 && + contour[i][CONTOUR_IMAGE_SEQUENCE]["Value"][0].isMember(REFERENCED_SOP_INSTANCE_UID) && + contour[i][CONTOUR_GEOMETRIC_TYPE]["Value"].asString() == "POINT") + { + std::string uid = contour[i][CONTOUR_IMAGE_SEQUENCE]["Value"][0][REFERENCED_SOP_INSTANCE_UID]["Value"].asString(); + + if (uid == instance["MainDicomTags"]["SOPInstanceUID"].asString()) + { + Json::Value p; + if (ContourToPoints(p, contour[i][CONTOUR_DATA]["Value"].asString()) && + p.size() == 1) + { + result.append(p[0]); + } + } + } + } + + call.GetOutput().AnswerJson(result); + } + } + + + static void GetRtStructuresVolume(RestApi::GetCall& call) + { + RETRIEVE_CONTEXT(call); + + Json::Value roi, contour, vertices; + std::string instanceId; + + if (GetRtStructuresRoi(roi, contour, instanceId, context, + call.GetUriComponent("id", ""), + call.GetUriComponent("roi", ""))) + { + double volume = 0; + + for (Json::Value::ArrayIndex i = 0; i < contour.size(); i++) + { + double area; + float thickness; + + if (contour[i].isMember(CONTOUR_GEOMETRIC_TYPE) && + contour[i][CONTOUR_GEOMETRIC_TYPE]["Value"].asString() == "CLOSED_PLANAR" && + GetClosedPlanarPoints(vertices, context, instanceId, roi, i) && + ComputeClosedPlanarArea(area, vertices) && + GetRtStructuresClosedPlanarThickness(thickness, context, contour[i])) + { + volume += area * static_cast<double>(thickness); + } + } + + call.GetOutput().AnswerBuffer(boost::lexical_cast<std::string>(volume), "text/plain"); + } + } + + + RadiotherapyRestApi::RadiotherapyRestApi(ServerContext& context) : OrthancRestApi(context) + { + Register("/series/{id}/rt-structures", GetRtStructuresInfo); + Register("/series/{id}/rt-structures/roi", GetRtStructuresListOfROIs); + Register("/series/{id}/rt-structures/roi/{roi}/info", GetRtStructuresROI); + Register("/series/{id}/rt-structures/roi/{roi}/points", GetRtStructuresROIPoints); + Register("/series/{id}/rt-structures/roi/{roi}/closed-planar", GetRtStructuresListOfClosedPlanars); + Register("/series/{id}/rt-structures/roi/{roi}/closed-planar/{polygon}/vertices", GetRtStructuresSingleClosedPlanar); + Register("/series/{id}/rt-structures/roi/{roi}/closed-planar/{polygon}/thickness", GetRtStructuresClosedPlanarThickness); + Register("/series/{id}/rt-structures/roi/{roi}/closed-planar/{polygon}/instance", GetRtStructuresInstanceOfClosedPlanar); + Register("/series/{id}/rt-structures/roi/{roi}/closed-planar/{polygon}/area", GetRtStructuresClosedPlanarArea); + Register("/series/{id}/rt-structures/roi/{roi}/instances", GetRtStructuresListOfInstances); + Register("/series/{id}/rt-structures/roi/{roi}/instances/{instance}/closed-planar", GetRtStructuresClosedPlanarsOfInstance); + Register("/series/{id}/rt-structures/roi/{roi}/instances/{instance}/points", GetRtStructuresPointsOfInstance); + Register("/series/{id}/rt-structures/roi/{roi}/volume", GetRtStructuresVolume); + } + +} + + +// curl http://localhost:8042/series/0b9e2bb2-605a59aa-f27c0260-9cc4faf6-9d8bf457/rt-structures +// curl http://localhost:8042/series/ef041e6b-c855e775-f7e0f7fe-dc3c17dc-533cb8c5/rt-structures
--- a/OrthancServer/RadiotherapyRestApi.cpp Wed Apr 16 16:04:55 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,815 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, - * Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#include "RadiotherapyRestApi.h" - -#include "ServerToolbox.h" - -#define RETRIEVE_CONTEXT(call) \ - OrthancRestApi& contextApi = \ - dynamic_cast<OrthancRestApi&>(call.GetContext()); \ - ServerContext& context = contextApi.GetContext() - - -// DICOM tags for RT-STRUCT - -/** - * REFERENCE: http://www.dabsoft.ch/dicom/3/C.8.8.6/ - * - * IMPORTANT: The points/vertices coordinates are reported in [mm]. - * - * TODO: Support "Contour Offset Vector" - **/ - - -#define REFERENCED_STUDY_SEQUENCE "0008,1110" -#define REFERENCED_SOP_INSTANCE_UID "0008,1155" -#define FRAME_OF_REFERENCE_UID "0020,0052" -#define REFERENCED_FRAME_OF_REFERENCE_SEQUENCE "3006,0010" -#define STRUCTURE_SET_ROI_SEQUENCE "3006,0020" -#define ROI_NUMBER "3006,0022" -#define ROI_NAME "3006,0026" -#define ROI_GENERATION_ALGORITHM "3006,0036" -#define ROI_CONTOUR_SEQUENCE "3006,0039" -#define REFERENCED_ROI_NUMBER "3006,0084" -#define ROI_DISPLAY_COLOR "3006,002a" -#define CONTOUR_SEQUENCE "3006,0040" -#define CONTOUR_IMAGE_SEQUENCE "3006,0016" -#define CONTOUR_GEOMETRIC_TYPE "3006,0042" -#define NUMBER_OF_CONTOUR_POINTS "3006,0046" -#define CONTOUR_DATA "3006,0050" -#define CONTOUR_SLAB_THICKNESS "3006,0044" -#define SLICE_THICKNESS "0018,0050" - - - -#include <boost/geometry.hpp> -#include <boost/geometry/geometries/point.hpp> -#include <boost/geometry/geometries/point_xy.hpp> -#include <boost/geometry/geometries/polygon.hpp> -#include <boost/geometry/geometries/linestring.hpp> - -namespace Orthanc -{ - static bool CheckSeriesModality(Json::Value& study, - Json::Value& series, - Json::Value& content, - ServerContext& context, - const std::string& seriesId, - const std::string& modality) - { - if (!context.GetIndex().LookupResource(series, seriesId, ResourceType_Series)) - { - return false; - } - - // Retrieve the parent study - std::string studyId = series["ParentStudy"].asString(); - if (!context.GetIndex().LookupResource(study, studyId, ResourceType_Study)) - { - return false; - } - - // Check the modality and that there is a single instance inside the series - if (!series["MainDicomTags"].isMember("Modality") || - series["MainDicomTags"]["Modality"].asString() != modality || - series["Instances"].size() != 1) - { - return false; - } - - // Retrieve the instance data - std::string instanceId = series["Instances"][0].asString(); - - context.ReadJson(content, instanceId); - - return true; - } - - - static bool ContourToPoints(Json::Value& result, - const Json::Value& source) - { - std::vector<std::string> points; - Toolbox::Split(points, source.asString(), '\\'); - - if (points.size() % 3 != 0) - { - return false; - } - - result = Json::arrayValue; - - for (size_t k = 0; k < points.size(); k += 3) - { - Json::Value p = Json::arrayValue; - - try - { - p.append(boost::lexical_cast<float>(points[k])); - p.append(boost::lexical_cast<float>(points[k + 1])); - p.append(boost::lexical_cast<float>(points[k + 2])); - } - catch (boost::bad_lexical_cast) - { - return false; - } - - result.append(p); - } - - return true; - } - - - static bool GetRtStructuresInfo(Json::Value& study, - Json::Value& series, - Json::Value& content, - std::string& frameOfReference, - ServerContext& context, - const std::string& seriesId) - { - if (!CheckSeriesModality(study, series, content, context, seriesId, "RTSTRUCT")) - { - return false; - } - - // Check that the "ReferencedStudySequence" (if any) is the same as the parent study. - if (content.isMember(REFERENCED_STUDY_SEQUENCE)) - { - if (content[REFERENCED_STUDY_SEQUENCE]["Value"].size() != 1 || - !content[REFERENCED_STUDY_SEQUENCE]["Value"][0].isMember(REFERENCED_SOP_INSTANCE_UID) || - content[REFERENCED_STUDY_SEQUENCE]["Value"][0][REFERENCED_SOP_INSTANCE_UID]["Value"].asString() != - study["MainDicomTags"]["StudyInstanceUID"].asString()) - { - return false; - } - } - - // Lookup for the frame of reference. Orthanc does not support - // RTSTRUCT with multiple frames of reference. - if (!content.isMember(REFERENCED_FRAME_OF_REFERENCE_SEQUENCE) || - content[REFERENCED_FRAME_OF_REFERENCE_SEQUENCE]["Value"].size() != 1 || - !content[REFERENCED_FRAME_OF_REFERENCE_SEQUENCE]["Value"][0].isMember(FRAME_OF_REFERENCE_UID)) - { - return false; - } - - frameOfReference = content[REFERENCED_FRAME_OF_REFERENCE_SEQUENCE]["Value"][0][FRAME_OF_REFERENCE_UID]["Value"].asString(); - - return true; - } - - - static bool GetRtStructuresRoi(Json::Value& result, - Json::Value& contourSequence, - std::string& instanceId, - ServerContext& context, - const std::string& seriesId, - const std::string& roiNumber) - { - Json::Value study, series, content; - std::string frameOfReference; - - if (!GetRtStructuresInfo(study, series, content, frameOfReference, context, seriesId)) - { - return false; - } - - if (!content.isMember(STRUCTURE_SET_ROI_SEQUENCE) || - !content.isMember(ROI_CONTOUR_SEQUENCE)) - { - return false; - } - - instanceId = series["Instances"][0].asString(); - - bool found = false; - - for (Json::Value::ArrayIndex i = 0; i < content[STRUCTURE_SET_ROI_SEQUENCE]["Value"].size(); i++) - { - const Json::Value& roi = content[STRUCTURE_SET_ROI_SEQUENCE]["Value"][i]; - - if (roi.isMember(ROI_NUMBER) && - roi.isMember(ROI_NAME) && - roi[ROI_NUMBER]["Value"].asString() == roiNumber) - { - result["InternalIndex"] = i; - result["Number"] = boost::lexical_cast<unsigned int>(roiNumber); - result["Name"] = roi[ROI_NAME]["Value"].asString(); - result["GenerationAlgorithm"] = roi[ROI_GENERATION_ALGORITHM]["Value"].asString(); - found = true; - } - } - - if (!found) - { - return false; - } - - for (Json::Value::ArrayIndex i = 0; i < content[ROI_CONTOUR_SEQUENCE]["Value"].size(); i++) - { - const Json::Value& contour = content[ROI_CONTOUR_SEQUENCE]["Value"][i]; - - if (contour.isMember(REFERENCED_ROI_NUMBER) && - contour.isMember(ROI_DISPLAY_COLOR) && - contour.isMember(CONTOUR_SEQUENCE) && - contour[REFERENCED_ROI_NUMBER]["Value"].asString() == roiNumber) - { - std::vector<std::string> color; - Toolbox::Split(color, contour[ROI_DISPLAY_COLOR]["Value"].asString(), '\\'); - - result["DisplayColor"] = Json::arrayValue; - if (color.size() != 3) - { - return false; - } - - for (size_t k = 0; k < color.size(); k++) - { - try - { - result["DisplayColor"].append(boost::lexical_cast<int>(color[k])); - } - catch (boost::bad_lexical_cast) - { - return false; - } - } - - contourSequence = contour[CONTOUR_SEQUENCE]["Value"]; - - return true; - } - } - - return false; - } - - - static bool GetClosedPlanarPoints(Json::Value& result, - ServerContext& context, - const std::string& instanceId, - const Json::Value& roi, - unsigned int index) - { - boost::mutex::scoped_lock lock(context.GetDicomFileMutex()); - ParsedDicomFile& dicom = context.GetDicomFile(instanceId); - - ParsedDicomFile::SequencePath path; - path.push_back(std::make_pair(DicomTag(0x3006, 0x0039 /* ROIContourSequence */), roi["InternalIndex"].asInt())); - path.push_back(std::make_pair(DicomTag(0x3006, 0x0040 /* ContourSequence */), index)); - - std::string contourData; - std::string numberOfPoints; - - if (dicom.GetTagValue(contourData, path, DicomTag(0x3006, 0x0050 /* ContourData */)) && - dicom.GetTagValue(numberOfPoints, path, DicomTag(0x3006, 0x0046 /* NumberOfContourPoints */)) && - ContourToPoints(result, contourData) && - result.size() == boost::lexical_cast<unsigned int>(numberOfPoints)) - { - return true; - } - else - { - return false; - } - } - - - - static bool LookupReferencedInstance(Json::Value& result, - ServerContext& context, - const Json::Value& contour) - { - if (contour.isMember(CONTOUR_IMAGE_SEQUENCE) && - contour[CONTOUR_IMAGE_SEQUENCE]["Value"].size() == 1 && - contour[CONTOUR_IMAGE_SEQUENCE]["Value"][0].isMember(REFERENCED_SOP_INSTANCE_UID)) - { - std::string uid = contour[CONTOUR_IMAGE_SEQUENCE]["Value"][0][REFERENCED_SOP_INSTANCE_UID]["Value"].asString(); - - std::list<std::string> instance; - context.GetIndex().LookupTagValue(instance, DICOM_TAG_SOP_INSTANCE_UID, uid); - - if (instance.size() == 1 && - context.GetIndex().LookupResource(result, instance.front(), ResourceType_Instance)) - { - return true; - } - } - - return false; - } - - - static bool GetRtStructuresClosedPlanarThickness(float& result, - ServerContext& context, - const Json::Value& contour) - { - if (contour.isMember(CONTOUR_SLAB_THICKNESS)) - { - result = boost::lexical_cast<float>(contour[CONTOUR_SLAB_THICKNESS]["Value"].asString()); - return true; - } - - // No slab thickness is explicitely specified: Fallback to - // the thickness of the referred instance - - Json::Value instance; - if (LookupReferencedInstance(instance, context, contour)) - { - Json::Value info; - context.ReadJson(info, instance["ID"].asString()); - - if (info.isMember(SLICE_THICKNESS)) - { - result = boost::lexical_cast<float>(info[SLICE_THICKNESS]["Value"].asString()); - return true; - } - } - - return false; - } - - - - static void GetRtStructuresInfo(RestApi::GetCall& call) - { - RETRIEVE_CONTEXT(call); - - Json::Value study, series, content; - std::string frameOfReference; - if (GetRtStructuresInfo(study, series, content, frameOfReference, context, call.GetUriComponent("id", ""))) - { - Json::Value result; - - result["Study"] = study["ID"]; - - - // Lookup the series with the same frame of reference inside this study - result["RelatedSeries"] = Json::arrayValue; - - for (Json::Value::ArrayIndex i = 0; i < study["Series"].size(); i++) - { - Json::Value otherSeries; - if (context.GetIndex().LookupResource(otherSeries, study["Series"][i].asString(), ResourceType_Series) && - otherSeries["Instances"].size() > 0) - { - Json::Value info; - context.ReadJson(info, otherSeries["Instances"][0].asString()); - - if (info.isMember(FRAME_OF_REFERENCE_UID)) - { - result["RelatedSeries"].append(study["Series"][i].asString()); - } - } - } - - - call.GetOutput().AnswerJson(result); - } - } - - - static void GetRtStructuresListOfROIs(RestApi::GetCall& call) - { - RETRIEVE_CONTEXT(call); - - Json::Value study, series, content; - std::string frameOfReference; - if (GetRtStructuresInfo(study, series, content, frameOfReference, context, call.GetUriComponent("id", ""))) - { - Json::Value result(Json::arrayValue); - - if (content.isMember(STRUCTURE_SET_ROI_SEQUENCE)) - { - for (Json::Value::ArrayIndex i = 0; i < content[STRUCTURE_SET_ROI_SEQUENCE]["Value"].size(); i++) - { - if (content[STRUCTURE_SET_ROI_SEQUENCE]["Value"][i].isMember(ROI_NUMBER)) - { - result.append(boost::lexical_cast<int>(content[STRUCTURE_SET_ROI_SEQUENCE]["Value"][i][ROI_NUMBER]["Value"].asString())); - } - } - } - - call.GetOutput().AnswerJson(result); - } - } - - - static void GetRtStructuresROI(RestApi::GetCall& call) - { - RETRIEVE_CONTEXT(call); - - Json::Value roi, contour; - std::string instanceId; - - if (GetRtStructuresRoi(roi, contour, instanceId, context, - call.GetUriComponent("id", ""), - call.GetUriComponent("roi", ""))) - { - roi.removeMember("InternalIndex"); - call.GetOutput().AnswerJson(roi); - } - } - - - static void GetRtStructuresROIPoints(RestApi::GetCall& call) - { - RETRIEVE_CONTEXT(call); - - Json::Value roi, contour; - std::string instanceId; - - if (GetRtStructuresRoi(roi, contour, instanceId, context, - call.GetUriComponent("id", ""), - call.GetUriComponent("roi", ""))) - { - Json::Value result = Json::arrayValue; - - for (Json::Value::ArrayIndex i = 0; i < contour.size(); i++) - { - if (contour[i][CONTOUR_GEOMETRIC_TYPE]["Value"].asString() == "POINT") - { - Json::Value p; - if (ContourToPoints(p, contour[i][CONTOUR_DATA]["Value"].asString()) && - p.size() == 1) - { - result.append(p[0]); - } - } - } - - call.GetOutput().AnswerJson(result); - } - } - - - static void GetRtStructuresListOfClosedPlanars(RestApi::GetCall& call) - { - RETRIEVE_CONTEXT(call); - - Json::Value roi, contour; - std::string instanceId; - - if (GetRtStructuresRoi(roi, contour, instanceId, context, - call.GetUriComponent("id", ""), - call.GetUriComponent("roi", ""))) - { - Json::Value result = Json::arrayValue; - - for (Json::Value::ArrayIndex i = 0; i < contour.size(); i++) - { - if (contour[i].isMember(CONTOUR_IMAGE_SEQUENCE) && - contour[i].isMember(CONTOUR_GEOMETRIC_TYPE) && - contour[i].isMember(NUMBER_OF_CONTOUR_POINTS) && - contour[i].isMember(CONTOUR_DATA) && - contour[i][CONTOUR_IMAGE_SEQUENCE]["Value"].size() == 1 && - contour[i][CONTOUR_IMAGE_SEQUENCE]["Value"][0].isMember(REFERENCED_SOP_INSTANCE_UID) && - contour[i][CONTOUR_GEOMETRIC_TYPE]["Value"].asString() == "CLOSED_PLANAR") - { - result.append(i); - } - } - - call.GetOutput().AnswerJson(result); - } - } - - - static void GetRtStructuresSingleClosedPlanar(RestApi::GetCall& call) - { - RETRIEVE_CONTEXT(call); - - Json::Value roi, contour, result; - std::string instanceId; - - if (GetRtStructuresRoi(roi, contour, instanceId, context, - call.GetUriComponent("id", ""), - call.GetUriComponent("roi", ""))) - { - unsigned int index = boost::lexical_cast<unsigned int>(call.GetUriComponent("polygon", "")); - - if (GetClosedPlanarPoints(result, context, instanceId, roi, index)) - { - call.GetOutput().AnswerJson(result); - } - } - } - - - static void GetRtStructuresClosedPlanarThickness(RestApi::GetCall& call) - { - RETRIEVE_CONTEXT(call); - - Json::Value roi, contour; - std::string instanceId; - - if (GetRtStructuresRoi(roi, contour, instanceId, context, - call.GetUriComponent("id", ""), - call.GetUriComponent("roi", ""))) - { - unsigned int index = boost::lexical_cast<unsigned int>(call.GetUriComponent("polygon", "")); - - float thickness; - if (GetRtStructuresClosedPlanarThickness(thickness, context, contour[index])) - { - call.GetOutput().AnswerBuffer(boost::lexical_cast<std::string>(thickness), "text/plain"); - } - } - } - - - static bool ComputeClosedPlanarArea(double& area, - const Json::Value& vertices) - { - if (vertices.size() <= 1) - { - area = 0; - return true; - } - - // Check that all the points share the same z coordinates - for (Json::Value::ArrayIndex i = 1; i < vertices.size(); i++) - { - static float THRESHOLD = 10.0f * std::numeric_limits<float>::epsilon(); - - assert(vertices[i].size() == 3); - if (fabs(vertices[i][2].asFloat() - vertices[0][2].asFloat()) > THRESHOLD) - { - // This point has not the same z coordinate - return false; - } - } - - // Calculate the area of a cartesian polygon - // TODO - What happens if self-crossing polygon? - typedef boost::geometry::model::d2::point_xy<float> point_type; - - boost::geometry::model::linestring<point_type> points; - - points.resize(vertices.size()); - for (Json::Value::ArrayIndex i = 0; i < vertices.size(); i++) - { - float x = vertices[i][0].asFloat(); - float y = vertices[i][1].asFloat(); - points[i] = boost::geometry::make<point_type>(x, y); - } - - boost::geometry::model::polygon<point_type> poly; - boost::geometry::append(poly, points); - - area = abs(boost::geometry::area(poly)); - return true; - } - - - static void GetRtStructuresClosedPlanarArea(RestApi::GetCall& call) - { - RETRIEVE_CONTEXT(call); - - Json::Value roi, contour, vertices; - std::string instanceId; - - if (GetRtStructuresRoi(roi, contour, instanceId, context, - call.GetUriComponent("id", ""), - call.GetUriComponent("roi", ""))) - { - unsigned int index = boost::lexical_cast<unsigned int>(call.GetUriComponent("polygon", "")); - - double area; - if (GetClosedPlanarPoints(vertices, context, instanceId, roi, index) && - ComputeClosedPlanarArea(area, vertices)) - { - call.GetOutput().AnswerBuffer(boost::lexical_cast<std::string>(area), "text/plain"); - } - } - } - - - static void GetRtStructuresInstanceOfClosedPlanar(RestApi::GetCall& call) - { - RETRIEVE_CONTEXT(call); - - Json::Value roi, contour; - std::string instanceId; - - if (GetRtStructuresRoi(roi, contour, instanceId, context, - call.GetUriComponent("id", ""), - call.GetUriComponent("roi", ""))) - { - unsigned int index = boost::lexical_cast<unsigned int>(call.GetUriComponent("polygon", "")); - - Json::Value result; - if (LookupReferencedInstance(result, context, contour[index])) - { - call.GetOutput().AnswerJson(result); - } - } - } - - - static void GetRtStructuresListOfInstances(RestApi::GetCall& call) - { - RETRIEVE_CONTEXT(call); - - Json::Value roi, contour; - std::string instanceId; - - if (GetRtStructuresRoi(roi, contour, instanceId, context, - call.GetUriComponent("id", ""), - call.GetUriComponent("roi", ""))) - { - Json::Value result = Json::arrayValue; - - for (Json::Value::ArrayIndex i = 0; i < contour.size(); i++) - { - if (contour[i].isMember(CONTOUR_IMAGE_SEQUENCE) && - contour[i][CONTOUR_IMAGE_SEQUENCE]["Value"].size() == 1 && - contour[i][CONTOUR_IMAGE_SEQUENCE]["Value"][0].isMember(REFERENCED_SOP_INSTANCE_UID)) - { - std::string uid = contour[i][CONTOUR_IMAGE_SEQUENCE]["Value"][0][REFERENCED_SOP_INSTANCE_UID]["Value"].asString(); - - std::list<std::string> instance; - context.GetIndex().LookupTagValue(instance, DICOM_TAG_SOP_INSTANCE_UID, uid); - - if (instance.size() == 1) - { - result.append(instance.front()); - } - } - } - - call.GetOutput().AnswerJson(result); - } - } - - - - static void GetRtStructuresClosedPlanarsOfInstance(RestApi::GetCall& call) - { - RETRIEVE_CONTEXT(call); - - Json::Value roi, contour, instance; - std::string instanceId; - - if (context.GetIndex().LookupResource(instance, call.GetUriComponent("instance", ""), ResourceType_Instance) && - GetRtStructuresRoi(roi, contour, instanceId, context, - call.GetUriComponent("id", ""), - call.GetUriComponent("roi", ""))) - { - Json::Value result = Json::arrayValue; - - for (Json::Value::ArrayIndex i = 0; i < contour.size(); i++) - { - if (contour[i].isMember(CONTOUR_DATA) && - contour[i].isMember(CONTOUR_IMAGE_SEQUENCE) && - contour[i].isMember(NUMBER_OF_CONTOUR_POINTS) && - contour[i].isMember(CONTOUR_GEOMETRIC_TYPE) && - contour[i][CONTOUR_IMAGE_SEQUENCE]["Value"].size() == 1 && - contour[i][CONTOUR_IMAGE_SEQUENCE]["Value"][0].isMember(REFERENCED_SOP_INSTANCE_UID) && - contour[i][CONTOUR_GEOMETRIC_TYPE]["Value"].asString() == "CLOSED_PLANAR") - { - std::string uid = contour[i][CONTOUR_IMAGE_SEQUENCE]["Value"][0][REFERENCED_SOP_INSTANCE_UID]["Value"].asString(); - - Json::Value points; - if (uid == instance["MainDicomTags"]["SOPInstanceUID"].asString() && - GetClosedPlanarPoints(points, context, instanceId, roi, i)) - { - result.append(points); - } - } - } - - call.GetOutput().AnswerJson(result); - } - } - - - - static void GetRtStructuresPointsOfInstance(RestApi::GetCall& call) - { - RETRIEVE_CONTEXT(call); - - Json::Value roi, contour, instance; - std::string instanceId; - - if (context.GetIndex().LookupResource(instance, call.GetUriComponent("instance", ""), ResourceType_Instance) && - GetRtStructuresRoi(roi, contour, instanceId, context, - call.GetUriComponent("id", ""), - call.GetUriComponent("roi", ""))) - { - Json::Value result = Json::arrayValue; - - for (Json::Value::ArrayIndex i = 0; i < contour.size(); i++) - { - if (contour[i].isMember(CONTOUR_DATA) && - contour[i].isMember(CONTOUR_IMAGE_SEQUENCE) && - contour[i].isMember(CONTOUR_GEOMETRIC_TYPE) && - contour[i][CONTOUR_IMAGE_SEQUENCE]["Value"].size() == 1 && - contour[i][CONTOUR_IMAGE_SEQUENCE]["Value"][0].isMember(REFERENCED_SOP_INSTANCE_UID) && - contour[i][CONTOUR_GEOMETRIC_TYPE]["Value"].asString() == "POINT") - { - std::string uid = contour[i][CONTOUR_IMAGE_SEQUENCE]["Value"][0][REFERENCED_SOP_INSTANCE_UID]["Value"].asString(); - - if (uid == instance["MainDicomTags"]["SOPInstanceUID"].asString()) - { - Json::Value p; - if (ContourToPoints(p, contour[i][CONTOUR_DATA]["Value"].asString()) && - p.size() == 1) - { - result.append(p[0]); - } - } - } - } - - call.GetOutput().AnswerJson(result); - } - } - - - static void GetRtStructuresVolume(RestApi::GetCall& call) - { - RETRIEVE_CONTEXT(call); - - Json::Value roi, contour, vertices; - std::string instanceId; - - if (GetRtStructuresRoi(roi, contour, instanceId, context, - call.GetUriComponent("id", ""), - call.GetUriComponent("roi", ""))) - { - double volume = 0; - - for (Json::Value::ArrayIndex i = 0; i < contour.size(); i++) - { - double area; - float thickness; - - if (contour[i].isMember(CONTOUR_GEOMETRIC_TYPE) && - contour[i][CONTOUR_GEOMETRIC_TYPE]["Value"].asString() == "CLOSED_PLANAR" && - GetClosedPlanarPoints(vertices, context, instanceId, roi, i) && - ComputeClosedPlanarArea(area, vertices) && - GetRtStructuresClosedPlanarThickness(thickness, context, contour[i])) - { - volume += area * static_cast<double>(thickness); - } - } - - call.GetOutput().AnswerBuffer(boost::lexical_cast<std::string>(volume), "text/plain"); - } - } - - - RadiotherapyRestApi::RadiotherapyRestApi(ServerContext& context) : OrthancRestApi(context) - { - Register("/series/{id}/rt-structures", GetRtStructuresInfo); - Register("/series/{id}/rt-structures/roi", GetRtStructuresListOfROIs); - Register("/series/{id}/rt-structures/roi/{roi}/info", GetRtStructuresROI); - Register("/series/{id}/rt-structures/roi/{roi}/points", GetRtStructuresROIPoints); - Register("/series/{id}/rt-structures/roi/{roi}/closed-planar", GetRtStructuresListOfClosedPlanars); - Register("/series/{id}/rt-structures/roi/{roi}/closed-planar/{polygon}/vertices", GetRtStructuresSingleClosedPlanar); - Register("/series/{id}/rt-structures/roi/{roi}/closed-planar/{polygon}/thickness", GetRtStructuresClosedPlanarThickness); - Register("/series/{id}/rt-structures/roi/{roi}/closed-planar/{polygon}/instance", GetRtStructuresInstanceOfClosedPlanar); - Register("/series/{id}/rt-structures/roi/{roi}/closed-planar/{polygon}/area", GetRtStructuresClosedPlanarArea); - Register("/series/{id}/rt-structures/roi/{roi}/instances", GetRtStructuresListOfInstances); - Register("/series/{id}/rt-structures/roi/{roi}/instances/{instance}/closed-planar", GetRtStructuresClosedPlanarsOfInstance); - Register("/series/{id}/rt-structures/roi/{roi}/instances/{instance}/points", GetRtStructuresPointsOfInstance); - Register("/series/{id}/rt-structures/roi/{roi}/volume", GetRtStructuresVolume); - } - -} - - -// curl http://localhost:8042/series/0b9e2bb2-605a59aa-f27c0260-9cc4faf6-9d8bf457/rt-structures -// curl http://localhost:8042/series/ef041e6b-c855e775-f7e0f7fe-dc3c17dc-533cb8c5/rt-structures
--- a/OrthancServer/RadiotherapyRestApi.h Wed Apr 16 16:04:55 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,44 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, - * Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - **/ - - -#pragma once - -#include "OrthancRestApi.h" - -namespace Orthanc -{ - class RadiotherapyRestApi : public OrthancRestApi - { - public: - RadiotherapyRestApi(ServerContext& context); - }; -}